From 3e153f3b7469b913dbfc9d00b25a8cb9f91acb2f Mon Sep 17 00:00:00 2001 From: Pavel Forkert Date: Fri, 13 Oct 2023 22:41:02 +0300 Subject: [PATCH] feat(core): add support for wildcards in dependsOn Now it is possible to define targets like this: ``` { "targets": { "build-css": {}, "build-js": {}, "test": { "dependsOn": ["build-*"] }, } } ``` --- packages/nx/src/tasks-runner/utils.spec.ts | 91 ++++++++++++++++------ packages/nx/src/tasks-runner/utils.ts | 63 +++++++++++---- 2 files changed, 114 insertions(+), 40 deletions(-) diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts index d2002f95fd836..4c945e9dcd9bf 100644 --- a/packages/nx/src/tasks-runner/utils.spec.ts +++ b/packages/nx/src/tasks-runner/utils.spec.ts @@ -443,17 +443,19 @@ describe('utils', () => { describe('expandDependencyConfigSyntaxSugar', () => { it('should expand syntax for simple target names', () => { - const result = expandDependencyConfigSyntaxSugar('build', { + const result = expandDependencyConfigSyntaxSugar('build', 'any', { dependencies: {}, nodes: {}, }); - expect(result).toEqual({ - target: 'build', - }); + expect(result).toEqual([ + { + target: 'build', + }, + ]); }); it('should assume target of self if simple target also matches project name', () => { - const result = expandDependencyConfigSyntaxSugar('build', { + const result = expandDependencyConfigSyntaxSugar('build', 'any', { dependencies: {}, nodes: { build: { @@ -465,24 +467,28 @@ describe('utils', () => { }, }, }); - expect(result).toEqual({ - target: 'build', - }); + expect(result).toEqual([ + { + target: 'build', + }, + ]); }); it('should expand syntax for simple target names targetting dependencies', () => { - const result = expandDependencyConfigSyntaxSugar('^build', { + const result = expandDependencyConfigSyntaxSugar('^build', 'any', { dependencies: {}, nodes: {}, }); - expect(result).toEqual({ - target: 'build', - dependencies: true, - }); + 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', { + const result = expandDependencyConfigSyntaxSugar('project:build', 'any', { dependencies: {}, nodes: { project: { @@ -494,20 +500,59 @@ describe('utils', () => { }, }, }); - expect(result).toEqual({ - target: 'build', - projects: ['project'], - }); + expect(result).toEqual([ + { + target: 'build', + projects: ['project'], + }, + ]); }); it('should expand syntax for strings like target:with:colons', () => { - const result = expandDependencyConfigSyntaxSugar('target:with:colons', { + const result = expandDependencyConfigSyntaxSugar( + 'target:with:colons', + 'any', + { + dependencies: {}, + nodes: {}, + } + ); + expect(result).toEqual([ + { + target: 'target:with:colons', + }, + ]); + }); + + it('supports wildcards in targets', () => { + const result = expandDependencyConfigSyntaxSugar('build-*', 'project', { dependencies: {}, - nodes: {}, - }); - expect(result).toEqual({ - target: 'target:with:colons', + nodes: { + project: { + name: 'project', + type: 'app', + data: { + root: 'libs/project', + targets: { + build: {}, + 'build-css': {}, + 'build-js': {}, + 'then-build-something-else': {}, + }, + }, + }, + }, }); + expect(result).toEqual([ + { + target: 'build-css', + projects: ['project'], + }, + { + target: 'build-js', + projects: ['project'], + }, + ]); }); }); diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index 5bfafa1329159..bd3721e418aa5 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -26,10 +26,10 @@ export function getDependencyConfigs( // This is passed into `run-command` from programmatic invocations extraTargetDependencies[target] ?? [] - ).map((config) => + ).flatMap((config) => typeof config === 'string' - ? expandDependencyConfigSyntaxSugar(config, projectGraph) - : config + ? expandDependencyConfigSyntaxSugar(config, project, projectGraph) + : [config] ); for (const dependencyConfig of dependencyConfigs) { if (dependencyConfig.projects && dependencyConfig.dependencies) { @@ -47,8 +47,9 @@ export function getDependencyConfigs( export function expandDependencyConfigSyntaxSugar( dependencyConfigString: string, + currentProject: string, graph: ProjectGraph -): TargetDependencyConfig { +): TargetDependencyConfig[] { const [dependencies, targetString] = dependencyConfigString.startsWith('^') ? [true, dependencyConfigString.substring(1)] : [false, dependencyConfigString]; @@ -56,28 +57,56 @@ export function expandDependencyConfigSyntaxSugar( // 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, - }; + return [ + { + target: targetString, + dependencies: true, + }, + ]; } // Support for both `project:target` and `target:with:colons` syntax const [maybeProject, ...segments] = splitByColons(targetString); - // if no additional segments are provided, then the string references - // a target of the same project + let target, projects; if (!segments.length) { - return { target: maybeProject }; - } - - return { + // if no additional segments are provided, then the string references + // a target of the same project + target = maybeProject; + } else if (maybeProject in graph.nodes) { // 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, + target = segments.join(':'); + projects = [maybeProject]; + } else { // If the first segment is a project, then we have a specific project. Otherwise, we don't. - projects: maybeProject in graph.nodes ? [maybeProject] : undefined, - }; + target = targetString; + } + + // handle target wildcards + if (target.indexOf('*') >= 0) { + const matches: TargetDependencyConfig[] = []; + const targetMatch = new RegExp('^' + target.replaceAll('*', '.*') + '$'); + const projectsToCheck = projects ? projects : [currentProject]; + for (const project of projectsToCheck) { + const projectTargets = graph.nodes[project].data?.targets; + if (projectTargets) { + for (const target in projectTargets) { + if (target.match(targetMatch)) { + matches.push({ target, projects: [project] }); + } + } + } + } + return matches; + } + + return [ + { + target, + projects, + }, + ]; } export function getOutputs(