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