diff --git a/docs/generated/devkit/createNodesFromFiles.md b/docs/generated/devkit/createNodesFromFiles.md index cbabd22fa4a92..4c3b09737d4a3 100644 --- a/docs/generated/devkit/createNodesFromFiles.md +++ b/docs/generated/devkit/createNodesFromFiles.md @@ -10,12 +10,12 @@ #### Parameters -| Name | Type | -| :------------ | :-------------------------------------------------------------------- | -| `createNodes` | [`CreateNodesFunction`](../../devkit/documents/CreateNodesFunction) | -| `configFiles` | readonly `string`[] | -| `options` | `T` | -| `context` | [`CreateNodesContextV2`](../../devkit/documents/CreateNodesContextV2) | +| Name | Type | +| :------------ | :------------------------------------------------------------------------- | +| `createNodes` | [`CreateNodesFunction`](../../devkit/documents/CreateNodesFunction)\<`T`\> | +| `configFiles` | readonly `string`[] | +| `options` | `T` | +| `context` | [`CreateNodesContextV2`](../../devkit/documents/CreateNodesContextV2) | #### Returns diff --git a/packages/gradle/src/plugin/nodes.ts b/packages/gradle/src/plugin/nodes.ts index 9ae4ebed829c7..0d1021eae98d6 100644 --- a/packages/gradle/src/plugin/nodes.ts +++ b/packages/gradle/src/plugin/nodes.ts @@ -123,13 +123,14 @@ export const makeCreateNodes = }; /** - * @deprecated `{@link createNodesV2} is replacing this. Update your plugin to export its own `createNodesV2` function that wraps this one instead.` + @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead. + This function will change to the v2 function in Nx 20. */ export const createNodes: CreateNodes = [ gradleConfigGlob, (configFile, options, context) => { logger.warn( - '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will error.' + '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.' ); populateGradleReport(context.workspaceRoot); const gradleReport = getCurrentGradleReport(); diff --git a/packages/jest/plugin.ts b/packages/jest/plugin.ts index c03221112c8a8..7a196460b873c 100644 --- a/packages/jest/plugin.ts +++ b/packages/jest/plugin.ts @@ -1,5 +1,5 @@ export { createNodes, - createDependencies, + createNodesV2, JestPluginOptions, } from './src/plugins/plugin'; diff --git a/packages/jest/src/generators/init/init.ts b/packages/jest/src/generators/init/init.ts index 11c2602a402bb..2dd26a7675e34 100644 --- a/packages/jest/src/generators/init/init.ts +++ b/packages/jest/src/generators/init/init.ts @@ -9,8 +9,8 @@ import { type GeneratorCallback, type Tree, } from '@nx/devkit'; -import { addPluginV1 } from '@nx/devkit/src/utils/add-plugin'; -import { createNodes } from '../../plugins/plugin'; +import { addPlugin } from '@nx/devkit/src/utils/add-plugin'; +import { createNodesV2 } from '../../plugins/plugin'; import { getPresetExt, type JestPresetExtension, @@ -104,11 +104,11 @@ export async function jestInitGeneratorInternal( if (!tree.exists(`jest.preset.${presetExt}`)) { updateProductionFileSet(tree); if (options.addPlugin) { - await addPluginV1( + await addPlugin( tree, await createProjectGraphAsync(), '@nx/jest/plugin', - createNodes, + createNodesV2, { targetName: ['test', 'jest:test', 'jest-test'], }, diff --git a/packages/jest/src/plugins/plugin.spec.ts b/packages/jest/src/plugins/plugin.spec.ts index f2e4a177a1cc3..ac929535359e7 100644 --- a/packages/jest/src/plugins/plugin.spec.ts +++ b/packages/jest/src/plugins/plugin.spec.ts @@ -1,11 +1,11 @@ import { CreateNodesContext } from '@nx/devkit'; import { join } from 'path'; -import { createNodes } from './plugin'; +import { createNodesV2 } from './plugin'; import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; describe('@nx/jest/plugin', () => { - let createNodesFunction = createNodes[1]; + let createNodesFunction = createNodesV2[1]; let context: CreateNodesContext; let tempFs: TempFs; let cwd: string; @@ -45,46 +45,55 @@ describe('@nx/jest/plugin', () => { }, context ); - const nodes = await createNodesFunction( - 'proj/jest.config.js', + const results = await createNodesFunction( + ['proj/jest.config.js'], { targetName: 'test', }, context ); - expect(nodes.projects.proj).toMatchInlineSnapshot(` - { - "metadata": undefined, - "root": "proj", - "targets": { - "test": { - "cache": true, - "command": "jest", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], + expect(results).toMatchInlineSnapshot(` + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": undefined, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + }, }, - ], - "metadata": { - "description": "Run Jest Tests", - "technologies": [ - "jest", - ], - }, - "options": { - "cwd": "proj", }, - "outputs": [ - "{workspaceRoot}/coverage", - ], }, - }, - } + ], + ] `); }); @@ -97,8 +106,8 @@ describe('@nx/jest/plugin', () => { }, context ); - const nodes = await createNodesFunction( - 'proj/jest.config.js', + const results = await createNodesFunction( + ['proj/jest.config.js'], { targetName: 'test', ciTargetName: 'test-ci', @@ -106,95 +115,104 @@ describe('@nx/jest/plugin', () => { context ); - expect(nodes.projects.proj).toMatchInlineSnapshot(` - { - "metadata": { - "targetGroups": { - "E2E (CI)": [ - "test-ci", - "test-ci--src/unit.spec.ts", - ], - }, - }, - "root": "proj", - "targets": { - "test": { - "cache": true, - "command": "jest", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], + expect(results).toMatchInlineSnapshot(` + [ + [ + "proj/jest.config.js", + { + "projects": { + "proj": { + "metadata": { + "targetGroups": { + "E2E (CI)": [ + "test-ci", + "test-ci--src/unit.spec.ts", + ], + }, + }, + "root": "proj", + "targets": { + "test": { + "cache": true, + "command": "jest", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests", + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci": { + "cache": true, + "dependsOn": [ + "test-ci--src/unit.spec.ts", + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in CI", + "technologies": [ + "jest", + ], + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + "test-ci--src/unit.spec.ts": { + "cache": true, + "command": "jest src/unit.spec.ts", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "jest", + ], + }, + ], + "metadata": { + "description": "Run Jest Tests in src/unit.spec.ts", + "technologies": [ + "jest", + ], + }, + "options": { + "cwd": "proj", + }, + "outputs": [ + "{workspaceRoot}/coverage", + ], + }, + }, }, - ], - "metadata": { - "description": "Run Jest Tests", - "technologies": [ - "jest", - ], }, - "options": { - "cwd": "proj", - }, - "outputs": [ - "{workspaceRoot}/coverage", - ], }, - "test-ci": { - "cache": true, - "dependsOn": [ - "test-ci--src/unit.spec.ts", - ], - "executor": "nx:noop", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], - }, - ], - "metadata": { - "description": "Run Jest Tests in CI", - "technologies": [ - "jest", - ], - }, - "outputs": [ - "{workspaceRoot}/coverage", - ], - }, - "test-ci--src/unit.spec.ts": { - "cache": true, - "command": "jest src/unit.spec.ts", - "inputs": [ - "default", - "^production", - { - "externalDependencies": [ - "jest", - ], - }, - ], - "metadata": { - "description": "Run Jest Tests in src/unit.spec.ts", - "technologies": [ - "jest", - ], - }, - "options": { - "cwd": "proj", - }, - "outputs": [ - "{workspaceRoot}/coverage", - ], - }, - }, - } + ], + ] `); }); }); diff --git a/packages/jest/src/plugins/plugin.ts b/packages/jest/src/plugins/plugin.ts index 37ca2b6824b39..3a1b634c47670 100644 --- a/packages/jest/src/plugins/plugin.ts +++ b/packages/jest/src/plugins/plugin.ts @@ -1,8 +1,10 @@ import { - CreateDependencies, CreateNodes, CreateNodesContext, + createNodesFromFiles, + CreateNodesV2, joinPathFragments, + logger, normalizePath, NxJsonConfiguration, ProjectConfiguration, @@ -10,7 +12,7 @@ import { TargetConfiguration, writeJsonFile, } from '@nx/devkit'; -import { dirname, join, normalize, relative, resolve } from 'path'; +import { dirname, join, relative, resolve } from 'path'; import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; import { existsSync, readdirSync, readFileSync } from 'fs'; @@ -21,100 +23,130 @@ import { clearRequireCache } from '@nx/devkit/src/utils/config-utils'; import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/src/plugins/package-json-workspaces'; import { combineGlobPatterns } from 'nx/src/utils/globs'; import { minimatch } from 'minimatch'; +import { hashObject } from 'nx/src/devkit-internals'; export interface JestPluginOptions { targetName?: string; ciTargetName?: string; } -const cachePath = join(projectGraphCacheDirectory, 'jest.hash'); -const targetsCache = readTargetsCache(); - type JestTargets = Awaited>; -function readTargetsCache(): Record { +function readTargetsCache(cachePath: string): Record { return existsSync(cachePath) ? readJsonFile(cachePath) : {}; } -function writeTargetsToCache() { - const cache = readTargetsCache(); - writeJsonFile(cachePath, { - ...cache, - ...targetsCache, - }); +function writeTargetsToCache( + cachePath: string, + results: Record +) { + writeJsonFile(cachePath, results); } -export const createDependencies: CreateDependencies = () => { - writeTargetsToCache(); - return []; -}; - -export const createNodes: CreateNodes = [ - '**/jest.config.{cjs,mjs,js,cts,mts,ts}', - async (configFilePath, options, context) => { - const projectRoot = dirname(configFilePath); +const jestConfigGlob = '**/jest.config.{cjs,mjs,js,cts,mts,ts}'; - const packageManagerWorkspacesGlob = combineGlobPatterns( - getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot) +export const createNodesV2: CreateNodesV2 = [ + jestConfigGlob, + async (configFiles, options, context) => { + const optionsHash = hashObject(options); + const cachePath = join( + projectGraphCacheDirectory, + `jest-${optionsHash}.hash` ); - - // Do not create a project if package.json and project.json isn't there. - const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); - if ( - !siblingFiles.includes('package.json') && - !siblingFiles.includes('project.json') - ) { - return {}; - } else if ( - !siblingFiles.includes('project.json') && - siblingFiles.includes('package.json') - ) { - const path = joinPathFragments(projectRoot, 'package.json'); - - const isPackageJsonProject = minimatch( - path, - packageManagerWorkspacesGlob + const targetsCache = readTargetsCache(cachePath); + try { + return await createNodesFromFiles( + (configFile, options, context) => + createNodesInternal(configFile, options, context, targetsCache), + configFiles, + options, + context ); - - if (!isPackageJsonProject) { - return {}; - } + } finally { + writeTargetsToCache(cachePath, targetsCache); } + }, +]; - const jestConfigContent = readFileSync( - resolve(context.workspaceRoot, configFilePath), - 'utf-8' +/** + * @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead. + * This function will change to the v2 function in Nx 20. + */ +export const createNodes: CreateNodes = [ + jestConfigGlob, + (...args) => { + logger.warn( + '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.' ); - if (jestConfigContent.includes('getJestProjectsAsync()')) { - // The `getJestProjectsAsync` function uses the project graph, which leads to a - // circular dependency. We can skip this since it's no intended to be used for - // an Nx project. + return createNodesInternal(...args, {}); + }, +]; + +async function createNodesInternal( + configFilePath, + options, + context, + targetsCache: Record +) { + const projectRoot = dirname(configFilePath); + + const packageManagerWorkspacesGlob = combineGlobPatterns( + getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot) + ); + + // Do not create a project if package.json and project.json isn't there. + const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); + if ( + !siblingFiles.includes('package.json') && + !siblingFiles.includes('project.json') + ) { + return {}; + } else if ( + !siblingFiles.includes('project.json') && + siblingFiles.includes('package.json') + ) { + const path = joinPathFragments(projectRoot, 'package.json'); + + const isPackageJsonProject = minimatch(path, packageManagerWorkspacesGlob); + + if (!isPackageJsonProject) { return {}; } + } - options = normalizeOptions(options); + const jestConfigContent = readFileSync( + resolve(context.workspaceRoot, configFilePath), + 'utf-8' + ); + if (jestConfigContent.includes('getJestProjectsAsync()')) { + // The `getJestProjectsAsync` function uses the project graph, which leads to a + // circular dependency. We can skip this since it's no intended to be used for + // an Nx project. + return {}; + } - const hash = calculateHashForCreateNodes(projectRoot, options, context); - targetsCache[hash] ??= await buildJestTargets( - configFilePath, - projectRoot, - options, - context - ); + options = normalizeOptions(options); - const { targets, metadata } = targetsCache[hash]; + const hash = calculateHashForCreateNodes(projectRoot, options, context); + targetsCache[hash] ??= await buildJestTargets( + configFilePath, + projectRoot, + options, + context + ); - return { - projects: { - [projectRoot]: { - root: projectRoot, - targets, - metadata, - }, + const { targets, metadata } = targetsCache[hash]; + + return { + projects: { + [projectRoot]: { + root: projectRoot, + targets, + metadata, }, - }; - }, -]; + }, + }; +} async function buildJestTargets( configFilePath: string, diff --git a/packages/nx/src/project-graph/plugins/utils.ts b/packages/nx/src/project-graph/plugins/utils.ts index 4ab9c8e7aaeff..8ad35be4e7131 100644 --- a/packages/nx/src/project-graph/plugins/utils.ts +++ b/packages/nx/src/project-graph/plugins/utils.ts @@ -58,7 +58,7 @@ export type AsyncFn = T extends ( : never; export async function createNodesFromFiles( - createNodes: CreateNodesFunction, + createNodes: CreateNodesFunction, configFiles: readonly string[], options: T, context: CreateNodesContextV2