@nx/jest 20.0.7 → 20.1.0-beta.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/jest",
3
- "version": "20.0.7",
3
+ "version": "20.1.0-beta.0",
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": "^29.4.1",
39
39
  "@jest/test-result": "^29.4.1",
40
- "@nx/devkit": "20.0.7",
41
- "@nx/js": "20.0.7",
40
+ "@nx/devkit": "20.1.0-beta.0",
41
+ "@nx/js": "20.1.0-beta.0",
42
42
  "@phenomnomnominal/tsquery": "~5.0.1",
43
43
  "chalk": "^4.1.0",
44
44
  "identity-obj-proxy": "3.0.0",
@@ -2,6 +2,12 @@ import { CreateNodes, CreateNodesV2 } from '@nx/devkit';
2
2
  export interface JestPluginOptions {
3
3
  targetName?: string;
4
4
  ciTargetName?: string;
5
+ /**
6
+ * Whether to use jest-config and jest-runtime to load Jest configuration and context.
7
+ * Disabling this is much faster but could be less correct since we are using our own config loader
8
+ * and test matcher instead of Jest's.
9
+ */
10
+ disableJestRuntime?: boolean;
5
11
  }
6
12
  export declare const createNodesV2: CreateNodesV2<JestPluginOptions>;
7
13
  /**
@@ -13,6 +13,8 @@ const cache_directory_1 = require("nx/src/utils/cache-directory");
13
13
  const globs_1 = require("nx/src/utils/globs");
14
14
  const path_1 = require("path");
15
15
  const version_utils_1 = require("../utils/version-utils");
16
+ const workspace_context_1 = require("nx/src/utils/workspace-context");
17
+ const node_path_1 = require("node:path");
16
18
  const pmc = (0, devkit_1.getPackageManagerCommand)();
17
19
  function readTargetsCache(cachePath) {
18
20
  return (0, fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {};
@@ -27,8 +29,10 @@ exports.createNodesV2 = [
27
29
  const optionsHash = (0, devkit_internals_1.hashObject)(options);
28
30
  const cachePath = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, `jest-${optionsHash}.hash`);
29
31
  const targetsCache = readTargetsCache(cachePath);
32
+ // Cache jest preset(s) to avoid penalties of module load times. Most of jest configs will use the same preset.
33
+ const presetCache = {};
30
34
  try {
31
- return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => createNodesInternal(configFile, options, context, targetsCache), configFiles, options, context);
35
+ return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => createNodesInternal(configFile, options, context, targetsCache, presetCache), configFiles, options, context);
32
36
  }
33
37
  finally {
34
38
  writeTargetsToCache(cachePath, targetsCache);
@@ -43,10 +47,10 @@ exports.createNodes = [
43
47
  jestConfigGlob,
44
48
  (...args) => {
45
49
  devkit_1.logger.warn('`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.');
46
- return createNodesInternal(...args, {});
50
+ return createNodesInternal(...args, {}, {});
47
51
  },
48
52
  ];
49
- async function createNodesInternal(configFilePath, options, context, targetsCache) {
53
+ async function createNodesInternal(configFilePath, options, context, targetsCache, presetCache) {
50
54
  const projectRoot = (0, path_1.dirname)(configFilePath);
51
55
  const packageManagerWorkspacesGlob = (0, globs_1.combineGlobPatterns)((0, package_json_1.getGlobPatternsFromPackageManagerWorkspaces)(context.workspaceRoot));
52
56
  // Do not create a project if package.json and project.json isn't there.
@@ -72,7 +76,7 @@ async function createNodesInternal(configFilePath, options, context, targetsCach
72
76
  }
73
77
  options = normalizeOptions(options);
74
78
  const hash = await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, options, context);
75
- targetsCache[hash] ??= await buildJestTargets(configFilePath, projectRoot, options, context);
79
+ targetsCache[hash] ??= await buildJestTargets(configFilePath, projectRoot, options, context, presetCache);
76
80
  const { targets, metadata } = targetsCache[hash];
77
81
  return {
78
82
  projects: {
@@ -84,19 +88,13 @@ async function createNodesInternal(configFilePath, options, context, targetsCach
84
88
  },
85
89
  };
86
90
  }
87
- async function buildJestTargets(configFilePath, projectRoot, options, context) {
91
+ async function buildJestTargets(configFilePath, projectRoot, options, context, presetCache) {
88
92
  const absConfigFilePath = (0, path_1.resolve)(context.workspaceRoot, configFilePath);
89
- if (require.cache[absConfigFilePath]) {
93
+ if (require.cache[absConfigFilePath])
90
94
  (0, config_utils_1.clearRequireCache)();
91
- }
92
95
  const rawConfig = await (0, config_utils_1.loadConfigFile)(absConfigFilePath);
93
- const { readConfig } = requireJestUtil('jest-config', projectRoot, context.workspaceRoot);
94
- const config = await readConfig({
95
- _: [],
96
- $0: undefined,
97
- }, rawConfig, undefined, (0, path_1.dirname)(absConfigFilePath));
98
- const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
99
96
  const targets = {};
97
+ const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
100
98
  const target = (targets[options.targetName] = {
101
99
  command: 'jest',
102
100
  options: {
@@ -116,56 +114,28 @@ async function buildJestTargets(configFilePath, projectRoot, options, context) {
116
114
  },
117
115
  });
118
116
  const cache = (target.cache = true);
119
- const inputs = (target.inputs = getInputs(namedInputs, rawConfig, projectRoot, context.workspaceRoot));
120
- const outputs = (target.outputs = getOutputs(projectRoot, config, context));
117
+ const inputs = (target.inputs = getInputs(namedInputs, rawConfig.preset, projectRoot, context.workspaceRoot, options.disableJestRuntime));
121
118
  let metadata;
122
- if (options?.ciTargetName) {
123
- // nx-ignore-next-line
124
- const { default: Runtime } = requireJestUtil('jest-runtime', projectRoot, context.workspaceRoot);
125
- const jestContext = await Runtime.createContext(config.projectConfig, {
126
- maxWorkers: 1,
127
- watchman: false,
128
- });
129
- const jest = require(resolveJestPath(projectRoot, context.workspaceRoot));
130
- const source = new jest.SearchSource(jestContext);
131
- const jestVersion = (0, version_utils_1.getInstalledJestMajorVersion)();
132
- const specs = jestVersion >= 30
133
- ? // @ts-expect-error Jest 30+ expects the project config as the second argument
134
- await source.getTestPaths(config.globalConfig, config.projectConfig)
135
- : await source.getTestPaths(config.globalConfig);
136
- const testPaths = new Set(specs.tests.map(({ path }) => path));
137
- if (testPaths.size > 0) {
138
- const groupName = 'E2E (CI)';
119
+ const groupName = 'E2E (CI)';
120
+ if (options.disableJestRuntime) {
121
+ const outputs = (target.outputs = getOutputs(projectRoot, rawConfig.coverageDirectory
122
+ ? (0, path_1.join)(context.workspaceRoot, projectRoot, rawConfig.coverageDirectory)
123
+ : undefined, undefined, context));
124
+ if (options?.ciTargetName) {
125
+ const testPaths = await getTestPaths(projectRoot, rawConfig, absConfigFilePath, context, presetCache);
139
126
  const targetGroup = [];
127
+ const dependsOn = [];
140
128
  metadata = {
141
129
  targetGroups: {
142
130
  [groupName]: targetGroup,
143
131
  },
144
132
  };
145
- const dependsOn = [];
146
- targets[options.ciTargetName] = {
147
- executor: 'nx:noop',
148
- cache: true,
149
- inputs,
150
- outputs,
151
- dependsOn,
152
- metadata: {
153
- technologies: ['jest'],
154
- description: 'Run Jest Tests in CI',
155
- nonAtomizedTarget: options.targetName,
156
- help: {
157
- command: `${pmc.exec} jest --help`,
158
- example: {
159
- options: {
160
- coverage: true,
161
- },
162
- },
163
- },
164
- },
165
- };
166
- targetGroup.push(options.ciTargetName);
133
+ const specIgnoreRegexes = rawConfig.testPathIgnorePatterns?.map((p) => new RegExp(replaceRootDirInPath(projectRoot, p)));
167
134
  for (const testPath of testPaths) {
168
135
  const relativePath = (0, devkit_1.normalizePath)((0, path_1.relative)((0, path_1.join)(context.workspaceRoot, projectRoot), testPath));
136
+ if (specIgnoreRegexes?.some((regex) => regex.test(relativePath))) {
137
+ continue;
138
+ }
169
139
  const targetName = `${options.ciTargetName}--${relativePath}`;
170
140
  dependsOn.push(targetName);
171
141
  targets[targetName] = {
@@ -191,18 +161,124 @@ async function buildJestTargets(configFilePath, projectRoot, options, context) {
191
161
  };
192
162
  targetGroup.push(targetName);
193
163
  }
164
+ if (targetGroup.length > 0) {
165
+ targets[options.ciTargetName] = {
166
+ executor: 'nx:noop',
167
+ cache: true,
168
+ inputs,
169
+ outputs,
170
+ dependsOn,
171
+ metadata: {
172
+ technologies: ['jest'],
173
+ description: 'Run Jest Tests in CI',
174
+ nonAtomizedTarget: options.targetName,
175
+ help: {
176
+ command: `${pmc.exec} jest --help`,
177
+ example: {
178
+ options: {
179
+ coverage: true,
180
+ },
181
+ },
182
+ },
183
+ },
184
+ };
185
+ targetGroup.unshift(options.ciTargetName);
186
+ }
187
+ }
188
+ }
189
+ else {
190
+ const { readConfig } = requireJestUtil('jest-config', projectRoot, context.workspaceRoot);
191
+ const config = await readConfig({
192
+ _: [],
193
+ $0: undefined,
194
+ }, rawConfig, undefined, (0, path_1.dirname)(absConfigFilePath));
195
+ const outputs = (target.outputs = getOutputs(projectRoot, config.globalConfig?.coverageDirectory, config.globalConfig?.outputFile, context));
196
+ if (options?.ciTargetName) {
197
+ // nx-ignore-next-line
198
+ const { default: Runtime } = requireJestUtil('jest-runtime', projectRoot, context.workspaceRoot);
199
+ const jestContext = await Runtime.createContext(config.projectConfig, {
200
+ maxWorkers: 1,
201
+ watchman: false,
202
+ });
203
+ const jest = require(resolveJestPath(projectRoot, context.workspaceRoot));
204
+ const source = new jest.SearchSource(jestContext);
205
+ const jestVersion = (0, version_utils_1.getInstalledJestMajorVersion)();
206
+ const specs = jestVersion >= 30
207
+ ? // @ts-expect-error Jest 30+ expects the project config as the second argument
208
+ await source.getTestPaths(config.globalConfig, config.projectConfig)
209
+ : await source.getTestPaths(config.globalConfig);
210
+ const testPaths = new Set(specs.tests.map(({ path }) => path));
211
+ if (testPaths.size > 0) {
212
+ const targetGroup = [];
213
+ metadata = {
214
+ targetGroups: {
215
+ [groupName]: targetGroup,
216
+ },
217
+ };
218
+ const dependsOn = [];
219
+ targets[options.ciTargetName] = {
220
+ executor: 'nx:noop',
221
+ cache: true,
222
+ inputs,
223
+ outputs,
224
+ dependsOn,
225
+ metadata: {
226
+ technologies: ['jest'],
227
+ description: 'Run Jest Tests in CI',
228
+ nonAtomizedTarget: options.targetName,
229
+ help: {
230
+ command: `${pmc.exec} jest --help`,
231
+ example: {
232
+ options: {
233
+ coverage: true,
234
+ },
235
+ },
236
+ },
237
+ },
238
+ };
239
+ targetGroup.push(options.ciTargetName);
240
+ for (const testPath of testPaths) {
241
+ const relativePath = (0, devkit_1.normalizePath)((0, path_1.relative)((0, path_1.join)(context.workspaceRoot, projectRoot), testPath));
242
+ const targetName = `${options.ciTargetName}--${relativePath}`;
243
+ dependsOn.push(targetName);
244
+ targets[targetName] = {
245
+ command: `jest ${relativePath}`,
246
+ cache,
247
+ inputs,
248
+ outputs,
249
+ options: {
250
+ cwd: projectRoot,
251
+ },
252
+ metadata: {
253
+ technologies: ['jest'],
254
+ description: `Run Jest Tests in ${relativePath}`,
255
+ help: {
256
+ command: `${pmc.exec} jest --help`,
257
+ example: {
258
+ options: {
259
+ coverage: true,
260
+ },
261
+ },
262
+ },
263
+ },
264
+ };
265
+ targetGroup.push(targetName);
266
+ }
267
+ }
194
268
  }
195
269
  }
196
270
  return { targets, metadata };
197
271
  }
198
- function getInputs(namedInputs, jestConfig, projectRoot, workspaceRoot) {
272
+ function getInputs(namedInputs, preset, projectRoot, workspaceRoot, disableJestRuntime) {
199
273
  const inputs = [
200
274
  ...('production' in namedInputs
201
275
  ? ['default', '^production']
202
276
  : ['default', '^default']),
203
277
  ];
204
278
  const externalDependencies = ['jest'];
205
- const presetInput = resolvePresetInput(jestConfig.preset, projectRoot, workspaceRoot);
279
+ const presetInput = disableJestRuntime
280
+ ? resolvePresetInputWithoutJestResolver(preset, projectRoot, workspaceRoot)
281
+ : resolvePresetInputWithJestResolver(preset, projectRoot, workspaceRoot);
206
282
  if (presetInput) {
207
283
  if (typeof presetInput !== 'string' &&
208
284
  'externalDependencies' in presetInput) {
@@ -215,13 +291,28 @@ function getInputs(namedInputs, jestConfig, projectRoot, workspaceRoot) {
215
291
  inputs.push({ externalDependencies });
216
292
  return inputs;
217
293
  }
294
+ function resolvePresetInputWithoutJestResolver(presetValue, projectRoot, workspaceRoot) {
295
+ if (!presetValue)
296
+ return null;
297
+ const presetPath = replaceRootDirInPath(projectRoot, presetValue);
298
+ const isNpmPackage = !presetValue.startsWith('.') && !(0, path_1.isAbsolute)(presetPath);
299
+ if (isNpmPackage) {
300
+ return { externalDependencies: [presetValue] };
301
+ }
302
+ if (presetPath.startsWith('..')) {
303
+ const relativePath = (0, path_1.relative)(workspaceRoot, (0, path_1.join)(projectRoot, presetPath));
304
+ return (0, path_1.join)('{workspaceRoot}', relativePath);
305
+ }
306
+ else {
307
+ const relativePath = (0, path_1.relative)(projectRoot, presetPath);
308
+ return (0, path_1.join)('{projectRoot}', relativePath);
309
+ }
310
+ }
218
311
  // preset resolution adapted from:
219
312
  // https://github.com/jestjs/jest/blob/c54bccd657fb4cf060898717c09f633b4da3eec4/packages/jest-config/src/normalize.ts#L122
220
- function resolvePresetInput(presetValue, projectRoot, workspaceRoot) {
221
- if (!presetValue) {
313
+ function resolvePresetInputWithJestResolver(presetValue, projectRoot, workspaceRoot) {
314
+ if (!presetValue)
222
315
  return null;
223
- }
224
- const { replaceRootDirInPath } = requireJestUtil('jest-config', projectRoot, workspaceRoot);
225
316
  let presetPath = replaceRootDirInPath(projectRoot, presetValue);
226
317
  const isNpmPackage = !presetValue.startsWith('.') && !(0, path_1.isAbsolute)(presetPath);
227
318
  presetPath = presetPath.startsWith('.')
@@ -243,7 +334,14 @@ function resolvePresetInput(presetValue, projectRoot, workspaceRoot) {
243
334
  ? (0, path_1.join)('{workspaceRoot}', (0, path_1.join)(projectRoot, relativePath))
244
335
  : (0, path_1.join)('{projectRoot}', relativePath);
245
336
  }
246
- function getOutputs(projectRoot, { globalConfig }, context) {
337
+ // Adapted from here https://github.com/jestjs/jest/blob/c13bca3/packages/jest-config/src/utils.ts#L57-L69
338
+ function replaceRootDirInPath(rootDir, filePath) {
339
+ if (!filePath.startsWith('<rootDir>')) {
340
+ return filePath;
341
+ }
342
+ return (0, path_1.resolve)(rootDir, (0, node_path_1.normalize)(`./${filePath.slice('<rootDir>'.length)}`));
343
+ }
344
+ function getOutputs(projectRoot, coverageDirectory, outputFile, context) {
247
345
  function getOutput(path) {
248
346
  const relativePath = (0, path_1.relative)((0, path_1.join)(context.workspaceRoot, projectRoot), path);
249
347
  if (relativePath.startsWith('..')) {
@@ -254,10 +352,7 @@ function getOutputs(projectRoot, { globalConfig }, context) {
254
352
  }
255
353
  }
256
354
  const outputs = [];
257
- for (const outputOption of [
258
- globalConfig.coverageDirectory,
259
- globalConfig.outputFile,
260
- ]) {
355
+ for (const outputOption of [coverageDirectory, outputFile]) {
261
356
  if (outputOption) {
262
357
  outputs.push(getOutput(outputOption));
263
358
  }
@@ -297,3 +392,56 @@ function requireJestUtil(packageName, projectRoot, workspaceRoot) {
297
392
  paths: [(0, path_1.dirname)(resolvedJestCorePaths[jestPath])],
298
393
  }));
299
394
  }
395
+ async function getTestPaths(projectRoot, rawConfig, absConfigFilePath, context, presetCache) {
396
+ const testMatch = await getJestOption(rawConfig, absConfigFilePath, 'testMatch', presetCache);
397
+ if (testMatch) {
398
+ return await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, testMatch.map((pattern) => (0, path_1.join)(projectRoot, pattern)), []);
399
+ }
400
+ else {
401
+ const testRegex = await getJestOption(rawConfig, absConfigFilePath, 'testRegex', presetCache);
402
+ if (testRegex) {
403
+ const files = [];
404
+ const testRegexes = Array.isArray(rawConfig.testRegex)
405
+ ? rawConfig.testRegex.map((r) => new RegExp(r))
406
+ : [new RegExp(rawConfig.testRegex)];
407
+ const projectFiles = await (0, workspace_context_1.getFilesInDirectoryUsingContext)(context.workspaceRoot, projectRoot);
408
+ for (const file of projectFiles) {
409
+ if (testRegexes.some((r) => r.test(file)))
410
+ files.push(file);
411
+ }
412
+ return files;
413
+ }
414
+ else {
415
+ // Default copied from https://github.com/jestjs/jest/blob/d1a2ed7/packages/jest-config/src/Defaults.ts#L84
416
+ const defaultTestMatch = [
417
+ '**/__tests__/**/*.?([mc])[jt]s?(x)',
418
+ '**/?(*.)+(spec|test).?([mc])[jt]s?(x)',
419
+ ];
420
+ return await (0, workspace_context_1.globWithWorkspaceContext)(context.workspaceRoot, defaultTestMatch.map((pattern) => (0, path_1.join)(projectRoot, pattern)), []);
421
+ }
422
+ }
423
+ }
424
+ async function getJestOption(rawConfig, absConfigFilePath, optionName, presetCache) {
425
+ if (rawConfig[optionName])
426
+ return rawConfig[optionName];
427
+ if (rawConfig.preset) {
428
+ const dir = (0, path_1.dirname)(absConfigFilePath);
429
+ const presetPath = (0, path_1.resolve)(dir, rawConfig.preset);
430
+ try {
431
+ let preset = presetCache[presetPath];
432
+ if (!preset) {
433
+ preset = await (0, config_utils_1.loadConfigFile)(presetPath);
434
+ presetCache[presetPath] = preset;
435
+ }
436
+ if (preset[optionName])
437
+ return preset[optionName];
438
+ }
439
+ catch {
440
+ // If preset fails to load, ignore the error and continue.
441
+ // This is safe and less jarring for users. They will need to fix the
442
+ // preset for Jest to run, and at that point we can read in the correct
443
+ // value.
444
+ }
445
+ }
446
+ return undefined;
447
+ }