@webpieces/nx-webpieces-rules 0.4.918 → 0.4.920
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/lib/graph-color-modes.d.ts +29 -11
- package/src/lib/graph-color-modes.js +40 -14
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-impact-reasons.d.ts +160 -0
- package/src/lib/graph-impact-reasons.js +412 -0
- package/src/lib/graph-impact-reasons.js.map +1 -0
- package/src/lib/graph-impact.d.ts +79 -44
- package/src/lib/graph-impact.js +143 -99
- package/src/lib/graph-impact.js.map +1 -1
- package/src/lib/graph-legend.d.ts +5 -2
- package/src/lib/graph-legend.js +17 -3
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +32 -2
- package/src/lib/graph-mode-state.client.js +137 -3
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +2 -2
- package/src/lib/graph-page-shell.js +4 -4
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +3 -0
- package/src/lib/graph-visualizer.client.js +20 -6
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.js +1 -1
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
- package/src/runtime-wiring-targets.js +11 -6
- package/src/runtime-wiring-targets.js.map +1 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WHY nx put each project into the affected set (#1264): the Impact view never claims a reason the
|
|
4
|
+
* graph cannot show.
|
|
5
|
+
*
|
|
6
|
+
* `nx show projects --affected` answers WHICH projects build; it does not say why. Painting every
|
|
7
|
+
* affected project that owns no changed file as "dependent: uses a changed project" was false for the
|
|
8
|
+
* projects nx marked through an IMPLICIT touch — a `{workspaceRoot}/...` target input matching a
|
|
9
|
+
* changed file — or through a ROOT FILE (a lockfile, package.json, tsconfig.base.json, nx.json). Those
|
|
10
|
+
* projects use no changed project, so the graph around them shows nothing changed and the view looked
|
|
11
|
+
* broken.
|
|
12
|
+
*
|
|
13
|
+
* So every affected project is classified by nx's OWN locators — the exact functions `filterAffected`
|
|
14
|
+
* runs (nx/src/project-graph/affected/affected-project-graph.js) — never a re-derivation of them:
|
|
15
|
+
*
|
|
16
|
+
* | class | nx locator | what the view says |
|
|
17
|
+
* |---|---|---|
|
|
18
|
+
* | changed | `getTouchedProjects` (workspace-projects) | the changed files it owns |
|
|
19
|
+
* | root-file touched | `nx.json` via `getImplicitlyTouchedProjects`; lockfile, package.json and tsconfig.base.json via the js plugin's locators; a deleted project config via `getTouchedProjectsFromProjectGlobChanges` | the root file, and which locator |
|
|
20
|
+
* | implicitly touched | `getImplicitlyTouchedProjects` (a `{workspaceRoot}` input glob) | the target, the glob, and the changed file it matched |
|
|
21
|
+
* | dependent | none: reached from one of the above over nx's REVERSED dependency edges, exactly as `filterAffectedProjects` walks them | the edge path to the nearest one (`a -> b -> c`) |
|
|
22
|
+
* | unexplained | — | nx marked it and nothing here explains it; said out loud, never guessed |
|
|
23
|
+
*
|
|
24
|
+
* A project with several reasons gets the first class in that table's order (changed, root-file,
|
|
25
|
+
* implicit, dependent). The locators are loaded from the WORKSPACE's own nx (the one that produced the
|
|
26
|
+
* affected set), resolved the way node resolves it from the workspace root. When they cannot be
|
|
27
|
+
* loaded, every affected project is "unexplained" with the load error as its reason — never a
|
|
28
|
+
* dependent.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.WorkspaceNx = exports.AffectedClassifier = exports.AffectedClassification = exports.AffectedInputs = exports.NxAffectedLocators = exports.AffectedReason = exports.AffectedClass = void 0;
|
|
32
|
+
const tslib_1 = require("tslib");
|
|
33
|
+
const child_process_1 = require("child_process");
|
|
34
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
35
|
+
const path = tslib_1.__importStar(require("path"));
|
|
36
|
+
/** The class a project's colour and click explanation come from. Values are the page's status keys. */
|
|
37
|
+
var AffectedClass;
|
|
38
|
+
(function (AffectedClass) {
|
|
39
|
+
AffectedClass["CHANGED"] = "touched";
|
|
40
|
+
AffectedClass["DEPENDENT"] = "dependent";
|
|
41
|
+
AffectedClass["IMPLICIT"] = "implicit";
|
|
42
|
+
AffectedClass["ROOT_FILE"] = "rootFile";
|
|
43
|
+
AffectedClass["UNEXPLAINED"] = "unexplained";
|
|
44
|
+
})(AffectedClass || (exports.AffectedClass = AffectedClass = {}));
|
|
45
|
+
/** nx's own nx.json implicit: a change to it marks every project. */
|
|
46
|
+
const NX_JSON = 'nx.json';
|
|
47
|
+
/** Why one affected project is in the build. Exactly one per affected project. */
|
|
48
|
+
class AffectedReason {
|
|
49
|
+
project;
|
|
50
|
+
kind;
|
|
51
|
+
files;
|
|
52
|
+
path;
|
|
53
|
+
target;
|
|
54
|
+
input;
|
|
55
|
+
detail;
|
|
56
|
+
constructor(
|
|
57
|
+
/** The node id (project name). */
|
|
58
|
+
project, kind,
|
|
59
|
+
/** CHANGED: every changed file it owns. IMPLICIT / ROOT_FILE: the changed file that matched. */
|
|
60
|
+
files,
|
|
61
|
+
/** DEPENDENT: `[project, …, the nearest changed or touched project]`, following dependency edges. */
|
|
62
|
+
path,
|
|
63
|
+
/** IMPLICIT: the target whose inputs matched. */
|
|
64
|
+
target,
|
|
65
|
+
/** IMPLICIT: the input glob as written, e.g. `{workspaceRoot}/libs/shared/wiring.ts`. */
|
|
66
|
+
input,
|
|
67
|
+
/** One sentence for the click panel. */
|
|
68
|
+
detail) {
|
|
69
|
+
this.project = project;
|
|
70
|
+
this.kind = kind;
|
|
71
|
+
this.files = files;
|
|
72
|
+
this.path = path;
|
|
73
|
+
this.target = target;
|
|
74
|
+
this.input = input;
|
|
75
|
+
this.detail = detail;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.AffectedReason = AffectedReason;
|
|
79
|
+
/** The six locators `filterAffected` runs, as functions. Injected so specs pass nx's own imports. */
|
|
80
|
+
class NxAffectedLocators {
|
|
81
|
+
touched;
|
|
82
|
+
implicit;
|
|
83
|
+
projectGlobs;
|
|
84
|
+
lockFile;
|
|
85
|
+
npmPackages;
|
|
86
|
+
tsConfig;
|
|
87
|
+
constructor(touched, implicit, projectGlobs, lockFile, npmPackages, tsConfig) {
|
|
88
|
+
this.touched = touched;
|
|
89
|
+
this.implicit = implicit;
|
|
90
|
+
this.projectGlobs = projectGlobs;
|
|
91
|
+
this.lockFile = lockFile;
|
|
92
|
+
this.npmPackages = npmPackages;
|
|
93
|
+
this.tsConfig = tsConfig;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.NxAffectedLocators = NxAffectedLocators;
|
|
97
|
+
/** One root-file locator and the words that name it. */
|
|
98
|
+
class RootFileLocator {
|
|
99
|
+
locate;
|
|
100
|
+
what;
|
|
101
|
+
constructor(locate, what) {
|
|
102
|
+
this.locate = locate;
|
|
103
|
+
this.what = what;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Everything the classification reads; built from the workspace's nx, or by hand in a spec. */
|
|
107
|
+
class AffectedInputs {
|
|
108
|
+
graph;
|
|
109
|
+
nxJson;
|
|
110
|
+
packageJson;
|
|
111
|
+
changes;
|
|
112
|
+
affected;
|
|
113
|
+
constructor(graph, nxJson, packageJson, changes,
|
|
114
|
+
/** nx's affected set (project names), from `nx show projects --affected`. */
|
|
115
|
+
affected) {
|
|
116
|
+
this.graph = graph;
|
|
117
|
+
this.nxJson = nxJson;
|
|
118
|
+
this.packageJson = packageJson;
|
|
119
|
+
this.changes = changes;
|
|
120
|
+
this.affected = affected;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.AffectedInputs = AffectedInputs;
|
|
124
|
+
/** The classification of one scan. */
|
|
125
|
+
class AffectedClassification {
|
|
126
|
+
reasons;
|
|
127
|
+
globalFiles;
|
|
128
|
+
constructor(
|
|
129
|
+
/** One per affected project, sorted by project. */
|
|
130
|
+
reasons,
|
|
131
|
+
/** Changed files no project owns (sorted): the workspace-global inputs. */
|
|
132
|
+
globalFiles) {
|
|
133
|
+
this.reasons = reasons;
|
|
134
|
+
this.globalFiles = globalFiles;
|
|
135
|
+
}
|
|
136
|
+
of(kind) {
|
|
137
|
+
return this.reasons
|
|
138
|
+
.filter((reason) => reason.kind === kind)
|
|
139
|
+
.map((reason) => reason.project);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.AffectedClassification = AffectedClassification;
|
|
143
|
+
/** Classifies an affected set with nx's own locators. Pure given its inputs; spawns nothing itself. */
|
|
144
|
+
class AffectedClassifier {
|
|
145
|
+
locators;
|
|
146
|
+
constructor(locators) {
|
|
147
|
+
this.locators = locators;
|
|
148
|
+
}
|
|
149
|
+
async classify(inputs) {
|
|
150
|
+
const affected = new Set(inputs.affected);
|
|
151
|
+
const seeds = new Map();
|
|
152
|
+
const owned = await this.owned(inputs);
|
|
153
|
+
for (const project of owned.keys()) {
|
|
154
|
+
const files = owned.get(project);
|
|
155
|
+
const detail = `owns ${files.length} changed file${files.length === 1 ? '' : 's'}`;
|
|
156
|
+
this.seed(seeds, affected, this.reason(project, AffectedClass.CHANGED, files, [], '', '', detail));
|
|
157
|
+
}
|
|
158
|
+
const external = new Map();
|
|
159
|
+
await this.rootFiles(inputs, affected, seeds, external);
|
|
160
|
+
await this.implicit(inputs, affected, seeds);
|
|
161
|
+
const reasons = this.dependents(inputs.graph, affected, seeds, external);
|
|
162
|
+
const globalFiles = [];
|
|
163
|
+
for (const change of inputs.changes)
|
|
164
|
+
if (!this.isOwned(owned, change.file))
|
|
165
|
+
globalFiles.push(change.file);
|
|
166
|
+
return new AffectedClassification(reasons.sort((a, b) => a.project.localeCompare(b.project)), globalFiles.sort());
|
|
167
|
+
}
|
|
168
|
+
isOwned(owned, file) {
|
|
169
|
+
for (const files of owned.values())
|
|
170
|
+
if (files.includes(file))
|
|
171
|
+
return true;
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/** project → the changed files it owns, by nx's own file → project locator. */
|
|
175
|
+
async owned(inputs) {
|
|
176
|
+
const owned = new Map();
|
|
177
|
+
for (const change of inputs.changes) {
|
|
178
|
+
for (const project of await this.locators.touched([change], inputs.graph.nodes)) {
|
|
179
|
+
const files = owned.get(project) ?? [];
|
|
180
|
+
if (!files.includes(change.file))
|
|
181
|
+
files.push(change.file);
|
|
182
|
+
owned.set(project, files);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return owned;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* nx.json, the lockfile, package.json, tsconfig.base.json and a deleted project config. A locator
|
|
189
|
+
* that answers even with NO change (nx.json's own `projectsAffectedByDependencyUpdates` list) is
|
|
190
|
+
* named as that configuration, never as a file.
|
|
191
|
+
*/
|
|
192
|
+
async rootFiles(inputs, affected, seeds, external) {
|
|
193
|
+
const nxJsonChange = inputs.changes.find((change) => change.file === NX_JSON);
|
|
194
|
+
if (nxJsonChange !== undefined) {
|
|
195
|
+
const marked = await this.locators.implicit([nxJsonChange], inputs.graph.nodes, inputs.nxJson);
|
|
196
|
+
for (const project of marked)
|
|
197
|
+
this.seed(seeds, affected, this.reason(project, AffectedClass.ROOT_FILE, [NX_JSON], [], '', '', 'nx.json changed: nx marks every project'));
|
|
198
|
+
}
|
|
199
|
+
for (const locator of this.rootFileLocators()) {
|
|
200
|
+
// Probe file by file only when the locator marks anything for the whole change set.
|
|
201
|
+
if ((await this.run(locator.locate, inputs.changes, inputs)).length === 0)
|
|
202
|
+
continue;
|
|
203
|
+
const baseline = new Set(await this.run(locator.locate, [], inputs));
|
|
204
|
+
for (const change of inputs.changes) {
|
|
205
|
+
for (const name of await this.run(locator.locate, [change], inputs)) {
|
|
206
|
+
if (baseline.has(name))
|
|
207
|
+
continue;
|
|
208
|
+
const reason = this.reason(name, AffectedClass.ROOT_FILE, [change.file], [], '', '', `${change.file} changed (nx ${locator.what})`);
|
|
209
|
+
if (inputs.graph.nodes[name] !== undefined)
|
|
210
|
+
this.seed(seeds, affected, reason);
|
|
211
|
+
else if (!external.has(name))
|
|
212
|
+
external.set(name, reason);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const name of baseline)
|
|
216
|
+
this.seed(seeds, affected, this.reason(name, AffectedClass.ROOT_FILE, [], [], '', '', `nx.json's own configuration marks it (nx ${locator.what})`));
|
|
217
|
+
}
|
|
218
|
+
// All-or-nothing in nx (a deleted project config marks every project), so it is asked once.
|
|
219
|
+
for (const name of await this.run(this.locators.projectGlobs, inputs.changes, inputs))
|
|
220
|
+
this.seed(seeds, affected, this.reason(name, AffectedClass.ROOT_FILE, [], [], '', '', 'a project configuration file was deleted: nx marks every project'));
|
|
221
|
+
}
|
|
222
|
+
rootFileLocators() {
|
|
223
|
+
return [
|
|
224
|
+
new RootFileLocator(this.locators.lockFile, 'lockfile changes'),
|
|
225
|
+
new RootFileLocator(this.locators.npmPackages, 'package.json dependency changes'),
|
|
226
|
+
new RootFileLocator(this.locators.tsConfig, 'root tsconfig changes'),
|
|
227
|
+
];
|
|
228
|
+
}
|
|
229
|
+
async run(locate, changes, inputs) {
|
|
230
|
+
return locate(changes, inputs.graph.nodes, inputs.nxJson, inputs.packageJson, inputs.graph);
|
|
231
|
+
}
|
|
232
|
+
/** `{workspaceRoot}` input globs: which target, which glob, which changed file. */
|
|
233
|
+
async implicit(inputs, affected, seeds) {
|
|
234
|
+
const changes = inputs.changes.filter((change) => change.file !== NX_JSON);
|
|
235
|
+
if (changes.length === 0)
|
|
236
|
+
return;
|
|
237
|
+
for (const project of await this.locators.implicit(changes, inputs.graph.nodes, inputs.nxJson)) {
|
|
238
|
+
const node = inputs.graph.nodes[project];
|
|
239
|
+
if (seeds.has(project) || !affected.has(project) || node === undefined)
|
|
240
|
+
continue;
|
|
241
|
+
const explained = await this.explainImplicit(node, changes, inputs.nxJson);
|
|
242
|
+
seeds.set(project, explained ?? this.reason(project, AffectedClass.IMPLICIT, [], [], '', '', 'a {workspaceRoot} target input matched a changed file'));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Which target, input and file: each candidate input is handed to nx's OWN implicit locator, alone,
|
|
247
|
+
* on a copy of the project carrying only that target, so the matcher is always nx's. Named inputs
|
|
248
|
+
* are opened (nx.json's merged with the project's, as the locator merges them).
|
|
249
|
+
*/
|
|
250
|
+
async explainImplicit(node, changes, nxJson) {
|
|
251
|
+
const named = { ...(nxJson.namedInputs ?? {}), ...(node.data.namedInputs ?? {}) };
|
|
252
|
+
const bare = { ...nxJson, namedInputs: {} };
|
|
253
|
+
const matching = [];
|
|
254
|
+
for (const change of changes)
|
|
255
|
+
if ((await this.locators.implicit([change], { [node.name]: node }, nxJson)).includes(node.name))
|
|
256
|
+
matching.push(change);
|
|
257
|
+
for (const [target, config] of Object.entries(node.data.targets ?? {})) {
|
|
258
|
+
for (const input of this.leaves(config.inputs ?? [], named, new Set())) {
|
|
259
|
+
const probe = this.probe(node, target, config, input);
|
|
260
|
+
for (const change of matching) {
|
|
261
|
+
const hit = await this.locators.implicit([change], { [node.name]: probe }, bare);
|
|
262
|
+
if (!hit.includes(node.name))
|
|
263
|
+
continue;
|
|
264
|
+
const glob = typeof input === 'string' ? input : this.fileset(input);
|
|
265
|
+
return this.reason(node.name, AffectedClass.IMPLICIT, [change.file], [], target, glob, `${target} input ${glob} matched ${change.file}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
/** Every input, with named inputs opened (cycle-safe). */
|
|
272
|
+
leaves(inputs, named, open) {
|
|
273
|
+
const out = [];
|
|
274
|
+
for (const input of inputs) {
|
|
275
|
+
if (typeof input !== 'string' || named[input] === undefined) {
|
|
276
|
+
out.push(input);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (open.has(input))
|
|
280
|
+
continue;
|
|
281
|
+
open.add(input);
|
|
282
|
+
out.push(...this.leaves(named[input], named, open));
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
fileset(input) {
|
|
287
|
+
return 'fileset' in input && typeof input.fileset === 'string' ? input.fileset : JSON.stringify(input);
|
|
288
|
+
}
|
|
289
|
+
probe(node, target, config, input) {
|
|
290
|
+
return { ...node, data: { ...node.data, namedInputs: {}, targets: { [target]: { ...config, inputs: [input] } } } };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Dependents: a breadth-first walk over the REVERSED dependency edges from every changed and
|
|
294
|
+
* touched project (and every npm package a root file touched), the walk nx's own
|
|
295
|
+
* `filterAffectedProjects` does — so the first seed reached is the NEAREST one. A project one edge
|
|
296
|
+
* from a touched npm package is root-file touched itself ("uses npm:x: package.json changed").
|
|
297
|
+
*/
|
|
298
|
+
dependents(graph, affected, seeds, external) {
|
|
299
|
+
const next = this.nearest(graph, [...seeds.keys(), ...external.keys()]);
|
|
300
|
+
for (const project of next.keys()) {
|
|
301
|
+
const hop = next.get(project);
|
|
302
|
+
const viaPackage = external.get(hop);
|
|
303
|
+
if (viaPackage === undefined)
|
|
304
|
+
continue;
|
|
305
|
+
this.seed(seeds, affected, this.reason(project, AffectedClass.ROOT_FILE, viaPackage.files, [project, hop], '', '', `uses ${hop}: ${viaPackage.detail}`));
|
|
306
|
+
}
|
|
307
|
+
const reasons = [...seeds.values()];
|
|
308
|
+
for (const project of [...affected].sort()) {
|
|
309
|
+
if (seeds.has(project) || graph.nodes[project] === undefined)
|
|
310
|
+
continue;
|
|
311
|
+
reasons.push(next.has(project) ? this.dependent(project, next, seeds) : this.unexplained(project));
|
|
312
|
+
}
|
|
313
|
+
return reasons;
|
|
314
|
+
}
|
|
315
|
+
/** For each project reachable from `starts` over reversed edges, the next hop toward the nearest start. */
|
|
316
|
+
nearest(graph, starts) {
|
|
317
|
+
const users = new Map();
|
|
318
|
+
for (const [source, deps] of Object.entries(graph.dependencies ?? {}))
|
|
319
|
+
for (const dep of deps)
|
|
320
|
+
users.set(dep.target, [...(users.get(dep.target) ?? []), source]);
|
|
321
|
+
const next = new Map();
|
|
322
|
+
const queue = [...starts];
|
|
323
|
+
const seen = new Set(queue);
|
|
324
|
+
for (let index = 0; index < queue.length; index++) {
|
|
325
|
+
for (const user of users.get(queue[index]) ?? []) {
|
|
326
|
+
if (seen.has(user))
|
|
327
|
+
continue;
|
|
328
|
+
seen.add(user);
|
|
329
|
+
next.set(user, queue[index]);
|
|
330
|
+
queue.push(user);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return next;
|
|
334
|
+
}
|
|
335
|
+
dependent(project, next, seeds) {
|
|
336
|
+
const route = [project];
|
|
337
|
+
let at = project;
|
|
338
|
+
while (!seeds.has(at) && next.has(at)) {
|
|
339
|
+
at = next.get(at);
|
|
340
|
+
route.push(at);
|
|
341
|
+
}
|
|
342
|
+
const end = seeds.get(at);
|
|
343
|
+
const what = end?.kind === AffectedClass.CHANGED ? 'a changed project' : 'a touched project';
|
|
344
|
+
return this.reason(project, AffectedClass.DEPENDENT, [], route, '', '', `uses ${what}: ${route.join(' -> ')}`);
|
|
345
|
+
}
|
|
346
|
+
unexplained(project, why = "nx marked it affected; none of nx's locators run here explains why") {
|
|
347
|
+
return this.reason(project, AffectedClass.UNEXPLAINED, [], [], '', '', why);
|
|
348
|
+
}
|
|
349
|
+
/** First reason wins (changed, root-file, implicit, dependent); only affected projects count. */
|
|
350
|
+
seed(seeds, affected, reason) {
|
|
351
|
+
if (!seeds.has(reason.project) && affected.has(reason.project))
|
|
352
|
+
seeds.set(reason.project, reason);
|
|
353
|
+
}
|
|
354
|
+
reason(project, kind, files, route, target, input, detail) {
|
|
355
|
+
return new AffectedReason(project, kind, files, route, target, input, detail);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.AffectedClassifier = AffectedClassifier;
|
|
359
|
+
/** The `nxArgs` shape `calculateFileChanges` reads: a base and no head (the working tree). */
|
|
360
|
+
class AffectedBase {
|
|
361
|
+
base;
|
|
362
|
+
constructor(base) {
|
|
363
|
+
this.base = base;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/** The workspace's own nx, loaded the way node resolves it from the workspace root. */
|
|
367
|
+
class WorkspaceNx {
|
|
368
|
+
workspaceRoot;
|
|
369
|
+
constructor(workspaceRoot) {
|
|
370
|
+
this.workspaceRoot = workspaceRoot;
|
|
371
|
+
}
|
|
372
|
+
locators() {
|
|
373
|
+
const workspace = this.load('nx/src/project-graph/affected/locators/workspace-projects');
|
|
374
|
+
const globs = this.load('nx/src/project-graph/affected/locators/project-glob-changes');
|
|
375
|
+
const lock = this.load('nx/src/plugins/js/project-graph/affected/lock-file-changes');
|
|
376
|
+
const npm = this.load('nx/src/plugins/js/project-graph/affected/npm-packages');
|
|
377
|
+
const ts = this.load('nx/src/plugins/js/project-graph/affected/tsconfig-json-changes');
|
|
378
|
+
return new NxAffectedLocators(workspace.getTouchedProjects, workspace.getImplicitlyTouchedProjects, globs.getTouchedProjectsFromProjectGlobChanges, lock.getTouchedProjectsFromLockFile, npm.getTouchedNpmPackages, ts.getTouchedProjectsFromTsConfig);
|
|
379
|
+
}
|
|
380
|
+
/** nx's own FileChange list: base → working tree (no head), as `nx affected` builds it. */
|
|
381
|
+
fileChanges(files, base) {
|
|
382
|
+
const utils = this.load('nx/src/project-graph/file-utils');
|
|
383
|
+
return utils.calculateFileChanges(files, new AffectedBase(base), (file, revision) => this.readAt(file, revision));
|
|
384
|
+
}
|
|
385
|
+
nxJson() {
|
|
386
|
+
return this.load('nx/src/config/nx-json').readNxJson(this.workspaceRoot);
|
|
387
|
+
}
|
|
388
|
+
packageJson() {
|
|
389
|
+
return this.load('nx/src/project-graph/file-utils').readPackageJson(this.workspaceRoot);
|
|
390
|
+
}
|
|
391
|
+
/** A file at `revision`, or in the working tree with none; '' when it does not exist there. */
|
|
392
|
+
readAt(file, revision) {
|
|
393
|
+
if (typeof revision === 'string' && revision !== '') {
|
|
394
|
+
const shown = (0, child_process_1.spawnSync)('git', ['show', `${revision}:${file}`], {
|
|
395
|
+
cwd: this.workspaceRoot,
|
|
396
|
+
encoding: 'utf8',
|
|
397
|
+
stdio: 'pipe',
|
|
398
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
399
|
+
});
|
|
400
|
+
return shown.status === 0 ? shown.stdout : '';
|
|
401
|
+
}
|
|
402
|
+
const full = path.join(this.workspaceRoot, file);
|
|
403
|
+
return fs.existsSync(full) ? fs.readFileSync(full, 'utf8') : '';
|
|
404
|
+
}
|
|
405
|
+
load(request) {
|
|
406
|
+
// webpieces-disable no-any-unknown -- a module loaded by path is untyped until the type-only import names it
|
|
407
|
+
const loaded = require(require.resolve(request, { paths: [this.workspaceRoot] }));
|
|
408
|
+
return loaded;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
exports.WorkspaceNx = WorkspaceNx;
|
|
412
|
+
//# sourceMappingURL=graph-impact-reasons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-impact-reasons.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-impact-reasons.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WHY nx put each project into the affected set (#1264): the Impact view never claims a reason the\n * graph cannot show.\n *\n * `nx show projects --affected` answers WHICH projects build; it does not say why. Painting every\n * affected project that owns no changed file as \"dependent: uses a changed project\" was false for the\n * projects nx marked through an IMPLICIT touch — a `{workspaceRoot}/...` target input matching a\n * changed file — or through a ROOT FILE (a lockfile, package.json, tsconfig.base.json, nx.json). Those\n * projects use no changed project, so the graph around them shows nothing changed and the view looked\n * broken.\n *\n * So every affected project is classified by nx's OWN locators — the exact functions `filterAffected`\n * runs (nx/src/project-graph/affected/affected-project-graph.js) — never a re-derivation of them:\n *\n * | class | nx locator | what the view says |\n * |---|---|---|\n * | changed | `getTouchedProjects` (workspace-projects) | the changed files it owns |\n * | root-file touched | `nx.json` via `getImplicitlyTouchedProjects`; lockfile, package.json and tsconfig.base.json via the js plugin's locators; a deleted project config via `getTouchedProjectsFromProjectGlobChanges` | the root file, and which locator |\n * | implicitly touched | `getImplicitlyTouchedProjects` (a `{workspaceRoot}` input glob) | the target, the glob, and the changed file it matched |\n * | dependent | none: reached from one of the above over nx's REVERSED dependency edges, exactly as `filterAffectedProjects` walks them | the edge path to the nearest one (`a -> b -> c`) |\n * | unexplained | — | nx marked it and nothing here explains it; said out loud, never guessed |\n *\n * A project with several reasons gets the first class in that table's order (changed, root-file,\n * implicit, dependent). The locators are loaded from the WORKSPACE's own nx (the one that produced the\n * affected set), resolved the way node resolves it from the workspace root. When they cannot be\n * loaded, every affected project is \"unexplained\" with the load error as its reason — never a\n * dependent.\n */\n\nimport { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { NxJsonConfiguration, ProjectGraph, ProjectGraphProjectNode, TargetConfiguration } from '@nx/devkit';\nimport type { FileChange } from 'nx/src/project-graph/file-utils';\nimport type { InputDefinition } from 'nx/src/config/workspace-json-project-json';\nimport type { PackageJson } from 'nx/src/utils/package-json';\nimport type { TouchedProjectLocator } from 'nx/src/project-graph/affected/affected-project-graph-models';\nimport type * as FileUtils from 'nx/src/project-graph/file-utils';\nimport type * as NxJsonModule from 'nx/src/config/nx-json';\nimport type * as WorkspaceProjects from 'nx/src/project-graph/affected/locators/workspace-projects';\nimport type * as ProjectGlobChanges from 'nx/src/project-graph/affected/locators/project-glob-changes';\nimport type * as LockFileChanges from 'nx/src/plugins/js/project-graph/affected/lock-file-changes';\nimport type * as NpmPackages from 'nx/src/plugins/js/project-graph/affected/npm-packages';\nimport type * as TsConfigChanges from 'nx/src/plugins/js/project-graph/affected/tsconfig-json-changes';\n\n/** The class a project's colour and click explanation come from. Values are the page's status keys. */\nexport enum AffectedClass {\n CHANGED = 'touched',\n DEPENDENT = 'dependent',\n IMPLICIT = 'implicit',\n ROOT_FILE = 'rootFile',\n UNEXPLAINED = 'unexplained',\n}\n\n/** nx's own nx.json implicit: a change to it marks every project. */\nconst NX_JSON = 'nx.json';\n\n/** Why one affected project is in the build. Exactly one per affected project. */\nexport class AffectedReason {\n constructor(\n /** The node id (project name). */\n public readonly project: string,\n public readonly kind: AffectedClass,\n /** CHANGED: every changed file it owns. IMPLICIT / ROOT_FILE: the changed file that matched. */\n public readonly files: string[],\n /** DEPENDENT: `[project, …, the nearest changed or touched project]`, following dependency edges. */\n public readonly path: string[],\n /** IMPLICIT: the target whose inputs matched. */\n public readonly target: string,\n /** IMPLICIT: the input glob as written, e.g. `{workspaceRoot}/libs/shared/wiring.ts`. */\n public readonly input: string,\n /** One sentence for the click panel. */\n public readonly detail: string,\n ) {}\n}\n\n/** The six locators `filterAffected` runs, as functions. Injected so specs pass nx's own imports. */\nexport class NxAffectedLocators {\n constructor(\n public readonly touched: TouchedProjectLocator,\n public readonly implicit: TouchedProjectLocator,\n public readonly projectGlobs: TouchedProjectLocator,\n public readonly lockFile: TouchedProjectLocator,\n public readonly npmPackages: TouchedProjectLocator,\n public readonly tsConfig: TouchedProjectLocator,\n ) {}\n}\n\n/** One root-file locator and the words that name it. */\nclass RootFileLocator {\n constructor(\n public readonly locate: TouchedProjectLocator,\n public readonly what: string,\n ) {}\n}\n\n/** Everything the classification reads; built from the workspace's nx, or by hand in a spec. */\nexport class AffectedInputs {\n constructor(\n public readonly graph: ProjectGraph,\n public readonly nxJson: NxJsonConfiguration,\n public readonly packageJson: PackageJson,\n public readonly changes: FileChange[],\n /** nx's affected set (project names), from `nx show projects --affected`. */\n public readonly affected: string[],\n ) {}\n}\n\n/** The classification of one scan. */\nexport class AffectedClassification {\n constructor(\n /** One per affected project, sorted by project. */\n public readonly reasons: AffectedReason[],\n /** Changed files no project owns (sorted): the workspace-global inputs. */\n public readonly globalFiles: string[],\n ) {}\n\n of(kind: AffectedClass): string[] {\n return this.reasons\n .filter((reason: AffectedReason): boolean => reason.kind === kind)\n .map((reason: AffectedReason): string => reason.project);\n }\n}\n\n/** Classifies an affected set with nx's own locators. Pure given its inputs; spawns nothing itself. */\nexport class AffectedClassifier {\n constructor(private readonly locators: NxAffectedLocators) {}\n\n async classify(inputs: AffectedInputs): Promise<AffectedClassification> {\n const affected = new Set<string>(inputs.affected);\n const seeds = new Map<string, AffectedReason>();\n const owned = await this.owned(inputs);\n for (const project of owned.keys()) {\n const files = owned.get(project) as string[];\n const detail = `owns ${files.length} changed file${files.length === 1 ? '' : 's'}`;\n this.seed(seeds, affected, this.reason(project, AffectedClass.CHANGED, files, [], '', '', detail));\n }\n const external = new Map<string, AffectedReason>();\n await this.rootFiles(inputs, affected, seeds, external);\n await this.implicit(inputs, affected, seeds);\n const reasons = this.dependents(inputs.graph, affected, seeds, external);\n const globalFiles: string[] = [];\n for (const change of inputs.changes)\n if (!this.isOwned(owned, change.file)) globalFiles.push(change.file);\n return new AffectedClassification(\n reasons.sort((a: AffectedReason, b: AffectedReason): number => a.project.localeCompare(b.project)),\n globalFiles.sort(),\n );\n }\n\n private isOwned(owned: Map<string, string[]>, file: string): boolean {\n for (const files of owned.values()) if (files.includes(file)) return true;\n return false;\n }\n\n /** project → the changed files it owns, by nx's own file → project locator. */\n private async owned(inputs: AffectedInputs): Promise<Map<string, string[]>> {\n const owned = new Map<string, string[]>();\n for (const change of inputs.changes) {\n for (const project of await this.locators.touched([change], inputs.graph.nodes)) {\n const files = owned.get(project) ?? [];\n if (!files.includes(change.file)) files.push(change.file);\n owned.set(project, files);\n }\n }\n return owned;\n }\n\n /**\n * nx.json, the lockfile, package.json, tsconfig.base.json and a deleted project config. A locator\n * that answers even with NO change (nx.json's own `projectsAffectedByDependencyUpdates` list) is\n * named as that configuration, never as a file.\n */\n private async rootFiles(\n inputs: AffectedInputs,\n affected: Set<string>,\n seeds: Map<string, AffectedReason>,\n external: Map<string, AffectedReason>,\n ): Promise<void> {\n const nxJsonChange = inputs.changes.find((change: FileChange): boolean => change.file === NX_JSON);\n if (nxJsonChange !== undefined) {\n const marked = await this.locators.implicit([nxJsonChange], inputs.graph.nodes, inputs.nxJson);\n for (const project of marked)\n this.seed(seeds, affected, this.reason(project, AffectedClass.ROOT_FILE, [NX_JSON], [], '', '', 'nx.json changed: nx marks every project'));\n }\n for (const locator of this.rootFileLocators()) {\n // Probe file by file only when the locator marks anything for the whole change set.\n if ((await this.run(locator.locate, inputs.changes, inputs)).length === 0) continue;\n const baseline = new Set<string>(await this.run(locator.locate, [], inputs));\n for (const change of inputs.changes) {\n for (const name of await this.run(locator.locate, [change], inputs)) {\n if (baseline.has(name)) continue;\n const reason = this.reason(name, AffectedClass.ROOT_FILE, [change.file], [], '', '', `${change.file} changed (nx ${locator.what})`);\n if (inputs.graph.nodes[name] !== undefined) this.seed(seeds, affected, reason);\n else if (!external.has(name)) external.set(name, reason);\n }\n }\n for (const name of baseline)\n this.seed(seeds, affected, this.reason(name, AffectedClass.ROOT_FILE, [], [], '', '', `nx.json's own configuration marks it (nx ${locator.what})`));\n }\n // All-or-nothing in nx (a deleted project config marks every project), so it is asked once.\n for (const name of await this.run(this.locators.projectGlobs, inputs.changes, inputs))\n this.seed(seeds, affected, this.reason(name, AffectedClass.ROOT_FILE, [], [], '', '', 'a project configuration file was deleted: nx marks every project'));\n }\n\n private rootFileLocators(): RootFileLocator[] {\n return [\n new RootFileLocator(this.locators.lockFile, 'lockfile changes'),\n new RootFileLocator(this.locators.npmPackages, 'package.json dependency changes'),\n new RootFileLocator(this.locators.tsConfig, 'root tsconfig changes'),\n ];\n }\n\n private async run(locate: TouchedProjectLocator, changes: FileChange[], inputs: AffectedInputs): Promise<string[]> {\n return locate(changes, inputs.graph.nodes, inputs.nxJson, inputs.packageJson, inputs.graph);\n }\n\n /** `{workspaceRoot}` input globs: which target, which glob, which changed file. */\n private async implicit(inputs: AffectedInputs, affected: Set<string>, seeds: Map<string, AffectedReason>): Promise<void> {\n const changes = inputs.changes.filter((change: FileChange): boolean => change.file !== NX_JSON);\n if (changes.length === 0) return;\n for (const project of await this.locators.implicit(changes, inputs.graph.nodes, inputs.nxJson)) {\n const node = inputs.graph.nodes[project];\n if (seeds.has(project) || !affected.has(project) || node === undefined) continue;\n const explained = await this.explainImplicit(node, changes, inputs.nxJson);\n seeds.set(\n project,\n explained ?? this.reason(project, AffectedClass.IMPLICIT, [], [], '', '', 'a {workspaceRoot} target input matched a changed file'),\n );\n }\n }\n\n /**\n * Which target, input and file: each candidate input is handed to nx's OWN implicit locator, alone,\n * on a copy of the project carrying only that target, so the matcher is always nx's. Named inputs\n * are opened (nx.json's merged with the project's, as the locator merges them).\n */\n private async explainImplicit(\n node: ProjectGraphProjectNode,\n changes: FileChange[],\n nxJson: NxJsonConfiguration,\n ): Promise<AffectedReason | null> {\n const named: Record<string, (string | InputDefinition)[]> = { ...(nxJson.namedInputs ?? {}), ...(node.data.namedInputs ?? {}) };\n const bare: NxJsonConfiguration = { ...nxJson, namedInputs: {} };\n const matching: FileChange[] = [];\n for (const change of changes)\n if ((await this.locators.implicit([change], { [node.name]: node }, nxJson)).includes(node.name)) matching.push(change);\n for (const [target, config] of Object.entries(node.data.targets ?? {})) {\n for (const input of this.leaves(config.inputs ?? [], named, new Set<string>())) {\n const probe = this.probe(node, target, config, input);\n for (const change of matching) {\n const hit = await this.locators.implicit([change], { [node.name]: probe }, bare);\n if (!hit.includes(node.name)) continue;\n const glob = typeof input === 'string' ? input : this.fileset(input);\n return this.reason(node.name, AffectedClass.IMPLICIT, [change.file], [], target, glob, `${target} input ${glob} matched ${change.file}`);\n }\n }\n }\n return null;\n }\n\n /** Every input, with named inputs opened (cycle-safe). */\n private leaves(\n inputs: (string | InputDefinition)[],\n named: Record<string, (string | InputDefinition)[]>,\n open: Set<string>,\n ): (string | InputDefinition)[] {\n const out: (string | InputDefinition)[] = [];\n for (const input of inputs) {\n if (typeof input !== 'string' || named[input] === undefined) {\n out.push(input);\n continue;\n }\n if (open.has(input)) continue;\n open.add(input);\n out.push(...this.leaves(named[input], named, open));\n }\n return out;\n }\n\n private fileset(input: InputDefinition): string {\n return 'fileset' in input && typeof input.fileset === 'string' ? input.fileset : JSON.stringify(input);\n }\n\n private probe(\n node: ProjectGraphProjectNode,\n target: string,\n config: TargetConfiguration,\n input: string | InputDefinition,\n ): ProjectGraphProjectNode {\n return { ...node, data: { ...node.data, namedInputs: {}, targets: { [target]: { ...config, inputs: [input] } } } };\n }\n\n /**\n * Dependents: a breadth-first walk over the REVERSED dependency edges from every changed and\n * touched project (and every npm package a root file touched), the walk nx's own\n * `filterAffectedProjects` does — so the first seed reached is the NEAREST one. A project one edge\n * from a touched npm package is root-file touched itself (\"uses npm:x: package.json changed\").\n */\n private dependents(\n graph: ProjectGraph,\n affected: Set<string>,\n seeds: Map<string, AffectedReason>,\n external: Map<string, AffectedReason>,\n ): AffectedReason[] {\n const next = this.nearest(graph, [...seeds.keys(), ...external.keys()]);\n for (const project of next.keys()) {\n const hop = next.get(project) as string;\n const viaPackage = external.get(hop);\n if (viaPackage === undefined) continue;\n this.seed(seeds, affected, this.reason(project, AffectedClass.ROOT_FILE, viaPackage.files, [project, hop], '', '', `uses ${hop}: ${viaPackage.detail}`));\n }\n const reasons = [...seeds.values()];\n for (const project of [...affected].sort()) {\n if (seeds.has(project) || graph.nodes[project] === undefined) continue;\n reasons.push(next.has(project) ? this.dependent(project, next, seeds) : this.unexplained(project));\n }\n return reasons;\n }\n\n /** For each project reachable from `starts` over reversed edges, the next hop toward the nearest start. */\n private nearest(graph: ProjectGraph, starts: string[]): Map<string, string> {\n const users = new Map<string, string[]>();\n for (const [source, deps] of Object.entries(graph.dependencies ?? {}))\n for (const dep of deps) users.set(dep.target, [...(users.get(dep.target) ?? []), source]);\n const next = new Map<string, string>();\n const queue = [...starts];\n const seen = new Set<string>(queue);\n for (let index = 0; index < queue.length; index++) {\n for (const user of users.get(queue[index]) ?? []) {\n if (seen.has(user)) continue;\n seen.add(user);\n next.set(user, queue[index]);\n queue.push(user);\n }\n }\n return next;\n }\n\n private dependent(project: string, next: Map<string, string>, seeds: Map<string, AffectedReason>): AffectedReason {\n const route = [project];\n let at = project;\n while (!seeds.has(at) && next.has(at)) {\n at = next.get(at) as string;\n route.push(at);\n }\n const end = seeds.get(at);\n const what = end?.kind === AffectedClass.CHANGED ? 'a changed project' : 'a touched project';\n return this.reason(project, AffectedClass.DEPENDENT, [], route, '', '', `uses ${what}: ${route.join(' -> ')}`);\n }\n\n unexplained(project: string, why = \"nx marked it affected; none of nx's locators run here explains why\"): AffectedReason {\n return this.reason(project, AffectedClass.UNEXPLAINED, [], [], '', '', why);\n }\n\n /** First reason wins (changed, root-file, implicit, dependent); only affected projects count. */\n private seed(seeds: Map<string, AffectedReason>, affected: Set<string>, reason: AffectedReason): void {\n if (!seeds.has(reason.project) && affected.has(reason.project)) seeds.set(reason.project, reason);\n }\n\n private reason(\n project: string,\n kind: AffectedClass,\n files: string[],\n route: string[],\n target: string,\n input: string,\n detail: string,\n ): AffectedReason {\n return new AffectedReason(project, kind, files, route, target, input, detail);\n }\n}\n\n/** The `nxArgs` shape `calculateFileChanges` reads: a base and no head (the working tree). */\nclass AffectedBase {\n constructor(public readonly base: string) {}\n}\n\n/** The workspace's own nx, loaded the way node resolves it from the workspace root. */\nexport class WorkspaceNx {\n constructor(private readonly workspaceRoot: string) {}\n\n locators(): NxAffectedLocators {\n const workspace = this.load<typeof WorkspaceProjects>('nx/src/project-graph/affected/locators/workspace-projects');\n const globs = this.load<typeof ProjectGlobChanges>('nx/src/project-graph/affected/locators/project-glob-changes');\n const lock = this.load<typeof LockFileChanges>('nx/src/plugins/js/project-graph/affected/lock-file-changes');\n const npm = this.load<typeof NpmPackages>('nx/src/plugins/js/project-graph/affected/npm-packages');\n const ts = this.load<typeof TsConfigChanges>('nx/src/plugins/js/project-graph/affected/tsconfig-json-changes');\n return new NxAffectedLocators(\n workspace.getTouchedProjects,\n workspace.getImplicitlyTouchedProjects,\n globs.getTouchedProjectsFromProjectGlobChanges,\n lock.getTouchedProjectsFromLockFile as TouchedProjectLocator,\n npm.getTouchedNpmPackages,\n ts.getTouchedProjectsFromTsConfig,\n );\n }\n\n /** nx's own FileChange list: base → working tree (no head), as `nx affected` builds it. */\n fileChanges(files: string[], base: string): FileChange[] {\n const utils = this.load<typeof FileUtils>('nx/src/project-graph/file-utils');\n return utils.calculateFileChanges(files, new AffectedBase(base), (file: string, revision: string | void): string =>\n this.readAt(file, revision),\n );\n }\n\n nxJson(): NxJsonConfiguration {\n return this.load<typeof NxJsonModule>('nx/src/config/nx-json').readNxJson(this.workspaceRoot);\n }\n\n packageJson(): PackageJson {\n return this.load<typeof FileUtils>('nx/src/project-graph/file-utils').readPackageJson(this.workspaceRoot) as PackageJson;\n }\n\n /** A file at `revision`, or in the working tree with none; '' when it does not exist there. */\n private readAt(file: string, revision: string | void): string {\n if (typeof revision === 'string' && revision !== '') {\n const shown = spawnSync('git', ['show', `${revision}:${file}`], {\n cwd: this.workspaceRoot,\n encoding: 'utf8',\n stdio: 'pipe',\n maxBuffer: 64 * 1024 * 1024,\n });\n return shown.status === 0 ? shown.stdout : '';\n }\n const full = path.join(this.workspaceRoot, file);\n return fs.existsSync(full) ? fs.readFileSync(full, 'utf8') : '';\n }\n\n private load<T>(request: string): T {\n // webpieces-disable no-any-unknown -- a module loaded by path is untyped until the type-only import names it\n const loaded: unknown = require(require.resolve(request, { paths: [this.workspaceRoot] }));\n return loaded as T;\n }\n}\n"]}
|