@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.
Files changed (25) hide show
  1. package/dist/src/executors/jest/jest.impl.js +0 -10
  2. package/dist/src/executors/jest/schema.d.ts +0 -7
  3. package/dist/src/executors/jest/schema.json +0 -5
  4. package/dist/src/generators/configuration/configuration.js +2 -5
  5. package/dist/src/generators/configuration/lib/ensure-dependencies.js +1 -1
  6. package/dist/src/generators/configuration/schema.d.ts +0 -4
  7. package/dist/src/generators/configuration/schema.json +0 -6
  8. package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +2 -0
  9. package/dist/src/generators/init/init.js +3 -5
  10. package/dist/src/generators/init/schema.json +1 -1
  11. package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.d.ts +2 -0
  12. package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.js +62 -0
  13. package/dist/src/migrations/update-23-0-0/migrate-jest-configuration-skip-setup-file.md +91 -0
  14. package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.d.ts +2 -0
  15. package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.js +476 -0
  16. package/dist/src/migrations/update-23-0-0/migrate-jest-executor-setup-file.md +169 -0
  17. package/dist/src/plugins/plugin.d.ts +3 -3
  18. package/dist/src/utils/assert-supported-jest-version.d.ts +2 -0
  19. package/dist/src/utils/assert-supported-jest-version.js +11 -0
  20. package/dist/src/utils/versions.d.ts +13 -2
  21. package/dist/src/utils/versions.js +26 -46
  22. package/migrations.json +29 -8
  23. package/package.json +16 -4
  24. package/dist/src/utils/version-utils.d.ts +0 -2
  25. 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 { CreateNodesV2 } from '@nx/devkit';
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: CreateNodesV2<JestPluginOptions>;
27
- export declare const createNodesV2: CreateNodesV2<JestPluginOptions>;
26
+ export declare const createNodes: CreateNodes<JestPluginOptions>;
27
+ export declare const createNodesV2: CreateNodes<JestPluginOptions>;
@@ -0,0 +1,2 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export declare function assertSupportedJestVersion(tree: Tree): void;
@@ -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
+ }