@backstage/cli-module-test-jest 0.0.0-nightly-20260317031259

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @backstage/cli-module-test-jest
2
+
3
+ ## 0.0.0-nightly-20260317031259
4
+
5
+ ### Minor Changes
6
+
7
+ - 329f394: Initial release of the CLI module packages. Each module provides a set of commands that can be discovered automatically by `@backstage/cli` or executed standalone.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/cli-node@0.0.0-nightly-20260317031259
13
+ - @backstage/cli-common@0.0.0-nightly-20260317031259
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @backstage/cli-module-test-jest
2
+
3
+ CLI module that provides Jest-based testing commands for the Backstage CLI.
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ | :------------- | :---------------------------------------------------------------- |
9
+ | `package test` | Run tests, forwarding arguments to Jest, defaulting to watch mode |
10
+ | `repo test` | Run tests, forwarding arguments to Jest, defaulting to watch mode |
11
+
12
+ ## Documentation
13
+
14
+ - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
15
+ - [Backstage Documentation](https://backstage.io/docs)
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright 2024 The Backstage Authors
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ const path = require('node:path');
19
+
20
+ /* eslint-disable-next-line no-restricted-syntax */
21
+ const isLocal = require('node:fs').existsSync(
22
+ path.resolve(__dirname, '../src'),
23
+ );
24
+
25
+ if (isLocal) {
26
+ require('@backstage/cli-node/config/nodeTransform.cjs');
27
+ }
28
+
29
+ const { runCliModule } = require('@backstage/cli-node');
30
+ const cliModule = require(isLocal ? '../src/index' : '..').default;
31
+ const pkg = require('../package.json');
32
+ runCliModule({ module: cliModule, name: pkg.name, version: pkg.version });
@@ -0,0 +1,49 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ function getJestMajorVersion() {
18
+ const jestVersion = require('jest/package.json').version;
19
+ const majorVersion = parseInt(jestVersion.split('.')[0], 10);
20
+ return majorVersion;
21
+ }
22
+
23
+ function getJestEnvironment() {
24
+ const majorVersion = getJestMajorVersion();
25
+
26
+ if (majorVersion >= 30) {
27
+ try {
28
+ require.resolve('@jest/environment-jsdom-abstract');
29
+ require.resolve('jsdom');
30
+ } catch {
31
+ throw new Error(
32
+ 'Jest 30+ requires @jest/environment-jsdom-abstract and jsdom. ' +
33
+ 'Please install them as dev dependencies.',
34
+ );
35
+ }
36
+ return require.resolve('./jest-environment-jsdom');
37
+ }
38
+ try {
39
+ require.resolve('jest-environment-jsdom');
40
+ } catch {
41
+ throw new Error(
42
+ 'Jest 29 requires jest-environment-jsdom. ' +
43
+ 'Please install it as a dev dependency.',
44
+ );
45
+ }
46
+ return require.resolve('jest-environment-jsdom');
47
+ }
48
+
49
+ module.exports = { getJestMajorVersion, getJestEnvironment };
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ const JSDOMEnvironment = require('@jest/environment-jsdom-abstract').default;
18
+ const jsdom = require('jsdom');
19
+
20
+ /**
21
+ * A custom JSDOM environment that extends the abstract base and applies
22
+ * fixes for Web API globals that are missing or incorrectly implemented
23
+ * in JSDOM.
24
+ *
25
+ * Based on https://github.com/mswjs/jest-fixed-jsdom
26
+ */
27
+ class FixedJSDOMEnvironment extends JSDOMEnvironment {
28
+ constructor(config, context) {
29
+ super(config, context, jsdom);
30
+
31
+ // Fix Web API globals that JSDOM doesn't properly expose
32
+ this.global.TextDecoder = TextDecoder;
33
+ this.global.TextEncoder = TextEncoder;
34
+ this.global.TextDecoderStream = TextDecoderStream;
35
+ this.global.TextEncoderStream = TextEncoderStream;
36
+ this.global.ReadableStream = ReadableStream;
37
+
38
+ this.global.Blob = Blob;
39
+ this.global.Headers = Headers;
40
+ this.global.FormData = FormData;
41
+ this.global.Request = Request;
42
+ this.global.Response = Response;
43
+ this.global.fetch = fetch;
44
+ this.global.AbortController = AbortController;
45
+ this.global.AbortSignal = AbortSignal;
46
+ this.global.structuredClone = structuredClone;
47
+ this.global.URL = URL;
48
+ this.global.URLSearchParams = URLSearchParams;
49
+
50
+ this.global.BroadcastChannel = BroadcastChannel;
51
+ this.global.TransformStream = TransformStream;
52
+ this.global.WritableStream = WritableStream;
53
+
54
+ // Needed to ensure `e instanceof Error` works as expected with errors thrown from
55
+ // any of the native APIs above. Without this, the JSDOM `Error` is what the test
56
+ // code will use for comparison with `e`, which fails the instanceof check.
57
+ this.global.Error = Error;
58
+ }
59
+ }
60
+
61
+ module.exports = FixedJSDOMEnvironment;
package/config/jest.js ADDED
@@ -0,0 +1,422 @@
1
+ /*
2
+ * Copyright 2020 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ const fs = require('fs-extra');
18
+ const path = require('node:path');
19
+ const crypto = require('node:crypto');
20
+ const glob = require('node:util').promisify(require('glob'));
21
+ const { version } = require('../package.json');
22
+ const paths = require('@backstage/cli-common').findPaths(process.cwd());
23
+ const {
24
+ getJestEnvironment,
25
+ getJestMajorVersion,
26
+ } = require('./getJestEnvironment');
27
+
28
+ const SRC_EXTS = ['ts', 'js', 'tsx', 'jsx', 'mts', 'cts', 'mjs', 'cjs'];
29
+
30
+ const FRONTEND_ROLES = [
31
+ 'frontend',
32
+ 'web-library',
33
+ 'common-library',
34
+ 'frontend-plugin',
35
+ 'frontend-plugin-module',
36
+ ];
37
+
38
+ const NODE_ROLES = [
39
+ 'backend',
40
+ 'cli',
41
+ 'cli-module',
42
+ 'node-library',
43
+ 'backend-plugin',
44
+ 'backend-plugin-module',
45
+ ];
46
+
47
+ const envOptions = {
48
+ oldTests: Boolean(process.env.BACKSTAGE_OLD_TESTS),
49
+ };
50
+
51
+ try {
52
+ require.resolve('react-dom/client', {
53
+ paths: [paths.targetRoot],
54
+ });
55
+ process.env.HAS_REACT_DOM_CLIENT = true;
56
+ } catch {
57
+ /* ignored */
58
+ }
59
+
60
+ /**
61
+ * A list of config keys that are valid for project-level config.
62
+ * Jest will complain if we forward any other root configuration to the projects.
63
+ *
64
+ * @type {Array<keyof import('@jest/types').Config.ProjectConfig>}
65
+ */
66
+ const projectConfigKeys = [
67
+ 'automock',
68
+ 'cache',
69
+ 'cacheDirectory',
70
+ 'clearMocks',
71
+ 'collectCoverageFrom',
72
+ 'coverageDirectory',
73
+ 'coveragePathIgnorePatterns',
74
+ 'cwd',
75
+ 'dependencyExtractor',
76
+ 'detectLeaks',
77
+ 'detectOpenHandles',
78
+ 'displayName',
79
+ 'errorOnDeprecated',
80
+ 'extensionsToTreatAsEsm',
81
+ 'fakeTimers',
82
+ 'filter',
83
+ 'forceCoverageMatch',
84
+ 'globalSetup',
85
+ 'globalTeardown',
86
+ 'globals',
87
+ 'haste',
88
+ 'id',
89
+ 'injectGlobals',
90
+ 'moduleDirectories',
91
+ 'moduleFileExtensions',
92
+ 'moduleNameMapper',
93
+ 'modulePathIgnorePatterns',
94
+ 'modulePaths',
95
+ 'openHandlesTimeout',
96
+ 'preset',
97
+ 'prettierPath',
98
+ 'resetMocks',
99
+ 'resetModules',
100
+ 'resolver',
101
+ 'restoreMocks',
102
+ 'rootDir',
103
+ 'roots',
104
+ 'runner',
105
+ 'runtime',
106
+ 'sandboxInjectedGlobals',
107
+ 'setupFiles',
108
+ 'setupFilesAfterEnv',
109
+ 'skipFilter',
110
+ 'skipNodeResolution',
111
+ 'slowTestThreshold',
112
+ 'snapshotResolver',
113
+ 'snapshotSerializers',
114
+ 'snapshotFormat',
115
+ 'testEnvironment',
116
+ 'testEnvironmentOptions',
117
+ 'testMatch',
118
+ 'testLocationInResults',
119
+ 'testPathIgnorePatterns',
120
+ 'testRegex',
121
+ 'testRunner',
122
+ 'transform',
123
+ 'transformIgnorePatterns',
124
+ 'watchPathIgnorePatterns',
125
+ 'unmockedModulePathPatterns',
126
+ 'workerIdleMemoryLimit',
127
+ ];
128
+
129
+ const transformIgnorePattern = [
130
+ '@material-ui',
131
+ 'ajv',
132
+ 'core-js',
133
+ 'jest-.*',
134
+ 'jsdom',
135
+ 'knex',
136
+ 'react',
137
+ 'react-dom',
138
+ 'highlight\\.js',
139
+ 'prismjs',
140
+ 'json-schema',
141
+ 'react-use/lib',
142
+ 'typescript',
143
+ ].join('|');
144
+
145
+ // Provides additional config that's based on the role of the target package
146
+ function getRoleConfig(role, pkgJson) {
147
+ // Only Node.js package roles support native ESM modules, frontend and common
148
+ // packages are always transpiled to CommonJS.
149
+ const moduleOpts = NODE_ROLES.includes(role)
150
+ ? {
151
+ module: {
152
+ ignoreDynamic: true,
153
+ exportInteropAnnotation: true,
154
+ },
155
+ }
156
+ : undefined;
157
+
158
+ const transform = {
159
+ '\\.(mjs|cjs|js)$': [
160
+ require.resolve('./jestSwcTransform'),
161
+ {
162
+ ...moduleOpts,
163
+ jsc: {
164
+ parser: {
165
+ syntax: 'ecmascript',
166
+ },
167
+ },
168
+ },
169
+ ],
170
+ '\\.jsx$': [
171
+ require.resolve('./jestSwcTransform'),
172
+ {
173
+ jsc: {
174
+ parser: {
175
+ syntax: 'ecmascript',
176
+ jsx: true,
177
+ },
178
+ transform: {
179
+ react: {
180
+ runtime: 'automatic',
181
+ },
182
+ },
183
+ },
184
+ },
185
+ ],
186
+ '\\.(mts|cts|ts)$': [
187
+ require.resolve('./jestSwcTransform'),
188
+ {
189
+ ...moduleOpts,
190
+ jsc: {
191
+ parser: {
192
+ syntax: 'typescript',
193
+ },
194
+ },
195
+ },
196
+ ],
197
+ '\\.tsx$': [
198
+ require.resolve('./jestSwcTransform'),
199
+ {
200
+ jsc: {
201
+ parser: {
202
+ syntax: 'typescript',
203
+ tsx: true,
204
+ },
205
+ transform: {
206
+ react: {
207
+ runtime: 'automatic',
208
+ },
209
+ },
210
+ },
211
+ },
212
+ ],
213
+ '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$':
214
+ require.resolve('./jestFileTransform.js'),
215
+ '\\.(yaml)$': require.resolve('./jestYamlTransform'),
216
+ };
217
+ if (FRONTEND_ROLES.includes(role)) {
218
+ return {
219
+ testEnvironment: getJestEnvironment(),
220
+ // The caching module loader is only used to speed up frontend tests,
221
+ // as it breaks real dynamic imports of ESM modules.
222
+ runtime: envOptions.oldTests
223
+ ? undefined
224
+ : require.resolve('./jestCachingModuleLoader'),
225
+ transform,
226
+ };
227
+ }
228
+ return {
229
+ testEnvironment: require.resolve('jest-environment-node'),
230
+ moduleFileExtensions: [...SRC_EXTS, 'json', 'node'],
231
+ // Jest doesn't let us dynamically detect type=module per transformed file,
232
+ // so we have to assume that if the entry point is ESM, all TS files are
233
+ // ESM.
234
+ //
235
+ // This means you can't switch a package to type=module until all of its
236
+ // monorepo dependencies are also type=module or does not contain any .ts
237
+ // files.
238
+ extensionsToTreatAsEsm:
239
+ pkgJson.type === 'module' ? ['.ts', '.mts'] : ['.mts'],
240
+ transform,
241
+ };
242
+ }
243
+
244
+ async function getProjectConfig(targetPath, extraConfig, extraOptions) {
245
+ const configJsPath = path.resolve(targetPath, 'jest.config.js');
246
+ const configTsPath = path.resolve(targetPath, 'jest.config.ts');
247
+ // If the package has it's own jest config, we use that instead.
248
+ if (await fs.pathExists(configJsPath)) {
249
+ return require(configJsPath);
250
+ } else if (await fs.pathExists(configTsPath)) {
251
+ return require(configTsPath);
252
+ }
253
+
254
+ // Jest config can be defined both in the root package.json and within each package. The root config
255
+ // gets forwarded to us through the `extraConfig` parameter, while the package config is read here.
256
+ // If they happen to be the same the keys will simply override each other.
257
+ // The merging of the configs is shallow, meaning e.g. all transforms are replaced if new ones are defined.
258
+ const pkgJson = await fs.readJson(path.resolve(targetPath, 'package.json'));
259
+
260
+ const options = {
261
+ ...extraConfig,
262
+ rootDir: path.resolve(targetPath, 'src'),
263
+ moduleNameMapper: {
264
+ '\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
265
+ },
266
+
267
+ // A bit more opinionated
268
+ testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`],
269
+
270
+ transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`],
271
+ ...getRoleConfig(pkgJson.backstage?.role, pkgJson),
272
+ };
273
+
274
+ options.setupFilesAfterEnv = options.setupFilesAfterEnv || [];
275
+
276
+ if (
277
+ extraOptions.rejectFrontendNetworkRequests &&
278
+ FRONTEND_ROLES.includes(pkgJson.backstage?.role)
279
+ ) {
280
+ // By adding this first we ensure that it's possible to for example override
281
+ // fetch with a mock in a custom setup file
282
+ options.setupFilesAfterEnv.unshift(
283
+ require.resolve('./jestRejectNetworkRequests.js'),
284
+ );
285
+ }
286
+
287
+ if (
288
+ options.testEnvironment === getJestEnvironment() &&
289
+ getJestMajorVersion() < 30 // Only needed when not running the custom env for Jest 30+
290
+ ) {
291
+ // FIXME https://github.com/jsdom/jsdom/issues/1724
292
+ options.setupFilesAfterEnv.unshift(require.resolve('cross-fetch/polyfill'));
293
+ }
294
+
295
+ // Use src/setupTests.* as the default location for configuring test env
296
+ for (const ext of SRC_EXTS) {
297
+ if (fs.existsSync(path.resolve(targetPath, `src/setupTests.${ext}`))) {
298
+ options.setupFilesAfterEnv.push(`<rootDir>/setupTests.${ext}`);
299
+ break;
300
+ }
301
+ }
302
+
303
+ const config = Object.assign(options, pkgJson.jest);
304
+
305
+ // The config id is a cache key that lets us share the jest cache across projects.
306
+ // If no explicit id was configured, generated one based on the configuration.
307
+ if (!config.id) {
308
+ const configHash = crypto
309
+ .createHash('sha256')
310
+ .update(version)
311
+ .update(Buffer.alloc(1))
312
+ .update(JSON.stringify(config.transform).replaceAll(paths.targetRoot, ''))
313
+ .digest('hex');
314
+ config.id = `backstage_cli_${configHash}`;
315
+ }
316
+
317
+ return config;
318
+ }
319
+
320
+ // This loads the root jest config, which in turn will either refer to a single
321
+ // configuration for the current package, or a collection of configurations for
322
+ // the target workspace packages
323
+ async function getRootConfig() {
324
+ const rootPkgJson = await fs.readJson(
325
+ paths.resolveTargetRoot('package.json'),
326
+ );
327
+
328
+ const baseCoverageConfig = {
329
+ coverageDirectory: paths.resolveTarget('coverage'),
330
+ coverageProvider: envOptions.oldTests ? 'v8' : 'babel',
331
+ collectCoverageFrom: ['**/*.{js,jsx,ts,tsx,mjs,cjs}', '!**/*.d.ts'],
332
+ };
333
+
334
+ const { rejectFrontendNetworkRequests, ...rootOptions } =
335
+ rootPkgJson.jest ?? {};
336
+ const extraRootOptions = {
337
+ rejectFrontendNetworkRequests,
338
+ };
339
+
340
+ const ws = rootPkgJson.workspaces;
341
+ const workspacePatterns = Array.isArray(ws) ? ws : ws?.packages;
342
+
343
+ // Check if we're running within a specific monorepo package. In that case just get the single project config.
344
+ if (!workspacePatterns || paths.targetRoot !== paths.targetDir) {
345
+ return getProjectConfig(
346
+ paths.targetDir,
347
+ {
348
+ ...baseCoverageConfig,
349
+ ...rootOptions,
350
+ },
351
+ extraRootOptions,
352
+ );
353
+ }
354
+
355
+ const globalRootConfig = { ...baseCoverageConfig };
356
+ const globalProjectConfig = {};
357
+
358
+ for (const [key, value] of Object.entries(rootOptions)) {
359
+ if (projectConfigKeys.includes(key)) {
360
+ globalProjectConfig[key] = value;
361
+ } else {
362
+ globalRootConfig[key] = value;
363
+ }
364
+ }
365
+
366
+ // If the target package is a workspace root, we find all packages in the
367
+ // workspace and load those in as separate jest projects instead.
368
+ const projectPaths = await Promise.all(
369
+ workspacePatterns.map(pattern =>
370
+ glob(path.join(paths.targetRoot, pattern)),
371
+ ),
372
+ ).then(_ => _.flat());
373
+
374
+ let projects = await Promise.all(
375
+ projectPaths.flat().map(async projectPath => {
376
+ const packagePath = path.resolve(projectPath, 'package.json');
377
+ if (!(await fs.pathExists(packagePath))) {
378
+ return undefined;
379
+ }
380
+
381
+ // We check for the presence of "backstage-cli test" in the package test
382
+ // script to determine whether a given package should be tested
383
+ const packageData = await fs.readJson(packagePath);
384
+ const testScript = packageData.scripts && packageData.scripts.test;
385
+ const isSupportedTestScript =
386
+ testScript?.includes('backstage-cli test') ||
387
+ testScript?.includes('backstage-cli package test');
388
+ if (testScript && isSupportedTestScript) {
389
+ return await getProjectConfig(
390
+ projectPath,
391
+ {
392
+ ...globalProjectConfig,
393
+ displayName: packageData.name,
394
+ },
395
+ extraRootOptions,
396
+ );
397
+ }
398
+
399
+ return undefined;
400
+ }),
401
+ ).then(cs => cs.filter(Boolean));
402
+
403
+ const cache = global.__backstageCli_jestSuccessCache;
404
+ if (cache) {
405
+ projects = await cache.filterConfigs(projects, globalRootConfig);
406
+ }
407
+ const watchProjectFilter = global.__backstageCli_watchProjectFilter;
408
+ if (watchProjectFilter) {
409
+ projects = await watchProjectFilter.filter(projects);
410
+ }
411
+
412
+ return {
413
+ rootDir: paths.targetRoot,
414
+ projects,
415
+ testResultsProcessor: cache
416
+ ? require.resolve('./jestCacheResultProcessor.cjs')
417
+ : undefined,
418
+ ...globalRootConfig,
419
+ };
420
+ }
421
+
422
+ module.exports = getRootConfig();
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ module.exports = async results => {
18
+ const cache = global.__backstageCli_jestSuccessCache;
19
+ if (cache) {
20
+ await cache.reportResults(results);
21
+ }
22
+ return results;
23
+ };
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Copyright 2022 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ // 'jest-runtime' is included with jest and should be kept in sync with the installed jest version
18
+ // eslint-disable-next-line @backstage/no-undeclared-imports
19
+ const { default: JestRuntime } = require('jest-runtime');
20
+
21
+ module.exports = class CachingJestRuntime extends JestRuntime {
22
+ constructor(config, ...restArgs) {
23
+ super(config, ...restArgs);
24
+ this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts');
25
+ }
26
+
27
+ // Unfortunately we need to use this unstable API to make sure that .js files
28
+ // are only loaded as modules where ESM is supported, i.e. Node.js packages.
29
+ unstable_shouldLoadAsEsm(path, ...restArgs) {
30
+ if (!this.allowLoadAsEsm) {
31
+ return false;
32
+ }
33
+ return super.unstable_shouldLoadAsEsm(path, ...restArgs);
34
+ }
35
+ };
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright 2020 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ const path = require('node:path');
18
+
19
+ module.exports = {
20
+ process(src, filename) {
21
+ const assetFilename = JSON.stringify(path.basename(filename));
22
+
23
+ if (filename.match(/\.icon\.svg$/)) {
24
+ return {
25
+ code: `const React = require('react');
26
+ const SvgIcon = require('@material-ui/core/SvgIcon').default;
27
+ module.exports = {
28
+ __esModule: true,
29
+ default: props => React.createElement(SvgIcon, props, {
30
+ $$typeof: Symbol.for('react.element'),
31
+ type: 'svg',
32
+ ref: ref,
33
+ key: null,
34
+ props: Object.assign({}, props, {
35
+ children: ${assetFilename}
36
+ })
37
+ })
38
+ };`,
39
+ };
40
+ }
41
+
42
+ return { code: `module.exports = ${assetFilename};` };
43
+ },
44
+ };