diff --git a/docs/shared/reference/project-configuration.md b/docs/shared/reference/project-configuration.md index 143038da6a4fa..6921290657b58 100644 --- a/docs/shared/reference/project-configuration.md +++ b/docs/shared/reference/project-configuration.md @@ -163,7 +163,7 @@ _Named Inputs_ Examples: - `inputs: ["production"]` -- same as `inputs: [{input: "production", projects: "self"}]` +- same as `"inputs": [{"input": "production", "projects": "self"}]` in versions prior to Nx 16, or `"inputs": [{"input": "production"}]` after version 16. Often the same glob will appear in many places (e.g., prod fileset will exclude spec files for all projects). Because keeping them in sync is error-prone, we recommend defining `namedInputs`, which you can then reference in all of those @@ -174,7 +174,7 @@ places. Examples: - `inputs: ["^production"]` -- same as `inputs: [{input: "production", projects: "dependencies"}]` +- same as `inputs: [{"input": "production", "projects": "dependencies"}]` prior to Nx 16, or `"inputs": [{"input": "production", "dependencies": true }]` after version 16. Similar to `dependsOn`, the "^" symbols means "dependencies". This is a very important idea, so let's illustrate it with an example. @@ -290,12 +290,37 @@ You can also express task dependencies with an object syntax: ``` {% /tab %} -{% tab label="Version 16+" %} +{% tab label="Version 16+ (self)" %} + +```json +"build": { + "dependsOn": [{ + "target": "build", // target name + "params": "ignore" // "forward" or "ignore", defaults to "ignore" + }] +} +``` + +{% /tab %} +{% tab label="Version 16+ (dependencies)" %} ```json "build": { "dependsOn": [{ - "projects": "{dependencies}", // "{dependencies}", "{self}" or "project-name" + "dependencies": true, // Run this target on all dependencies first + "target": "build", // target name + "params": "ignore" // "forward" or "ignore", defaults to "ignore" + }] +} +``` + +{% /tab %} +{% tab label="Version 16+ (specific projects)" %} + +```json +"build": { + "dependsOn": [{ + "projects": ["my-app"], // Run build on "my-app" first "target": "build", // target name "params": "ignore" // "forward" or "ignore", defaults to "ignore" }] @@ -326,10 +351,10 @@ You can write the shorthand configuration above in the object syntax like this: ```json "build": { - "dependsOn": [{ "projects": "{dependencies}", "target": "build" }] + "dependsOn": [{ "dependencies": true, "target": "build" }] // Run build on my dependencies first }, "test": { - "dependsOn": [{ "projects": "{self}", "target": "build" }] + "dependsOn": [{ "target": "build" }] // Run build on myself first } ``` @@ -395,7 +420,7 @@ This also works when defining a relation for the target of the project itself us ```json "build": { // forward params passed to this target to the project target - "dependsOn": [{ "projects": "{self}", "target": "pre-build", "params": "forward" }] + "dependsOn": [{ "target": "pre-build", "params": "forward" }] } ``` diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index a51cba374ef77..435d8b795782a 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -62,8 +62,8 @@ }, "16.0.0-tokens-for-depends-on": { "cli": "nx", - "version": "16.0.0-beta.0", - "description": "Replace `dependsOn.projects` with {self} or {dependencies} tokens so that it matches the new expected formatting.", + "version": "16.0.0-beta.9", + "description": "Replace `dependsOn.projects` and `inputs` definitions with new configuration format.", "implementation": "./src/migrations/update-16-0-0/update-depends-on-to-tokens" }, "16.0.0-update-nx-cloud-runner": { diff --git a/packages/nx/schemas/nx-schema.json b/packages/nx/schemas/nx-schema.json index 19bc1ffb15885..5a9c255927789 100644 --- a/packages/nx/schemas/nx-schema.json +++ b/packages/nx/schemas/nx-schema.json @@ -98,15 +98,44 @@ "type": "object", "properties": { "projects": { - "type": "string", - "description": "The projects that the targets belong to.", - "enum": ["self", "dependencies"] + "oneOf": [ + { + "type": "string", + "description": "The project that the targets belong to." + }, + { + "type": "array", + "description": "The projects that the targets belong to.", + "items": { "type": "string" } + } + ] + }, + "dependencies": { + "type": "boolean", + "description": "Include files belonging to the input for all the project dependencies of this target." }, "input": { "type": "string", "description": "The name of the input." } }, + "oneOf": [ + { + "required": ["projects", "input"] + }, + { + "required": ["dependencies", "input"] + }, + { + "required": ["input"], + "not": { + "anyOf": [ + { "required": ["projects"] }, + { "required": ["dependencies"] } + ] + } + } + ], "additionalProperties": false }, { @@ -211,17 +240,20 @@ "oneOf": [ { "type": "string", - "description": "{self}, {dependencies}, or a project name." + "description": "A project name" }, { "type": "array", - "description": "An array of project specifiers: {self}, {dependencies}, or a project name.", + "description": "An array of project names", "items": { "type": "string" } } ] }, + "dependencies": { + "type": "boolean" + }, "target": { "type": "string", "description": "The name of the target." @@ -233,8 +265,24 @@ "default": "ignore" } }, - "additionalProperties": false, - "required": ["projects", "target"] + "oneOf": [ + { + "required": ["projects", "target"] + }, + { + "required": ["dependencies", "target"] + }, + { + "required": ["target"], + "not": { + "anyOf": [ + { "required": ["projects"] }, + { "required": ["dependencies"] } + ] + } + } + ], + "additionalProperties": false } ] } diff --git a/packages/nx/schemas/project-schema.json b/packages/nx/schemas/project-schema.json index 4c8cdc862c609..34e9305c998f7 100644 --- a/packages/nx/schemas/project-schema.json +++ b/packages/nx/schemas/project-schema.json @@ -58,17 +58,20 @@ "oneOf": [ { "type": "string", - "description": "{self}, {dependencies}, or a project name." + "description": "A project name" }, { "type": "array", - "description": "An array of project specifiers: {self}, {dependencies}, or a project name.", + "description": "An array of project names", "items": { "type": "string" } } ] }, + "dependencies": { + "type": "boolean" + }, "target": { "type": "string", "description": "The name of the target." @@ -80,8 +83,24 @@ "default": "ignore" } }, - "additionalProperties": false, - "required": ["projects", "target"] + "oneOf": [ + { + "required": ["projects", "target"] + }, + { + "required": ["dependencies", "target"] + }, + { + "required": ["target"], + "not": { + "anyOf": [ + { "required": ["projects"] }, + { "required": ["dependencies"] } + ] + } + } + ], + "additionalProperties": false } ] } @@ -128,15 +147,44 @@ "type": "object", "properties": { "projects": { - "type": "string", - "description": "The projects that the input belong to.", - "enum": ["self", "dependencies"] + "oneOf": [ + { + "type": "string", + "description": "The project that the targets belong to." + }, + { + "type": "array", + "description": "The projects that the targets belong to.", + "items": { "type": "string" } + } + ] + }, + "dependencies": { + "type": "boolean", + "description": "Include files belonging to the input for all the project dependencies of this target." }, "input": { "type": "string", - "description": "Named input." + "description": "The name of the input." } }, + "oneOf": [ + { + "required": ["projects", "input"] + }, + { + "required": ["dependencies", "input"] + }, + { + "required": ["input"], + "not": { + "anyOf": [ + { "required": ["projects"] }, + { "required": ["dependencies"] } + ] + } + } + ], "additionalProperties": false }, { diff --git a/packages/nx/schemas/workspace-schema.json b/packages/nx/schemas/workspace-schema.json index df2cc3d6b7eb6..3b31a61fd6f0c 100644 --- a/packages/nx/schemas/workspace-schema.json +++ b/packages/nx/schemas/workspace-schema.json @@ -68,17 +68,20 @@ "oneOf": [ { "type": "string", - "description": "{self}, {dependencies}, or a project name." + "description": "A project name" }, { "type": "array", - "description": "An array of project specifiers: {self}, {dependencies}, or a project name.", + "description": "An array of project names", "items": { "type": "string" } } ] }, + "dependencies": { + "type": "boolean" + }, "target": { "type": "string", "description": "The name of the target." @@ -90,8 +93,24 @@ "default": "ignore" } }, - "additionalProperties": false, - "required": ["projects", "target"] + "oneOf": [ + { + "required": ["projects", "target"] + }, + { + "required": ["dependencies", "target"] + }, + { + "required": ["target"], + "not": { + "anyOf": [ + { "required": ["projects"] }, + { "required": ["dependencies"] } + ] + } + } + ], + "additionalProperties": false } ] } diff --git a/packages/nx/src/config/workspace-json-project-json.ts b/packages/nx/src/config/workspace-json-project-json.ts index 901bb779b5ed2..f0a5a8efa7c87 100644 --- a/packages/nx/src/config/workspace-json-project-json.ts +++ b/packages/nx/src/config/workspace-json-project-json.ts @@ -102,18 +102,20 @@ export interface ProjectConfiguration { export interface TargetDependencyConfig { /** - * A list of projects that have `target`. Supports project names or two special values: - * - * - '{self}': This target depends on another target of the same project - * - '{dependencies}': This target depends on targets of the projects of it's deps. - * - * The special values {self}/{dependencies} should be preferred - they prevent cases where a project - * that needs to be built is missed. + * A list of projects that have `target`. + * Should not be specified together with `dependencies`. + */ + projects?: string[] | string; + + /** + * If true, the target will be executed for each project that this project depends on. + * Should not be specified together with `projects`. */ - projects: string[] | string; + dependencies?: boolean; /** - * The name of the target + * The name of the target to run. If `projects` and `dependencies` are not specified, + * the target will be executed for the same project the the current target is running on`. */ target: string; @@ -124,7 +126,9 @@ export interface TargetDependencyConfig { } export type InputDefinition = - | { input: string; projects: 'self' | 'dependencies' } + | { input: string; projects: string | string[] } + | { input: string; dependencies: true } + | { input: string } | { fileset: string } | { runtime: string } | { env: string }; diff --git a/packages/nx/src/hasher/hasher.spec.ts b/packages/nx/src/hasher/hasher.spec.ts index 3947154338236..833e45ee6b032 100644 --- a/packages/nx/src/hasher/hasher.spec.ts +++ b/packages/nx/src/hasher/hasher.spec.ts @@ -88,12 +88,22 @@ describe('Hasher', () => { { runtime: 'echo runtime123' }, { env: 'TESTENV' }, { env: 'NONEXISTENTENV' }, + { input: 'default', projects: ['unrelated'] }, ], }, }, files: [{ file: '/file', hash: 'file.hash' }], }, }, + unrelated: { + name: 'unrelated', + type: 'lib', + data: { + root: 'libs/unrelated', + targets: { build: {} }, + files: [{ file: 'libs/unrelated/filec.ts', hash: 'filec.hash' }], + }, + }, }, dependencies: { parent: [], @@ -121,12 +131,15 @@ describe('Hasher', () => { expect(hash.value).toContain('runtime123'); expect(hash.value).toContain('runtime456'); expect(hash.value).toContain('env123'); + expect(hash.value).toContain('filec.hash'); expect(hash.details.command).toEqual('parent|build||{"prop":"prop-value"}'); expect(hash.details.nodes).toEqual({ 'parent:{projectRoot}/**/*': - '/file|file.hash|{"root":"libs/parent","targets":{"build":{"executor":"unknown","inputs":["default","^default",{"runtime":"echo runtime123"},{"env":"TESTENV"},{"env":"NONEXISTENTENV"}]}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', + '/file|file.hash|{"root":"libs/parent","targets":{"build":{"executor":"unknown","inputs":["default","^default",{"runtime":"echo runtime123"},{"env":"TESTENV"},{"env":"NONEXISTENTENV"},{"input":"default","projects":["unrelated"]}]}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', parent: 'unknown', + 'unrelated:{projectRoot}/**/*': + 'libs/unrelated/filec.ts|filec.hash|{"root":"libs/unrelated","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nx/parent":["libs/parent/src/index.ts"],"@nx/child":["libs/child/src/index.ts"]}}}', '{workspaceRoot}/nx.json': 'nx.json.hash', '{workspaceRoot}/.gitignore': '', '{workspaceRoot}/.nxignore': '', @@ -774,7 +787,7 @@ describe('Hasher', () => { const expanded = expandNamedInput('c', { a: ['a.txt', { fileset: 'myfileset' }], b: ['b.txt'], - c: ['a', { input: 'b', projects: 'self' }], + c: ['a', { input: 'b' }], }); expect(expanded).toEqual([ { fileset: 'a.txt' }, @@ -787,7 +800,7 @@ describe('Hasher', () => { expect(() => expandNamedInput('c', {})).toThrow(); expect(() => expandNamedInput('b', { - b: [{ input: 'c', projects: 'self' }], + b: [{ input: 'c' }], }) ).toThrow(); }); diff --git a/packages/nx/src/hasher/hasher.ts b/packages/nx/src/hasher/hasher.ts index b52784c7cbd06..1174462e7cab7 100644 --- a/packages/nx/src/hasher/hasher.ts +++ b/packages/nx/src/hasher/hasher.ts @@ -164,11 +164,10 @@ export class Hasher { const DEFAULT_INPUTS: ReadonlyArray = [ { - projects: 'self', fileset: '{projectRoot}/**/*', }, { - projects: 'dependencies', + dependencies: true, input: 'default', }, ]; @@ -201,15 +200,19 @@ class TaskHasher { const targetDefaults = (this.nxJson.targetDefaults || {})[ task.target.target ]; - const { selfInputs, depsInputs } = splitInputsIntoSelfAndDependencies( - targetData.inputs || targetDefaults?.inputs || (DEFAULT_INPUTS as any), - namedInputs - ); + const { selfInputs, depsInputs, projectInputs } = + splitInputsIntoSelfAndDependencies( + targetData.inputs || + targetDefaults?.inputs || + (DEFAULT_INPUTS as any), + namedInputs + ); const selfAndInputs = await this.hashSelfAndDepsInputs( task.target.project, selfInputs, depsInputs, + projectInputs, visited ); @@ -224,7 +227,7 @@ class TaskHasher { }); } - private async hashNamedInput( + private async hashNamedInputForDependencies( projectName: string, namedInput: string, visited: string[] @@ -240,11 +243,12 @@ class TaskHasher { }; const selfInputs = expandNamedInput(namedInput, namedInputs); - const depsInputs = [{ input: namedInput }]; + const depsInputs = [{ input: namedInput, dependencies: true as true }]; // true is boolean by default return this.hashSelfAndDepsInputs( projectName, selfInputs, depsInputs, + [], visited ); } @@ -252,19 +256,21 @@ class TaskHasher { private async hashSelfAndDepsInputs( projectName: string, selfInputs: ExpandedSelfInput[], - depsInputs: { input: string }[], + depsInputs: { input: string; dependencies: true }[], + projectInputs: { input: string; projects: string[] }[], visited: string[] ) { const projectGraphDeps = this.projectGraph.dependencies[projectName] ?? []; // we don't want random order of dependencies to change the hash projectGraphDeps.sort((a, b) => a.target.localeCompare(b.target)); - const self = await this.hashSelfInputs(projectName, selfInputs); + const self = await this.hashSingleProjectInputs(projectName, selfInputs); const deps = await this.hashDepsInputs( depsInputs, projectGraphDeps, visited ); + const projects = await this.hashProjectInputs(projectInputs, visited); let details = {}; for (const s of self) { @@ -273,10 +279,14 @@ class TaskHasher { for (const s of deps) { details = { ...details, ...s.details }; } + for (const s of projects) { + details = { ...details, ...s.details }; + } const value = this.hashing.hashArray([ ...self.map((d) => d.value), ...deps.map((d) => d.value), + ...projects.map((d) => d.value), ]); return { value, details }; @@ -296,7 +306,7 @@ class TaskHasher { return null; } else { visited.push(d.target); - return await this.hashNamedInput( + return await this.hashNamedInputForDependencies( d.target, input.input || 'default', visited @@ -366,7 +376,7 @@ class TaskHasher { }; } - private async hashSelfInputs( + private async hashSingleProjectInputs( projectName: string, inputs: ExpandedSelfInput[] ): Promise { @@ -422,6 +432,29 @@ class TaskHasher { ]); } + private async hashProjectInputs( + projectInputs: { input: string; projects: string[] }[], + visited: string[] + ): Promise { + const partialHashes: Promise[] = []; + for (const input of projectInputs) { + for (const project of input.projects) { + const namedInputs = getNamedInputs( + this.nxJson, + this.projectGraph.nodes[project] + ); + const expandedInput = expandSingleProjectInputs( + [{ input: input.input }], + namedInputs + ); + partialHashes.push( + this.hashSingleProjectInputs(project, expandedInput) + ); + } + } + return Promise.all(partialHashes).then((hashes) => hashes.flat()); + } + private async hashRootFileset(fileset: string): Promise { const mapKey = fileset; const withoutWorkspaceRoot = fileset.substring(16); @@ -559,30 +592,55 @@ export function splitInputsIntoSelfAndDependencies( inputs: ReadonlyArray, namedInputs: { [inputName: string]: ReadonlyArray } ): { - depsInputs: { input: string }[]; + depsInputs: { input: string; dependencies: true }[]; + projectInputs: { input: string; projects: string[] }[]; selfInputs: ExpandedSelfInput[]; } { - const depsInputs = []; + const depsInputs: { input: string; dependencies: true }[] = []; + const projectInputs: { input: string; projects: string[] }[] = []; const selfInputs = []; for (const d of inputs) { if (typeof d === 'string') { if (d.startsWith('^')) { - depsInputs.push({ input: d.substring(1) }); + depsInputs.push({ input: d.substring(1), dependencies: true }); } else { selfInputs.push(d); } } else { - if ((d as any).projects === 'dependencies') { - depsInputs.push(d as any); + if ( + ('dependencies' in d && d.dependencies) || + // Todo(@AgentEnder): Remove check in v17 + ('projects' in d && + typeof d.projects === 'string' && + d.projects === 'dependencies') + ) { + depsInputs.push({ + input: d.input, + dependencies: true, + }); + } else if ( + 'projects' in d && + d.projects && + // Todo(@AgentEnder): Remove check in v17 + !(d.projects === 'self') + ) { + projectInputs.push({ + input: d.input, + projects: Array.isArray(d.projects) ? d.projects : [d.projects], + }); } else { selfInputs.push(d); } } } - return { depsInputs, selfInputs: expandSelfInputs(selfInputs, namedInputs) }; + return { + depsInputs, + projectInputs, + selfInputs: expandSingleProjectInputs(selfInputs, namedInputs), + }; } -function expandSelfInputs( +function expandSingleProjectInputs( inputs: ReadonlyArray, namedInputs: { [inputName: string]: ReadonlyArray } ): ExpandedSelfInput[] { @@ -598,9 +656,9 @@ function expandSelfInputs( expanded.push({ fileset: d }); } } else { - if ((d as any).projects === 'dependencies') { + if ((d as any).projects || (d as any).dependencies) { throw new Error( - `namedInputs definitions cannot contain any inputs with projects == 'dependencies'` + `namedInputs definitions can only refer to other namedInputs definitions within the same project.` ); } if ((d as any).fileset || (d as any).env || (d as any).runtime) { @@ -619,7 +677,7 @@ export function expandNamedInput( ): ExpandedSelfInput[] { namedInputs ||= {}; if (!namedInputs[input]) throw new Error(`Input '${input}' is not defined`); - return expandSelfInputs(namedInputs[input], namedInputs); + return expandSingleProjectInputs(namedInputs[input], namedInputs); } export function filterUsingGlobPatterns( diff --git a/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.spec.ts b/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.spec.ts index 8f80e43c9496f..70daa8bdceced 100644 --- a/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.spec.ts +++ b/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.spec.ts @@ -19,6 +19,7 @@ describe('update-depends-on-to-tokens', () => { projects: 'self', }, ], + inputs: [{ projects: 'self', input: 'someInput' }], }, test: { dependsOn: [ @@ -26,6 +27,7 @@ describe('update-depends-on-to-tokens', () => { projects: 'dependencies', }, ], + inputs: [{ projects: 'dependencies', input: 'someInput' }], }, other: { dependsOn: ['^deps'], @@ -35,10 +37,21 @@ describe('update-depends-on-to-tokens', () => { }); await update(tree); const nxJson = readNxJson(tree); - const build = nxJson.targetDefaults.build.dependsOn[0] as any; - const test = nxJson.targetDefaults.test.dependsOn[0] as any; - expect(build.projects).toEqual('{self}'); - expect(test.projects).toEqual('{dependencies}'); + const buildDependencyConfiguration = nxJson.targetDefaults.build + .dependsOn[0] as any; + const testDependencyConfiguration = nxJson.targetDefaults.test + .dependsOn[0] as any; + const buildInputConfiguration = nxJson.targetDefaults.build + .inputs[0] as any; + const testInputConfiguration = nxJson.targetDefaults.test.inputs[0] as any; + expect(buildDependencyConfiguration.projects).not.toBeDefined(); + expect(buildDependencyConfiguration.dependencies).not.toBeDefined(); + expect(buildInputConfiguration.projects).not.toBeDefined(); + expect(buildInputConfiguration.dependencies).not.toBeDefined(); + expect(testInputConfiguration.projects).not.toBeDefined(); + expect(testInputConfiguration.dependencies).toEqual(true); + expect(testDependencyConfiguration.projects).not.toBeDefined(); + expect(testDependencyConfiguration.dependencies).toEqual(true); expect(nxJson.targetDefaults.other.dependsOn).toEqual(['^deps']); }); @@ -54,6 +67,7 @@ describe('update-depends-on-to-tokens', () => { target: 'build', }, ], + inputs: [{ projects: 'self', input: 'someInput' }], }, test: { dependsOn: [ @@ -62,6 +76,7 @@ describe('update-depends-on-to-tokens', () => { target: 'test', }, ], + inputs: [{ projects: 'dependencies', input: 'someInput' }], }, other: { dependsOn: ['^deps'], @@ -70,11 +85,22 @@ describe('update-depends-on-to-tokens', () => { }); await update(tree); const project = readProjectConfiguration(tree, 'proj1'); - const build = project.targets.build.dependsOn[0] as any; - const test = project.targets.test.dependsOn[0] as any; - expect(build.projects).toEqual('{self}'); - expect(test.projects).toEqual('{dependencies}'); + const buildDependencyConfiguration = project.targets.build + .dependsOn[0] as any; + const testDependencyConfiguration = project.targets.test + .dependsOn[0] as any; + const buildInputConfiguration = project.targets.build.inputs[0] as any; + const testInputConfiguration = project.targets.test.inputs[0] as any; + expect(buildDependencyConfiguration.projects).not.toBeDefined(); + expect(buildDependencyConfiguration.dependencies).not.toBeDefined(); + expect(buildInputConfiguration.projects).not.toBeDefined(); + expect(buildInputConfiguration.dependencies).not.toBeDefined(); + expect(testDependencyConfiguration.projects).not.toBeDefined(); + expect(testDependencyConfiguration.dependencies).toEqual(true); + expect(testInputConfiguration.projects).not.toBeDefined(); + expect(testInputConfiguration.dependencies).toEqual(true); expect(project.targets.other.dependsOn).toEqual(['^deps']); + expect(project.targets.other.inputs).not.toBeDefined(); }); it('should not throw on nulls', async () => { diff --git a/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.ts b/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.ts index 8c68b5289de23..fa7f68b55a2da 100644 --- a/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.ts +++ b/packages/nx/src/migrations/update-16-0-0/update-depends-on-to-tokens.ts @@ -7,7 +7,7 @@ import { import { Tree } from '../../generators/tree'; export default async function (tree: Tree) { - updateDependsOnInsideNxJson(tree); + updateDependsOnAndInputsInsideNxJson(tree); const projectsConfigurations = getProjects(tree); for (const [projectName, projectConfiguration] of projectsConfigurations) { @@ -17,11 +17,41 @@ export default async function (tree: Tree) { )) { for (const dependency of targetConfiguration.dependsOn ?? []) { if (typeof dependency !== 'string') { - if (dependency.projects === 'self') { - dependency.projects = '{self}'; + if ( + dependency.projects === 'self' || + dependency.projects === '{self}' + ) { + delete dependency.projects; projectChanged = true; - } else if (dependency.projects === 'dependencies') { - dependency.projects = '{dependencies}'; + } else if ( + dependency.projects === 'dependencies' || + dependency.projects === '{dependencies}' + ) { + delete dependency.projects; + dependency.dependencies = true; + projectChanged = true; + } + } + } + for (let i = 0; i < targetConfiguration.inputs?.length ?? 0; i++) { + const input = targetConfiguration.inputs[i]; + if (typeof input !== 'string') { + if ( + 'projects' in input && + (input.projects === 'self' || input.projects === '{self}') + ) { + delete input.projects; + projectChanged = true; + } else if ( + 'projects' in input && + (input.projects === 'dependencies' || + input.projects === '{dependencies}') + ) { + delete input.projects; + targetConfiguration.inputs[i] = { + ...input, + dependencies: true, + }; projectChanged = true; } } @@ -32,7 +62,7 @@ export default async function (tree: Tree) { } } } -function updateDependsOnInsideNxJson(tree: Tree) { +function updateDependsOnAndInputsInsideNxJson(tree: Tree) { const nxJson = readNxJson(tree); let nxJsonChanged = false; for (const [target, defaults] of Object.entries( @@ -40,11 +70,41 @@ function updateDependsOnInsideNxJson(tree: Tree) { )) { for (const dependency of defaults.dependsOn ?? []) { if (typeof dependency !== 'string') { - if (dependency.projects === 'self') { - dependency.projects = '{self}'; + if ( + dependency.projects === 'self' || + dependency.projects === '{self}' + ) { + delete dependency.projects; + nxJsonChanged = true; + } else if ( + dependency.projects === 'dependencies' || + dependency.projects === '{dependencies}' + ) { + delete dependency.projects; + dependency.dependencies = true; + nxJsonChanged = true; + } + } + } + for (let i = 0; i < defaults.inputs?.length ?? 0; i++) { + const input = defaults.inputs[i]; + if (typeof input !== 'string') { + if ( + 'projects' in input && + (input.projects === 'self' || input.projects === '{self}') + ) { + delete input.projects; nxJsonChanged = true; - } else if (dependency.projects === 'dependencies') { - dependency.projects = '{dependencies}'; + } else if ( + 'projects' in input && + (input.projects === 'dependencies' || + input.projects === '{dependencies}') + ) { + delete input.projects; + defaults.inputs[i] = { + ...input, + dependencies: true, + }; nxJsonChanged = true; } } diff --git a/packages/nx/src/tasks-runner/create-task-graph.spec.ts b/packages/nx/src/tasks-runner/create-task-graph.spec.ts index 992dd1221b51f..9ee524d83bf71 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.spec.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.spec.ts @@ -24,11 +24,10 @@ describe('createTaskGraph', () => { executor: 'nx:run-commands', dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', }, { - projects: '{self}', target: 'prebuild', }, { @@ -173,7 +172,7 @@ describe('createTaskGraph', () => { }, dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', }, ], @@ -196,7 +195,7 @@ describe('createTaskGraph', () => { defaultConfiguration: 'libDefault', dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', }, ], @@ -337,7 +336,7 @@ describe('createTaskGraph', () => { executor: 'my-executor', dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', }, ], @@ -508,11 +507,11 @@ describe('createTaskGraph', () => { executor: 'nx:run-commands', dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', params: 'forward', }, - { projects: '{self}', target: 'prebuild', params: 'forward' }, + { target: 'prebuild', params: 'forward' }, ], }, test: { @@ -535,7 +534,7 @@ describe('createTaskGraph', () => { executor: 'nx:run-commands', dependsOn: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', params: 'ignore', }, @@ -844,7 +843,7 @@ describe('createTaskGraph', () => { { build: [ { - projects: '{dependencies}', + dependencies: true, target: 'build', }, ], @@ -1005,9 +1004,9 @@ describe('createTaskGraph', () => { { build: ['^build'], apply: [ - { projects: '{dependencies}', target: 'build' }, + { dependencies: true, target: 'build' }, { - projects: '{dependencies}', + dependencies: true, target: 'apply', params: 'forward', }, @@ -1083,11 +1082,11 @@ describe('createTaskGraph', () => { targets: { build: { executor: 'nx:run-commands', - dependsOn: [{ target: 'test', projects: '{self}' }], + dependsOn: [{ target: 'test' }], }, test: { executor: 'nx:run-commands', - dependsOn: [{ target: 'build', projects: '{self}' }], + dependsOn: [{ target: 'build' }], }, }, }, @@ -1192,7 +1191,7 @@ describe('createTaskGraph', () => { const taskGraph = createTaskGraph( projectGraph, { - build: [{ target: 'build', projects: '{dependencies}' }], + build: [{ target: 'build', dependencies: true }], }, ['app1'], ['build'], @@ -1283,7 +1282,7 @@ describe('createTaskGraph', () => { const taskGraph = createTaskGraph( projectGraph, { - build: [{ target: 'build', projects: '{dependencies}' }], + build: [{ target: 'build', dependencies: true }], }, ['app1'], ['build'], @@ -1338,8 +1337,8 @@ describe('createTaskGraph', () => { build: { executor: 'nx:run-commands', dependsOn: [ - { target: 'prebuild', projects: '{self}' }, - { target: 'build', projects: '{dependencies}' }, + { target: 'prebuild' }, + { target: 'build', dependencies: true }, ], }, prebuild: { diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts index ff31a05658d72..9022b7906a789 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.ts @@ -105,47 +105,83 @@ export class ProcessTasks { dependencyConfig.params === 'forward' ? overrides : { __overrides_unparsed__: [] }; - const targetProjectSpecifiers = - typeof dependencyConfig.projects === 'string' - ? [dependencyConfig.projects] - : dependencyConfig.projects; - for (const projectSpecifier of targetProjectSpecifiers) { - // Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain - // support for it until lerna can be updated to use the new tokens. + if (dependencyConfig.projects) { + this.processTasksForMatchingProjects( + dependencyConfig, + projectUsedToDeriveDependencies, + configuration, + task, + taskOverrides, + overrides + ); + } else if (dependencyConfig.dependencies) { + this.processTasksForDependencies( + projectUsedToDeriveDependencies, + dependencyConfig, + configuration, + task, + taskOverrides, + overrides + ); + } else { + this.processTasksForSingleProject( + task, + task.target.project, + dependencyConfig, + configuration, + taskOverrides, + overrides + ); + } + } + } + + private processTasksForMatchingProjects( + dependencyConfig: TargetDependencyConfig, + projectUsedToDeriveDependencies: string, + configuration: string, + task: Task, + taskOverrides: Object | { __overrides_unparsed__: any[] }, + overrides: Object + ) { + const targetProjectSpecifiers = + typeof dependencyConfig.projects === 'string' + ? [dependencyConfig.projects] + : dependencyConfig.projects; + for (const projectSpecifier of targetProjectSpecifiers) { + // Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain + // support for it until lerna can be updated to use the syntax. + // TODO(@agentender): Remove this part in v17 + if ( + projectSpecifier === 'dependencies' && + !this.projectGraph.nodes[projectSpecifier] + ) { + this.processTasksForDependencies( + projectUsedToDeriveDependencies, + dependencyConfig, + configuration, + task, + taskOverrides, + overrides + ); + } else { + // Since we need to maintain support for dependencies, it is more coherent + // that we also support self. // TODO(@agentender): Remove this part in v17 - if ( - projectSpecifier === '{dependencies}' || - (projectSpecifier === 'dependencies' && - !this.projectGraph.nodes[projectSpecifier]) - ) { - this.processTasksForDependencies( - projectUsedToDeriveDependencies, - dependencyConfig, - configuration, - task, - taskOverrides, - overrides - ); - } else { - // Since we need to maintain support for dependencies, it is more coherent - // that we also support self. - // TODO(@agentender): Remove this part in v17 - const projectName = - projectSpecifier === '{self}' || - (projectSpecifier === 'self' && - !this.projectGraph.nodes[projectSpecifier]) - ? task.target.project - : projectSpecifier; + const projectName = + projectSpecifier === 'self' && + !this.projectGraph.nodes[projectSpecifier] + ? task.target.project + : projectSpecifier; - this.processTasksForSingleProject( - task, - projectName, - dependencyConfig, - configuration, - taskOverrides, - overrides - ); - } + this.processTasksForSingleProject( + task, + projectName, + dependencyConfig, + configuration, + taskOverrides, + overrides + ); } } } diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index 0f98ff049fc77..f81a95be0a5b5 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -36,23 +36,30 @@ export function getDependencyConfigs( typeof dependencyConfig.projects === 'string' ? [dependencyConfig.projects] : dependencyConfig.projects; - for (const specifier of specifiers) { + for (const specifier of specifiers ?? []) { if ( !(specifier in projectGraph.nodes) && - // Todo(@agentender): Remove the non-token forms of these for v17 - !['{self}', '{dependencies}', 'self', 'dependencies'].includes( - specifier - ) + // Todo(@agentender): Remove the check for self / dependencies in v17 + !['self', 'dependencies'].includes(specifier) ) { output.error({ title: `dependsOn is improperly configured for ${project}:${target}`, bodyLines: [ - `${specifier} in dependsOn.projects is invalid. It should be "{self}", "{dependencies}", or a project name.`, + `${specifier} in dependsOn.projects is invalid. It should be "self", "dependencies", or a project name.`, ], }); process.exit(1); } } + if (dependencyConfig.projects && dependencyConfig.dependencies) { + output.error({ + title: `dependsOn is improperly configured for ${project}:${target}`, + bodyLines: [ + `dependsOn.projects and dependsOn.dependencies cannot be used together.`, + ], + }); + process.exit(1); + } } return dependencyConfigs; } @@ -63,9 +70,9 @@ function expandDependencyConfigSyntaxSugar( return deps.map((d) => { if (typeof d === 'string') { if (d.startsWith('^')) { - return { projects: '{dependencies}', target: d.substring(1) }; + return { dependencies: true, target: d.substring(1) }; } else { - return { projects: '{self}', target: d }; + return { target: d }; } } else { return d;