1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | const fs = require('fs-extra');
|
18 | const path = require('path');
|
19 | const crypto = require('crypto');
|
20 | const glob = require('util').promisify(require('glob'));
|
21 | const { version } = require('../package.json');
|
22 |
|
23 | const envOptions = {
|
24 | nextTests: Boolean(process.env.BACKSTAGE_NEXT_TESTS),
|
25 | enableSourceMaps: Boolean(process.env.ENABLE_SOURCE_MAPS),
|
26 | };
|
27 |
|
28 | const transformIgnorePattern = [
|
29 | '@material-ui',
|
30 | '@rjsf',
|
31 | 'ajv',
|
32 | 'core-js',
|
33 | 'jest-.*',
|
34 | 'jsdom',
|
35 | 'knex',
|
36 | 'react',
|
37 | 'react-dom',
|
38 | 'highlight\\.js',
|
39 | 'prismjs',
|
40 | 'react-use',
|
41 | 'typescript',
|
42 | ].join('|');
|
43 |
|
44 |
|
45 | function getRoleConfig(role) {
|
46 | switch (role) {
|
47 | case 'frontend':
|
48 | case 'web-library':
|
49 | case 'common-library':
|
50 | case 'frontend-plugin':
|
51 | case 'frontend-plugin-module':
|
52 | return { testEnvironment: 'jsdom' };
|
53 | case 'cli':
|
54 | case 'backend':
|
55 | case 'node-library':
|
56 | case 'backend-plugin':
|
57 | case 'backend-plugin-module':
|
58 | default:
|
59 | return { testEnvironment: 'node' };
|
60 | }
|
61 | }
|
62 |
|
63 | async function getProjectConfig(targetPath, displayName) {
|
64 | const configJsPath = path.resolve(targetPath, 'jest.config.js');
|
65 | const configTsPath = path.resolve(targetPath, 'jest.config.ts');
|
66 |
|
67 | if (await fs.pathExists(configJsPath)) {
|
68 | return require(configJsPath);
|
69 | } else if (await fs.pathExists(configTsPath)) {
|
70 | return require(configTsPath);
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | const pkgJsonConfigs = [];
|
77 | let closestPkgJson = undefined;
|
78 | let currentPath = targetPath;
|
79 |
|
80 |
|
81 | for (let i = 0; i < 100; i++) {
|
82 | const packagePath = path.resolve(currentPath, 'package.json');
|
83 | const exists = fs.pathExistsSync(packagePath);
|
84 | if (exists) {
|
85 | try {
|
86 | const data = fs.readJsonSync(packagePath);
|
87 | if (!closestPkgJson) {
|
88 | closestPkgJson = data;
|
89 | }
|
90 | if (data.jest) {
|
91 | pkgJsonConfigs.unshift(data.jest);
|
92 | }
|
93 | } catch (error) {
|
94 | throw new Error(
|
95 | `Failed to parse package.json file reading jest configs, ${error}`,
|
96 | );
|
97 | }
|
98 | }
|
99 |
|
100 | const newPath = path.dirname(currentPath);
|
101 | if (newPath === currentPath) {
|
102 | break;
|
103 | }
|
104 | currentPath = newPath;
|
105 | }
|
106 |
|
107 |
|
108 | const transformModules = pkgJsonConfigs
|
109 | .flatMap(conf => {
|
110 | const modules = conf.transformModules || [];
|
111 | delete conf.transformModules;
|
112 | return modules;
|
113 | })
|
114 | .map(name => `${name}/`)
|
115 | .join('|');
|
116 | if (transformModules.length > 0) {
|
117 | console.warn(
|
118 | 'The Backstage CLI jest transformModules option is no longer used and will be ignored. All modules are now always transformed.',
|
119 | );
|
120 | }
|
121 |
|
122 | const options = {
|
123 | ...(displayName && { displayName }),
|
124 | rootDir: path.resolve(targetPath, 'src'),
|
125 | coverageDirectory: path.resolve(targetPath, 'coverage'),
|
126 | coverageProvider: envOptions.nextTests ? 'babel' : 'v8',
|
127 | collectCoverageFrom: ['**/*.{js,jsx,ts,tsx,mjs,cjs}', '!**/*.d.ts'],
|
128 | moduleNameMapper: {
|
129 | '\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
|
130 | },
|
131 |
|
132 | transform: {
|
133 | '\\.(js|jsx|ts|tsx|mjs|cjs)$': [
|
134 | require.resolve('./jestSucraseTransform.js'),
|
135 | {
|
136 | enableSourceMaps: envOptions.enableSourceMaps || envOptions.nextTests,
|
137 | },
|
138 | ],
|
139 | '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg|eot|woff|woff2|ttf)$':
|
140 | require.resolve('./jestFileTransform.js'),
|
141 | '\\.(yaml)$': require.resolve('jest-transform-yaml'),
|
142 | },
|
143 |
|
144 |
|
145 | testMatch: ['**/*.test.{js,jsx,ts,tsx,mjs,cjs}'],
|
146 |
|
147 | moduleLoader: envOptions.nextTests
|
148 | ? require.resolve('./jestCachingModuleLoader')
|
149 | : undefined,
|
150 |
|
151 | transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`],
|
152 |
|
153 | ...getRoleConfig(closestPkgJson?.backstage?.role),
|
154 | };
|
155 |
|
156 |
|
157 | if (fs.existsSync(path.resolve(targetPath, 'src/setupTests.ts'))) {
|
158 | options.setupFilesAfterEnv = ['<rootDir>/setupTests.ts'];
|
159 | }
|
160 |
|
161 | const config = Object.assign(options, ...pkgJsonConfigs);
|
162 |
|
163 |
|
164 |
|
165 | if (!config.name) {
|
166 | const configHash = crypto
|
167 | .createHash('md5')
|
168 | .update(version)
|
169 | .update(Buffer.alloc(1))
|
170 | .update(JSON.stringify(config.transform))
|
171 | .digest('hex');
|
172 | config.name = `backstage_cli_${configHash}`;
|
173 | }
|
174 |
|
175 | return config;
|
176 | }
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | async function getRootConfig() {
|
182 | const targetPath = process.cwd();
|
183 | const targetPackagePath = path.resolve(targetPath, 'package.json');
|
184 | const exists = await fs.pathExists(targetPackagePath);
|
185 |
|
186 | if (!exists) {
|
187 | return getProjectConfig(targetPath);
|
188 | }
|
189 |
|
190 |
|
191 | const data = await fs.readJson(targetPackagePath);
|
192 | const workspacePatterns = data.workspaces && data.workspaces.packages;
|
193 | if (!workspacePatterns) {
|
194 | return getProjectConfig(targetPath);
|
195 | }
|
196 |
|
197 |
|
198 |
|
199 | const projectPaths = await Promise.all(
|
200 | workspacePatterns.map(pattern => glob(path.join(targetPath, pattern))),
|
201 | ).then(_ => _.flat());
|
202 |
|
203 | const configs = await Promise.all(
|
204 | projectPaths.flat().map(async projectPath => {
|
205 | const packagePath = path.resolve(projectPath, 'package.json');
|
206 | if (!(await fs.pathExists(packagePath))) {
|
207 | return undefined;
|
208 | }
|
209 |
|
210 |
|
211 |
|
212 | const packageData = await fs.readJson(packagePath);
|
213 | const testScript = packageData.scripts && packageData.scripts.test;
|
214 | const isSupportedTestScript =
|
215 | testScript?.includes('backstage-cli test') ||
|
216 | testScript?.includes('backstage-cli package test');
|
217 | if (testScript && isSupportedTestScript) {
|
218 | return await getProjectConfig(projectPath, packageData.name);
|
219 | }
|
220 |
|
221 | return undefined;
|
222 | }),
|
223 | ).then(cs => cs.filter(Boolean));
|
224 |
|
225 | return {
|
226 | rootDir: targetPath,
|
227 | projects: configs,
|
228 | };
|
229 | }
|
230 |
|
231 | module.exports = getRootConfig();
|