mirror of https://github.com/facebook/jest.git
feat(config): add read initial options helper (#13356)
This commit is contained in:
parent
8219820a87
commit
d78baab693
|
@ -2,6 +2,7 @@
|
|||
|
||||
### Features
|
||||
|
||||
- `[jest-config]` Add `readInitialConfig` utility function ([#13356](https://github.com/facebook/jest/pull/13356))
|
||||
- `[jest-core]` Enable testResultsProcessor to be async ([#13343](https://github.com/facebook/jest/pull/13343))
|
||||
- `[expect, @jest/expect-utils]` Allow `isA` utility to take a type argument ([#13355](https://github.com/facebook/jest/pull/13355))
|
||||
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import path = require('path');
|
||||
import execa = require('execa');
|
||||
import type {ReadJestConfigOptions, readInitialOptions} from 'jest-config';
|
||||
|
||||
function resolveFixture(...pathSegments: Array<string>) {
|
||||
return path.resolve(__dirname, '..', 'read-initial-options', ...pathSegments);
|
||||
}
|
||||
|
||||
interface ProxyReadJestConfigOptions extends ReadJestConfigOptions {
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* These e2e tests are running via a child process, because we're running in a VM and are not allowed to `import` directly
|
||||
* It also represents a more real-world example of how to run.
|
||||
*/
|
||||
async function proxyReadInitialOptions(
|
||||
configFile: string | undefined,
|
||||
options: ProxyReadJestConfigOptions,
|
||||
): ReturnType<typeof readInitialOptions> {
|
||||
const {stdout} = await execa(
|
||||
'node',
|
||||
[
|
||||
require.resolve('../read-initial-options/readOptions.js'),
|
||||
configFile ?? '',
|
||||
JSON.stringify(options),
|
||||
],
|
||||
{cwd: options?.cwd},
|
||||
);
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
|
||||
describe('readInitialOptions', () => {
|
||||
test('should read from the cwd by default', async () => {
|
||||
const configFile = resolveFixture('js-config', 'jest.config.js');
|
||||
const rootDir = resolveFixture('js-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a jest.config.js file', async () => {
|
||||
const configFile = resolveFixture('js-config', 'jest.config.js');
|
||||
const rootDir = resolveFixture('js-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'jest.config.js', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a package.json file', async () => {
|
||||
const configFile = resolveFixture('pkg-config', 'package.json');
|
||||
const rootDir = resolveFixture('pkg-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'package.json', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a jest.config.ts file', async () => {
|
||||
const configFile = resolveFixture('ts-config', 'jest.config.ts');
|
||||
const rootDir = resolveFixture('ts-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'jest.config.ts', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a jest.config.mjs file', async () => {
|
||||
const configFile = resolveFixture('mjs-config', 'jest.config.mjs');
|
||||
const rootDir = resolveFixture('mjs-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'jest.config.mjs', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a jest.config.json file', async () => {
|
||||
const configFile = resolveFixture('json-config', 'jest.config.json');
|
||||
const rootDir = resolveFixture('json-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'jest.config.json', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
test('should read a jest config exporting an async function', async () => {
|
||||
const configFile = resolveFixture('async-config', 'jest.config.js');
|
||||
const rootDir = resolveFixture('async-config');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd: rootDir,
|
||||
});
|
||||
expect(config).toEqual({jestConfig: 'async-config', rootDir});
|
||||
expect(configPath).toEqual(configFile);
|
||||
});
|
||||
|
||||
test('should be able to skip config reading, instead read from cwd', async () => {
|
||||
const expectedConfigFile = resolveFixture(
|
||||
'json-config',
|
||||
'jest.config.json',
|
||||
);
|
||||
const {config, configPath} = await proxyReadInitialOptions(
|
||||
resolveFixture('js-config', 'jest.config.js'),
|
||||
{
|
||||
cwd: resolveFixture('json-config'),
|
||||
readFromCwd: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(config).toEqual({
|
||||
jestConfig: 'jest.config.json',
|
||||
rootDir: path.dirname(expectedConfigFile),
|
||||
});
|
||||
expect(configPath).toEqual(expectedConfigFile);
|
||||
});
|
||||
|
||||
test('should give an error when there are multiple config files', async () => {
|
||||
const cwd = resolveFixture('multiple-config-files');
|
||||
const error: Error = await proxyReadInitialOptions(undefined, {cwd}).catch(
|
||||
error => error,
|
||||
);
|
||||
expect(error.message).toContain('Multiple configurations found');
|
||||
expect(error.message).toContain('multiple-config-files/jest.config.js');
|
||||
expect(error.message).toContain('multiple-config-files/jest.config.json');
|
||||
});
|
||||
|
||||
test('should be able to ignore multiple config files error', async () => {
|
||||
const cwd = resolveFixture('multiple-config-files');
|
||||
const {config, configPath} = await proxyReadInitialOptions(undefined, {
|
||||
cwd,
|
||||
skipMultipleConfigError: true,
|
||||
});
|
||||
expect(config).toEqual({
|
||||
jestConfig: 'jest.config.js',
|
||||
rootDir: resolveFixture('multiple-config-files'),
|
||||
});
|
||||
expect(configPath).toEqual(
|
||||
resolveFixture('multiple-config-files', 'jest.config.js'),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
module.exports = async function () {
|
||||
return {
|
||||
jestConfig: 'async-config',
|
||||
};
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
module.exports = {
|
||||
jestConfig: 'jest.config.js',
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"jestConfig": "jest.config.json"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
export default {
|
||||
jestConfig: 'jest.config.mjs',
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
module.exports = {
|
||||
jestConfig: 'jest.config.js',
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"jestConfig": "jest.config.json"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"jest": {
|
||||
"jestConfig": "package.json"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
const {readInitialOptions} = require('jest-config');
|
||||
async function readConfig() {
|
||||
let config = process.argv[2];
|
||||
let options = undefined;
|
||||
if (config === '') {
|
||||
config = undefined;
|
||||
}
|
||||
if (process.argv[3]) {
|
||||
options = JSON.parse(process.argv[3]);
|
||||
}
|
||||
console.log(JSON.stringify(await readInitialOptions(config, options)));
|
||||
}
|
||||
readConfig().catch(err => {
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
export default {
|
||||
jestConfig: 'jest.config.ts',
|
||||
};
|
|
@ -10,8 +10,7 @@ import {readConfig} from '../index';
|
|||
test('readConfig() throws when an object is passed without a file path', async () => {
|
||||
await expect(
|
||||
readConfig(
|
||||
// @ts-expect-error
|
||||
null /* argv */,
|
||||
{$0: '', _: []},
|
||||
{} /* packageRootOrConfig */,
|
||||
false /* skipArgvConfigOption */,
|
||||
null /* parentConfigPath */,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import type {Config} from '@jest/types';
|
||||
import {readInitialOptions} from '../index';
|
||||
|
||||
describe(readInitialOptions, () => {
|
||||
test('should be able to use serialized jest config', async () => {
|
||||
const inputConfig = {jestConfig: 'serialized'};
|
||||
const {config, configPath} = await readInitialOptions(
|
||||
JSON.stringify(inputConfig),
|
||||
);
|
||||
expect(config).toEqual({...inputConfig, rootDir: process.cwd()});
|
||||
expect(configPath).toBeNull();
|
||||
});
|
||||
|
||||
test('should allow deserialized options', async () => {
|
||||
const inputConfig = {jestConfig: 'deserialized'};
|
||||
const {config, configPath} = await readInitialOptions(undefined, {
|
||||
packageRootOrConfig: inputConfig as Config.InitialOptions,
|
||||
parentConfigDirname: process.cwd(),
|
||||
});
|
||||
expect(config).toEqual({...inputConfig, rootDir: process.cwd()});
|
||||
expect(configPath).toBeNull();
|
||||
});
|
||||
// Note: actual file reading is tested in e2e test
|
||||
});
|
|
@ -43,56 +43,18 @@ export async function readConfig(
|
|||
projectIndex = Infinity,
|
||||
skipMultipleConfigError = false,
|
||||
): Promise<ReadConfig> {
|
||||
let rawOptions: Config.InitialOptions;
|
||||
let configPath = null;
|
||||
|
||||
if (typeof packageRootOrConfig !== 'string') {
|
||||
if (parentConfigDirname) {
|
||||
rawOptions = packageRootOrConfig;
|
||||
rawOptions.rootDir = rawOptions.rootDir
|
||||
? replaceRootDirInPath(parentConfigDirname, rawOptions.rootDir)
|
||||
: parentConfigDirname;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Jest: Cannot use configuration as an object without a file path.',
|
||||
);
|
||||
}
|
||||
} else if (isJSONString(argv.config)) {
|
||||
// A JSON string was passed to `--config` argument and we can parse it
|
||||
// and use as is.
|
||||
let config;
|
||||
try {
|
||||
config = JSON.parse(argv.config);
|
||||
} catch {
|
||||
throw new Error(
|
||||
'There was an error while parsing the `--config` argument as a JSON string.',
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: we might need to resolve this dir to an absolute path in the future
|
||||
config.rootDir = config.rootDir || packageRootOrConfig;
|
||||
rawOptions = config;
|
||||
// A string passed to `--config`, which is either a direct path to the config
|
||||
// or a path to directory containing `package.json`, `jest.config.js` or `jest.config.ts`
|
||||
} else if (!skipArgvConfigOption && typeof argv.config == 'string') {
|
||||
configPath = resolveConfigPath(
|
||||
argv.config,
|
||||
process.cwd(),
|
||||
skipMultipleConfigError,
|
||||
);
|
||||
rawOptions = await readConfigFileAndSetRootDir(configPath);
|
||||
} else {
|
||||
// Otherwise just try to find config in the current rootDir.
|
||||
configPath = resolveConfigPath(
|
||||
const {config: initialOptions, configPath} = await readInitialOptions(
|
||||
argv.config,
|
||||
{
|
||||
packageRootOrConfig,
|
||||
process.cwd(),
|
||||
parentConfigDirname,
|
||||
readFromCwd: skipArgvConfigOption,
|
||||
skipMultipleConfigError,
|
||||
);
|
||||
rawOptions = await readConfigFileAndSetRootDir(configPath);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const {options, hasDeprecationWarnings} = await normalize(
|
||||
rawOptions,
|
||||
initialOptions,
|
||||
argv,
|
||||
configPath,
|
||||
projectIndex,
|
||||
|
@ -267,6 +229,90 @@ This usually means that your ${chalk.bold(
|
|||
}
|
||||
};
|
||||
|
||||
export interface ReadJestConfigOptions {
|
||||
/**
|
||||
* The package root or deserialized config (default is cwd)
|
||||
*/
|
||||
packageRootOrConfig?: string | Config.InitialOptions;
|
||||
/**
|
||||
* When the `packageRootOrConfig` contains config, this parameter should
|
||||
* contain the dirname of the parent config
|
||||
*/
|
||||
parentConfigDirname?: null | string;
|
||||
/**
|
||||
* Indicates whether or not to read the specified config file from disk.
|
||||
* When true, jest will read try to read config from the current working directory.
|
||||
* (default is false)
|
||||
*/
|
||||
readFromCwd?: boolean;
|
||||
/**
|
||||
* Indicates whether or not to ignore the error of jest finding multiple config files.
|
||||
* (default is false)
|
||||
*/
|
||||
skipMultipleConfigError?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the jest config, without validating them or filling it out with defaults.
|
||||
* @param config The path to the file or serialized config.
|
||||
* @param param1 Additional options
|
||||
* @returns The raw initial config (not validated)
|
||||
*/
|
||||
export async function readInitialOptions(
|
||||
config?: string,
|
||||
{
|
||||
packageRootOrConfig = process.cwd(),
|
||||
parentConfigDirname = null,
|
||||
readFromCwd = false,
|
||||
skipMultipleConfigError = false,
|
||||
}: ReadJestConfigOptions = {},
|
||||
): Promise<{config: Config.InitialOptions; configPath: string | null}> {
|
||||
if (typeof packageRootOrConfig !== 'string') {
|
||||
if (parentConfigDirname) {
|
||||
const rawOptions = packageRootOrConfig;
|
||||
rawOptions.rootDir = rawOptions.rootDir
|
||||
? replaceRootDirInPath(parentConfigDirname, rawOptions.rootDir)
|
||||
: parentConfigDirname;
|
||||
return {config: rawOptions, configPath: null};
|
||||
} else {
|
||||
throw new Error(
|
||||
'Jest: Cannot use configuration as an object without a file path.',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (isJSONString(config)) {
|
||||
try {
|
||||
// A JSON string was passed to `--config` argument and we can parse it
|
||||
// and use as is.
|
||||
const initialOptions = JSON.parse(config);
|
||||
// NOTE: we might need to resolve this dir to an absolute path in the future
|
||||
initialOptions.rootDir = initialOptions.rootDir || packageRootOrConfig;
|
||||
return {config: initialOptions, configPath: null};
|
||||
} catch {
|
||||
throw new Error(
|
||||
'There was an error while parsing the `--config` argument as a JSON string.',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!readFromCwd && typeof config == 'string') {
|
||||
// A string passed to `--config`, which is either a direct path to the config
|
||||
// or a path to directory containing `package.json`, `jest.config.js` or `jest.config.ts`
|
||||
const configPath = resolveConfigPath(
|
||||
config,
|
||||
process.cwd(),
|
||||
skipMultipleConfigError,
|
||||
);
|
||||
return {config: await readConfigFileAndSetRootDir(configPath), configPath};
|
||||
}
|
||||
// Otherwise just try to find config in the current rootDir.
|
||||
const configPath = resolveConfigPath(
|
||||
packageRootOrConfig,
|
||||
process.cwd(),
|
||||
skipMultipleConfigError,
|
||||
);
|
||||
return {config: await readConfigFileAndSetRootDir(configPath), configPath};
|
||||
}
|
||||
|
||||
// Possible scenarios:
|
||||
// 1. jest --config config.json
|
||||
// 2. jest --projects p1 p2
|
||||
|
|
Loading…
Reference in New Issue