@nx/jest 23.0.0-beta.21 → 23.0.0-beta.23
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/dist/src/executors/jest/jest.impl.js +0 -10
- package/dist/src/executors/jest/schema.d.ts +0 -7
- package/dist/src/executors/jest/schema.json +0 -5
- package/dist/src/generators/configuration/configuration.js +2 -5
- package/dist/src/generators/configuration/lib/ensure-dependencies.js +1 -1
- package/dist/src/generators/configuration/schema.d.ts +0 -4
- package/dist/src/generators/configuration/schema.json +0 -6
- package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +2 -0
- package/dist/src/generators/init/init.js +3 -5
- package/dist/src/generators/init/schema.json +1 -1
- package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.d.ts +2 -0
- package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.js +62 -0
- package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.md +91 -0
- package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.d.ts +2 -0
- package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.js +476 -0
- package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.md +169 -0
- package/dist/src/plugins/plugin.d.ts +3 -3
- package/dist/src/utils/assert-supported-jest-version.d.ts +2 -0
- package/dist/src/utils/assert-supported-jest-version.js +11 -0
- package/dist/src/utils/versions.d.ts +13 -2
- package/dist/src/utils/versions.js +26 -46
- package/migrations.json +29 -8
- package/package.json +16 -4
- package/dist/src/utils/version-utils.d.ts +0 -2
- package/dist/src/utils/version-utils.js +0 -18
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
5
|
+
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const internal_2 = require("@nx/js/internal");
|
|
7
|
+
const devkit_internals_1 = require("nx/src/devkit-internals");
|
|
8
|
+
const project_configuration_utils_1 = require("nx/src/project-graph/utils/project-configuration-utils");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const functions_1 = require("../../utils/config/functions");
|
|
11
|
+
const EXECUTOR_TO_MIGRATE = '@nx/jest:jest';
|
|
12
|
+
const ROOT_DIR_TOKEN = '<rootDir>';
|
|
13
|
+
const SETUP_FILES_AFTER_ENV = 'setupFilesAfterEnv';
|
|
14
|
+
const SETUP_FILE = 'setupFile';
|
|
15
|
+
const JEST_CONFIG = 'jestConfig';
|
|
16
|
+
const ROOT_DIR = 'rootDir';
|
|
17
|
+
let tsModule;
|
|
18
|
+
async function default_1(tree) {
|
|
19
|
+
const warnLists = {
|
|
20
|
+
unparseable: [],
|
|
21
|
+
nonLiteralRootDir: [],
|
|
22
|
+
sharedConfigConflict: [],
|
|
23
|
+
passthroughCollision: [],
|
|
24
|
+
configurationOnly: [],
|
|
25
|
+
noResolvableJestConfig: [],
|
|
26
|
+
};
|
|
27
|
+
// configPath -> the setupFile string the AST rewrite committed for it.
|
|
28
|
+
const rewrittenJestConfigs = new Map();
|
|
29
|
+
// `${project}::${target}` -> base setupFile snapshotted before mutation.
|
|
30
|
+
const baseSetupFiles = new Map();
|
|
31
|
+
// Per-project ProjectConfiguration cache so multi-target / multi-configuration
|
|
32
|
+
// projects don't re-read + re-write project.json on every callback iteration.
|
|
33
|
+
const projectConfigCache = new Map();
|
|
34
|
+
const dirtyProjects = new Set();
|
|
35
|
+
const nxJson = (0, devkit_1.readNxJson)(tree);
|
|
36
|
+
(0, internal_1.forEachExecutorOptions)(tree, EXECUTOR_TO_MIGRATE, (snapshotOptions, project, target, configuration) => {
|
|
37
|
+
if (configuration === undefined &&
|
|
38
|
+
snapshotOptions.setupFile !== undefined) {
|
|
39
|
+
baseSetupFiles.set(`${project}::${target}`, snapshotOptions.setupFile);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, internal_1.forEachExecutorOptions)(tree, EXECUTOR_TO_MIGRATE, (options, project, target, configuration) => {
|
|
43
|
+
if (options.setupFile === undefined) {
|
|
44
|
+
// Configuration passthrough that inherits the base setupFile would
|
|
45
|
+
// silently override the migrated value at runtime — warn so the user
|
|
46
|
+
// can consolidate.
|
|
47
|
+
if (configuration !== undefined &&
|
|
48
|
+
options.setupFilesAfterEnv !== undefined &&
|
|
49
|
+
baseSetupFiles.has(`${project}::${target}`)) {
|
|
50
|
+
warnLists.passthroughCollision.push(formatLocation({ project, target, configuration }));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Base target inheriting `setupFile` from `nx.json` `targetDefaults`
|
|
54
|
+
// (no own value). The defaults strip-pass would otherwise leave the
|
|
55
|
+
// jest config with no setup file at runtime — migrate now using the
|
|
56
|
+
// inherited value.
|
|
57
|
+
if (configuration === undefined) {
|
|
58
|
+
migrateInheritedSetupFile(tree, project, target, nxJson, projectConfigCache, rewrittenJestConfigs, warnLists);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const projectConfiguration = getProjectConfig(tree, project, projectConfigCache);
|
|
63
|
+
const targetOptions = projectConfiguration.targets[target]?.options;
|
|
64
|
+
const baseSetupFile = baseSetupFiles.get(`${project}::${target}`);
|
|
65
|
+
const jestConfigPath = resolveJestConfigPath(options.jestConfig, targetOptions?.jestConfig, projectConfiguration.root, target, nxJson);
|
|
66
|
+
const location = {
|
|
67
|
+
project,
|
|
68
|
+
target,
|
|
69
|
+
configuration,
|
|
70
|
+
jestConfig: jestConfigPath,
|
|
71
|
+
};
|
|
72
|
+
const hasPassthroughInScope = options.setupFilesAfterEnv !== undefined ||
|
|
73
|
+
(configuration !== undefined &&
|
|
74
|
+
targetOptions?.setupFilesAfterEnv !== undefined);
|
|
75
|
+
if (hasPassthroughInScope) {
|
|
76
|
+
warnLists.passthroughCollision.push(formatLocation(location));
|
|
77
|
+
}
|
|
78
|
+
else if (configuration !== undefined &&
|
|
79
|
+
baseSetupFile !== options.setupFile) {
|
|
80
|
+
// Configuration's setupFile diverges from base. When the configuration
|
|
81
|
+
// also overrides `jestConfig` (separate file), we can write the setup
|
|
82
|
+
// file there without leaking to the base run. Otherwise the only safe
|
|
83
|
+
// move is to bail: writing into a shared jest config would make the
|
|
84
|
+
// configuration's setup file run for every invocation.
|
|
85
|
+
if (options.jestConfig !== undefined && jestConfigPath) {
|
|
86
|
+
migrateOneJestConfig(tree, jestConfigPath, options.setupFile, location, rewrittenJestConfigs, warnLists);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
warnLists.configurationOnly.push(formatLocation(location));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (!jestConfigPath) {
|
|
93
|
+
warnLists.noResolvableJestConfig.push(formatLocation(location));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
migrateOneJestConfig(tree, jestConfigPath, options.setupFile, location, rewrittenJestConfigs, warnLists);
|
|
97
|
+
if (configuration === undefined) {
|
|
98
|
+
migrateInheritingConfigurations(tree, projectConfiguration, target, options.setupFile, jestConfigPath, location, rewrittenJestConfigs, warnLists);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (configuration) {
|
|
102
|
+
stripFromConfiguration(projectConfiguration.targets[target], configuration);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
stripFromOptions(projectConfiguration.targets[target]);
|
|
106
|
+
}
|
|
107
|
+
dirtyProjects.add(project);
|
|
108
|
+
});
|
|
109
|
+
for (const project of dirtyProjects) {
|
|
110
|
+
(0, devkit_1.updateProjectConfiguration)(tree, project, projectConfigCache.get(project));
|
|
111
|
+
}
|
|
112
|
+
const nxJsonHadSetupFile = stripSetupFileFromNxJson(tree, nxJson);
|
|
113
|
+
await (0, devkit_1.formatFiles)(tree);
|
|
114
|
+
return buildFollowUp(warnLists, nxJsonHadSetupFile);
|
|
115
|
+
}
|
|
116
|
+
function getProjectConfig(tree, project, cache) {
|
|
117
|
+
let cached = cache.get(project);
|
|
118
|
+
if (!cached) {
|
|
119
|
+
cached = (0, devkit_1.readProjectConfiguration)(tree, project);
|
|
120
|
+
cache.set(project, cached);
|
|
121
|
+
}
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
function migrateInheritingConfigurations(tree, projectConfiguration, target, baseSetupFile, baseJestConfigPath, baseLocation, rewrittenJestConfigs, warnLists) {
|
|
125
|
+
const configurations = projectConfiguration.targets[target]?.configurations ?? {};
|
|
126
|
+
for (const [configName, rawConfigOptions] of Object.entries(configurations)) {
|
|
127
|
+
const configOptions = rawConfigOptions;
|
|
128
|
+
if (configOptions.setupFile !== undefined)
|
|
129
|
+
continue;
|
|
130
|
+
if (configOptions.jestConfig === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
const configJestConfigPath = expandWorkspaceRelativePath(configOptions.jestConfig, projectConfiguration.root);
|
|
133
|
+
if (configJestConfigPath === baseJestConfigPath)
|
|
134
|
+
continue;
|
|
135
|
+
migrateOneJestConfig(tree, configJestConfigPath, baseSetupFile, {
|
|
136
|
+
...baseLocation,
|
|
137
|
+
configuration: configName,
|
|
138
|
+
jestConfig: configJestConfigPath,
|
|
139
|
+
}, rewrittenJestConfigs, warnLists);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function migrateOneJestConfig(tree, jestConfigPath, setupFile, location, rewrittenJestConfigs, warnLists) {
|
|
143
|
+
const previouslyMigrated = rewrittenJestConfigs.get(jestConfigPath);
|
|
144
|
+
if (previouslyMigrated !== undefined) {
|
|
145
|
+
if (previouslyMigrated !== setupFile) {
|
|
146
|
+
warnLists.sharedConfigConflict.push(formatLocation(location));
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const result = pushSetupFileIntoJestConfig(tree, jestConfigPath, setupFile);
|
|
151
|
+
switch (result) {
|
|
152
|
+
case 'written':
|
|
153
|
+
case 'already-present':
|
|
154
|
+
rewrittenJestConfigs.set(jestConfigPath, setupFile);
|
|
155
|
+
break;
|
|
156
|
+
case 'custom-root-dir-non-literal':
|
|
157
|
+
warnLists.nonLiteralRootDir.push(formatLocation(location));
|
|
158
|
+
break;
|
|
159
|
+
case 'unparseable':
|
|
160
|
+
warnLists.unparseable.push(formatLocation(location));
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// For a base target that doesn't declare its own `setupFile` but inherits
|
|
165
|
+
// one from `nx.json` `targetDefaults`, migrate the inherited value into the
|
|
166
|
+
// project's jest config — otherwise stripping `setupFile` from `nx.json`
|
|
167
|
+
// would silently drop the setup file for every inheriting target at runtime.
|
|
168
|
+
function migrateInheritedSetupFile(tree, project, target, nxJson, projectConfigCache, rewrittenJestConfigs, warnLists) {
|
|
169
|
+
if (!nxJson?.targetDefaults)
|
|
170
|
+
return;
|
|
171
|
+
const matched = (0, project_configuration_utils_1.readTargetDefaultsForTarget)(target, nxJson.targetDefaults, EXECUTOR_TO_MIGRATE);
|
|
172
|
+
const inheritedSetupFile = matched?.options?.[SETUP_FILE];
|
|
173
|
+
if (inheritedSetupFile === undefined)
|
|
174
|
+
return;
|
|
175
|
+
const projectConfiguration = getProjectConfig(tree, project, projectConfigCache);
|
|
176
|
+
const targetOptions = projectConfiguration.targets[target]?.options;
|
|
177
|
+
const expandedSetupFile = expandWorkspaceRelativePath(inheritedSetupFile, projectConfiguration.root);
|
|
178
|
+
const jestConfigPath = resolveJestConfigPath(undefined, targetOptions?.jestConfig, projectConfiguration.root, target, nxJson);
|
|
179
|
+
const location = {
|
|
180
|
+
project,
|
|
181
|
+
target,
|
|
182
|
+
jestConfig: jestConfigPath,
|
|
183
|
+
};
|
|
184
|
+
if (!jestConfigPath) {
|
|
185
|
+
warnLists.noResolvableJestConfig.push(formatLocation(location));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
migrateOneJestConfig(tree, jestConfigPath, expandedSetupFile, location, rewrittenJestConfigs, warnLists);
|
|
189
|
+
}
|
|
190
|
+
function stripSetupFileFromNxJson(tree, nxJson) {
|
|
191
|
+
if (!nxJson?.targetDefaults)
|
|
192
|
+
return false;
|
|
193
|
+
let changed = false;
|
|
194
|
+
let hadSetupFile = false;
|
|
195
|
+
for (const [targetOrExecutor, targetConfig] of Object.entries(nxJson.targetDefaults)) {
|
|
196
|
+
if (targetOrExecutor !== EXECUTOR_TO_MIGRATE &&
|
|
197
|
+
targetConfig.executor !== EXECUTOR_TO_MIGRATE) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (targetConfig.options?.setupFile !== undefined) {
|
|
201
|
+
hadSetupFile = true;
|
|
202
|
+
changed = true;
|
|
203
|
+
delete targetConfig.options.setupFile;
|
|
204
|
+
if (!Object.keys(targetConfig.options).length) {
|
|
205
|
+
delete targetConfig.options;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (const config of Object.keys(targetConfig.configurations ?? {})) {
|
|
209
|
+
if (targetConfig.configurations[config]?.setupFile !== undefined) {
|
|
210
|
+
hadSetupFile = true;
|
|
211
|
+
changed = true;
|
|
212
|
+
delete targetConfig.configurations[config].setupFile;
|
|
213
|
+
if (!Object.keys(targetConfig.configurations[config]).length &&
|
|
214
|
+
(!targetConfig.defaultConfiguration ||
|
|
215
|
+
targetConfig.defaultConfiguration !== config)) {
|
|
216
|
+
delete targetConfig.configurations[config];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (targetConfig.configurations &&
|
|
221
|
+
!Object.keys(targetConfig.configurations).length) {
|
|
222
|
+
delete targetConfig.configurations;
|
|
223
|
+
}
|
|
224
|
+
if (!Object.keys(targetConfig).length ||
|
|
225
|
+
(Object.keys(targetConfig).length === 1 &&
|
|
226
|
+
Object.keys(targetConfig)[0] === 'executor')) {
|
|
227
|
+
delete nxJson.targetDefaults[targetOrExecutor];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!Object.keys(nxJson.targetDefaults).length) {
|
|
231
|
+
delete nxJson.targetDefaults;
|
|
232
|
+
}
|
|
233
|
+
if (changed)
|
|
234
|
+
(0, devkit_1.updateNxJson)(tree, nxJson);
|
|
235
|
+
return hadSetupFile;
|
|
236
|
+
}
|
|
237
|
+
function buildFollowUp(warnLists, nxJsonHadSetupFile) {
|
|
238
|
+
const hasWarnings = warnLists.unparseable.length > 0 ||
|
|
239
|
+
warnLists.nonLiteralRootDir.length > 0 ||
|
|
240
|
+
warnLists.sharedConfigConflict.length > 0 ||
|
|
241
|
+
warnLists.passthroughCollision.length > 0 ||
|
|
242
|
+
warnLists.configurationOnly.length > 0 ||
|
|
243
|
+
warnLists.noResolvableJestConfig.length > 0 ||
|
|
244
|
+
nxJsonHadSetupFile;
|
|
245
|
+
if (!hasWarnings)
|
|
246
|
+
return;
|
|
247
|
+
return () => {
|
|
248
|
+
warn(warnLists.unparseable, 'The deprecated `setupFile` option of `@nx/jest:jest` was removed from the following targets, ' +
|
|
249
|
+
'but the corresponding Jest config could not be parsed automatically. Add the setup file path ' +
|
|
250
|
+
`manually to \`${SETUP_FILES_AFTER_ENV}\` in each Jest config:`);
|
|
251
|
+
warn(warnLists.nonLiteralRootDir, 'The deprecated `setupFile` option of `@nx/jest:jest` was removed from the following targets, ' +
|
|
252
|
+
'but their Jest config sets `rootDir` to a non-literal value (e.g. a function call or ' +
|
|
253
|
+
'imported variable) so the path could not be migrated automatically. Add the setup file path ' +
|
|
254
|
+
`to \`${SETUP_FILES_AFTER_ENV}\` in each Jest config using the correct \`rootDir\`-relative path:`);
|
|
255
|
+
warn(warnLists.sharedConfigConflict, 'The following targets reuse a Jest config that another target already migrated with a ' +
|
|
256
|
+
`different \`setupFile\`. Their \`setupFile\` was removed but not added to \`${SETUP_FILES_AFTER_ENV}\`, ` +
|
|
257
|
+
'since per-target setup files require separate Jest configs. Either give each target its own ' +
|
|
258
|
+
'Jest config or merge the setup files in the shared config:');
|
|
259
|
+
warn(warnLists.passthroughCollision, 'The following targets had both `setupFile` (now removed) and a `setupFilesAfterEnv` option in ' +
|
|
260
|
+
'the same scope. Pre-migration the executor was overriding the passthrough silently; ' +
|
|
261
|
+
'post-migration the passthrough wins, which may change behavior. Consolidate the setup files ' +
|
|
262
|
+
`manually under \`${SETUP_FILES_AFTER_ENV}\` in either the target options or the Jest config:`);
|
|
263
|
+
warn(warnLists.configurationOnly, 'The following targets declared `setupFile` only under a named configuration (or with a value ' +
|
|
264
|
+
"different from the base target's `setupFile`). Pre-migration that setup file ran only when " +
|
|
265
|
+
'the configuration was selected. Configuration-scoped setup files cannot be expressed in a ' +
|
|
266
|
+
'shared Jest config without leaking to the base run, so the option was removed without being ' +
|
|
267
|
+
'migrated. Add the setup file to a configuration-scoped Jest config or guard it via ' +
|
|
268
|
+
'`process.env.NX_TASK_TARGET_CONFIGURATION`:');
|
|
269
|
+
warn(warnLists.noResolvableJestConfig, 'The following targets had a `setupFile` option but no resolvable `jestConfig` (neither in the ' +
|
|
270
|
+
'target options nor in `nx.json` target defaults). The deprecated option was removed; add the ' +
|
|
271
|
+
`setup file path to \`${SETUP_FILES_AFTER_ENV}\` in the Jest config you intend the target to use:`);
|
|
272
|
+
if (nxJsonHadSetupFile) {
|
|
273
|
+
devkit_1.logger.warn('Removed the deprecated `setupFile` option from the `@nx/jest:jest` target defaults in `nx.json`. ' +
|
|
274
|
+
"If you relied on this default, add the setup file path to each project's Jest config under " +
|
|
275
|
+
`\`${SETUP_FILES_AFTER_ENV}\`.`);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function warn(items, header) {
|
|
280
|
+
if (items.length === 0)
|
|
281
|
+
return;
|
|
282
|
+
devkit_1.logger.warn(`${header}\n${items.map((p) => ` - ${p}`).join('\n')}`);
|
|
283
|
+
}
|
|
284
|
+
function formatLocation(loc) {
|
|
285
|
+
const targetRef = loc.configuration
|
|
286
|
+
? `${loc.target}:${loc.configuration}`
|
|
287
|
+
: loc.target;
|
|
288
|
+
const configPart = loc.jestConfig ? ` (${loc.jestConfig})` : '';
|
|
289
|
+
return `${loc.project} -> ${targetRef}${configPart}`;
|
|
290
|
+
}
|
|
291
|
+
function stripFromOptions(target) {
|
|
292
|
+
delete target.options.setupFile;
|
|
293
|
+
if (!Object.keys(target.options).length) {
|
|
294
|
+
delete target.options;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function stripFromConfiguration(target, configuration) {
|
|
298
|
+
delete target.configurations[configuration].setupFile;
|
|
299
|
+
if (!Object.keys(target.configurations[configuration]).length &&
|
|
300
|
+
(!target.defaultConfiguration ||
|
|
301
|
+
target.defaultConfiguration !== configuration)) {
|
|
302
|
+
delete target.configurations[configuration];
|
|
303
|
+
}
|
|
304
|
+
if (!Object.keys(target.configurations).length) {
|
|
305
|
+
delete target.configurations;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Falls through `callbackOptions → targetOptions → nx.json defaults`,
|
|
309
|
+
// expanding `{projectRoot}` / `{workspaceRoot}` tokens via the canonical
|
|
310
|
+
// `interpolate` helper. Defaults lookup uses `readTargetDefaultsForTarget`
|
|
311
|
+
// for the canonical executor-key → target-name → glob-match precedence.
|
|
312
|
+
function resolveJestConfigPath(callbackJestConfig, targetJestConfig, projectRoot, target, nxJson) {
|
|
313
|
+
const explicit = callbackJestConfig ?? targetJestConfig;
|
|
314
|
+
if (explicit)
|
|
315
|
+
return expandWorkspaceRelativePath(explicit, projectRoot);
|
|
316
|
+
if (!nxJson?.targetDefaults)
|
|
317
|
+
return undefined;
|
|
318
|
+
const matched = (0, project_configuration_utils_1.readTargetDefaultsForTarget)(target, nxJson.targetDefaults, EXECUTOR_TO_MIGRATE);
|
|
319
|
+
const fromDefaults = matched?.options?.[JEST_CONFIG];
|
|
320
|
+
if (fromDefaults)
|
|
321
|
+
return expandWorkspaceRelativePath(fromDefaults, projectRoot);
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
function expandWorkspaceRelativePath(value, projectRoot) {
|
|
325
|
+
return path_1.posix.normalize((0, devkit_internals_1.interpolate)(value, {
|
|
326
|
+
projectRoot: projectRoot || '.',
|
|
327
|
+
workspaceRoot: '.',
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
function pushSetupFileIntoJestConfig(tree, jestConfigPath, setupFileFromOptions) {
|
|
331
|
+
if (!tree.exists(jestConfigPath))
|
|
332
|
+
return 'unparseable';
|
|
333
|
+
const content = tree.read(jestConfigPath, 'utf-8');
|
|
334
|
+
if (!content)
|
|
335
|
+
return 'unparseable';
|
|
336
|
+
if (!tsModule) {
|
|
337
|
+
tsModule = (0, internal_2.ensureTypescript)();
|
|
338
|
+
}
|
|
339
|
+
let configObject;
|
|
340
|
+
try {
|
|
341
|
+
configObject = (0, functions_1.jestConfigObjectAst)(content);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return 'unparseable';
|
|
345
|
+
}
|
|
346
|
+
const configDir = path_1.posix.dirname(jestConfigPath);
|
|
347
|
+
const rootDirInfo = computeEffectiveRootDir(configDir, configObject);
|
|
348
|
+
if (rootDirInfo.kind === 'non-literal') {
|
|
349
|
+
return 'custom-root-dir-non-literal';
|
|
350
|
+
}
|
|
351
|
+
const effectiveRootDir = rootDirInfo.absolute;
|
|
352
|
+
const setupFileWithRootDir = toRootDirRelative(effectiveRootDir, setupFileFromOptions);
|
|
353
|
+
const properties = configObject.properties;
|
|
354
|
+
const existingIndex = properties.findIndex((p) => tsModule.isPropertyAssignment(p) &&
|
|
355
|
+
getPropertyName(p) === SETUP_FILES_AFTER_ENV);
|
|
356
|
+
const spreadIndices = properties
|
|
357
|
+
.map((p, i) => (tsModule.isSpreadAssignment(p) ? i : -1))
|
|
358
|
+
.filter((i) => i >= 0);
|
|
359
|
+
if (existingIndex >= 0) {
|
|
360
|
+
if (spreadIndices.some((i) => i > existingIndex)) {
|
|
361
|
+
return 'unparseable';
|
|
362
|
+
}
|
|
363
|
+
const existing = properties[existingIndex];
|
|
364
|
+
if (!tsModule.isArrayLiteralExpression(existing.initializer)) {
|
|
365
|
+
return 'unparseable';
|
|
366
|
+
}
|
|
367
|
+
const arr = existing.initializer;
|
|
368
|
+
const newPathResolved = resolveJestPath(setupFileWithRootDir, configDir, effectiveRootDir);
|
|
369
|
+
const alreadyPresent = arr.elements.some((e) => tsModule.isStringLiteral(e) &&
|
|
370
|
+
resolveJestPath(e.text, configDir, effectiveRootDir) === newPathResolved);
|
|
371
|
+
if (alreadyPresent)
|
|
372
|
+
return 'already-present';
|
|
373
|
+
const insertPos = arr.getEnd() - 1; // position of `]`
|
|
374
|
+
const hasElements = arr.elements.length > 0;
|
|
375
|
+
let newContent;
|
|
376
|
+
if (hasElements) {
|
|
377
|
+
const lastElement = arr.elements[arr.elements.length - 1];
|
|
378
|
+
const between = content.slice(lastElement.getEnd(), insertPos);
|
|
379
|
+
const hasTrailingComma = /,/.test(between);
|
|
380
|
+
const sep = hasTrailingComma ? ' ' : ', ';
|
|
381
|
+
newContent =
|
|
382
|
+
content.slice(0, insertPos) +
|
|
383
|
+
`${sep}'${setupFileWithRootDir}'` +
|
|
384
|
+
content.slice(insertPos);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
newContent =
|
|
388
|
+
content.slice(0, insertPos) +
|
|
389
|
+
`'${setupFileWithRootDir}'` +
|
|
390
|
+
content.slice(insertPos);
|
|
391
|
+
}
|
|
392
|
+
tree.write(jestConfigPath, newContent);
|
|
393
|
+
return 'written';
|
|
394
|
+
}
|
|
395
|
+
// Object-spread "last wins": mirror it with a nullish-coalescing fallback
|
|
396
|
+
// ordered last-spread-first so the emitted property resolves to the same
|
|
397
|
+
// array the runtime would have seen pre-migration. `as any` lets the chain
|
|
398
|
+
// compile when the spread source's type omits `setupFilesAfterEnv`.
|
|
399
|
+
const spreadExpressions = properties
|
|
400
|
+
.filter((p) => tsModule.isSpreadAssignment(p))
|
|
401
|
+
.map((p) => p.expression.getText());
|
|
402
|
+
const isTs = /\.(c|m)?tsx?$/.test(jestConfigPath);
|
|
403
|
+
const wrap = (expr) => isTs
|
|
404
|
+
? `((${expr}) as any)?.${SETUP_FILES_AFTER_ENV}`
|
|
405
|
+
: `(${expr})?.${SETUP_FILES_AFTER_ENV}`;
|
|
406
|
+
let spreadElement;
|
|
407
|
+
if (spreadExpressions.length === 1) {
|
|
408
|
+
spreadElement = `...${wrap(spreadExpressions[0])} ?? []`;
|
|
409
|
+
}
|
|
410
|
+
else if (spreadExpressions.length > 1) {
|
|
411
|
+
const fallbacks = [...spreadExpressions].reverse().map(wrap);
|
|
412
|
+
spreadElement = `...(${fallbacks.join(' ?? ')} ?? [])`;
|
|
413
|
+
}
|
|
414
|
+
const arrayLiteral = spreadElement
|
|
415
|
+
? `[${spreadElement}, '${setupFileWithRootDir}']`
|
|
416
|
+
: `['${setupFileWithRootDir}']`;
|
|
417
|
+
const newProp = `${SETUP_FILES_AFTER_ENV}: ${arrayLiteral}`;
|
|
418
|
+
const insertPos = configObject.getEnd() - 1; // position of `}`
|
|
419
|
+
const hasProps = properties.length > 0;
|
|
420
|
+
let insertion;
|
|
421
|
+
if (hasProps) {
|
|
422
|
+
const lastProp = properties[properties.length - 1];
|
|
423
|
+
const between = content.slice(lastProp.getEnd(), insertPos);
|
|
424
|
+
const hasTrailingComma = /,/.test(between);
|
|
425
|
+
insertion = hasTrailingComma ? ` ${newProp},` : `, ${newProp}`;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
insertion = newProp;
|
|
429
|
+
}
|
|
430
|
+
const newContent = content.slice(0, insertPos) + insertion + content.slice(insertPos);
|
|
431
|
+
tree.write(jestConfigPath, newContent);
|
|
432
|
+
return 'written';
|
|
433
|
+
}
|
|
434
|
+
function computeEffectiveRootDir(configDir, configObject) {
|
|
435
|
+
const rootDirNode = configObject.properties.find((p) => tsModule.isPropertyAssignment(p) && getPropertyName(p) === ROOT_DIR);
|
|
436
|
+
if (!rootDirNode) {
|
|
437
|
+
return { kind: 'static', absolute: configDir };
|
|
438
|
+
}
|
|
439
|
+
const initializer = rootDirNode.initializer;
|
|
440
|
+
if (tsModule.isStringLiteral(initializer) ||
|
|
441
|
+
tsModule.isNoSubstitutionTemplateLiteral(initializer)) {
|
|
442
|
+
const value = initializer.text;
|
|
443
|
+
if (path_1.posix.isAbsolute(value))
|
|
444
|
+
return { kind: 'non-literal' };
|
|
445
|
+
return {
|
|
446
|
+
kind: 'static',
|
|
447
|
+
absolute: path_1.posix.normalize(path_1.posix.join(configDir, value)),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return { kind: 'non-literal' };
|
|
451
|
+
}
|
|
452
|
+
function getPropertyName(p) {
|
|
453
|
+
if (tsModule.isIdentifier(p.name) ||
|
|
454
|
+
tsModule.isStringLiteral(p.name) ||
|
|
455
|
+
tsModule.isNoSubstitutionTemplateLiteral(p.name)) {
|
|
456
|
+
return p.name.text;
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
function toRootDirRelative(effectiveRootDir, workspacePath) {
|
|
461
|
+
return `${ROOT_DIR_TOKEN}/${path_1.posix.relative(effectiveRootDir, workspacePath)}`;
|
|
462
|
+
}
|
|
463
|
+
// Normalize a `setupFilesAfterEnv` entry to a workspace-root-relative path
|
|
464
|
+
// for dedup. Handles `<rootDir>/...`, `./...`, and absolute strings.
|
|
465
|
+
function resolveJestPath(rawValue, configDir, rootDir) {
|
|
466
|
+
if (rawValue.startsWith(ROOT_DIR_TOKEN)) {
|
|
467
|
+
const rest = rawValue.slice(ROOT_DIR_TOKEN.length).replace(/^\/+/, '');
|
|
468
|
+
return path_1.posix.normalize(path_1.posix.join(rootDir, rest));
|
|
469
|
+
}
|
|
470
|
+
if (path_1.posix.isAbsolute(rawValue))
|
|
471
|
+
return rawValue;
|
|
472
|
+
if (rawValue.startsWith('./') || rawValue.startsWith('../')) {
|
|
473
|
+
return path_1.posix.normalize(path_1.posix.join(configDir, rawValue));
|
|
474
|
+
}
|
|
475
|
+
return rawValue;
|
|
476
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#### Migrate `setupFile` Option to `setupFilesAfterEnv`
|
|
2
|
+
|
|
3
|
+
Migrates the previously deprecated `setupFile` option of the `@nx/jest:jest` executor. The setup file path is appended to the `setupFilesAfterEnv` array in the project's Jest configuration (using `<rootDir>/...` form), and the deprecated option is removed from `project.json` and `nx.json` target defaults.
|
|
4
|
+
|
|
5
|
+
If the Jest configuration cannot be parsed automatically (e.g. it exports a factory function or assigns `setupFilesAfterEnv` to a non-array value), the deprecated option is still removed and a warning is logged listing the affected projects so the setup file path can be moved manually.
|
|
6
|
+
|
|
7
|
+
#### Examples
|
|
8
|
+
|
|
9
|
+
Push the setup file into the project's Jest configuration and remove the option from `project.json`:
|
|
10
|
+
|
|
11
|
+
##### Before
|
|
12
|
+
|
|
13
|
+
```json title="apps/myapp/project.json" {7}
|
|
14
|
+
{
|
|
15
|
+
"targets": {
|
|
16
|
+
"test": {
|
|
17
|
+
"executor": "@nx/jest:jest",
|
|
18
|
+
"options": {
|
|
19
|
+
"jestConfig": "apps/myapp/jest.config.ts",
|
|
20
|
+
"setupFile": "apps/myapp/src/test-setup.ts"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts title="apps/myapp/jest.config.ts"
|
|
28
|
+
export default {
|
|
29
|
+
displayName: 'myapp',
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
##### After
|
|
34
|
+
|
|
35
|
+
```json title="apps/myapp/project.json"
|
|
36
|
+
{
|
|
37
|
+
"targets": {
|
|
38
|
+
"test": {
|
|
39
|
+
"executor": "@nx/jest:jest",
|
|
40
|
+
"options": {
|
|
41
|
+
"jestConfig": "apps/myapp/jest.config.ts"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts title="apps/myapp/jest.config.ts"
|
|
49
|
+
export default {
|
|
50
|
+
displayName: 'myapp',
|
|
51
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Append to an existing `setupFilesAfterEnv` array:
|
|
56
|
+
|
|
57
|
+
##### Before
|
|
58
|
+
|
|
59
|
+
```json title="apps/myapp/project.json" {7}
|
|
60
|
+
{
|
|
61
|
+
"targets": {
|
|
62
|
+
"test": {
|
|
63
|
+
"executor": "@nx/jest:jest",
|
|
64
|
+
"options": {
|
|
65
|
+
"jestConfig": "apps/myapp/jest.config.ts",
|
|
66
|
+
"setupFile": "apps/myapp/src/test-setup.ts"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```ts title="apps/myapp/jest.config.ts"
|
|
74
|
+
export default {
|
|
75
|
+
displayName: 'myapp',
|
|
76
|
+
setupFilesAfterEnv: ['<rootDir>/src/existing-setup.ts'],
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
##### After
|
|
81
|
+
|
|
82
|
+
```json title="apps/myapp/project.json"
|
|
83
|
+
{
|
|
84
|
+
"targets": {
|
|
85
|
+
"test": {
|
|
86
|
+
"executor": "@nx/jest:jest",
|
|
87
|
+
"options": {
|
|
88
|
+
"jestConfig": "apps/myapp/jest.config.ts"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```ts title="apps/myapp/jest.config.ts"
|
|
96
|
+
export default {
|
|
97
|
+
displayName: 'myapp',
|
|
98
|
+
setupFilesAfterEnv: [
|
|
99
|
+
'<rootDir>/src/existing-setup.ts',
|
|
100
|
+
'<rootDir>/src/test-setup.ts',
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Remove the option from a target default using the `@nx/jest:jest` executor:
|
|
106
|
+
|
|
107
|
+
##### Before
|
|
108
|
+
|
|
109
|
+
```json title="nx.json" {7}
|
|
110
|
+
{
|
|
111
|
+
"targetDefaults": {
|
|
112
|
+
"test": {
|
|
113
|
+
"executor": "@nx/jest:jest",
|
|
114
|
+
"options": {
|
|
115
|
+
"jestConfig": "{projectRoot}/jest.config.ts",
|
|
116
|
+
"setupFile": "{projectRoot}/src/test-setup.ts"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
##### After
|
|
124
|
+
|
|
125
|
+
```json title="nx.json"
|
|
126
|
+
{
|
|
127
|
+
"targetDefaults": {
|
|
128
|
+
"test": {
|
|
129
|
+
"executor": "@nx/jest:jest",
|
|
130
|
+
"options": {
|
|
131
|
+
"jestConfig": "{projectRoot}/jest.config.ts"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Per-project paths don't make sense as workspace defaults, so the option is removed without rewriting individual project Jest configs. A warning is logged so the setup file path can be added to each project's Jest config manually if needed.
|
|
139
|
+
|
|
140
|
+
Remove the option from a target default using the `@nx/jest:jest` executor as the key:
|
|
141
|
+
|
|
142
|
+
##### Before
|
|
143
|
+
|
|
144
|
+
```json title="nx.json" {6}
|
|
145
|
+
{
|
|
146
|
+
"targetDefaults": {
|
|
147
|
+
"@nx/jest:jest": {
|
|
148
|
+
"options": {
|
|
149
|
+
"jestConfig": "{projectRoot}/jest.config.ts",
|
|
150
|
+
"setupFile": "{projectRoot}/src/test-setup.ts"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
##### After
|
|
158
|
+
|
|
159
|
+
```json title="nx.json"
|
|
160
|
+
{
|
|
161
|
+
"targetDefaults": {
|
|
162
|
+
"@nx/jest:jest": {
|
|
163
|
+
"options": {
|
|
164
|
+
"jestConfig": "{projectRoot}/jest.config.ts"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CreateNodes } from '@nx/devkit';
|
|
2
2
|
export interface JestPluginOptions {
|
|
3
3
|
targetName?: string;
|
|
4
4
|
ciTargetName?: string;
|
|
@@ -23,5 +23,5 @@ export interface JestPluginOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
useJestResolver?: boolean;
|
|
25
25
|
}
|
|
26
|
-
export declare const createNodes:
|
|
27
|
-
export declare const createNodesV2:
|
|
26
|
+
export declare const createNodes: CreateNodes<JestPluginOptions>;
|
|
27
|
+
export declare const createNodesV2: CreateNodes<JestPluginOptions>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertSupportedJestVersion = assertSupportedJestVersion;
|
|
4
|
+
const internal_1 = require("@nx/devkit/internal");
|
|
5
|
+
const versions_1 = require("./versions");
|
|
6
|
+
function assertSupportedJestVersion(tree) {
|
|
7
|
+
(0, internal_1.assertSupportedPackageVersion)(tree, 'jest', versions_1.minSupportedJestVersion);
|
|
8
|
+
// ts-jest installs on an independent versioning train from jest, so its
|
|
9
|
+
// floor is asserted separately.
|
|
10
|
+
(0, internal_1.assertSupportedPackageVersion)(tree, 'ts-jest', versions_1.minSupportedTsJestVersion);
|
|
11
|
+
}
|