Skip to content

Commit

Permalink
feat(create-jest): Add npm init / yarn create initialiser (#14453)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom Mrazauskas <tom@mrazauskas.de>
  • Loading branch information
dj-stormtrooper and mrazauskas committed Sep 7, 2023
1 parent 008caa9 commit 0b0cf73
Show file tree
Hide file tree
Showing 45 changed files with 187 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ module.exports = {
files: [
'scripts/*',
'packages/*/__benchmarks__/test.js',
'packages/jest-cli/src/init/index.ts',
'packages/create-jest/src/runCreate.ts',
'packages/jest-repl/src/cli/runtime-cli.ts',
],
rules: {
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

- `[create-jest]` Add `npm init` / `yarn create` initialiser for Jest projects ([#14465](https://github.com/jestjs/jest/pull/14453))

### Fixes

- `[jest-snapshot]` Allow for strings as well as template literals in inline snapshots ([#14465](https://github.com/jestjs/jest/pull/14465))
Expand All @@ -13,6 +15,8 @@

### Chore & Maintenance

- `[jest-cli]` Move internal config initialisation logic to the `create-jest` package ([#14465](https://github.com/jestjs/jest/pull/14453))

## 29.6.4

### Fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ If you'd like to learn more about running `jest` through the command line, take
Based on your project, Jest will ask you a few questions and will create a basic configuration file with a short description for each option:

```bash
jest --init
yarn create jest
```

### Using Babel
Expand Down
4 changes: 2 additions & 2 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ If you'd like to learn more about running `jest` through the command line, take

Based on your project, Jest will ask you a few questions and will create a basic configuration file with a short description for each option:

```bash
jest --init
```bash npm2yarn
npm init jest@latest
```

### Using Babel
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"test": "yarn lint && yarn jest",
"typecheck": "yarn typecheck:examples && yarn typecheck:tests",
"typecheck:examples": "tsc -p examples/angular --noEmit && tsc -p examples/expect-extend --noEmit && tsc -p examples/typescript --noEmit",
"typecheck:tests": "tsc -b packages/{babel-jest,babel-plugin-jest-hoist,diff-sequences,expect,expect-utils,jest-circus,jest-cli,jest-config,jest-console,jest-snapshot,jest-util,jest-validate,jest-watcher,jest-worker,pretty-format}/**/__tests__",
"typecheck:tests": "tsc -b packages/{babel-jest,babel-plugin-jest-hoist,create-jest,diff-sequences,expect,expect-utils,jest-circus,jest-cli,jest-config,jest-console,jest-snapshot,jest-util,jest-validate,jest-watcher,jest-worker,pretty-format}/**/__tests__",
"verify-old-ts": "node ./scripts/verifyOldTs.mjs",
"verify-pnp": "node ./scripts/verifyPnP.mjs",
"watch": "yarn build:js && node ./scripts/watch.mjs",
Expand Down
8 changes: 8 additions & 0 deletions packages/create-jest/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**/__mocks__/**
**/__tests__/**
__typetests__
src
tsconfig.json
tsconfig.tsbuildinfo
api-extractor.json
.eslintcache
11 changes: 11 additions & 0 deletions packages/create-jest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# create-jest

> Getting started with Jest with a single command
```bash
npm init jest@latest
# Or for Yarn
yarn create jest
# Or for pnpm
pnpm create jest
```
8 changes: 8 additions & 0 deletions packages/create-jest/bin/create-jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
require('..').runCLI();
42 changes: 42 additions & 0 deletions packages/create-jest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "create-jest",
"description": "Create a new Jest project",
"version": "29.6.4",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/create-jest"
},
"license": "MIT",
"bin": "./bin/create-jest.js",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
},
"./package.json": "./package.json",
"./bin/create-jest": "./bin/create-jest.js"
},
"dependencies": {
"@jest/types": "workspace:^",
"chalk": "^4.0.0",
"exit": "^0.1.2",
"graceful-fs": "^4.2.9",
"jest-config": "workspace:^",
"jest-util": "workspace:^",
"prompts": "^2.0.1"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/exit": "^0.1.30",
"@types/graceful-fs": "^4.1.3",
"@types/prompts": "^2.0.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as path from 'path';
import {writeFileSync} from 'graceful-fs';
import * as prompts from 'prompts';
import {constants} from 'jest-config';
import init from '../';
import {runCreate} from '../runCreate';

const {JEST_CONFIG_EXT_ORDER} = constants;

Expand Down Expand Up @@ -44,7 +44,7 @@ describe('init', () => {
it('should return the default configuration (an empty config)', async () => {
jest.mocked(prompts).mockResolvedValueOnce({});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfigFilename =
jest.mocked(writeFileSync).mock.calls[0][0];
Expand All @@ -71,7 +71,7 @@ describe('init', () => {
it('should generate empty config with mjs extension', async () => {
jest.mocked(prompts).mockResolvedValueOnce({});

await init(resolveFromFixture('type-module'));
await runCreate(resolveFromFixture('type-module'));

const writtenJestConfigFilename =
jest.mocked(writeFileSync).mock.calls[0][0];
Expand All @@ -93,7 +93,7 @@ describe('init', () => {
it('should create configuration for {clearMocks: true}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({clearMocks: true});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -107,7 +107,7 @@ describe('init', () => {
it('should create configuration for {coverage: true}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({coverage: true});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -124,7 +124,7 @@ describe('init', () => {
it('should create configuration for {coverageProvider: "babel"}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({coverageProvider: 'babel'});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -138,7 +138,7 @@ describe('init', () => {
it('should create configuration for {coverageProvider: "v8"}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({coverageProvider: 'v8'});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -152,7 +152,7 @@ describe('init', () => {
it('should create configuration for {environment: "jsdom"}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({environment: 'jsdom'});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -165,7 +165,7 @@ describe('init', () => {
it('should create configuration for {environment: "node"}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({environment: 'node'});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenJestConfig = jest.mocked(writeFileSync).mock.calls[0][1];
const evaluatedConfig = eval(writtenJestConfig as string) as Record<
Expand All @@ -178,7 +178,7 @@ describe('init', () => {
it('should create package.json with configured test command when {scripts: true}', async () => {
jest.mocked(prompts).mockResolvedValueOnce({scripts: true});

await init(resolveFromFixture('only-package-json'));
await runCreate(resolveFromFixture('only-package-json'));

const writtenPackageJson = jest.mocked(writeFileSync).mock.calls[0][1];
const parsedPackageJson = JSON.parse(writtenPackageJson as string) as {
Expand All @@ -196,7 +196,7 @@ describe('init', () => {
expect.assertions(1);

try {
await init(resolveFromFixture('no-package-json'));
await runCreate(resolveFromFixture('no-package-json'));
} catch (error) {
expect((error as Error).message).toMatch(
'Could not find a "package.json" file in',
Expand All @@ -215,7 +215,9 @@ describe('init', () => {
.mockResolvedValueOnce({continue: true})
.mockResolvedValueOnce({});

await init(resolveFromFixture(`has-jest-config-file-${extension}`));
await runCreate(
resolveFromFixture(`has-jest-config-file-${extension}`),
);

expect(jest.mocked(prompts).mock.calls[0][0]).toMatchSnapshot();

Expand All @@ -230,7 +232,9 @@ describe('init', () => {
it('user answered with "No"', async () => {
jest.mocked(prompts).mockResolvedValueOnce({continue: false});

await init(resolveFromFixture(`has-jest-config-file-${extension}`));
await runCreate(
resolveFromFixture(`has-jest-config-file-${extension}`),
);
// return after first prompt
expect(prompts).toHaveBeenCalledTimes(1);
});
Expand All @@ -243,7 +247,7 @@ describe('init', () => {
it('user answered with "Yes"', async () => {
jest.mocked(prompts).mockResolvedValueOnce({useTypescript: true});

await init(resolveFromFixture('test-generated-jest-config-ts'));
await runCreate(resolveFromFixture('test-generated-jest-config-ts'));

expect(jest.mocked(prompts).mock.calls[0][0]).toMatchSnapshot();

Expand All @@ -264,7 +268,7 @@ describe('init', () => {
it('user answered with "No"', async () => {
jest.mocked(prompts).mockResolvedValueOnce({useTypescript: false});

await init(resolveFromFixture('test-generated-jest-config-ts'));
await runCreate(resolveFromFixture('test-generated-jest-config-ts'));

const jestConfigFileName = jest.mocked(writeFileSync).mock.calls[0][0];

Expand All @@ -282,7 +286,7 @@ describe('init', () => {
.mockResolvedValueOnce({continue: true})
.mockResolvedValueOnce({});

await init(resolveFromFixture('has-jest-config-in-package-json'));
await runCreate(resolveFromFixture('has-jest-config-in-package-json'));

expect(jest.mocked(prompts).mock.calls[0][0]).toMatchSnapshot();

Expand All @@ -296,7 +300,7 @@ describe('init', () => {
it('should not ask "test script question"', async () => {
jest.mocked(prompts).mockResolvedValueOnce({});

await init(resolveFromFixture('test-script-configured'));
await runCreate(resolveFromFixture('test-script-configured'));

const questions = jest.mocked(prompts).mock.calls[0][0] as Array<
prompts.PromptObject<string>
Expand Down
5 changes: 5 additions & 0 deletions packages/create-jest/src/__tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../../../tsconfig.test.json",
"include": ["./**/*"],
"references": [{"path": "../../"}]
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ export class NotFoundPackageJsonError extends Error {

export class MalformedPackageJsonError extends Error {
constructor(packageJsonPath: string) {
super(
`There is malformed json in ${packageJsonPath}\n` +
'Fix it, and then run "jest --init"',
);
super(`There is malformed json in ${packageJsonPath}`);
this.name = '';
// eslint-disable-next-line @typescript-eslint/no-empty-function
Error.captureStackTrace(this, () => {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

import type {Config} from '@jest/types';
import {defaults, descriptions} from 'jest-config';
import type {PromptsResults} from './types';

const stringifyOption = (
option: keyof Config.InitialOptions,
map: Partial<Config.InitialOptions>,
linePrefix = '',
): string => {
const optionDescription = ` // ${descriptions[option]}`;
const description = descriptions[option];
const optionDescription =
description != null && description.length > 0 ? ` // ${description}` : '';
const stringifiedObject = `${option}: ${JSON.stringify(
map[option],
null,
Expand All @@ -27,7 +30,7 @@ const stringifyOption = (
};

const generateConfigFile = (
results: Record<string, unknown>,
results: PromptsResults,
generateEsm = false,
): string => {
const {useTypescript, coverage, coverageProvider, clearMocks, environment} =
Expand Down
8 changes: 8 additions & 0 deletions packages/create-jest/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export {runCreate, runCLI} from './runCreate';
File renamed without changes.
Loading

0 comments on commit 0b0cf73

Please sign in to comment.