From e164d50f928ecc0a687dc18f8f3fd28f43cc1d5c Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Tue, 2 May 2023 12:12:30 -0400 Subject: [PATCH] feat(core): support specific project dependency in dependsOn with string syntax (#16674) --- packages/nx/src/tasks-runner/utils.spec.ts | 54 ++++++++++++++++++++++ packages/nx/src/tasks-runner/utils.ts | 51 +++++++++++++------- packages/nx/src/utils/split-target.ts | 2 +- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts index 7027317e25..7743874d78 100644 --- a/packages/nx/src/tasks-runner/utils.spec.ts +++ b/packages/nx/src/tasks-runner/utils.spec.ts @@ -1,4 +1,5 @@ import { + expandDependencyConfigSyntaxSugar, getOutputsForTargetAndConfiguration, transformLegacyOutputs, validateOutputs, @@ -397,4 +398,57 @@ describe('utils', () => { expect(result).toEqual(['{projectRoot}/dist']); } }); + + describe('expandDependencyConfigSyntaxSugar', () => { + it('should expand syntax for simple target names', () => { + const result = expandDependencyConfigSyntaxSugar('build', { + dependencies: {}, + nodes: {}, + }); + expect(result).toEqual({ + target: 'build', + }); + }); + + it('should expand syntax for simple target names targetting dependencies', () => { + const result = expandDependencyConfigSyntaxSugar('^build', { + dependencies: {}, + nodes: {}, + }); + expect(result).toEqual({ + target: 'build', + dependencies: true, + }); + }); + + it('should expand syntax for strings like project:target if project is a valid project', () => { + const result = expandDependencyConfigSyntaxSugar('project:build', { + dependencies: {}, + nodes: { + project: { + name: 'project', + type: 'app', + data: { + root: 'libs/project', + files: [], + }, + }, + }, + }); + expect(result).toEqual({ + target: 'build', + projects: ['project'], + }); + }); + + it('should expand syntax for strings like target:with:colons', () => { + const result = expandDependencyConfigSyntaxSugar('target:with:colons', { + dependencies: {}, + nodes: {}, + }); + expect(result).toEqual({ + target: 'target:with:colons', + }); + }); + }); }); diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index f81a95be0a..0583cb104a 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -15,6 +15,7 @@ import { NxJsonConfiguration } from '../config/nx-json'; import { joinPathFragments } from '../utils/path'; import { isRelativePath } from '../utils/fileutils'; import { serializeOverridesIntoCommandLine } from '../utils/serialize-overrides-into-command-line'; +import { splitByColons, splitTarget } from '../utils/split-target'; export function getCommandAsString(execCommand: string, task: Task) { const args = getPrintableCommandArgsForTask(task); @@ -26,10 +27,14 @@ export function getDependencyConfigs( defaultDependencyConfigs: Record, projectGraph: ProjectGraph ): TargetDependencyConfig[] | undefined { - const dependencyConfigs = expandDependencyConfigSyntaxSugar( + const dependencyConfigs = ( projectGraph.nodes[project].data?.targets[target]?.dependsOn ?? - defaultDependencyConfigs[target] ?? - [] + defaultDependencyConfigs[target] ?? + [] + ).map((config) => + typeof config === 'string' + ? expandDependencyConfigSyntaxSugar(config, projectGraph) + : config ); for (const dependencyConfig of dependencyConfigs) { const specifiers = @@ -64,20 +69,32 @@ export function getDependencyConfigs( return dependencyConfigs; } -function expandDependencyConfigSyntaxSugar( - deps: (TargetDependencyConfig | string)[] -): TargetDependencyConfig[] { - return deps.map((d) => { - if (typeof d === 'string') { - if (d.startsWith('^')) { - return { dependencies: true, target: d.substring(1) }; - } else { - return { target: d }; - } - } else { - return d; - } - }); +export function expandDependencyConfigSyntaxSugar( + dependencyConfigString: string, + graph: ProjectGraph +): TargetDependencyConfig { + const [dependencies, targetString] = dependencyConfigString.startsWith('^') + ? [true, dependencyConfigString.substring(1)] + : [false, dependencyConfigString]; + + // Support for `project:target` syntax doesn't make sense for + // dependencies, so we only support `target` syntax for dependencies. + if (dependencies) { + return { + target: targetString, + dependencies: true, + }; + } + + // Support for both `project:target` and `target:with:colons` syntax + const [maybeProject, ...segments] = splitByColons(targetString); + return { + // Only the first segment could be a project. If it is, the rest is a target. + // If its not, then the whole targetString was a target with colons in its name. + target: maybeProject in graph.nodes ? segments.join(':') : targetString, + // If the first segment is a project, then we have a specific project. Otherwise, we don't. + projects: maybeProject in graph.nodes ? [maybeProject] : undefined, + }; } export function getOutputs( diff --git a/packages/nx/src/utils/split-target.ts b/packages/nx/src/utils/split-target.ts index 16bb2cbc3f..78b5e9e38f 100644 --- a/packages/nx/src/utils/split-target.ts +++ b/packages/nx/src/utils/split-target.ts @@ -37,7 +37,7 @@ function groupJointSegments(segments: string[], validTargetNames: Set) { return segments; } -function splitByColons(s: string) { +export function splitByColons(s: string) { const parts = [] as string[]; let currentPart = ''; for (let i = 0; i < s.length; ++i) {