@nx/jest 22.2.0-beta.0 → 22.2.0-beta.2
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/migrations.json +5 -0
- package/package.json +4 -4
- package/src/migrations/update-22-2-0/convert-jest-config-to-cjs.d.ts +23 -0
- package/src/migrations/update-22-2-0/convert-jest-config-to-cjs.d.ts.map +1 -0
- package/src/migrations/update-22-2-0/convert-jest-config-to-cjs.js +204 -0
- package/src/plugins/plugin.d.ts.map +1 -1
- package/src/plugins/plugin.js +0 -11
package/migrations.json
CHANGED
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
},
|
|
30
30
|
"description": "Replace removed matcher aliases in Jest v30 with their corresponding matcher",
|
|
31
31
|
"implementation": "./src/migrations/update-21-3-0/replace-removed-matcher-aliases"
|
|
32
|
+
},
|
|
33
|
+
"convert-jest-config-to-cjs": {
|
|
34
|
+
"version": "22.2.0-beta.2",
|
|
35
|
+
"description": "Convert jest.config.ts files from ESM to CJS syntax (export default -> module.exports, import -> require) for projects using CommonJS resolution to ensure correct loading under Node.js type-stripping.",
|
|
36
|
+
"implementation": "./src/migrations/update-22-2-0/convert-jest-config-to-cjs"
|
|
32
37
|
}
|
|
33
38
|
},
|
|
34
39
|
"packageJsonUpdates": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/jest",
|
|
3
|
-
"version": "22.2.0-beta.
|
|
3
|
+
"version": "22.2.0-beta.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Nx Plugin for Jest contains executors and generators allowing your workspace to use the powerful Jest testing capabilities.",
|
|
6
6
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@jest/reporters": "^30.0.2",
|
|
39
39
|
"@jest/test-result": "^30.0.2",
|
|
40
|
-
"@nx/devkit": "22.2.0-beta.
|
|
41
|
-
"@nx/js": "22.2.0-beta.
|
|
40
|
+
"@nx/devkit": "22.2.0-beta.2",
|
|
41
|
+
"@nx/js": "22.2.0-beta.2",
|
|
42
42
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
|
43
43
|
"identity-obj-proxy": "3.0.0",
|
|
44
44
|
"jest-config": "^30.0.2",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"yargs-parser": "21.1.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"nx": "22.2.0-beta.
|
|
55
|
+
"nx": "22.2.0-beta.2"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Tree } from '@nx/devkit';
|
|
2
|
+
/**
|
|
3
|
+
* Migration to convert jest.config.ts files from ESM to CJS syntax for projects
|
|
4
|
+
* using CommonJS resolution. This is needed because Node.js type-stripping
|
|
5
|
+
* in newer versions (22+, 24+) can cause issues with ESM syntax in .ts files
|
|
6
|
+
* when the project is configured for CommonJS.
|
|
7
|
+
*
|
|
8
|
+
* This migration only runs if @nx/jest/plugin is registered in nx.json.
|
|
9
|
+
*
|
|
10
|
+
* Conversions:
|
|
11
|
+
* - `export default { ... }` -> `module.exports = { ... }`
|
|
12
|
+
* - `import { x } from 'y'` -> `const { x } = require('y')`
|
|
13
|
+
* - `import x from 'y'` -> `const x = require('y').default ?? require('y')`
|
|
14
|
+
*
|
|
15
|
+
* ESM-only features that cannot be converted (will warn user):
|
|
16
|
+
* - `import.meta`
|
|
17
|
+
* - top-level `await`
|
|
18
|
+
*
|
|
19
|
+
* Projects with `type: module` in package.json will be warned as they are
|
|
20
|
+
* incompatible with @nx/jest/plugin which forces CommonJS resolution.
|
|
21
|
+
*/
|
|
22
|
+
export default function convertJestConfigToCjs(tree: Tree): Promise<() => void>;
|
|
23
|
+
//# sourceMappingURL=convert-jest-config-to-cjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert-jest-config-to-cjs.d.ts","sourceRoot":"","sources":["../../../../../../packages/jest/src/migrations/update-22-2-0/convert-jest-config-to-cjs.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,IAAI,EACL,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAA8B,sBAAsB,CAAC,IAAI,EAAE,IAAI,uBA+F9D"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = convertJestConfigToCjs;
|
|
4
|
+
const devkit_1 = require("@nx/devkit");
|
|
5
|
+
const find_plugin_for_config_file_1 = require("@nx/devkit/src/utils/find-plugin-for-config-file");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
/**
|
|
8
|
+
* Migration to convert jest.config.ts files from ESM to CJS syntax for projects
|
|
9
|
+
* using CommonJS resolution. This is needed because Node.js type-stripping
|
|
10
|
+
* in newer versions (22+, 24+) can cause issues with ESM syntax in .ts files
|
|
11
|
+
* when the project is configured for CommonJS.
|
|
12
|
+
*
|
|
13
|
+
* This migration only runs if @nx/jest/plugin is registered in nx.json.
|
|
14
|
+
*
|
|
15
|
+
* Conversions:
|
|
16
|
+
* - `export default { ... }` -> `module.exports = { ... }`
|
|
17
|
+
* - `import { x } from 'y'` -> `const { x } = require('y')`
|
|
18
|
+
* - `import x from 'y'` -> `const x = require('y').default ?? require('y')`
|
|
19
|
+
*
|
|
20
|
+
* ESM-only features that cannot be converted (will warn user):
|
|
21
|
+
* - `import.meta`
|
|
22
|
+
* - top-level `await`
|
|
23
|
+
*
|
|
24
|
+
* Projects with `type: module` in package.json will be warned as they are
|
|
25
|
+
* incompatible with @nx/jest/plugin which forces CommonJS resolution.
|
|
26
|
+
*/
|
|
27
|
+
async function convertJestConfigToCjs(tree) {
|
|
28
|
+
// If @nx/jest/plugin not used, then there will not be any problems with graph construction, which
|
|
29
|
+
// is what we're trying to address.
|
|
30
|
+
if (!isJestPluginRegistered(tree))
|
|
31
|
+
return;
|
|
32
|
+
const { tsquery } = require('@phenomnomnominal/tsquery');
|
|
33
|
+
const jestConfigPaths = await (0, devkit_1.globAsync)(tree, ['**/jest.config.ts']);
|
|
34
|
+
const projectsWithEsmOnlyFeatures = [];
|
|
35
|
+
const projectsWithTypeModule = [];
|
|
36
|
+
const modifiedFiles = [];
|
|
37
|
+
for (const configPath of jestConfigPaths) {
|
|
38
|
+
// Skip config files that are excluded from the plugin via include/exclude patterns
|
|
39
|
+
const pluginRegistration = await (0, find_plugin_for_config_file_1.findPluginForConfigFile)(tree, '@nx/jest/plugin', configPath);
|
|
40
|
+
if (!pluginRegistration)
|
|
41
|
+
continue;
|
|
42
|
+
const projectRoot = (0, path_1.dirname)(configPath);
|
|
43
|
+
const packageJsonPath = (0, devkit_1.joinPathFragments)(projectRoot, 'package.json');
|
|
44
|
+
const rootPackageJsonPath = 'package.json';
|
|
45
|
+
// Check project-level package.json first, then root
|
|
46
|
+
let projectPackageJson = null;
|
|
47
|
+
let rootPackageJson = null;
|
|
48
|
+
if (tree.exists(packageJsonPath)) {
|
|
49
|
+
projectPackageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
|
|
50
|
+
}
|
|
51
|
+
if (tree.exists(rootPackageJsonPath)) {
|
|
52
|
+
rootPackageJson = (0, devkit_1.readJson)(tree, rootPackageJsonPath);
|
|
53
|
+
}
|
|
54
|
+
const effectiveType = projectPackageJson?.type ?? rootPackageJson?.type ?? 'commonjs'; // CJS is default if missing
|
|
55
|
+
// If type is "module", warn user - this is incompatible with @nx/jest/plugin
|
|
56
|
+
// Should not be possible, but it's possible that there's a way to get this working that we're unaware of
|
|
57
|
+
if (effectiveType === 'module') {
|
|
58
|
+
projectsWithTypeModule.push(configPath);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let content = tree.read(configPath, 'utf-8');
|
|
62
|
+
// Check for ESM-only features that can't be converted
|
|
63
|
+
const hasImportMeta = tsquery.query(content, 'MetaProperty').length > 0 ||
|
|
64
|
+
content.includes('import.meta');
|
|
65
|
+
const hasTopLevelAwait = checkForTopLevelAwait(content, tsquery);
|
|
66
|
+
if (hasImportMeta || hasTopLevelAwait) {
|
|
67
|
+
projectsWithEsmOnlyFeatures.push(configPath);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
content = convertImportsToRequire(content, tsquery);
|
|
71
|
+
content = convertExportDefaultToModuleExports(content, tsquery);
|
|
72
|
+
tree.write(configPath, content);
|
|
73
|
+
modifiedFiles.push(configPath);
|
|
74
|
+
}
|
|
75
|
+
if (modifiedFiles.length > 0) {
|
|
76
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
77
|
+
}
|
|
78
|
+
const hasWarnings = projectsWithEsmOnlyFeatures.length > 0 || projectsWithTypeModule.length > 0;
|
|
79
|
+
if (hasWarnings) {
|
|
80
|
+
return () => {
|
|
81
|
+
if (projectsWithTypeModule.length > 0) {
|
|
82
|
+
devkit_1.logger.warn(`The following projects have "type": "module" in their package.json which is incompatible ` +
|
|
83
|
+
`with @nx/jest/plugin. Consider removing "type": "module" ` +
|
|
84
|
+
`or using a different Jest configuration approach:\n` +
|
|
85
|
+
projectsWithTypeModule.map((p) => ` - ${p}`).join('\n'));
|
|
86
|
+
}
|
|
87
|
+
if (projectsWithEsmOnlyFeatures.length > 0) {
|
|
88
|
+
devkit_1.logger.warn(`The following jest.config.ts files use ESM-only features (import.meta or top-level await) ` +
|
|
89
|
+
`and could not be automatically converted to CommonJS. Please update them manually:\n` +
|
|
90
|
+
projectsWithEsmOnlyFeatures.map((p) => ` - ${p}`).join('\n'));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function checkForTopLevelAwait(content, tsquery) {
|
|
96
|
+
const ts = require('typescript');
|
|
97
|
+
// Check for await expressions that are not inside a function
|
|
98
|
+
const ast = tsquery.ast(content);
|
|
99
|
+
const awaitExpressions = tsquery.query(ast, 'AwaitExpression');
|
|
100
|
+
for (const awaitExpr of awaitExpressions) {
|
|
101
|
+
let parent = awaitExpr.parent;
|
|
102
|
+
let isInsideFunction = false;
|
|
103
|
+
while (parent) {
|
|
104
|
+
if (ts.isFunctionLike(parent)) {
|
|
105
|
+
isInsideFunction = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
parent = parent.parent;
|
|
109
|
+
}
|
|
110
|
+
if (!isInsideFunction) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function convertImportsToRequire(content, tsquery) {
|
|
117
|
+
const ts = require('typescript');
|
|
118
|
+
const ast = tsquery.ast(content);
|
|
119
|
+
const importDeclarations = tsquery.query(ast, 'ImportDeclaration');
|
|
120
|
+
if (importDeclarations.length === 0) {
|
|
121
|
+
return content;
|
|
122
|
+
}
|
|
123
|
+
// Sort imports by position (descending) to replace from end to start
|
|
124
|
+
// This preserves positions of earlier nodes
|
|
125
|
+
const sortedImports = [...importDeclarations].sort((a, b) => b.getStart() - a.getStart());
|
|
126
|
+
for (const importDecl of sortedImports) {
|
|
127
|
+
const moduleSpecifier = importDecl.moduleSpecifier
|
|
128
|
+
.getText()
|
|
129
|
+
.replace(/['"]/g, '');
|
|
130
|
+
const importClause = importDecl.importClause;
|
|
131
|
+
if (!importClause) {
|
|
132
|
+
// Side-effect import: import 'module'
|
|
133
|
+
const requireStatement = `require('${moduleSpecifier}')`;
|
|
134
|
+
content = replaceNode(content, importDecl, requireStatement);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const parts = [];
|
|
138
|
+
// Default import: import x from 'module'
|
|
139
|
+
if (importClause.name) {
|
|
140
|
+
const defaultName = importClause.name.getText();
|
|
141
|
+
parts.push(`const ${defaultName} = require('${moduleSpecifier}').default ?? require('${moduleSpecifier}')`);
|
|
142
|
+
}
|
|
143
|
+
// Named imports: import { a, b } from 'module'
|
|
144
|
+
if (importClause.namedBindings) {
|
|
145
|
+
if (ts.isNamedImports(importClause.namedBindings)) {
|
|
146
|
+
const namedImports = importClause.namedBindings.elements
|
|
147
|
+
.map((element) => {
|
|
148
|
+
const name = element.name.getText();
|
|
149
|
+
const propertyName = element.propertyName?.getText();
|
|
150
|
+
if (propertyName) {
|
|
151
|
+
return `${propertyName}: ${name}`;
|
|
152
|
+
}
|
|
153
|
+
return name;
|
|
154
|
+
})
|
|
155
|
+
.join(', ');
|
|
156
|
+
parts.push(`const { ${namedImports} } = require('${moduleSpecifier}')`);
|
|
157
|
+
}
|
|
158
|
+
else if (ts.isNamespaceImport(importClause.namedBindings)) {
|
|
159
|
+
// Namespace import: import * as x from 'module'
|
|
160
|
+
const namespaceName = importClause.namedBindings.name.getText();
|
|
161
|
+
parts.push(`const ${namespaceName} = require('${moduleSpecifier}')`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const requireStatement = parts.join(';\n');
|
|
165
|
+
content = replaceNode(content, importDecl, requireStatement);
|
|
166
|
+
}
|
|
167
|
+
return content;
|
|
168
|
+
}
|
|
169
|
+
function convertExportDefaultToModuleExports(content, tsquery) {
|
|
170
|
+
// Handle: export default { ... }
|
|
171
|
+
const exportAssignments = tsquery.query(content, 'ExportAssignment');
|
|
172
|
+
if (exportAssignments.length > 0) {
|
|
173
|
+
for (const exportAssignment of exportAssignments) {
|
|
174
|
+
const expression = exportAssignment.expression;
|
|
175
|
+
if (expression) {
|
|
176
|
+
const exportedValue = expression.getText();
|
|
177
|
+
const replacement = `module.exports = ${exportedValue}`;
|
|
178
|
+
content = replaceNode(content, exportAssignment, replacement);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return content;
|
|
183
|
+
}
|
|
184
|
+
function replaceNode(content, node, replacement) {
|
|
185
|
+
const start = node.getStart();
|
|
186
|
+
const end = node.getEnd();
|
|
187
|
+
// Remove trailing semicolon if present to avoid double semicolons
|
|
188
|
+
let endPos = end;
|
|
189
|
+
if (content[end] === ';') {
|
|
190
|
+
endPos = end + 1;
|
|
191
|
+
}
|
|
192
|
+
return content.slice(0, start) + replacement + ';' + content.slice(endPos);
|
|
193
|
+
}
|
|
194
|
+
function isJestPluginRegistered(tree) {
|
|
195
|
+
if (!tree.exists('nx.json')) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const nxJson = (0, devkit_1.readJson)(tree, 'nx.json');
|
|
199
|
+
const plugins = nxJson.plugins ?? [];
|
|
200
|
+
return plugins.some((plugin) => {
|
|
201
|
+
const pluginName = typeof plugin === 'string' ? plugin : plugin.plugin;
|
|
202
|
+
return pluginName === '@nx/jest/plugin' || pluginName === '@nx/jest';
|
|
203
|
+
});
|
|
204
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../../packages/jest/src/plugins/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,aAAa,EASd,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../../packages/jest/src/plugins/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,aAAa,EASd,MAAM,YAAY,CAAC;AA4BpB,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAmBD,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAiFxD,CAAC;AAEF,eAAO,MAAM,aAAa,kCAAc,CAAC"}
|
package/src/plugins/plugin.js
CHANGED
|
@@ -15,7 +15,6 @@ const globs_1 = require("nx/src/utils/globs");
|
|
|
15
15
|
const installation_directory_1 = require("nx/src/utils/installation-directory");
|
|
16
16
|
const plugins_1 = require("nx/src/utils/plugins");
|
|
17
17
|
const workspace_context_1 = require("nx/src/utils/workspace-context");
|
|
18
|
-
const semver_1 = require("semver");
|
|
19
18
|
const versions_1 = require("../utils/versions");
|
|
20
19
|
const pmc = (0, devkit_1.getPackageManagerCommand)();
|
|
21
20
|
function readTargetsCache(cachePath) {
|
|
@@ -122,19 +121,9 @@ async function buildJestTargets(configFilePath, projectRoot, options, context, p
|
|
|
122
121
|
module: 'commonjs',
|
|
123
122
|
customConditions: null,
|
|
124
123
|
});
|
|
125
|
-
// Jest 30 + Node.js 24 can't parse TS configs with imports.
|
|
126
|
-
// This flag does not exist in Node 20/22.
|
|
127
|
-
// https://github.com/jestjs/jest/issues/15682
|
|
128
|
-
const nodeVersion = (0, semver_1.major)(process.version);
|
|
129
124
|
const env = {
|
|
130
125
|
TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions,
|
|
131
126
|
};
|
|
132
|
-
if (nodeVersion >= 24) {
|
|
133
|
-
const currentOptions = process.env.NODE_OPTIONS || '';
|
|
134
|
-
if (!currentOptions.includes('--no-experimental-strip-types')) {
|
|
135
|
-
env.NODE_OPTIONS = (currentOptions + ' --no-experimental-strip-types').trim();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
127
|
const target = (targets[options.targetName] = {
|
|
139
128
|
command: 'jest',
|
|
140
129
|
options: {
|