Skip to content

Commit ca6d497

Browse files
committed
chore(misc): apply review feedback to clean up findMatchingProjects
1 parent f86eaf1 commit ca6d497

File tree

12 files changed

+139
-77
lines changed

12 files changed

+139
-77
lines changed

docs/generated/cli/run-many.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Test all projects ending with `*-app` except `excluded-app`. Note: your shell ma
4747
nx run-many --target=test --projects=*-app --exclude=excluded-app
4848
```
4949

50-
Test all projects with tags starting with `api-*`. Note: your shell may require you to escape the `*` like this: `\*`:
50+
Test all projects with tags starting with `api-`. Note: your shell may require you to escape the `*` like this: `\*`:
5151

5252
```shell
5353
nx run-many --target=test --projects=tag:api-*

docs/generated/packages/angular/generators/application.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
},
166166
"additionalProperties": false,
167167
"required": ["name"],
168-
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/angular:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory and style extension\" %}\n\nCreate an application named `my-app` in the `my-dir` directory and use `scss` for styles:\n\n```bash\nnx g @nrwl/angular:app my-app --directory=my-dir --style=scss\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Components application\" %}\n\nCreate an application with Single File Components (inline styles and inline templates):\n\n```bash\nnx g @nrwl/angular:app my-app --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Standalone Components application\" %}\n\nCreate an application that is setup to use standalone components:\n\n```bash\nnx g @nrwl/angular:app my-app --standalone\n```\n\n{% /tab %}\n\n{% tab label=\"Set custom prefix and tags\" %}\n\nSet the prefix to apply to generated selectors and add tags to the application.\n\n```bash\nnx g @nrwl/angular:app my-app --prefix=admin --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
168+
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/angular:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory and style extension\" %}\n\nCreate an application named `my-app` in the `my-dir` directory and use `scss` for styles:\n\n```bash\nnx g @nrwl/angular:app my-app --directory=my-dir --style=scss\n```\n\n{% /tab %}\n\n{% tab label=\"Single File Components application\" %}\n\nCreate an application with Single File Components (inline styles and inline templates):\n\n```bash\nnx g @nrwl/angular:app my-app --inlineStyle --inlineTemplate\n```\n\n{% /tab %}\n\n{% tab label=\"Standalone Components application\" %}\n\nCreate an application that is setup to use standalone components:\n\n```bash\nnx g @nrwl/angular:app my-app --standalone\n```\n\n{% /tab %}\n\n{% tab label=\"Set custom prefix and tags\" %}\n\nSet the prefix to apply to generated selectors and add tags to the application (used for linting).\n\n```bash\nnx g @nrwl/angular:app my-app --prefix=admin --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
169169
"presets": []
170170
},
171171
"aliases": ["app"],

docs/generated/packages/nx/documents/run-many.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Test all projects ending with `*-app` except `excluded-app`. Note: your shell ma
4747
nx run-many --target=test --projects=*-app --exclude=excluded-app
4848
```
4949

50-
Test all projects with tags starting with `api-*`. Note: your shell may require you to escape the `*` like this: `\*`:
50+
Test all projects with tags starting with `api-`. Note: your shell may require you to escape the `*` like this: `\*`:
5151

5252
```shell
5353
nx run-many --target=test --projects=tag:api-*

docs/generated/packages/react/generators/application.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
}
193193
},
194194
"required": [],
195-
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory and style extension\" %}\n\nCreate an application named `my-app` in the `my-dir` directory and use `scss` for styles:\n\n```bash\nnx g @nrwl/react:app my-app --directory=my-dir --style=scss\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application.\n\n```bash\nnx g @nrwl/react:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
195+
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory and style extension\" %}\n\nCreate an application named `my-app` in the `my-dir` directory and use `scss` for styles:\n\n```bash\nnx g @nrwl/react:app my-app --directory=my-dir --style=scss\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application (used for linting).\n\n```bash\nnx g @nrwl/react:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
196196
"presets": []
197197
},
198198
"aliases": ["app"],

docs/generated/packages/web/generators/application.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
}
105105
},
106106
"required": ["name"],
107-
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory\" %}\n\nCreate an application named `my-app` in the `my-dir` directory:\n\n```bash\nnx g @nrwl/web:app my-app --directory=my-dir\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application.\n\n```bash\nnx g @nrwl/web:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
107+
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory\" %}\n\nCreate an application named `my-app` in the `my-dir` directory:\n\n```bash\nnx g @nrwl/web:app my-app --directory=my-dir\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application (used for linting).\n\n```bash\nnx g @nrwl/web:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
108108
"presets": []
109109
},
110110
"aliases": ["app"],

docs/shared/recipes/generators/generator-options.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ The alias of this property. Example:
533533
{
534534
"tags": {
535535
"type": "string",
536-
"description": "Add tags to the project",
536+
"description": "Add tags to the project (used for linting)",
537537
"alias": "t"
538538
},
539539
"directory": {

packages/nx/src/command-line/examples.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ export const examples: Record<string, Example[]> = {
312312
{
313313
command: 'run-many --target=test --projects=tag:api-*',
314314
description:
315-
'Test all projects with tags starting with `api-*`. Note: your shell may require you to escape the `*` like this: `\\*`',
315+
'Test all projects with tags starting with `api-`. Note: your shell may require you to escape the `*` like this: `\\*`',
316316
},
317317
{
318318
command: 'run-many --targets=lint,test,build --all',

packages/nx/src/project-graph/build-dependencies/implicit-project-dependencies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export function buildImplicitProjectDependencies(
55
ctx: ProjectGraphProcessorContext,
66
builder: ProjectGraphBuilder
77
) {
8-
Object.keys(ctx.workspace.projects).forEach((source) => {
9-
const p = ctx.workspace.projects[source];
8+
Object.keys(ctx.projectsConfigurations.projects).forEach((source) => {
9+
const p = ctx.projectsConfigurations.projects[source];
1010
if (p.implicitDependencies && p.implicitDependencies.length > 0) {
1111
p.implicitDependencies.forEach((target) => {
1212
if (target.startsWith('!')) {

packages/nx/src/project-graph/build-nodes/workspace-projects.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ export async function buildWorkspaceProjectNodes(
2828
nxJson: NxJsonConfiguration
2929
) {
3030
const toAdd = [];
31-
const projects = Object.keys(ctx.workspace.projects);
32-
const projectsGraph = projects.reduce((graph, project) => {
33-
const projectConfiguration = ctx.workspace.projects[project];
31+
const projects = Object.keys(ctx.projectsConfigurations.projects);
32+
33+
// Used for expanding implicit dependencies (e.g. `@proj/*` or `tag:foo`)
34+
const partialProjectGraphNodes = projects.reduce((graph, project) => {
35+
const projectConfiguration = ctx.projectsConfigurations.projects[project];
3436
graph[project] = {
3537
name: project,
3638
type: projectConfiguration.projectType === 'library' ? 'lib' : 'app', // missing fallback to `e2e`
@@ -43,7 +45,7 @@ export async function buildWorkspaceProjectNodes(
4345
}, {} as Record<string, ProjectGraphProjectNode>);
4446

4547
for (const key of projects) {
46-
const p = ctx.workspace.projects[key];
48+
const p = ctx.projectsConfigurations.projects[key];
4749
const projectRoot = join(workspaceRoot, p.root);
4850

4951
if (existsSync(join(projectRoot, 'package.json'))) {
@@ -73,13 +75,13 @@ export async function buildWorkspaceProjectNodes(
7375
p.implicitDependencies = normalizeImplicitDependencies(
7476
key,
7577
p.implicitDependencies,
76-
projectsGraph
78+
partialProjectGraphNodes
7779
);
7880

7981
p.targets = mergePluginTargetsWithNxTargets(
8082
p.root,
8183
p.targets,
82-
await loadNxPlugins(ctx.workspace.plugins)
84+
await loadNxPlugins(ctx.nxJsonConfiguration.plugins)
8385
);
8486

8587
p.targets = normalizeProjectTargets(p, nxJson.targetDefaults, key);
@@ -91,7 +93,7 @@ export async function buildWorkspaceProjectNodes(
9193
? 'e2e'
9294
: 'app'
9395
: 'lib';
94-
const tags = ctx.workspace.projects?.[key]?.tags || [];
96+
const tags = ctx.projectsConfigurations.projects?.[key]?.tags || [];
9597

9698
toAdd.push({
9799
name: key,

packages/nx/src/utils/assert-workspace-validity.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
22
import { NxJsonConfiguration } from '../config/nx-json';
33
import { findMatchingProjects } from './find-matching-projects';
4-
import {output} from './output';
4+
import { output } from './output';
55
import { ProjectGraphProjectNode } from '../config/project-graph';
66

77
export function assertWorkspaceValidity(
88
projectsConfigurations: ProjectsConfigurations,
99
nxJson: NxJsonConfiguration
1010
) {
1111
const projectNames = Object.keys(projectsConfigurations.projects);
12-
const projectsGraph = projectNames.reduce((graph, project) => {
12+
const projectGraphNodes = projectNames.reduce((graph, project) => {
1313
const projectConfiguration = projectsConfigurations.projects[project];
1414
graph[project] = {
1515
name: project,
@@ -51,7 +51,7 @@ export function assertWorkspaceValidity(
5151
projectName,
5252
project.implicitDependencies,
5353
projects,
54-
projectsGraph
54+
projectGraphNodes
5555
);
5656
return map;
5757
}, invalidImplicitDependencies);

packages/nx/src/utils/find-matching-projects.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { findMatchingProjects } from './find-matching-projects';
2-
import { type ProjectGraphProjectNode } from '../config/project-graph';
2+
import type { ProjectGraphProjectNode } from '../config/project-graph';
33

44
describe('findMatchingProjects', () => {
55
let projectGraph: Record<string, ProjectGraphProjectNode> = {
@@ -94,7 +94,7 @@ describe('findMatchingProjects', () => {
9494
});
9595

9696
it('should expand "*" for tags', () => {
97-
expect(findMatchingProjects(['tags:*'], projectGraph)).toEqual([
97+
expect(findMatchingProjects(['tag:*'], projectGraph)).toEqual([
9898
'test-project',
9999
'a',
100100
'b',
@@ -103,9 +103,6 @@ describe('findMatchingProjects', () => {
103103
});
104104

105105
it('should support negation "!" for tags', () => {
106-
expect(findMatchingProjects(['*', 'tag:!api'], projectGraph)).toEqual([
107-
'b',
108-
]);
109106
expect(findMatchingProjects(['*', '!tag:api'], projectGraph)).toEqual([
110107
'b',
111108
]);
Lines changed: 116 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import minimatch = require('minimatch');
2-
import { type ProjectGraphProjectNode } from '../config/project-graph';
2+
import type { ProjectGraphProjectNode } from '../config/project-graph';
33

44
const globCharacters = ['*', '|', '{', '}', '(', ')'];
55

6+
const validPatternTypes = [
7+
'name', // Pattern is based on the project's name
8+
'tag', // Pattern is based on the project's tags
9+
] as const;
10+
type ProjectPatternType = typeof validPatternTypes[number];
11+
12+
interface ProjectPattern {
13+
// If true, the pattern is an exclude pattern
14+
exclude: boolean;
15+
// The type of pattern to match against
16+
type: ProjectPatternType;
17+
// The pattern to match against
18+
value: string;
19+
}
20+
621
/**
722
* Find matching project names given a list of potential project names or globs.
823
*
@@ -16,81 +31,72 @@ export function findMatchingProjects(
1631
| Record<string, ProjectGraphProjectNode>
1732
| Map<string, ProjectGraphProjectNode>
1833
): string[] {
19-
const projectObject =
20-
projects instanceof Map ? Object.fromEntries(projects) : projects;
21-
const projectNames = Object.keys(projectObject);
22-
const patternObjects = patterns.map((pattern) => {
23-
let isExclude = false;
24-
if (pattern.startsWith('!')) {
25-
isExclude = true;
26-
pattern = pattern.substring(1);
27-
}
28-
let [value, type] = pattern.split(':').reverse();
29-
if (value.startsWith('!')) {
30-
isExclude ||= true;
31-
value = value.substring(1);
32-
}
33-
return {
34-
not: isExclude,
35-
type: type,
36-
value,
37-
};
38-
});
34+
const projectNames = keys(projects);
35+
36+
const patternObjects: ProjectPattern[] = patterns.map((p) =>
37+
parseStringPattern(p, projects)
38+
);
3939

4040
const selectedProjects: Set<string> = new Set();
4141
const excludedProjects: Set<string> = new Set();
4242

43-
for (const patternObject of patternObjects) {
44-
if (patternObject.value === '*') {
45-
projectNames.every((projectName) =>
46-
(patternObject.not ? excludedProjects : selectedProjects).add(
47-
projectName
48-
)
49-
);
50-
if (patternObjects.length === 1) continue;
43+
for (const pattern of patternObjects) {
44+
// Handle wildcard with short-circuit, as its a common case with potentially
45+
// large project sets and we can avoid the more expensive glob matching.
46+
if (pattern.value === '*') {
47+
for (const projectName of projectNames) {
48+
if (pattern.exclude) {
49+
excludedProjects.add(projectName);
50+
} else {
51+
selectedProjects.add(projectName);
52+
}
53+
}
54+
continue;
5155
}
5256

53-
if (patternObject.type === 'tag') {
57+
if (pattern.type === 'tag') {
5458
for (const projectName of projectNames) {
55-
const tags = projectObject[projectName].data.tags || [];
59+
const tags =
60+
getItemInMapOrRecord(projects, projectName).data.tags || [];
5661

57-
if (tags.includes(patternObject.value)) {
58-
(patternObject.not ? excludedProjects : selectedProjects).add(
62+
if (tags.includes(pattern.value)) {
63+
(pattern.exclude ? excludedProjects : selectedProjects).add(
5964
projectName
6065
);
6166
continue;
6267
}
6368

64-
if (!globCharacters.some((c) => patternObject.value.includes(c))) {
69+
if (!globCharacters.some((c) => pattern.value.includes(c))) {
6570
continue;
6671
}
6772

68-
if (minimatch.match(tags, patternObject.value).length)
69-
(patternObject.not ? excludedProjects : selectedProjects).add(
73+
if (minimatch.match(tags, pattern.value).length)
74+
(pattern.exclude ? excludedProjects : selectedProjects).add(
7075
projectName
7176
);
7277
}
7378
continue;
74-
}
79+
} else if (pattern.type === 'name') {
80+
if (hasKey(projects, pattern.value)) {
81+
(pattern.exclude ? excludedProjects : selectedProjects).add(
82+
pattern.value
83+
);
84+
continue;
85+
}
7586

76-
if (projectNames.includes(patternObject.value)) {
77-
(patternObject.not ? excludedProjects : selectedProjects).add(
78-
patternObject.value
79-
);
80-
continue;
81-
}
87+
if (!globCharacters.some((c) => pattern.value.includes(c))) {
88+
continue;
89+
}
8290

83-
if (!globCharacters.some((c) => patternObject.value.includes(c))) {
84-
continue;
91+
const matchedProjectNames = minimatch.match(projectNames, pattern.value);
92+
for (const projectName of matchedProjectNames) {
93+
if (pattern.exclude) {
94+
excludedProjects.add(projectName);
95+
} else {
96+
selectedProjects.add(projectName);
97+
}
98+
}
8599
}
86-
87-
const matchedProjectNames = minimatch.match(
88-
projectNames,
89-
patternObject.value
90-
);
91-
matchedProjectNames.every((projectName) =>
92-
(patternObject.not ? excludedProjects : selectedProjects).add(projectName)
93-
);
94100
}
95101

96102
for (const project of excludedProjects) {
@@ -99,3 +105,60 @@ export function findMatchingProjects(
99105

100106
return Array.from(selectedProjects);
101107
}
108+
109+
function keys(
110+
object: Record<string, unknown> | Map<string, unknown>
111+
): string[] {
112+
return object instanceof Map ? [...object.keys()] : Object.keys(object);
113+
}
114+
115+
function hasKey(
116+
object: Record<string, unknown> | Map<string, unknown>,
117+
key: string
118+
) {
119+
return object instanceof Map ? object.has(key) : key in object;
120+
}
121+
122+
function getItemInMapOrRecord<T>(
123+
object: Record<string, T> | Map<string, T>,
124+
key: string
125+
): T {
126+
return object instanceof Map ? object.get(key) : object[key];
127+
}
128+
129+
function parseStringPattern(
130+
pattern: string,
131+
projects:
132+
| Map<string, ProjectGraphProjectNode>
133+
| Record<string, ProjectGraphProjectNode>
134+
): ProjectPattern {
135+
let type: ProjectPatternType;
136+
let value: string;
137+
const isExclude = pattern.startsWith('!');
138+
139+
// Support for things like: `!{type}:value`
140+
if (isExclude) {
141+
pattern = pattern.substring(1);
142+
}
143+
144+
const indexOfFirstPotentialSeparator = pattern.indexOf(':');
145+
if (indexOfFirstPotentialSeparator === -1 || hasKey(projects, pattern)) {
146+
type = 'name';
147+
value = pattern;
148+
} else {
149+
const potentialType = pattern.substring(0, indexOfFirstPotentialSeparator);
150+
if (isValidPatternType(potentialType)) {
151+
type = potentialType;
152+
value = pattern.substring(indexOfFirstPotentialSeparator + 1);
153+
} else {
154+
type = 'name';
155+
value = pattern;
156+
}
157+
}
158+
159+
return { type, value, exclude: isExclude };
160+
}
161+
162+
function isValidPatternType(type: string): type is ProjectPatternType {
163+
return validPatternTypes.includes(type as ProjectPatternType);
164+
}

0 commit comments

Comments
 (0)