From 06717de5903b3007523be70e38e99a0333239ce1 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Fri, 19 Jan 2024 18:22:11 -0500 Subject: [PATCH] feat(core): move target defaults handling to nx plugin (#21104) --- packages/jest/src/plugins/plugin.ts | 2 +- packages/nx/plugins/package-json.ts | 2 +- .../generators/utils/project-configuration.ts | 21 +- .../run-migration-against-this-workspace.ts | 6 +- packages/nx/src/plugins/js/index.ts | 2 +- .../create-nodes.spec.ts} | 4 +- .../package-json-workspaces/create-nodes.ts} | 22 +- .../plugins/package-json-workspaces/index.ts | 1 + .../package-json-next-to-project-json.spec.ts | 4 +- .../package-json-next-to-project-json.ts | 6 +- .../build-nodes/project-json.spec.ts | 4 +- .../project-json/build-nodes/project-json.ts | 4 +- .../target-defaults-plugin.spec.ts | 80 ++++++ .../target-defaults/target-defaults-plugin.ts | 129 +++++++++ .../src/project-graph/build-project-graph.ts | 2 +- packages/nx/src/project-graph/file-utils.ts | 4 +- .../utils/normalize-project-nodes.spec.ts | 265 ------------------ .../utils/normalize-project-nodes.ts | 37 +-- .../utils/project-configuration-utils.spec.ts | 42 +++ .../utils/project-configuration-utils.ts | 46 ++- .../utils/retrieve-workspace-files.ts | 6 +- packages/nx/src/utils/nx-plugin.deprecated.ts | 9 +- packages/nx/src/utils/nx-plugin.ts | 12 +- 23 files changed, 353 insertions(+), 357 deletions(-) rename packages/nx/{plugins/package-json-workspaces.spec.ts => src/plugins/package-json-workspaces/create-nodes.spec.ts} (96%) rename packages/nx/{plugins/package-json-workspaces.ts => src/plugins/package-json-workspaces/create-nodes.ts} (86%) create mode 100644 packages/nx/src/plugins/package-json-workspaces/index.ts create mode 100644 packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts create mode 100644 packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts diff --git a/packages/jest/src/plugins/plugin.ts b/packages/jest/src/plugins/plugin.ts index 0bfe934ac2590..7391445ddd465 100644 --- a/packages/jest/src/plugins/plugin.ts +++ b/packages/jest/src/plugins/plugin.ts @@ -16,7 +16,7 @@ import { existsSync, readdirSync } from 'fs'; import { readConfig } from 'jest-config'; import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; -import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/plugins/package-json-workspaces'; +import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/src/plugins/package-json-workspaces'; import { combineGlobPatterns } from 'nx/src/utils/globs'; import { minimatch } from 'minimatch'; diff --git a/packages/nx/plugins/package-json.ts b/packages/nx/plugins/package-json.ts index 8e9df510bd566..9ba453f652994 100644 --- a/packages/nx/plugins/package-json.ts +++ b/packages/nx/plugins/package-json.ts @@ -1,6 +1,6 @@ import type { NxPluginV2 } from '../src/utils/nx-plugin'; import { workspaceRoot } from '../src/utils/workspace-root'; -import { createNodeFromPackageJson } from './package-json-workspaces'; +import { createNodeFromPackageJson } from '../src/plugins/package-json-workspaces'; const plugin: NxPluginV2 = { name: 'nx-all-package-jsons-plugin', diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 2d8a25f852fd7..4bf2eab1e63a4 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -4,9 +4,12 @@ import { basename, join, relative } from 'path'; import { buildProjectConfigurationFromPackageJson, getGlobPatternsFromPackageManagerWorkspaces, -} from '../../../plugins/package-json-workspaces'; -import { buildProjectFromProjectJson } from '../../plugins/project-json/build-nodes/project-json'; -import { getDefaultPluginsSync } from '../../utils/nx-plugin.deprecated'; + getNxPackageJsonWorkspacesPlugin, +} from '../../plugins/package-json-workspaces'; +import { + buildProjectFromProjectJson, + ProjectJsonProjectsPlugin, +} from '../../plugins/project-json/build-nodes/project-json'; import { renamePropertyWithStableKeys } from '../../adapter/angular-json'; import { ProjectConfiguration, @@ -16,7 +19,8 @@ import { mergeProjectConfigurationIntoRootMap, readProjectConfigurationsFromRootMap, } from '../../project-graph/utils/project-configuration-utils'; -import { retrieveProjectConfigurationPaths } from '../../project-graph/utils/retrieve-workspace-files'; +import { configurationGlobs } from '../../project-graph/utils/retrieve-workspace-files'; +import { globWithWorkspaceContext } from '../../utils/workspace-context'; import { output } from '../../utils/output'; import { PackageJson } from '../../utils/package-json'; import { joinPathFragments, normalizePath } from '../../utils/path'; @@ -191,10 +195,11 @@ function readAndCombineAllProjectConfigurations(tree: Tree): { readJson(tree, p) ), ]; - const globbedFiles = retrieveProjectConfigurationPaths( - tree.root, - getDefaultPluginsSync(tree.root) - ); + const projectGlobPatterns = configurationGlobs([ + { plugin: ProjectJsonProjectsPlugin }, + { plugin: getNxPackageJsonWorkspacesPlugin(tree.root) }, + ]); + const globbedFiles = globWithWorkspaceContext(tree.root, projectGlobPatterns); const createdFiles = findCreatedProjectFiles(tree, patterns); const deletedFiles = findDeletedProjectFiles(tree, patterns); const projectFiles = [...globbedFiles, ...createdFiles].filter( diff --git a/packages/nx/src/internal-testing-utils/run-migration-against-this-workspace.ts b/packages/nx/src/internal-testing-utils/run-migration-against-this-workspace.ts index 0d8056a2e8d83..1657f02efc32e 100644 --- a/packages/nx/src/internal-testing-utils/run-migration-against-this-workspace.ts +++ b/packages/nx/src/internal-testing-utils/run-migration-against-this-workspace.ts @@ -9,11 +9,7 @@ export function assertRunsAgainstNxRepo( let resultOrPromise: void | Promise = migrateFn(tree); if (resultOrPromise && 'then' in resultOrPromise) { - try { - await resultOrPromise; - } catch (e) { - fail(e); - } + await resultOrPromise; } }); } diff --git a/packages/nx/src/plugins/js/index.ts b/packages/nx/src/plugins/js/index.ts index d9d26a47be2dc..466340e1f2b28 100644 --- a/packages/nx/src/plugins/js/index.ts +++ b/packages/nx/src/plugins/js/index.ts @@ -25,7 +25,7 @@ import { detectPackageManager } from '../../utils/package-manager'; import { workspaceRoot } from '../../utils/workspace-root'; import { nxVersion } from '../../utils/versions'; -export const name = 'nx-js-graph-plugin'; +export const name = 'nx/js/dependencies-and-lockfile'; interface ParsedLockFile { externalNodes?: ProjectGraph['externalNodes']; diff --git a/packages/nx/plugins/package-json-workspaces.spec.ts b/packages/nx/src/plugins/package-json-workspaces/create-nodes.spec.ts similarity index 96% rename from packages/nx/plugins/package-json-workspaces.spec.ts rename to packages/nx/src/plugins/package-json-workspaces/create-nodes.spec.ts index 8fa06b6a3fbd1..dc3aa60310b61 100644 --- a/packages/nx/plugins/package-json-workspaces.spec.ts +++ b/packages/nx/src/plugins/package-json-workspaces/create-nodes.spec.ts @@ -1,7 +1,7 @@ import * as memfs from 'memfs'; -import '../src/internal-testing-utils/mock-fs'; -import { createNodeFromPackageJson } from './package-json-workspaces'; +import '../../internal-testing-utils/mock-fs'; +import { createNodeFromPackageJson } from './create-nodes'; describe('nx package.json workspaces plugin', () => { it('should build projects from package.json files', () => { diff --git a/packages/nx/plugins/package-json-workspaces.ts b/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts similarity index 86% rename from packages/nx/plugins/package-json-workspaces.ts rename to packages/nx/src/plugins/package-json-workspaces/create-nodes.ts index e94c3fb3bf7f4..88b411c53bd11 100644 --- a/packages/nx/plugins/package-json-workspaces.ts +++ b/packages/nx/src/plugins/package-json-workspaces/create-nodes.ts @@ -1,24 +1,24 @@ import { existsSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import { NxJsonConfiguration, readNxJson } from '../src/config/nx-json'; -import { ProjectConfiguration } from '../src/config/workspace-json-project-json'; -import { toProjectName } from '../src/config/workspaces'; -import { readJsonFile, readYamlFile } from '../src/utils/fileutils'; -import { combineGlobPatterns } from '../src/utils/globs'; -import { NX_PREFIX } from '../src/utils/logger'; -import { NxPluginV2 } from '../src/utils/nx-plugin'; -import { output } from '../src/utils/output'; +import { NxJsonConfiguration, readNxJson } from '../../config/nx-json'; +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; +import { toProjectName } from '../../config/workspaces'; +import { readJsonFile, readYamlFile } from '../../utils/fileutils'; +import { combineGlobPatterns } from '../../utils/globs'; +import { NX_PREFIX } from '../../utils/logger'; +import { NxPluginV2 } from '../../utils/nx-plugin'; +import { output } from '../../utils/output'; import { PackageJson, readTargetsFromPackageJson, -} from '../src/utils/package-json'; -import { joinPathFragments } from '../src/utils/path'; +} from '../../utils/package-json'; +import { joinPathFragments } from '../../utils/path'; export function getNxPackageJsonWorkspacesPlugin(root: string): NxPluginV2 { const readJson = (f) => readJsonFile(join(root, f)); return { - name: 'nx-core-build-package-json-nodes', + name: 'nx/core/package-json-workspaces', createNodes: [ combineGlobPatterns( getGlobPatternsFromPackageManagerWorkspaces(root, readJson) diff --git a/packages/nx/src/plugins/package-json-workspaces/index.ts b/packages/nx/src/plugins/package-json-workspaces/index.ts new file mode 100644 index 0000000000000..e675dd81f1475 --- /dev/null +++ b/packages/nx/src/plugins/package-json-workspaces/index.ts @@ -0,0 +1 @@ +export * from './create-nodes'; diff --git a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts index bdcc5088f74c8..688c85af1b493 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.spec.ts @@ -2,9 +2,9 @@ import * as memfs from 'memfs'; import '../../../internal-testing-utils/mock-fs'; -import { CreatePackageJsonProjectsNextToProjectJson } from './package-json-next-to-project-json'; +import { PackageJsonProjectsNextToProjectJsonPlugin } from './package-json-next-to-project-json'; import { CreateNodesContext } from '../../../utils/nx-plugin'; -const { createNodes } = CreatePackageJsonProjectsNextToProjectJson; +const { createNodes } = PackageJsonProjectsNextToProjectJsonPlugin; describe('nx project.json plugin', () => { let context: CreateNodesContext; diff --git a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts index 5b8f5e57c2045..bb241cf307a2b 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/package-json-next-to-project-json.ts @@ -10,8 +10,10 @@ import { // TODO: Remove this one day, this should not need to be done. -export const CreatePackageJsonProjectsNextToProjectJson: NxPluginV2 = { - name: 'nx-core-build-package-json-nodes-next-to-project-json-nodes', +export const PackageJsonProjectsNextToProjectJsonPlugin: NxPluginV2 = { + // Its not a problem if plugins happen to have same name, and this + // will look least confusing in the source map. + name: 'nx/core/package-json', createNodes: [ '{project.json,**/project.json}', (file, _, { workspaceRoot }) => { diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts index f4bbe1d6934d2..138be521698e9 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts @@ -2,9 +2,9 @@ import * as memfs from 'memfs'; import '../../../internal-testing-utils/mock-fs'; -import { CreateProjectJsonProjectsPlugin } from './project-json'; +import { ProjectJsonProjectsPlugin } from './project-json'; import { CreateNodesContext } from '../../../utils/nx-plugin'; -const { createNodes } = CreateProjectJsonProjectsPlugin; +const { createNodes } = ProjectJsonProjectsPlugin; describe('nx project.json plugin', () => { let context: CreateNodesContext; diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts index bfaa8d03fe056..90048a133f8b6 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts @@ -5,8 +5,8 @@ import { toProjectName } from '../../../config/workspaces'; import { readJsonFile } from '../../../utils/fileutils'; import { NxPluginV2 } from '../../../utils/nx-plugin'; -export const CreateProjectJsonProjectsPlugin: NxPluginV2 = { - name: 'nx-core-build-project-json-nodes', +export const ProjectJsonProjectsPlugin: NxPluginV2 = { + name: 'nx/core/project-json', createNodes: [ '{project.json,**/project.json}', (file, _, { workspaceRoot }) => { diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts new file mode 100644 index 0000000000000..97576e723d6ba --- /dev/null +++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.spec.ts @@ -0,0 +1,80 @@ +import * as memfs from 'memfs'; + +import '../../../src/internal-testing-utils/mock-fs'; + +import { TargetDefaultsPlugin } from './target-defaults-plugin'; +import { CreateNodesContext } from '../../utils/nx-plugin'; +const { + createNodes: [, createNodesFn], +} = TargetDefaultsPlugin; + +describe('nx project.json plugin', () => { + let context: CreateNodesContext; + beforeEach(() => { + context = { + nxJsonConfiguration: { + targetDefaults: { + build: { + dependsOn: ['^build'], + }, + }, + }, + workspaceRoot: '/root', + }; + }); + + it('should add target default info to project json projects', () => { + memfs.vol.fromJSON( + { + 'project.json': JSON.stringify({ + name: 'root', + targets: { echo: { command: 'echo root project' } }, + }), + 'packages/lib-a/project.json': JSON.stringify({ + name: 'lib-a', + targets: { + build: { + executor: 'nx:run-commands', + options: {}, + }, + }, + }), + }, + '/root' + ); + + expect(createNodesFn('project.json', undefined, context)) + .toMatchInlineSnapshot(` + { + "projects": { + ".": { + "targets": { + "build": { + "dependsOn": [ + "^build", + ], + Symbol(ONLY_MODIFIES_EXISTING_TARGET): true, + }, + }, + }, + }, + } + `); + expect(createNodesFn('packages/lib-a/project.json', undefined, context)) + .toMatchInlineSnapshot(` + { + "projects": { + "packages/lib-a": { + "targets": { + "build": { + "dependsOn": [ + "^build", + ], + }, + }, + }, + }, + } + `); + }); +}); diff --git a/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts new file mode 100644 index 0000000000000..1f1cfe1d0bb11 --- /dev/null +++ b/packages/nx/src/plugins/target-defaults/target-defaults-plugin.ts @@ -0,0 +1,129 @@ +import { minimatch } from 'minimatch'; +import { existsSync } from 'node:fs'; +import { basename, dirname, join } from 'node:path'; + +import { + ProjectConfiguration, + TargetConfiguration, +} from '../../config/workspace-json-project-json'; +import { readJsonFile } from '../../utils/fileutils'; +import { combineGlobPatterns } from '../../utils/globs'; +import { NxPluginV2 } from '../../utils/nx-plugin'; +import { PackageJson } from '../../utils/package-json'; +import { getGlobPatternsFromPackageManagerWorkspaces } from '../package-json-workspaces'; + +/** + * This symbol marks that a target provides information which should modify a target already registered + * on the project via other plugins. If the target has not already been registered, and this symbol is true, + * the information provided by it will be discarded. + */ +export const ONLY_MODIFIES_EXISTING_TARGET = Symbol( + 'ONLY_MODIFIES_EXISTING_TARGET' +); + +export const TargetDefaultsPlugin: NxPluginV2 = { + name: 'nx/core/target-defaults', + createNodes: [ + '{package.json,**/package.json,project.json,**/project.json}', + (configFile, _, ctx) => { + const fileName = basename(configFile); + const root = dirname(configFile); + + const packageManagerWorkspacesGlob = combineGlobPatterns( + getGlobPatternsFromPackageManagerWorkspaces(ctx.workspaceRoot) + ); + + // Only process once if package.json + project.json both exist + if ( + fileName === 'package.json' && + existsSync(join(ctx.workspaceRoot, root, 'project.json')) + ) { + return {}; + } else if ( + fileName === 'package.json' && + !minimatch(configFile, packageManagerWorkspacesGlob) + ) { + return {}; + } + + // If no target defaults, this does nothing + const targetDefaults = ctx.nxJsonConfiguration?.targetDefaults; + if (!targetDefaults) { + return {}; + } + + const projectJson = readJsonOrNull( + join(ctx.workspaceRoot, root, 'project.json') + ); + const packageJson = readJsonOrNull( + join(ctx.workspaceRoot, root, 'package.json') + ); + const projectDefinedTargets = new Set( + Object.keys({ + ...packageJson?.scripts, + ...projectJson?.targets, + }) + ); + + const executorToTargetMap = getExecutorToTargetMap( + packageJson, + projectJson + ); + + const newTargets: Record< + string, + TargetConfiguration & { [ONLY_MODIFIES_EXISTING_TARGET]?: boolean } + > = {}; + for (const defaultSpecifier in targetDefaults) { + const targetName = + executorToTargetMap.get(defaultSpecifier) ?? defaultSpecifier; + newTargets[targetName] = structuredClone( + targetDefaults[defaultSpecifier] + ); + // TODO: Remove this after we figure out a way to define new targets + // in target defaults + if (!projectDefinedTargets.has(targetName)) { + newTargets[targetName][ONLY_MODIFIES_EXISTING_TARGET] = true; + } + } + + return { + projects: { + [root]: { + targets: newTargets, + }, + }, + }; + }, + ], +}; + +function getExecutorToTargetMap( + packageJson: PackageJson, + projectJson: ProjectConfiguration +) { + const executorToTargetMap = new Map(); + if (packageJson?.scripts) { + for (const script in packageJson.scripts) { + executorToTargetMap.set('nx:run-script', script); + } + } + if (projectJson?.targets) { + for (const target in projectJson.targets) { + if (projectJson.targets[target].executor) { + executorToTargetMap.set(projectJson.targets[target].executor, target); + } else if (projectJson.targets[target].command) { + executorToTargetMap.set('nx:run-commands', target); + } + } + } + return executorToTargetMap; +} + +function readJsonOrNull(path: string) { + if (existsSync(path)) { + return readJsonFile(path); + } else { + return null; + } +} diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index 912694614e603..1cfaeada7ca5b 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -176,7 +176,7 @@ async function buildProjectGraphUsingContext( builder.addExternalNode(knownExternalNodes[node]); } - await normalizeProjectNodes(ctx, builder, nxJson); + await normalizeProjectNodes(ctx, builder); const initProjectGraph = builder.getUpdatedProjectGraph(); const r = await updateProjectGraphWithPlugins(ctx, initProjectGraph); diff --git a/packages/nx/src/project-graph/file-utils.ts b/packages/nx/src/project-graph/file-utils.ts index 68c66e134fee3..1760fdf383cac 100644 --- a/packages/nx/src/project-graph/file-utils.ts +++ b/packages/nx/src/project-graph/file-utils.ts @@ -26,7 +26,7 @@ import { NxJsonConfiguration } from '../config/nx-json'; import { getDefaultPluginsSync } from '../utils/nx-plugin.deprecated'; import { minimatch } from 'minimatch'; import { CreateNodesResult } from '../devkit-exports'; -import { CreatePackageJsonProjectsNextToProjectJson } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; +import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; import { LoadedNxPlugin } from '../utils/nx-plugin'; export interface Change { @@ -187,7 +187,7 @@ function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) { getDefaultPluginsSync(root) ); const plugins: LoadedNxPlugin[] = [ - { plugin: CreatePackageJsonProjectsNextToProjectJson }, + { plugin: PackageJsonProjectsNextToProjectJsonPlugin }, ...getDefaultPluginsSync(root), ]; diff --git a/packages/nx/src/project-graph/utils/normalize-project-nodes.spec.ts b/packages/nx/src/project-graph/utils/normalize-project-nodes.spec.ts index cff93f54e96a4..a34162a266b6b 100644 --- a/packages/nx/src/project-graph/utils/normalize-project-nodes.spec.ts +++ b/packages/nx/src/project-graph/utils/normalize-project-nodes.spec.ts @@ -80,104 +80,6 @@ describe('workspace-projects', () => { }); describe('normalizeTargets', () => { - it('should apply target defaults', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - executor: 'target', - options: { - a: 'a', - }, - }, - }, - }, - { - build: { - executor: 'target', - options: { - b: 'b', - }, - }, - }, - 'build' - ).build.options - ).toEqual({ a: 'a', b: 'b' }); - }); - - it('should overwrite target defaults when type doesnt match or provided an array', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - executor: 'target', - options: { - a: 'a', - b: ['project-value'], - c: 'project-value', - }, - }, - }, - }, - { - build: { - executor: 'target', - options: { - a: 1, - b: ['default-value'], - c: ['default-value'], - }, - }, - }, - 'build' - ).build.options - ).toEqual({ a: 'a', b: ['project-value'], c: 'project-value' }); - }); - - it('should overwrite object options from target defaults', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - executor: 'target', - options: { - a: 'a', - b: { - a: 'a', - b: 'project-value', - }, - }, - }, - }, - }, - { - build: { - executor: 'target', - options: { - b: { - b: 'default-value', - c: 'c', - }, - }, - }, - }, - 'build' - ).build.options - ).toEqual({ - a: 'a', - b: { - a: 'a', - b: 'project-value', - }, - }); - }); - it('should convert command property to run-commands executor', () => { expect( normalizeProjectTargets( @@ -189,7 +91,6 @@ describe('workspace-projects', () => { }, }, }, - {}, 'build' ).build ).toEqual({ @@ -201,66 +102,6 @@ describe('workspace-projects', () => { }); }); - it('should apply defaults to run-commands from syntactic sugar', () => { - const result = normalizeProjectTargets( - { - name: 'mylib', - root: 'projects/mylib', - targets: { - echo: { - command: 'echo "hello world"', - }, - }, - }, - { - 'nx:run-commands': { - options: { - cwd: '{projectRoot}', - }, - }, - }, - 'echo' - ); - expect(result.echo).toEqual({ - executor: 'nx:run-commands', - options: { - command: 'echo "hello world"', - cwd: 'projects/mylib', - }, - configurations: {}, - }); - }); - - it('should not apply defaults when executor is not nx:run-commands and using command syntactic sugar', () => { - const result = normalizeProjectTargets( - { - name: 'mylib', - root: 'projects/mylib', - targets: { - echo: { - command: 'echo "hello world"', - }, - }, - }, - { - echo: { - executor: 'nx:noop', - options: { - cwd: '{projectRoot}', - }, - }, - }, - 'echo' - ); - expect(result.echo).toEqual({ - executor: 'nx:run-commands', - options: { - command: 'echo "hello world"', - }, - configurations: {}, - }); - }); - it('should support {projectRoot}, {workspaceRoot}, and {projectName} tokens', () => { expect( normalizeProjectTargets( @@ -278,115 +119,9 @@ describe('workspace-projects', () => { }, }, }, - {}, 'build' ).build.options ).toEqual({ a: 'my/project', b: '', c: 'project' }); }); - - it('should suppport {projectRoot} token in targetDefaults', () => { - expect( - normalizeProjectTargets( - { - name: 'project', - root: 'my/project', - targets: { - build: { - executor: 'target', - options: { - a: 'a', - }, - }, - }, - }, - { - build: { - executor: 'target', - options: { - b: '{projectRoot}', - }, - }, - }, - 'build' - ).build.options - ).toEqual({ a: 'a', b: 'my/project' }); - }); - - it('should merge options when targets use executors with defaults', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - executor: '@nx/jest:jest', - options: { - a: 'a', - }, - }, - }, - }, - { - '@nx/jest:jest': { - options: { - b: 'b', - }, - }, - }, - 'build' - ).build.options - ).toEqual({ a: 'a', b: 'b' }); - }); - - it('should not merge options when targets use different executors', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - executor: 'target', - options: { - a: 'a', - }, - }, - }, - }, - { - build: { - executor: 'different-target', - options: { - b: 'c', - }, - }, - }, - 'build' - ).build.options - ).toEqual({ a: 'a' }); - }); - - it('should not merge options when either target or target defaults use `command`', () => { - expect( - normalizeProjectTargets( - { - root: 'my/project', - targets: { - build: { - command: 'echo', - }, - }, - }, - { - build: { - executor: 'target', - options: { - b: 'c', - }, - }, - }, - 'build' - ).build.options - ).toEqual({ command: 'echo' }); - }); }); }); diff --git a/packages/nx/src/project-graph/utils/normalize-project-nodes.ts b/packages/nx/src/project-graph/utils/normalize-project-nodes.ts index 187f2a0ccb31e..2b4f8f5f2b35c 100644 --- a/packages/nx/src/project-graph/utils/normalize-project-nodes.ts +++ b/packages/nx/src/project-graph/utils/normalize-project-nodes.ts @@ -1,26 +1,17 @@ -import { - ProjectGraphProcessorContext, - ProjectGraphProjectNode, -} from '../../config/project-graph'; +import { ProjectGraphProjectNode } from '../../config/project-graph'; import { ProjectGraphBuilder } from '../project-graph-builder'; -import { NxJsonConfiguration } from '../../config/nx-json'; import { ProjectConfiguration, TargetConfiguration, } from '../../config/workspace-json-project-json'; import { findMatchingProjects } from '../../utils/find-matching-projects'; import { NX_PREFIX } from '../../utils/logger'; -import { - mergeTargetConfigurations, - readTargetDefaultsForTarget, - resolveNxTokensInOptions, -} from '../utils/project-configuration-utils'; +import { resolveNxTokensInOptions } from '../utils/project-configuration-utils'; import { CreateDependenciesContext } from '../../utils/nx-plugin'; export async function normalizeProjectNodes( ctx: CreateDependenciesContext, - builder: ProjectGraphBuilder, - nxJson: NxJsonConfiguration + builder: ProjectGraphBuilder ) { const toAdd = []; // Sorting projects by name to make sure that the order of projects in the graph is deterministic. @@ -50,7 +41,7 @@ export async function normalizeProjectNodes( partialProjectGraphNodes ); - p.targets = normalizeProjectTargets(p, nxJson.targetDefaults, key); + p.targets = normalizeProjectTargets(p, key); // TODO: remove in v16 const projectType = @@ -92,34 +83,22 @@ export async function normalizeProjectNodes( */ export function normalizeProjectTargets( project: ProjectConfiguration, - targetDefaults: NxJsonConfiguration['targetDefaults'], projectName: string ): Record { // Any node on the graph will have a targets object, it just may be empty const targets = project.targets ?? {}; for (const target in targets) { - // We need to know the executor for use in readTargetDefaultsForTarget, - // but we haven't resolved the `command` syntactic sugar yet. - const executor = - targets[target].executor ?? - (targets[target].command ? 'nx:run-commands' : null); - - // Allows things like { targetDefaults: { build: { command: tsc } } } - const defaults = resolveCommandSyntacticSugar( - readTargetDefaultsForTarget(target, targetDefaults, executor), - `targetDefaults:${target}` - ); + if (!targets[target].command && !targets[target].executor) { + delete targets[target]; + continue; + } targets[target] = resolveCommandSyntacticSugar( targets[target], `${projectName}:${target}` ); - if (defaults) { - targets[target] = mergeTargetConfigurations(targets[target], defaults); - } - targets[target].options = resolveNxTokensInOptions( targets[target].options, project, diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts index dc7fa996dab4d..7df856b50cea6 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts @@ -1,3 +1,4 @@ +import { ONLY_MODIFIES_EXISTING_TARGET } from '../../plugins/target-defaults/target-defaults-plugin'; import { ProjectConfiguration, TargetConfiguration, @@ -448,6 +449,47 @@ describe('project-configuration-utils', () => { expect(merged.targets['newTarget']).toEqual(newTargetConfiguration); }); + it('should not create new targets if ONLY_MODIFIES_EXISTING_TARGET is true', () => { + const rootMap = new RootMapBuilder() + .addProject({ + root: 'libs/lib-a', + name: 'lib-a', + targets: { + echo: { + command: 'echo lib-a', + }, + }, + }) + .getRootMap(); + mergeProjectConfigurationIntoRootMap(rootMap, { + root: 'libs/lib-a', + name: 'lib-a', + targets: { + build: { + command: 'tsc', + [ONLY_MODIFIES_EXISTING_TARGET]: true, + } as any, + echo: { + options: { + cwd: '{projectRoot}', + }, + [ONLY_MODIFIES_EXISTING_TARGET]: true, + } as any, + }, + }); + const { targets } = rootMap.get('libs/lib-a'); + expect(targets.build).toBeUndefined(); + // cwd was merged in, and ONLY_MODIFIES_EXISTING_TARGET was removed + expect(targets.echo).toMatchInlineSnapshot(` + { + "command": "echo lib-a", + "options": { + "cwd": "{projectRoot}", + }, + } + `); + }); + it('should concatenate tags and implicitDependencies', () => { const rootMap = new RootMapBuilder() .addProject({ diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index e1c4a78883d38..d7105f94ac96d 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -1,4 +1,3 @@ -import { writeFileSync } from 'fs'; import { NxJsonConfiguration, TargetDefaults } from '../../config/nx-json'; import { ProjectGraphExternalNode } from '../../config/project-graph'; import { @@ -9,11 +8,12 @@ import { NX_PREFIX } from '../../utils/logger'; import { CreateNodesResult, LoadedNxPlugin } from '../../utils/nx-plugin'; import { readJsonFile } from '../../utils/fileutils'; import { workspaceRoot } from '../../utils/workspace-root'; +import { ONLY_MODIFIES_EXISTING_TARGET } from '../../plugins/target-defaults/target-defaults-plugin'; import { minimatch } from 'minimatch'; import { join } from 'path'; -export type SourceInformation = [string, string]; +export type SourceInformation = [file: string, plugin: string]; export type ConfigurationSourceMaps = Record< string, Record @@ -21,7 +21,12 @@ export type ConfigurationSourceMaps = Record< export function mergeProjectConfigurationIntoRootMap( projectRootMap: Map, - project: ProjectConfiguration, + project: ProjectConfiguration & { + targets?: Record< + string, + TargetConfiguration & { [ONLY_MODIFIES_EXISTING_TARGET]?: boolean } + >; + }, configurationSourceMaps?: ConfigurationSourceMaps, sourceInformation?: SourceInformation ): void { @@ -123,15 +128,34 @@ export function mergeProjectConfigurationIntoRootMap( } if (project.targets) { - updatedProjectConfiguration.targets = { - ...matchingProject.targets, - ...project.targets, - }; + // We merge the targets with special handling, so clear this back to the + // targets as defined originally before merging. + updatedProjectConfiguration.targets = matchingProject?.targets ?? {}; + // For each target defined in the new config for (const target in project.targets) { - if (sourceMap) { + // Always set source map info for the target, but don't overwrite info already there + // if augmenting an existing target. + if ( + sourceMap && + !project.targets[target]?.[ONLY_MODIFIES_EXISTING_TARGET] + ) { sourceMap[`targets.${target}`] = sourceInformation; } + + // If ONLY_MODIFIES_EXISTING_TARGET is true, and its not on the matching project + // we shouldn't merge its info into the graph + if ( + project.targets[target]?.[ONLY_MODIFIES_EXISTING_TARGET] && + !matchingProject.targets?.[target] + ) { + continue; + } + + // We don't want the symbol to live on past the merge process + if (project.targets[target]?.[ONLY_MODIFIES_EXISTING_TARGET]) + delete project.targets[target]?.[ONLY_MODIFIES_EXISTING_TARGET]; + updatedProjectConfiguration.targets[target] = mergeTargetConfigurations( project.targets[target], matchingProject.targets?.[target], @@ -392,12 +416,12 @@ export function mergeTargetConfigurations( const { configurations: defaultConfigurations, options: defaultOptions, - ...defaults + ...baseTargetProperties } = baseTarget ?? {}; // Target is "compatible", e.g. executor is defined only once or is the same // in both places. This means that it is likely safe to merge - const isCompatible = isCompatibleTarget(defaults, target); + const isCompatible = isCompatibleTarget(baseTargetProperties, target); if (!isCompatible && projectConfigSourceMap) { // if the target is not compatible, we will simply override the options @@ -411,7 +435,7 @@ export function mergeTargetConfigurations( // merge top level properties if they're compatible const result = { - ...(isCompatible ? defaults : {}), + ...(isCompatible ? baseTargetProperties : {}), ...target, }; diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index 1ea0ca31835c4..271f4746b58eb 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -8,7 +8,7 @@ import { } from '../../adapter/angular-json'; import { NxJsonConfiguration, readNxJson } from '../../config/nx-json'; import { ProjectGraphExternalNode } from '../../config/project-graph'; -import { getNxPackageJsonWorkspacesPlugin } from '../../../plugins/package-json-workspaces'; +import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; import { buildProjectsConfigurationsFromProjectPathsAndPlugins, ConfigurationSourceMaps, @@ -18,7 +18,7 @@ import { LoadedNxPlugin, loadNxPlugins, } from '../../utils/nx-plugin'; -import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json'; +import { ProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json'; import { getNxWorkspaceFilesFromContext, globWithWorkspaceContext, @@ -159,7 +159,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference( projectFiles, [ { plugin: getNxPackageJsonWorkspacesPlugin(root) }, - { plugin: CreateProjectJsonProjectsPlugin }, + { plugin: ProjectJsonProjectsPlugin }, ] ); diff --git a/packages/nx/src/utils/nx-plugin.deprecated.ts b/packages/nx/src/utils/nx-plugin.deprecated.ts index 3153f7d393300..d28a5022a4c7a 100644 --- a/packages/nx/src/utils/nx-plugin.deprecated.ts +++ b/packages/nx/src/utils/nx-plugin.deprecated.ts @@ -1,9 +1,9 @@ -import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; import { shouldMergeAngularProjects } from '../adapter/angular-json'; import { ProjectGraphProcessor } from '../config/project-graph'; import { TargetConfiguration } from '../config/workspace-json-project-json'; -import { CreatePackageJsonProjectsNextToProjectJson } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; -import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin'; +import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces'; import { LoadedNxPlugin, NxPluginV2 } from './nx-plugin'; /** @@ -45,8 +45,9 @@ export function getDefaultPluginsSync(root: string): LoadedNxPlugin[] { ...(shouldMergeAngularProjects(root, false) ? [require('../adapter/angular-json').NxAngularJsonPlugin] : []), + TargetDefaultsPlugin, getNxPackageJsonWorkspacesPlugin(root), - CreateProjectJsonProjectsPlugin, + ProjectJsonProjectsPlugin, ]; return plugins.map((p) => ({ diff --git a/packages/nx/src/utils/nx-plugin.ts b/packages/nx/src/utils/nx-plugin.ts index 60fb631466489..836dfe23337fb 100644 --- a/packages/nx/src/utils/nx-plugin.ts +++ b/packages/nx/src/utils/nx-plugin.ts @@ -38,10 +38,11 @@ import { NxPluginV1 } from './nx-plugin.deprecated'; import { RawProjectGraphDependency } from '../project-graph/project-graph-builder'; import { combineGlobPatterns } from './globs'; import { shouldMergeAngularProjects } from '../adapter/angular-json'; -import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; -import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; -import { CreatePackageJsonProjectsNextToProjectJson } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; +import { getNxPackageJsonWorkspacesPlugin } from '../plugins/package-json-workspaces'; +import { ProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files'; +import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults-plugin'; /** * Context for {@link CreateNodesFunction} @@ -251,7 +252,7 @@ export async function loadNxPlugins( projects?: Record ): Promise { const result: LoadedNxPlugin[] = [ - { plugin: CreatePackageJsonProjectsNextToProjectJson }, + { plugin: PackageJsonProjectsNextToProjectJsonPlugin }, ]; plugins ??= []; @@ -510,6 +511,7 @@ export async function getDefaultPlugins( ): Promise { const plugins: NxPluginV2[] = [ await import('../plugins/js'), + TargetDefaultsPlugin, ...(shouldMergeAngularProjects(root, false) ? [ await import('../adapter/angular-json').then( @@ -518,7 +520,7 @@ export async function getDefaultPlugins( ] : []), getNxPackageJsonWorkspacesPlugin(root), - CreateProjectJsonProjectsPlugin, + ProjectJsonProjectsPlugin, ]; return plugins.map((p) => ({