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 f382d4ba75..f51d82501d 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 @@ -699,6 +699,99 @@ describe('project-configuration-utils', () => { expect(merged.targets['newTarget']).toEqual(newTargetConfiguration); }); + it('should merge target configurations with glob pattern matching', () => { + const existingTargetConfiguration = { + command: 'already present', + }; + const partialA = { + executor: 'build', + dependsOn: ['^build'], + }; + const partialB = { + executor: 'build', + dependsOn: ['^build'], + }; + const partialC = { + executor: 'build', + dependsOn: ['^build'], + }; + const globMatch = { + dependsOn: ['^build', { project: 'app', target: 'build' }], + }; + const nonMatchingGlob = { + dependsOn: ['^production', 'build'], + }; + + const rootMap = new RootMapBuilder() + .addProject({ + root: 'libs/lib-a', + name: 'lib-a', + targets: { + existingTarget: existingTargetConfiguration, + 'partial-path/a': partialA, + 'partial-path/b': partialB, + 'partial-path/c': partialC, + }, + }) + .getRootMap(); + mergeProjectConfigurationIntoRootMap(rootMap, { + root: 'libs/lib-a', + name: 'lib-a', + targets: { + 'partial-**/*': globMatch, + 'ci-*': nonMatchingGlob, + }, + }); + const merged = rootMap['libs/lib-a']; + expect(merged.targets['partial-path/a']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + { + "project": "app", + "target": "build", + }, + ], + "executor": "build", + } + `); + expect(merged.targets['partial-path/b']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + { + "project": "app", + "target": "build", + }, + ], + "executor": "build", + } + `); + expect(merged.targets['partial-path/c']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^build", + { + "project": "app", + "target": "build", + }, + ], + "executor": "build", + } + `); + // if the glob pattern doesn't match, the target is not merged + expect(merged.targets['ci-*']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^production", + "build", + ], + } + `); + // if the glob pattern matches, the target is merged + expect(merged.targets['partial-**/*']).toBeUndefined(); + }); + 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 7b259671cf..ee7f731fdc 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -194,15 +194,30 @@ export function mergeProjectConfigurationIntoRootMap( ? target : resolveCommandSyntacticSugar(target, project.root); - const mergedTarget = mergeTargetConfigurations( - normalizedTarget, - matchingProject.targets?.[targetName], - sourceMap, - sourceInformation, - `targets.${targetName}` - ); + let matchingTargets = []; + if (isGlobPattern(targetName)) { + // find all targets matching the glob pattern + // this will map atomized targets to the glob pattern same as it does for targetDefaults + matchingTargets = Object.keys( + updatedProjectConfiguration.targets + ).filter((key) => minimatch(key, targetName)); + } + // If no matching targets were found, we can assume that the target name is not (meant to be) a glob pattern + if (!matchingTargets.length) { + matchingTargets = [targetName]; + } - updatedProjectConfiguration.targets[targetName] = mergedTarget; + for (const matchingTargetName of matchingTargets) { + const mergedTarget = mergeTargetConfigurations( + normalizedTarget, + matchingProject.targets?.[matchingTargetName], + sourceMap, + sourceInformation, + `targets.${matchingTargetName}` + ); + + updatedProjectConfiguration.targets[matchingTargetName] = mergedTarget; + } } }