From 5aa0c4050f801d367f7b7b4bad9d40b34a5c67fe Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Fri, 9 May 2025 18:43:36 -0400 Subject: [PATCH] fix(core): handle npm scope when matching project substring (#31160) This PR fixes an issue if a JS project uses the same name as the npm scope. For example, if you have `@acme/acme` and `@acme/acme-e2e` projects, then running `nx lint acme` will fail with an error: ``` NX Multiple projects matched: @acme/acme-e2e @acme/acme ``` ## Current Behavior Nx fails to run the task ## Expected Behavior Nx should find the project to run the task for (ignoring scope) ## Related Issue(s) Fixes # --- .../src/utils/find-matching-projects.spec.ts | 45 ++++++++++++++++++- .../nx/src/utils/find-matching-projects.ts | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/nx/src/utils/find-matching-projects.spec.ts b/packages/nx/src/utils/find-matching-projects.spec.ts index 671a41d8a2..842fc470f3 100644 --- a/packages/nx/src/utils/find-matching-projects.spec.ts +++ b/packages/nx/src/utils/find-matching-projects.spec.ts @@ -88,6 +88,22 @@ describe('findMatchingProjects', () => { tags: [], }, }, + '@test/test': { + name: '@test/test', + type: 'app', + data: { + root: 'apps/test', + tags: [], + }, + }, + '@test/test-e2e': { + name: '@test/test-e2e', + type: 'app', + data: { + root: 'apps/test-e2e', + tags: [], + }, + }, }; it('should return no projects when passed no patterns', () => { @@ -114,6 +130,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', ]); }); @@ -128,6 +146,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', ]); expect(findMatchingProjects(['a', '!*'], projectGraph)).toEqual([]); }); @@ -176,6 +196,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', ]); }); @@ -187,7 +209,7 @@ describe('findMatchingProjects', () => { projectGraph ); expect(matches).toEqual(expect.arrayContaining(['a', 'b', 'nested'])); - expect(matches.length).toEqual(8); + expect(matches.length).toEqual(10); }); it('should expand generic glob patterns for tags', () => { @@ -217,7 +239,11 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', ]); - expect(findMatchingProjects(['apps/*'], projectGraph)).toEqual(['c']); + expect(findMatchingProjects(['apps/*'], projectGraph)).toEqual([ + 'c', + '@test/test', + '@test/test-e2e', + ]); expect(findMatchingProjects(['**/nested'], projectGraph)).toEqual([ 'nested', ]); @@ -234,6 +260,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', ]); expect(findMatchingProjects(['!tag:api'], projectGraph)).toEqual([ 'b', @@ -243,6 +271,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', ]); expect( findMatchingProjects(['!tag:api', 'test-project'], projectGraph) @@ -254,6 +284,8 @@ describe('findMatchingProjects', () => { '@acme/bar', 'foo_bar1', '@acme/nested/foo', + '@test/test', + '@test/test-e2e', 'test-project', ]); }); @@ -280,6 +312,15 @@ describe('findMatchingProjects', () => { expect(findMatchingProjects(['fo'], projectGraph)).toEqual([]); expect(findMatchingProjects(['nested/fo'], projectGraph)).toEqual([]); }); + + it('should handle case where scope and names are the same', () => { + expect(findMatchingProjects(['test'], projectGraph)).toEqual([ + '@test/test', + ]); + expect(findMatchingProjects(['test-e2e'], projectGraph)).toEqual([ + '@test/test-e2e', + ]); + }); }); const projects = [ diff --git a/packages/nx/src/utils/find-matching-projects.ts b/packages/nx/src/utils/find-matching-projects.ts index faf9c252a6..9590e09380 100644 --- a/packages/nx/src/utils/find-matching-projects.ts +++ b/packages/nx/src/utils/find-matching-projects.ts @@ -169,7 +169,7 @@ function addMatchingProjectsByName( if (!isGlobPattern(pattern.value)) { // Custom regex that is basically \b but includes hyphens (-) and excludes underscores (_), so "foo" pattern matches "foo_bar" but not "foo-e2e". const regex = new RegExp( - `(?