cli: add support for using the default jest config from project roots
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The default jest configuration used by the `test` command now supports yarn workspaces. By running `backstage-cli test` in the root of a monorepo, all packages will now automatically be included in the test suite and it will run just like it does within a package. Each package in the monorepo will still use its own local jest configuration, and only packages that have `backstage-cli test` in the `test` script within `package.json` will be included.
|
||||
@@ -424,7 +424,10 @@ This command uses a default Jest configuration that is included in the CLI,
|
||||
which is set up with similar goals for speed, scale, and working within a
|
||||
monorepo. The configuration sets the `src` as the root directory, enforces the
|
||||
`.test.` infix for tests, and uses `src/setupTests.ts` as the test setup
|
||||
location.
|
||||
location. The included configuration also supports test execution at the root of
|
||||
a yarn workspaces monorepo by automatically creating one grouped configuration
|
||||
that includes all packages that have `backstage-cli test` in their package
|
||||
`test` script.
|
||||
|
||||
If needed, the configuration can be extended using a `"jest"` field in
|
||||
`package.json`, both within the target package and the monorepo root, with
|
||||
|
||||
+63
-10
@@ -16,20 +16,23 @@
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const glob = require('util').promisify(require('glob'));
|
||||
|
||||
async function getConfig() {
|
||||
async function getProjectConfig(targetPath) {
|
||||
const configJsPath = path.resolve(targetPath, 'jest.config.js');
|
||||
const configTsPath = path.resolve(targetPath, 'jest.config.ts');
|
||||
// If the package has it's own jest config, we use that instead.
|
||||
if (await fs.pathExists('jest.config.js')) {
|
||||
return require(path.resolve('jest.config.js'));
|
||||
} else if (await fs.pathExists('jest.config.ts')) {
|
||||
return require(path.resolve('jest.config.ts'));
|
||||
if (await fs.pathExists(configJsPath)) {
|
||||
return require(configJsPath);
|
||||
} else if (await fs.pathExists(configTsPath)) {
|
||||
return require(configTsPath);
|
||||
}
|
||||
|
||||
// We read all "jest" config fields in package.json files all the way to the filesystem root.
|
||||
// All configs are merged together to create the final config, with longer paths taking precedence.
|
||||
// The merging of the configs is shallow, meaning e.g. all transforms are replaced if new ones are defined.
|
||||
const pkgJsonConfigs = [];
|
||||
let currentPath = process.cwd();
|
||||
let currentPath = targetPath;
|
||||
|
||||
// Some sanity check to avoid infinite loop
|
||||
for (let i = 0; i < 100; i++) {
|
||||
@@ -70,8 +73,8 @@ async function getConfig() {
|
||||
const transformModulePattern = transformModules && `(?!${transformModules})`;
|
||||
|
||||
const options = {
|
||||
rootDir: path.resolve('src'),
|
||||
coverageDirectory: path.resolve('coverage'),
|
||||
rootDir: path.resolve(targetPath, 'src'),
|
||||
coverageDirectory: path.resolve(targetPath, 'coverage'),
|
||||
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
|
||||
@@ -96,11 +99,61 @@ async function getConfig() {
|
||||
};
|
||||
|
||||
// Use src/setupTests.ts as the default location for configuring test env
|
||||
if (fs.existsSync('src/setupTests.ts')) {
|
||||
if (fs.existsSync(path.resolve(targetPath, 'src/setupTests.ts'))) {
|
||||
options.setupFilesAfterEnv = ['<rootDir>/setupTests.ts'];
|
||||
}
|
||||
|
||||
return Object.assign(options, ...pkgJsonConfigs);
|
||||
}
|
||||
|
||||
module.exports = getConfig();
|
||||
// This loads the root jest config, which in turn will either refer to a single
|
||||
// configuration for the current package, or a collection of configurations for
|
||||
// the target workspace packages
|
||||
async function getRootConfig() {
|
||||
const targetPath = process.cwd();
|
||||
const targetPackagePath = path.resolve(targetPath, 'package.json');
|
||||
const exists = await fs.pathExists(targetPackagePath);
|
||||
|
||||
if (!exists) {
|
||||
return getProjectConfig(targetPath);
|
||||
}
|
||||
|
||||
// Check whether the current package is a workspace root or not
|
||||
const data = await fs.readJson(targetPackagePath);
|
||||
const workspacePatterns = data.workspaces && data.workspaces.packages;
|
||||
if (!workspacePatterns) {
|
||||
return getProjectConfig(targetPath);
|
||||
}
|
||||
|
||||
// If the target package is a workspace root, we find all packages in the
|
||||
// workspace and load those in as separate jest projects instead.
|
||||
const projectPaths = await Promise.all(
|
||||
workspacePatterns.map(pattern => glob(path.join(targetPath, pattern))),
|
||||
).then(_ => _.flat());
|
||||
|
||||
const configs = await Promise.all(
|
||||
projectPaths.flat().map(async projectPath => {
|
||||
const packagePath = path.resolve(projectPath, 'package.json');
|
||||
if (!(await fs.pathExists(packagePath))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We check for the presence of "backstage-cli test" in the package test
|
||||
// script to determine whether a given package should be tested
|
||||
const packageData = await fs.readJson(packagePath);
|
||||
const testScript = packageData.scripts && packageData.scripts.test;
|
||||
if (testScript && testScript.includes('backstage-cli test')) {
|
||||
return await getProjectConfig(projectPath);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}),
|
||||
).then(cs => cs.filter(Boolean));
|
||||
|
||||
return {
|
||||
rootDir: targetPath,
|
||||
projects: configs,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = getRootConfig();
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"express": "^4.17.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "9.1.0",
|
||||
"glob": "^7.1.7",
|
||||
"handlebars": "^4.7.3",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"inquirer": "^7.0.4",
|
||||
|
||||
Reference in New Issue
Block a user