nx 22.7.0 → 23.0.0-beta.0

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.
Files changed (33) hide show
  1. package/dist/src/config/workspace-json-project-json.d.ts +10 -0
  2. package/dist/src/daemon/server/handle-hash-tasks.js +1 -1
  3. package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -4
  4. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +11 -20
  5. package/dist/src/executors/utils/convert-nx-executor.js +2 -2
  6. package/dist/src/hasher/create-task-hasher.js +1 -1
  7. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  8. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  9. package/dist/src/project-graph/build-project-graph.d.ts +2 -4
  10. package/dist/src/project-graph/build-project-graph.js +2 -7
  11. package/dist/src/project-graph/file-map-utils.d.ts +2 -4
  12. package/dist/src/project-graph/file-map-utils.js +0 -3
  13. package/dist/src/project-graph/plugins/get-plugins.d.ts +15 -0
  14. package/dist/src/project-graph/plugins/get-plugins.js +21 -3
  15. package/dist/src/project-graph/project-graph.js +7 -6
  16. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +40 -64
  17. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +182 -411
  18. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +10 -4
  19. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +22 -8
  20. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +4 -61
  21. package/dist/src/project-graph/utils/project-configuration/source-maps.js +14 -59
  22. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +16 -0
  23. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +117 -0
  24. package/dist/src/project-graph/utils/project-configuration/target-merging.d.ts +1 -4
  25. package/dist/src/project-graph/utils/project-configuration/target-merging.js +261 -136
  26. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +0 -7
  27. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +23 -0
  28. package/dist/src/project-graph/utils/project-configuration/utils.js +164 -0
  29. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +33 -9
  30. package/dist/src/project-graph/utils/project-configuration-utils.js +153 -65
  31. package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +6 -3
  32. package/dist/src/project-graph/utils/retrieve-workspace-files.js +32 -13
  33. package/package.json +11 -11
@@ -1,468 +1,239 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProjectNameInNodePropsManager = void 0;
3
+ exports.ProjectNameInNodePropsManager = exports.UsageRef = exports.RootRef = exports.NameRef = void 0;
4
+ exports.isNameRef = isNameRef;
5
+ exports.isRootRef = isRootRef;
6
+ exports.isUsageRef = isUsageRef;
4
7
  const globs_1 = require("../../../utils/globs");
5
8
  const split_target_1 = require("../../../utils/split-target");
6
- const minimatch_1 = require("minimatch");
7
9
  /**
8
- * Manages deferred project name substitutions across the plugin result
9
- * merge phase of project graph construction.
10
- *
11
- * ### Why this exists
12
- *
13
- * When plugins return `createNodes` results, a node `A` may declare a
14
- * `dependsOn` or `inputs` entry that references another project `B` by
15
- * name. A *later* plugin is allowed to rename project `B` to `C`. After
16
- * all plugin results are merged into the root map, node `A` would still
17
- * hold a stale reference to the now-nonexistent name `B`.
10
+ * Sentinel placed in `inputs` / `dependsOn` for a pending project-name
11
+ * reference. `RootRef` carries the referenced project's root (resolved
12
+ * via nameMap lookup); `UsageRef` carries the raw written name (for
13
+ * forward refs, promoted to `RootRef` in place when the name is
14
+ * identified). `parent` + `key` let the final pass write the resolved
15
+ * name back; `targetPart` preserves the `:target` suffix from
16
+ * `dependsOn` strings.
17
+ */
18
+ class NameRef {
19
+ constructor(value, parent, key, targetPart) {
20
+ this.value = value;
21
+ this.parent = parent;
22
+ this.key = key;
23
+ this.targetPart = targetPart;
24
+ }
25
+ }
26
+ exports.NameRef = NameRef;
27
+ class RootRef extends NameRef {
28
+ }
29
+ exports.RootRef = RootRef;
30
+ class UsageRef extends NameRef {
31
+ }
32
+ exports.UsageRef = UsageRef;
33
+ function isNameRef(value) {
34
+ return value instanceof NameRef;
35
+ }
36
+ function isRootRef(value) {
37
+ return value instanceof RootRef;
38
+ }
39
+ function isUsageRef(value) {
40
+ return value instanceof UsageRef;
41
+ }
42
+ /**
43
+ * Replaces project-name refs in plugin results with in-place sentinels,
44
+ * then resolves them after all merging is done.
18
45
  *
19
- * This class solves that by:
20
- * 1. Receiving a live nameMap accessor (maintained by ProjectNodesManager)
21
- * for name root resolution and colon-delimited string parsing.
22
- * 2. Tracking dirty roots via {@link identifyProjectWithRoot} when a
23
- * project name changes at a root.
24
- * 3. Scanning each plugin's results for project-name references in
25
- * `inputs` and `dependsOn` ({@link registerSubstitutorsForNodeResults}).
26
- * 4. After all results are merged, applying the substitutors for every
27
- * renamed project so that references are updated to the final name
28
- * ({@link applySubstitutions}).
46
+ * Tracking by array position breaks once `'...'` spreads shuffle indices,
47
+ * so each ref becomes a sentinel object. Arrays spread-merge by pushing
48
+ * element references, so sentinel identity survives any downstream
49
+ * merges the final pass walks a flat registry and writes the resolved
50
+ * name back through each sentinel's `parent` back-reference. Orphaned
51
+ * sentinels (from arrays dropped by a full-replace) write harmlessly.
29
52
  */
30
53
  class ProjectNameInNodePropsManager {
31
54
  constructor(getNameMap) {
32
- // Maps the *root of the referenced project* → set of substitutor entries
33
- // that should run when that project is renamed. Keying by root (not name)
34
- // ensures that when project "A" is renamed to "B" and a *new* project
35
- // takes the name "A" at a different root, references to the new "A" are
36
- // not incorrectly rewritten.
37
- this.substitutorsByReferencedRoot = new Map();
38
- // Tracks substitutor entries by (array path, index, subIndex). This
39
- // serves two purposes:
40
- //
41
- // 1. Per-index deduplication: if the same array index is registered
42
- // again (e.g. two plugin results contribute to the same position),
43
- // the old substitutor is evicted before the new one is added.
44
- //
45
- // 2. Tail-clearing: when a later plugin provides a shorter array at the
46
- // same path, splice removes all tail entries in one call.
47
- //
48
- // Outer key: array path (e.g. "proj-a:targets.build.inputs")
49
- // Inner array: indexed by position; each slot holds an array so that a
50
- // single `projects` array can hold multiple name references.
51
- this.substitutorsByArrayKey = new Map();
52
- // Holds substitutors for project names that haven't been identified yet
53
- // (forward references). When identifyProjectWithRoot is later called for
54
- // a name in this map, the entries are promoted to substitutorsByReferencedRoot.
55
- this.pendingSubstitutorsByName = new Map();
56
- // Roots of projects whose names changed during the merge phase.
57
- this.dirtyRoots = new Set();
55
+ this.allRefs = new Set();
56
+ this.pendingByName = new Map();
58
57
  this.getNameMap = getNameMap ?? (() => ({}));
59
58
  }
60
- removeSubstitutorEntry(item) {
61
- if (item.referencedRoot !== undefined) {
62
- const substitutors = this.substitutorsByReferencedRoot.get(item.referencedRoot);
63
- if (substitutors) {
64
- substitutors.delete(item.entry);
65
- if (substitutors.size === 0) {
66
- this.substitutorsByReferencedRoot.delete(item.referencedRoot);
67
- }
68
- }
69
- }
70
- if (item.referencedName !== undefined) {
71
- const substitutors = this.pendingSubstitutorsByName.get(item.referencedName);
72
- if (substitutors) {
73
- substitutors.delete(item.entry);
74
- if (substitutors.size === 0) {
75
- this.pendingSubstitutorsByName.delete(item.referencedName);
76
- }
77
- }
78
- }
79
- }
80
- // Removes the substitutor registered at the given index (and optional
81
- // subIndex) of an array, if any. Used when re-registering for the same
82
- // position (overwritten by a later plugin).
83
- clearSubstitutorAtIndex(arrayKey, index, subIndex) {
84
- const byIndex = this.substitutorsByArrayKey.get(arrayKey);
85
- const atIndex = byIndex?.[index];
86
- if (!atIndex) {
87
- return;
88
- }
89
- if (subIndex === undefined) {
90
- // Clear the entire index entry (single project reference)
91
- for (const item of atIndex) {
92
- if (item) {
93
- this.removeSubstitutorEntry(item);
94
- }
95
- }
96
- byIndex[index] = undefined;
97
- }
98
- else {
99
- // Clear only the specific subIndex (within a projects array)
100
- const existing = atIndex[subIndex];
101
- if (existing) {
102
- this.removeSubstitutorEntry(existing);
103
- atIndex[subIndex] = undefined;
104
- }
105
- }
106
- }
107
- // Removes all substitutors at indices >= `fromIndex` for the given array
108
- // path. Uses splice so the tail is dropped in one operation.
109
- clearSubstitutorsFromIndex(arrayKey, fromIndex) {
110
- const byIndex = this.substitutorsByArrayKey.get(arrayKey);
111
- if (!byIndex) {
112
- return;
113
- }
114
- const removed = byIndex.splice(fromIndex);
115
- for (const atIndex of removed) {
116
- if (atIndex) {
117
- for (const item of atIndex) {
118
- if (item) {
119
- this.removeSubstitutorEntry(item);
120
- }
121
- }
122
- }
123
- }
124
- }
125
- // Removes all substitutors at sub-indices >= `fromSubIndex` for one
126
- // specific array index of the given array key.
127
- clearSubstitutorsFromSubIndex(arrayKey, index, fromSubIndex) {
128
- const byIndex = this.substitutorsByArrayKey.get(arrayKey);
129
- const atIndex = byIndex?.[index];
130
- if (!atIndex) {
131
- return;
132
- }
133
- const removed = atIndex.splice(fromSubIndex);
134
- for (const item of removed) {
135
- if (item) {
136
- this.removeSubstitutorEntry(item);
137
- }
138
- }
139
- let hasAnyItem = false;
140
- for (const item of atIndex) {
141
- if (item) {
142
- hasAnyItem = true;
143
- break;
144
- }
145
- }
146
- if (!hasAnyItem) {
147
- byIndex[index] = undefined;
148
- }
149
- }
150
- forEachTargetConfig(ownerConfig, targetName, callback) {
151
- const ownerTargets = ownerConfig.targets;
152
- if (!ownerTargets) {
153
- return;
154
- }
155
- const exactMatch = ownerTargets[targetName];
156
- if (exactMatch && typeof exactMatch === 'object') {
157
- callback(exactMatch);
158
- return;
159
- }
160
- if (!(0, globs_1.isGlobPattern)(targetName)) {
161
- return;
162
- }
163
- for (const candidateTargetName in ownerTargets) {
164
- if (!(0, minimatch_1.minimatch)(candidateTargetName, targetName)) {
165
- continue;
166
- }
167
- const targetConfig = ownerTargets[candidateTargetName];
168
- if (!targetConfig || typeof targetConfig !== 'object') {
169
- continue;
170
- }
171
- callback(targetConfig);
172
- }
173
- }
174
- // Registers a new substitutor for `referencedName`, tracked at
175
- // (arrayKey, index, subIndex) for deduplication and tail-clearing.
176
- // The substitutor is keyed by root when the referenced project is
177
- // already in the nameMap, otherwise parked in pendingSubstitutorsByName.
178
- registerProjectNameSubstitutor(referencedName, ownerRoot, arrayKey, index, substitutor, subIndex) {
179
- // Evict any existing substitutor at this exact position first.
180
- this.clearSubstitutorAtIndex(arrayKey, index, subIndex);
181
- const entry = { ownerRoot, substitutor };
182
- const nameMap = this.getNameMap();
183
- const referencedRoot = nameMap[referencedName]?.root;
184
- let trackingItem;
185
- if (referencedRoot !== undefined) {
186
- // Project is already known — key directly by root.
187
- let substitutorsForRoot = this.substitutorsByReferencedRoot.get(referencedRoot);
188
- if (!substitutorsForRoot) {
189
- substitutorsForRoot = new Set();
190
- this.substitutorsByReferencedRoot.set(referencedRoot, substitutorsForRoot);
191
- }
192
- substitutorsForRoot.add(entry);
193
- trackingItem = { referencedRoot, entry };
194
- }
195
- else {
196
- // Forward reference — park in pending map keyed by name.
197
- let pendingSet = this.pendingSubstitutorsByName.get(referencedName);
198
- if (!pendingSet) {
199
- pendingSet = new Set();
200
- this.pendingSubstitutorsByName.set(referencedName, pendingSet);
201
- }
202
- pendingSet.add(entry);
203
- trackingItem = { referencedName, entry };
204
- }
205
- let byIndex = this.substitutorsByArrayKey.get(arrayKey);
206
- if (!byIndex) {
207
- byIndex = [];
208
- this.substitutorsByArrayKey.set(arrayKey, byIndex);
209
- }
210
- if (subIndex === undefined) {
211
- byIndex[index] = [trackingItem];
212
- }
213
- else {
214
- if (!byIndex[index]) {
215
- byIndex[index] = [];
216
- }
217
- const subArray = byIndex[index];
218
- subArray[subIndex] = trackingItem;
219
- }
220
- }
221
- /**
222
- * Scans `pluginResultProjects` for `inputs` and `dependsOn` entries that
223
- * reference another project by name, and registers substitutors so those
224
- * references are updated if the target project is later renamed.
225
- *
226
- * **Important**: call {@link identifyProjectWithRoot} for all projects in
227
- * this result (and all prior results) before calling this method, so that
228
- * referenced project names can be resolved to roots.
229
- *
230
- * @param pluginResultProjects Projects from a single plugin's createNodes call.
231
- */
232
- registerSubstitutorsForNodeResults(pluginResultProjects) {
233
- if (!pluginResultProjects) {
59
+ // Replaces each project-name ref in `inputs`/`dependsOn` with a sentinel.
60
+ // Call after `identifyProjectWithRoot` for the batch so same-batch forward
61
+ // refs resolve straight to RootRefs.
62
+ registerNameRefs(pluginResultProjects) {
63
+ if (!pluginResultProjects)
234
64
  return;
235
- }
236
65
  for (const ownerRoot in pluginResultProjects) {
237
66
  const project = pluginResultProjects[ownerRoot];
238
- if (!project.targets) {
67
+ if (!project?.targets)
239
68
  continue;
240
- }
241
69
  for (const targetName in project.targets) {
242
70
  const targetConfig = project.targets[targetName];
243
- if (!targetConfig || typeof targetConfig !== 'object') {
71
+ if (!targetConfig || typeof targetConfig !== 'object')
244
72
  continue;
245
- }
246
73
  if (Array.isArray(targetConfig.inputs)) {
247
- this.registerSubstitutorsForInputs(ownerRoot, targetName, targetConfig.inputs);
74
+ this.processInputs(targetConfig.inputs);
248
75
  }
249
76
  if (Array.isArray(targetConfig.dependsOn)) {
250
- this.registerSubstitutorsForDependsOn(ownerRoot, targetName, targetConfig.dependsOn, project.targets, project.name);
77
+ this.processDependsOn(targetConfig.dependsOn, project.targets, project.name);
251
78
  }
252
79
  }
253
80
  }
254
81
  }
255
- // Factory methods for creating substitutors. Using factory functions
256
- // ensures that index variables (i, j) are captured as function parameters
257
- // (always by value), preventing closure-over-loop-variable bugs.
258
- createInputsStringSubstitutor(targetName, i) {
259
- return (finalName, ownerConfig) => {
260
- this.forEachTargetConfig(ownerConfig, targetName, (targetConfig) => {
261
- const finalInput = targetConfig.inputs?.[i];
262
- if (finalInput &&
263
- typeof finalInput === 'object' &&
264
- 'projects' in finalInput) {
265
- finalInput.projects = finalName;
266
- }
267
- });
268
- };
269
- }
270
- createInputsArraySubstitutor(targetName, i, j) {
271
- return (finalName, ownerConfig) => {
272
- this.forEachTargetConfig(ownerConfig, targetName, (targetConfig) => {
273
- const finalInput = targetConfig.inputs?.[i];
274
- if (finalInput &&
275
- typeof finalInput === 'object' &&
276
- 'projects' in finalInput) {
277
- finalInput['projects'][j] = finalName;
278
- }
279
- });
280
- };
281
- }
282
- createDependsOnStringSubstitutor(targetName, i) {
283
- return (finalName, ownerConfig) => {
284
- this.forEachTargetConfig(ownerConfig, targetName, (targetConfig) => {
285
- const finalDep = targetConfig.dependsOn?.[i];
286
- if (finalDep &&
287
- typeof finalDep === 'object' &&
288
- 'projects' in finalDep) {
289
- finalDep.projects = finalName;
290
- }
291
- });
292
- };
293
- }
294
- createDependsOnArraySubstitutor(targetName, i, j) {
295
- return (finalName, ownerConfig) => {
296
- this.forEachTargetConfig(ownerConfig, targetName, (targetConfig) => {
297
- const finalDep = targetConfig.dependsOn?.[i];
298
- if (finalDep &&
299
- typeof finalDep === 'object' &&
300
- 'projects' in finalDep) {
301
- finalDep['projects'][j] = finalName;
302
- }
303
- });
304
- };
305
- }
306
- createDependsOnTargetStringSubstitutor(targetName, i, targetPart) {
307
- return (finalName, ownerConfig) => {
308
- this.forEachTargetConfig(ownerConfig, targetName, (targetConfig) => {
309
- const finalDep = targetConfig.dependsOn?.[i];
310
- if (typeof finalDep === 'string') {
311
- targetConfig.dependsOn[i] =
312
- `${finalName}:${targetPart}`;
313
- }
314
- });
315
- };
316
- }
317
- registerSubstitutorsForInputs(ownerRoot, targetName, inputs) {
318
- const arrayKey = `${ownerRoot}:targets.${targetName}.inputs`;
82
+ processInputs(inputs) {
319
83
  for (let i = 0; i < inputs.length; i++) {
320
- const input = inputs[i];
321
- if (typeof input !== 'object' || !('projects' in input)) {
84
+ const entry = inputs[i];
85
+ // Existing sentinel: spread merges may have copied it out of its
86
+ // original array, so rebind parent to this one.
87
+ if (isNameRef(entry)) {
88
+ entry.parent = inputs;
322
89
  continue;
323
90
  }
324
- const inputProjectNames = input['projects'];
325
- if (typeof inputProjectNames === 'string') {
326
- // `self` and `dependencies` are keywords, not project names.
327
- if (inputProjectNames === 'self' ||
328
- inputProjectNames === 'dependencies') {
91
+ if (!entry || typeof entry !== 'object')
92
+ continue;
93
+ if (!('projects' in entry))
94
+ continue;
95
+ const element = entry;
96
+ const projects = element.projects;
97
+ if (isNameRef(projects)) {
98
+ // Object-parent sentinel — element identity is stable across spread.
99
+ continue;
100
+ }
101
+ if (typeof projects === 'string') {
102
+ if (projects === 'self' || projects === 'dependencies')
329
103
  continue;
330
- }
331
- this.registerProjectNameSubstitutor(inputProjectNames, ownerRoot, arrayKey, i, this.createInputsStringSubstitutor(targetName, i));
104
+ element.projects = this.createRef(projects, element, 'projects');
332
105
  }
333
- else if (Array.isArray(inputProjectNames)) {
334
- for (let j = 0; j < inputProjectNames.length; j++) {
335
- const projectName = inputProjectNames[j];
336
- this.registerProjectNameSubstitutor(projectName, ownerRoot, arrayKey, i, this.createInputsArraySubstitutor(targetName, i, j), j // subIndex for array elements
337
- );
338
- }
339
- // Clear stale sub-indices if a later plugin shrinks the array.
340
- this.clearSubstitutorsFromSubIndex(arrayKey, i, inputProjectNames.length);
106
+ else if (Array.isArray(projects)) {
107
+ this.processProjectsArray(projects);
341
108
  }
342
109
  }
343
- // Evict any dangling substitutors at indices beyond the new array length —
344
- // the array may have shrunk compared to a previous plugin's contribution.
345
- this.clearSubstitutorsFromIndex(arrayKey, inputs.length);
346
110
  }
347
- registerSubstitutorsForDependsOn(ownerRoot, targetName, dependsOn, ownerTargets, ownerProjectName) {
348
- const arrayKey = `${ownerRoot}:targets.${targetName}.dependsOn`;
111
+ processDependsOn(dependsOn, ownerTargets, ownerName) {
349
112
  for (let i = 0; i < dependsOn.length; i++) {
350
113
  const dep = dependsOn[i];
114
+ // Existing sentinel: rebind parent to this array in case a spread
115
+ // merge copied it out of its original.
116
+ if (isNameRef(dep)) {
117
+ dep.parent = dependsOn;
118
+ continue;
119
+ }
351
120
  if (typeof dep === 'string') {
352
- // String-form dependsOn entries like "project:target". Strings
353
- // starting with '^' are dependency-mode references (no project
354
- // name). Use splitTargetFromConfigurations with the nameMap to
355
- // properly handle project / target names containing colons.
356
- //
357
- // However, if the string matches a target name in the owning
358
- // project, it is a same-project target reference (e.g. a target
359
- // literally named "nx:echo"), not a cross-project reference.
360
- if (!dep.startsWith('^') && !(ownerTargets && dep in ownerTargets)) {
361
- const [maybeProject, ...rest] = (0, split_target_1.splitTargetFromConfigurations)(dep, this.getNameMap(), { silent: true, currentProject: ownerProjectName });
362
- if (rest.length > 0) {
363
- const targetPart = rest.join(':');
364
- this.registerProjectNameSubstitutor(maybeProject, ownerRoot, arrayKey, i, this.createDependsOnTargetStringSubstitutor(targetName, i, targetPart));
365
- }
121
+ // `^target` and same-project targets aren't cross-project refs.
122
+ if (dep.startsWith('^') || (ownerTargets && dep in ownerTargets)) {
123
+ continue;
366
124
  }
125
+ const [maybeProject, ...rest] = (0, split_target_1.splitTargetFromConfigurations)(dep, this.getNameMap(), { silent: true, currentProject: ownerName });
126
+ if (rest.length === 0)
127
+ continue;
128
+ const targetPart = rest.join(':');
129
+ dependsOn[i] = this.createRef(maybeProject, dependsOn, undefined, targetPart);
367
130
  continue;
368
131
  }
369
- if (typeof dep !== 'object' || !dep.projects) {
132
+ if (!dep || typeof dep !== 'object' || !('projects' in dep))
133
+ continue;
134
+ const element = dep;
135
+ const projects = element.projects;
136
+ if (isNameRef(projects)) {
370
137
  continue;
371
138
  }
372
- const depProjects = dep.projects;
373
- if (typeof depProjects === 'string') {
374
- // `*`, `self`, and `dependencies` are keywords, not project names.
375
- if (['*', 'self', 'dependencies'].includes(depProjects)) {
139
+ if (typeof projects === 'string') {
140
+ if (projects === '*' ||
141
+ projects === 'self' ||
142
+ projects === 'dependencies') {
376
143
  continue;
377
144
  }
378
- this.registerProjectNameSubstitutor(depProjects, ownerRoot, arrayKey, i, this.createDependsOnStringSubstitutor(targetName, i));
145
+ element.projects = this.createRef(projects, element, 'projects');
379
146
  }
380
- else if (Array.isArray(depProjects)) {
381
- // Glob patterns can match multiple projects and can't be resolved
382
- // to a single project name at this stage, so we skip them.
383
- for (let j = 0; j < depProjects.length; j++) {
384
- const projectName = depProjects[j];
385
- if ((0, globs_1.isGlobPattern)(projectName)) {
386
- continue;
387
- }
388
- this.registerProjectNameSubstitutor(projectName, ownerRoot, arrayKey, i, this.createDependsOnArraySubstitutor(targetName, i, j), j // subIndex for array elements
389
- );
390
- }
391
- // Clear stale sub-indices if a later plugin shrinks the array.
392
- this.clearSubstitutorsFromSubIndex(arrayKey, i, depProjects.length);
147
+ else if (Array.isArray(projects)) {
148
+ this.processProjectsArray(projects);
393
149
  }
394
150
  }
395
- // Evict any dangling substitutors at indices beyond the new array length —
396
- // the array may have shrunk compared to a previous plugin's contribution.
397
- this.clearSubstitutorsFromIndex(arrayKey, dependsOn.length);
398
151
  }
399
- /**
400
- * Records that a project with `name` exists at the given `root`. Call
401
- * this during the merge phase whenever a project's name changes at a
402
- * root — **before** calling
403
- * {@link registerSubstitutorsForNodeResults} for that result.
404
- *
405
- * The nameMap (maintained externally by ProjectNodesManager) is always
406
- * current — this method only needs to mark the root as dirty and
407
- * promote any pending substitutors keyed by name.
408
- */
409
- identifyProjectWithRoot(root, name) {
410
- // Always mark dirty when called — the caller only invokes this when
411
- // the name actually changed at this root (first identification or
412
- // rename). If there are pending substitutors for this name, those
413
- // forward refs need updating. If it's a rename, existing refs need
414
- // updating. Either way, the root is dirty.
415
- this.dirtyRoots.add(root);
416
- // Promote any pending substitutors that were waiting for this name.
417
- const pending = this.pendingSubstitutorsByName.get(name);
418
- if (pending) {
419
- this.pendingSubstitutorsByName.delete(name);
420
- let substitutorsForRoot = this.substitutorsByReferencedRoot.get(root);
421
- if (!substitutorsForRoot) {
422
- substitutorsForRoot = new Set();
423
- this.substitutorsByReferencedRoot.set(root, substitutorsForRoot);
424
- }
425
- for (const entry of pending) {
426
- substitutorsForRoot.add(entry);
152
+ processProjectsArray(projects) {
153
+ for (let j = 0; j < projects.length; j++) {
154
+ const name = projects[j];
155
+ if (isNameRef(name)) {
156
+ name.parent = projects;
157
+ continue;
427
158
  }
428
- // Update tracking items to reflect the promotion from name root.
429
- for (const [, byIndex] of this.substitutorsByArrayKey) {
430
- for (const atIndex of byIndex) {
431
- if (!atIndex)
432
- continue;
433
- for (const item of atIndex) {
434
- if (item &&
435
- item.referencedName === name &&
436
- pending.has(item.entry)) {
437
- item.referencedName = undefined;
438
- item.referencedRoot = root;
439
- }
440
- }
441
- }
159
+ if (typeof name !== 'string')
160
+ continue;
161
+ if ((0, globs_1.isGlobPattern)(name))
162
+ continue;
163
+ projects[j] = this.createRef(name, projects, undefined);
164
+ }
165
+ }
166
+ // Builds a sentinel and registers it.
167
+ createRef(referencedName, parent, key, targetPart) {
168
+ const referencedRoot = this.getNameMap()[referencedName]?.root;
169
+ const ref = referencedRoot !== undefined
170
+ ? new RootRef(referencedRoot, parent, key, targetPart)
171
+ : new UsageRef(referencedName, parent, key, targetPart);
172
+ this.allRefs.add(ref);
173
+ if (ref instanceof UsageRef) {
174
+ let set = this.pendingByName.get(referencedName);
175
+ if (!set) {
176
+ set = new Set();
177
+ this.pendingByName.set(referencedName, set);
442
178
  }
179
+ set.add(ref);
443
180
  }
181
+ return ref;
444
182
  }
445
- /**
446
- * Executes all registered substitutors for renamed projects, updating
447
- * stale project name references in the final merged `rootMap`. Should be
448
- * called once after all plugin results have been merged.
449
- */
450
- applySubstitutions(rootMap) {
451
- for (const root of this.dirtyRoots) {
452
- const finalName = rootMap[root]?.name;
453
- if (!finalName) {
183
+ // Records `name` → `root` and promotes any waiting UsageRef sentinels to
184
+ // RootRef by prototype swap. Sentinel identity across spread copies means
185
+ // one promotion updates every array the sentinel reached.
186
+ identifyProjectWithRoot(root, name) {
187
+ const pending = this.pendingByName.get(name);
188
+ if (!pending)
189
+ return;
190
+ this.pendingByName.delete(name);
191
+ for (const ref of pending) {
192
+ if (!(ref instanceof UsageRef))
454
193
  continue;
455
- }
456
- const substitutors = this.substitutorsByReferencedRoot.get(root);
457
- if (!substitutors) {
194
+ Object.setPrototypeOf(ref, RootRef.prototype);
195
+ ref.value = root;
196
+ }
197
+ }
198
+ // Writes each sentinel's current resolved name back into its owning slot.
199
+ // Called once after all plugin results have been merged.
200
+ applySubstitutions(rootMap) {
201
+ const nameByRoot = {};
202
+ for (const root in rootMap) {
203
+ nameByRoot[root] = rootMap[root]?.name;
204
+ }
205
+ for (const ref of this.allRefs) {
206
+ const finalName = this.resolveFinalName(ref, nameByRoot);
207
+ if (finalName === undefined)
458
208
  continue;
209
+ const replacement = ref.targetPart !== undefined
210
+ ? `${finalName}:${ref.targetPart}`
211
+ : finalName;
212
+ this.writeReplacement(ref, replacement);
213
+ }
214
+ this.allRefs.clear();
215
+ this.pendingByName.clear();
216
+ }
217
+ resolveFinalName(ref, nameByRoot) {
218
+ if (ref instanceof RootRef) {
219
+ return nameByRoot[ref.value];
220
+ }
221
+ // Unpromoted forward ref — best effort, fall back to the written name.
222
+ return this.getNameMap()[ref.value]?.name ?? ref.value;
223
+ }
224
+ writeReplacement(ref, replacement) {
225
+ const parent = ref.parent;
226
+ if (Array.isArray(parent)) {
227
+ // One sentinel may appear at multiple indices (e.g. `[..., ...]`
228
+ // pushed the same reference twice via spread), so replace all.
229
+ for (let i = 0; i < parent.length; i++) {
230
+ if (parent[i] === ref)
231
+ parent[i] = replacement;
459
232
  }
460
- for (const { ownerRoot, substitutor } of substitutors) {
461
- const ownerConfig = rootMap[ownerRoot];
462
- if (ownerConfig) {
463
- substitutor(finalName, ownerConfig);
464
- }
465
- }
233
+ return;
234
+ }
235
+ if (parent && typeof parent === 'object' && ref.key !== undefined) {
236
+ parent[ref.key] = replacement;
466
237
  }
467
238
  }
468
239
  }