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:
Patrik Oldsberg
2021-10-02 16:41:56 +02:00
parent 9697e91074
commit 185fec5c0c
4 changed files with 73 additions and 11 deletions
+5
View File
@@ -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.
+4 -1
View File
@@ -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
View File
@@ -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();
+1
View File
@@ -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",