nx 23.2.0-beta.2 → 23.2.0-beta.4
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/dist/bin/nx.js +1 -0
- package/dist/src/analytics/analytics.js +9 -7
- package/dist/src/command-line/graph/graph.d.ts +1 -0
- package/dist/src/command-line/graph/graph.js +15 -6
- package/dist/src/command-line/migrate/migrate.js +16 -1
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
- package/dist/src/command-line/release/utils/remote-release-clients/github.js +1 -1
- package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
- package/dist/src/config/nx-json.d.ts +4 -2
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/native/index.d.ts +2 -2
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +4 -2
- package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +12 -4
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +1 -0
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +16 -2
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +11 -14
- package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
- package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
- package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
- package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
- package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
- package/dist/src/project-graph/utils/project-configuration/target-normalization.js +16 -3
- package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
- package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
- package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
- package/dist/src/project-graph/utils/project-configuration-utils.js +79 -99
- package/dist/src/tasks-runner/task-env.d.ts +9 -0
- package/dist/src/tasks-runner/task-env.js +26 -2
- package/dist/src/tasks-runner/task-orchestrator.js +2 -6
- package/dist/src/utils/analytics-prompt.d.ts +0 -6
- package/dist/src/utils/analytics-prompt.js +0 -51
- package/dist/src/utils/catalog/bun-manager-utils.d.ts +8 -0
- package/dist/src/utils/catalog/bun-manager-utils.js +174 -0
- package/dist/src/utils/catalog/bun-manager.d.ts +28 -0
- package/dist/src/utils/catalog/bun-manager.js +130 -0
- package/dist/src/utils/catalog/index.d.ts +1 -1
- package/dist/src/utils/catalog/index.js +1 -1
- package/dist/src/utils/catalog/manager-factory.js +3 -0
- package/dist/src/utils/catalog/manager-utils.d.ts +8 -1
- package/dist/src/utils/catalog/manager-utils.js +9 -4
- package/dist/src/utils/catalog/manager.d.ts +13 -1
- package/dist/src/utils/catalog/manager.js +30 -0
- package/dist/src/utils/catalog/pnpm-manager.d.ts +2 -1
- package/dist/src/utils/catalog/pnpm-manager.js +3 -0
- package/dist/src/utils/catalog/types.d.ts +4 -0
- package/dist/src/utils/catalog/yarn-manager.d.ts +6 -2
- package/dist/src/utils/catalog/yarn-manager.js +23 -32
- package/dist/src/utils/git-utils.d.ts +15 -0
- package/dist/src/utils/git-utils.js +64 -6
- package/dist/src/utils/package-json.js +11 -7
- package/dist/src/utils/package-manager.d.ts +3 -1
- package/dist/src/utils/package-manager.js +22 -2
- package/dist/src/utils/workspace-id.d.ts +20 -0
- package/dist/src/utils/workspace-id.js +54 -0
- package/package.json +22 -17
|
@@ -492,14 +492,14 @@ export interface HashInputs {
|
|
|
492
492
|
* so the caller can set it as an env var for child processes.
|
|
493
493
|
* Used by CLI and daemon.
|
|
494
494
|
*/
|
|
495
|
-
export declare function initializeTelemetry(connection: ExternalObject<NxDbConnection>, workspaceId: string, userId: string, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): string
|
|
495
|
+
export declare function initializeTelemetry(connection: ExternalObject<NxDbConnection>, workspaceId: string, userId: string | undefined | null, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): string
|
|
496
496
|
|
|
497
497
|
/**
|
|
498
498
|
* Initialize telemetry with a pre-fetched session ID.
|
|
499
499
|
* No DB connection — used by plugin workers that inherit the
|
|
500
500
|
* session ID from their parent process via env var.
|
|
501
501
|
*/
|
|
502
|
-
export declare function initializeTelemetryWithSessionId(sessionId: string, workspaceId: string, userId: string, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): void
|
|
502
|
+
export declare function initializeTelemetryWithSessionId(sessionId: string, workspaceId: string, userId: string | undefined | null, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): void
|
|
503
503
|
|
|
504
504
|
export interface InputsInput {
|
|
505
505
|
input: string
|
|
Binary file
|
|
Binary file
|
|
@@ -183,7 +183,8 @@ function getNodes(data, isV5) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
const packageNames = new Set();
|
|
186
|
-
for
|
|
186
|
+
// pnpm omits the packages block for workspace-only lockfiles (no external deps)
|
|
187
|
+
for (const [key, snapshot] of Object.entries(data.packages ?? {})) {
|
|
187
188
|
let packageNameObj;
|
|
188
189
|
const originalPackageName = extractNameFromKey(key, isV5);
|
|
189
190
|
if (!originalPackageName) {
|
|
@@ -364,7 +365,8 @@ function getHoistedVersion(packageName, isV5, hoistedKeysByPackage) {
|
|
|
364
365
|
}
|
|
365
366
|
function getDependencies(data, keyMap, isV5, ctx) {
|
|
366
367
|
const results = [];
|
|
367
|
-
|
|
368
|
+
// pnpm omits the packages block for workspace-only lockfiles (no external deps)
|
|
369
|
+
Object.keys(data.packages ?? {}).forEach((key) => {
|
|
368
370
|
const snapshot = data.packages[key];
|
|
369
371
|
const nodes = keyMap.get(key);
|
|
370
372
|
nodes.forEach((node) => {
|
|
@@ -161,11 +161,19 @@ class TargetProjectLocator {
|
|
|
161
161
|
return externalNodeName;
|
|
162
162
|
}
|
|
163
163
|
const version = (0, semver_1.clean)(externalPackageJson.version);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const isAliasImport = packageName !== externalPackageJson.name;
|
|
165
|
+
let matchingExternalNode = null;
|
|
166
|
+
if (isAliasImport) {
|
|
167
|
+
// Prefer the alias node when both the alias import and the resolved package
|
|
168
|
+
// exist in the graph, otherwise generated package.json files lose the alias key.
|
|
167
169
|
const aliasNpmProjectKey = `npm:${packageName}@npm:${externalPackageJson.name}@${version}`;
|
|
168
|
-
matchingExternalNode =
|
|
170
|
+
matchingExternalNode =
|
|
171
|
+
this.npmProjects[aliasNpmProjectKey] ??
|
|
172
|
+
this.npmProjects[`npm:${packageName}`];
|
|
173
|
+
}
|
|
174
|
+
if (!matchingExternalNode) {
|
|
175
|
+
matchingExternalNode =
|
|
176
|
+
this.npmProjects[`npm:${externalPackageJson.name}@${version}`];
|
|
169
177
|
}
|
|
170
178
|
if (!matchingExternalNode) {
|
|
171
179
|
// Fallback to package name as key. This can happen if the version in project graph is not the same as in the resolved package.json.
|
|
@@ -34,6 +34,7 @@ export declare function isUsageRef(value: unknown): value is UsageRef;
|
|
|
34
34
|
export declare class ProjectNameInNodePropsManager {
|
|
35
35
|
private getNameMap;
|
|
36
36
|
private pendingByName;
|
|
37
|
+
private nameHistory;
|
|
37
38
|
constructor(getNameMap?: () => Record<string, ProjectConfiguration>);
|
|
38
39
|
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;
|
|
39
40
|
private processInputs;
|
|
@@ -51,6 +51,9 @@ function isUsageRef(value) {
|
|
|
51
51
|
class ProjectNameInNodePropsManager {
|
|
52
52
|
constructor(getNameMap) {
|
|
53
53
|
this.pendingByName = new Map();
|
|
54
|
+
// name → root for every name a project has ever been identified by, so refs
|
|
55
|
+
// to a since-renamed name still bind to the right root (see createRef).
|
|
56
|
+
this.nameHistory = new Map();
|
|
54
57
|
this.getNameMap = getNameMap ?? (() => ({}));
|
|
55
58
|
}
|
|
56
59
|
// Replaces each project-name ref in `inputs`/`dependsOn` with a sentinel.
|
|
@@ -148,9 +151,14 @@ class ProjectNameInNodePropsManager {
|
|
|
148
151
|
projects[j] = this.createRef(name);
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
// Builds a sentinel and registers it.
|
|
154
|
+
// Builds a sentinel and registers it. When `referencedName` isn't in the
|
|
155
|
+
// current name map, fall back to the rename history: a batch may reference a
|
|
156
|
+
// project by a name that a project earlier in the same batch already renamed
|
|
157
|
+
// (refs register after the whole batch merges), and the old name still
|
|
158
|
+
// identifies the same root.
|
|
152
159
|
createRef(referencedName, targetPart) {
|
|
153
|
-
const referencedRoot = this.getNameMap()[referencedName]?.root
|
|
160
|
+
const referencedRoot = this.getNameMap()[referencedName]?.root ??
|
|
161
|
+
this.nameHistory.get(referencedName);
|
|
154
162
|
const ref = referencedRoot !== undefined
|
|
155
163
|
? new RootRef(referencedRoot, targetPart)
|
|
156
164
|
: new UsageRef(referencedName, targetPart);
|
|
@@ -168,6 +176,11 @@ class ProjectNameInNodePropsManager {
|
|
|
168
176
|
// RootRef by prototype swap. Sentinel identity across spread copies means
|
|
169
177
|
// one promotion updates every array the sentinel reached.
|
|
170
178
|
identifyProjectWithRoot(root, name) {
|
|
179
|
+
// Every name a root has ever gone by, including pre-rename names. A later
|
|
180
|
+
// ref to a stale name resolves to the root it identified at the time.
|
|
181
|
+
// (If two roots use the same name over a graph construction, the later
|
|
182
|
+
// one wins — matching how the live name map would have resolved it.)
|
|
183
|
+
this.nameHistory.set(name, root);
|
|
171
184
|
const pending = this.pendingByName.get(name);
|
|
172
185
|
if (!pending)
|
|
173
186
|
return;
|
|
@@ -207,6 +220,7 @@ class ProjectNameInNodePropsManager {
|
|
|
207
220
|
}
|
|
208
221
|
}
|
|
209
222
|
this.pendingByName.clear();
|
|
223
|
+
this.nameHistory.clear();
|
|
210
224
|
}
|
|
211
225
|
substituteInArray(entries, nameByRoot) {
|
|
212
226
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -28,15 +28,12 @@ export declare class ProjectNodesManager {
|
|
|
28
28
|
mergeProjectNode(project: ProjectConfiguration, configurationSourceMaps?: ConfigurationSourceMaps, sourceInformation?: SourceInformation): void;
|
|
29
29
|
/**
|
|
30
30
|
* Inserts project-name sentinels into `inputs` and `dependsOn` on the
|
|
31
|
-
* merged objects
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* Pass a different `mergedRootMap` for the default-plugin intermediate
|
|
36
|
-
* pass, then call again with `this.rootMap` after it's applied so
|
|
37
|
-
* name refs introduced by that merge are sentinelized as well.
|
|
31
|
+
* merged objects in this manager's rootMap. Walking the merged entries —
|
|
32
|
+
* not the plugin results — matters because a spread-produced array is a
|
|
33
|
+
* fresh instance, and because sentinels written into plugin-result arrays
|
|
34
|
+
* would corrupt them for any later merge that re-reads the results.
|
|
38
35
|
*/
|
|
39
|
-
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration
|
|
36
|
+
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;
|
|
40
37
|
/**
|
|
41
38
|
* Applies all pending name substitutions. Call once after all plugin
|
|
42
39
|
* results have been merged.
|
|
@@ -129,10 +129,10 @@ deferSpreadsWithoutBase = true) {
|
|
|
129
129
|
// if augmenting an existing target.
|
|
130
130
|
const target = project.targets?.[targetName];
|
|
131
131
|
if (sourceMap) {
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
132
|
+
// Claims the node key for a newly-created target (or reclaims it from
|
|
133
|
+
// a weak target-defaults stamp). Whether an *existing* target changes
|
|
134
|
+
// owners is decided inside `mergeTargetConfigurations`, which knows
|
|
135
|
+
// whether this merge changed the target's identity.
|
|
136
136
|
(0, source_maps_1.recordTargetIdentitySourceMapInfo)(sourceMap, (0, source_maps_1.targetSourceMapKey)(targetName), sourceInformation);
|
|
137
137
|
}
|
|
138
138
|
const normalizedTarget = skipTargetNormalization
|
|
@@ -257,21 +257,18 @@ class ProjectNodesManager {
|
|
|
257
257
|
}
|
|
258
258
|
/**
|
|
259
259
|
* Inserts project-name sentinels into `inputs` and `dependsOn` on the
|
|
260
|
-
* merged objects
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* Pass a different `mergedRootMap` for the default-plugin intermediate
|
|
265
|
-
* pass, then call again with `this.rootMap` after it's applied so
|
|
266
|
-
* name refs introduced by that merge are sentinelized as well.
|
|
260
|
+
* merged objects in this manager's rootMap. Walking the merged entries —
|
|
261
|
+
* not the plugin results — matters because a spread-produced array is a
|
|
262
|
+
* fresh instance, and because sentinels written into plugin-result arrays
|
|
263
|
+
* would corrupt them for any later merge that re-reads the results.
|
|
267
264
|
*/
|
|
268
|
-
registerNameRefs(pluginResultProjects
|
|
265
|
+
registerNameRefs(pluginResultProjects) {
|
|
269
266
|
if (!pluginResultProjects)
|
|
270
267
|
return;
|
|
271
268
|
const scoped = {};
|
|
272
269
|
for (const root in pluginResultProjects) {
|
|
273
|
-
if (
|
|
274
|
-
scoped[root] =
|
|
270
|
+
if (this.rootMap[root]) {
|
|
271
|
+
scoped[root] = this.rootMap[root];
|
|
275
272
|
}
|
|
276
273
|
}
|
|
277
274
|
this.nameSubstitutionManager.registerNameRefs(scoped);
|
|
@@ -9,12 +9,19 @@ export type SourceInformation = [file: string | null, plugin: string];
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const TARGET_DEFAULTS_PLUGIN_NAME = "nx/target-defaults";
|
|
11
11
|
/**
|
|
12
|
-
* Write the source for
|
|
13
|
-
* its
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Write the source for the target node key (`targets.<name>`). Ownership of a
|
|
13
|
+
* target follows its identity, not the last writer:
|
|
14
|
+
*
|
|
15
|
+
* - An unowned key goes to whoever writes it first (the creator).
|
|
16
|
+
* - A target-defaults stamp is weak — it never authors a target's existence,
|
|
17
|
+
* so any real plugin reclaims the key from it, and it can never take the
|
|
18
|
+
* key from a real plugin.
|
|
19
|
+
* - Between real plugins, the key only changes hands when the merge changed
|
|
20
|
+
* the target's identity (executor/command) — a plugin that merely layers
|
|
21
|
+
* fields (dependsOn, options, …) onto an existing target does not become
|
|
22
|
+
* its owner.
|
|
16
23
|
*/
|
|
17
|
-
export declare function recordTargetIdentitySourceMapInfo(sourceMap: Record<string, SourceInformation>, key: string, sourceInfo: SourceInformation): void;
|
|
24
|
+
export declare function recordTargetIdentitySourceMapInfo(sourceMap: Record<string, SourceInformation>, key: string, sourceInfo: SourceInformation, identityChanged?: boolean): void;
|
|
18
25
|
/** Source map per project root. */
|
|
19
26
|
export type ConfigurationSourceMaps = Record<string, Record<string, SourceInformation>>;
|
|
20
27
|
export declare function forEachSourceMapKeyForArray(prefixKey: string, array: unknown[], callback: (key: string, index: number) => void, startIndex?: number): void;
|
|
@@ -21,14 +21,28 @@ exports.targetConfigurationsSourceMapKey = targetConfigurationsSourceMapKey;
|
|
|
21
21
|
*/
|
|
22
22
|
exports.TARGET_DEFAULTS_PLUGIN_NAME = 'nx/target-defaults';
|
|
23
23
|
/**
|
|
24
|
-
* Write the source for
|
|
25
|
-
* its
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Write the source for the target node key (`targets.<name>`). Ownership of a
|
|
25
|
+
* target follows its identity, not the last writer:
|
|
26
|
+
*
|
|
27
|
+
* - An unowned key goes to whoever writes it first (the creator).
|
|
28
|
+
* - A target-defaults stamp is weak — it never authors a target's existence,
|
|
29
|
+
* so any real plugin reclaims the key from it, and it can never take the
|
|
30
|
+
* key from a real plugin.
|
|
31
|
+
* - Between real plugins, the key only changes hands when the merge changed
|
|
32
|
+
* the target's identity (executor/command) — a plugin that merely layers
|
|
33
|
+
* fields (dependsOn, options, …) onto an existing target does not become
|
|
34
|
+
* its owner.
|
|
28
35
|
*/
|
|
29
|
-
function recordTargetIdentitySourceMapInfo(sourceMap, key, sourceInfo) {
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
function recordTargetIdentitySourceMapInfo(sourceMap, key, sourceInfo, identityChanged = false) {
|
|
37
|
+
const existing = sourceMap[key];
|
|
38
|
+
if (existing === undefined) {
|
|
39
|
+
sourceMap[key] = sourceInfo;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (sourceInfo[1] === exports.TARGET_DEFAULTS_PLUGIN_NAME) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (existing[1] === exports.TARGET_DEFAULTS_PLUGIN_NAME || identityChanged) {
|
|
32
46
|
sourceMap[key] = sourceInfo;
|
|
33
47
|
}
|
|
34
48
|
}
|
|
@@ -19,7 +19,7 @@ type CreateNodesResultEntry = readonly [
|
|
|
19
19
|
* fly. That's all the matcher needs; the full target merge happens
|
|
20
20
|
* downstream in the real merge.
|
|
21
21
|
*/
|
|
22
|
-
export declare function createTargetDefaultsResults(specifiedPluginRootMap: Record<string, ProjectConfiguration>, defaultPluginRootMap: Record<string, ProjectConfiguration>, nxJsonConfiguration: NxJsonConfiguration, specifiedSourceMaps?: ConfigurationSourceMaps
|
|
22
|
+
export declare function createTargetDefaultsResults(specifiedPluginRootMap: Record<string, ProjectConfiguration>, defaultPluginRootMap: Record<string, ProjectConfiguration>, nxJsonConfiguration: NxJsonConfiguration, specifiedSourceMaps?: ConfigurationSourceMaps): CreateNodesResultEntry[];
|
|
23
23
|
/**
|
|
24
24
|
* Public reader that resolves the target defaults applying to a given
|
|
25
25
|
* target. Accepts the nested map shape (a value is either a plain config
|
|
@@ -21,7 +21,7 @@ const utils_1 = require("./utils");
|
|
|
21
21
|
* fly. That's all the matcher needs; the full target merge happens
|
|
22
22
|
* downstream in the real merge.
|
|
23
23
|
*/
|
|
24
|
-
function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps
|
|
24
|
+
function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps) {
|
|
25
25
|
const targetDefaultsConfig = nxJsonConfiguration.targetDefaults;
|
|
26
26
|
if (!targetDefaultsConfig) {
|
|
27
27
|
return [];
|
|
@@ -65,7 +65,7 @@ function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMa
|
|
|
65
65
|
if (!effective)
|
|
66
66
|
continue;
|
|
67
67
|
const sourcePlugin = needsSourcePlugin
|
|
68
|
-
? resolveSourcePlugin(root, targetName,
|
|
68
|
+
? resolveSourcePlugin(root, targetName, defaultTargets[targetName], specifiedSourceMaps)
|
|
69
69
|
: undefined;
|
|
70
70
|
const syntheticTargets = buildSyntheticTargetsForRoot(targetName, root, effective, targetDefaults, projectName, projectNode, sourcePlugin);
|
|
71
71
|
for (const { key, index, target } of syntheticTargets) {
|
|
@@ -372,34 +372,33 @@ function normalizeTargetDefaultValue(value) {
|
|
|
372
372
|
// A bare config object is functionally a one-element array.
|
|
373
373
|
return [value ?? {}];
|
|
374
374
|
}
|
|
375
|
-
function resolveSourcePlugin(root, targetName,
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
375
|
+
function resolveSourcePlugin(root, targetName, defaultTarget, specifiedSourceMaps) {
|
|
376
|
+
// `filter.plugin` ("targets originated by X") can only name a plugin from
|
|
377
|
+
// nx.json's `plugins` — the specified set. When the default layer authors
|
|
378
|
+
// the target's identity (its merged config carries an executor or command,
|
|
379
|
+
// which the merge lets win), the originator is a default plugin and the
|
|
380
|
+
// target has no matchable source plugin; no source maps are needed to see
|
|
381
|
+
// that. Otherwise the specified layer's executor/command attribution names
|
|
382
|
+
// the originator. The top-level `targets.<name>` node key is deliberately
|
|
383
|
+
// NOT used as a fallback: it tracks ownership, so a later plugin augmenting
|
|
384
|
+
// the target would mis-attribute it. The trade-off is that a target with
|
|
385
|
+
// neither an executor nor a command (a rare, non-runnable shape) resolves to
|
|
386
|
+
// no source plugin and won't match a `filter.plugin` default — accepted,
|
|
387
|
+
// since every runnable target carries one of these keys.
|
|
388
|
+
if (defaultTarget &&
|
|
389
|
+
(defaultTarget.executor !== undefined ||
|
|
390
|
+
defaultTarget.command !== undefined)) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
const sourceMap = specifiedSourceMaps?.[root];
|
|
394
|
+
for (const identityKey of ['executor', 'command']) {
|
|
395
|
+
const plugin = sourceMap?.[`${(0, source_maps_1.targetSourceMapKey)(targetName)}.${identityKey}`]?.[1];
|
|
396
|
+
if (plugin && plugin !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
|
|
397
|
+
return plugin;
|
|
398
|
+
}
|
|
396
399
|
}
|
|
397
400
|
return undefined;
|
|
398
401
|
}
|
|
399
|
-
function pluginFromSourceMap(maps, root, key) {
|
|
400
|
-
const entry = maps?.[root]?.[key];
|
|
401
|
-
return entry?.[1];
|
|
402
|
-
}
|
|
403
402
|
// Builds a name → MatcherProjectNode view for `findMatchingProjects` to
|
|
404
403
|
// consult, across both layered rootMaps. Tags are unioned across layers. A
|
|
405
404
|
// full `mergeProjectConfigurationIntoRootMap` would be overkill — the matcher
|
|
@@ -170,6 +170,11 @@ function mergeConfigurations(newConfigurations, baseConfigurations, projectConfi
|
|
|
170
170
|
// Before '...': base wins for shared names. Keep base's source-map
|
|
171
171
|
// entries when it owns the config.
|
|
172
172
|
if (baseHasConfig) {
|
|
173
|
+
// Base wins, so the incoming config is dropped without reaching
|
|
174
|
+
// `mergeConfigurationValue`. Validate its nested spread here so an
|
|
175
|
+
// integer-like-key ambiguity throws regardless of which side owns
|
|
176
|
+
// the config name (mirrors the base-independent target-level check).
|
|
177
|
+
(0, utils_1.assertNoIntegerLikeSpreadKey)(newConfigurations?.[configName], configIdentifier ? `Object at "${configIdentifier}"` : 'Object');
|
|
173
178
|
mergedConfigurations[configName] = baseConfigurations[configName];
|
|
174
179
|
}
|
|
175
180
|
else {
|
|
@@ -282,8 +287,11 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
|
|
|
282
287
|
? new Set(targetKeys.slice(0, spreadPosInTarget))
|
|
283
288
|
: new Set();
|
|
284
289
|
// Integer-like keys get hoisted to targetKeys[0], making their position
|
|
285
|
-
// relative to '...' unrecoverable.
|
|
286
|
-
|
|
290
|
+
// relative to '...' unrecoverable. This is a property of the authored
|
|
291
|
+
// config, not of the base, so it throws regardless of compatibility —
|
|
292
|
+
// which also keeps the error identical between the target-defaults staging
|
|
293
|
+
// merge and the real merge, whose bases differ.
|
|
294
|
+
if (spreadPosInTarget >= 0 &&
|
|
287
295
|
targetKeys[0] &&
|
|
288
296
|
utils_1.INTEGER_LIKE_KEY_PATTERN.test(targetKeys[0])) {
|
|
289
297
|
throw new utils_1.IntegerLikeSpreadKeyError(targetKeys[0], targetIdentifier ? `Target at "${targetIdentifier}"` : 'Target');
|
|
@@ -304,6 +312,13 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
|
|
|
304
312
|
return;
|
|
305
313
|
if (hasSpread && keysBeforeSpread.has(key)) {
|
|
306
314
|
// Before '...': base wins; fall through to target only if base lacks it.
|
|
315
|
+
// When base wins, the incoming `target[key]` is dropped without ever
|
|
316
|
+
// reaching `getMergeValueResult`, so validate its nested spread here —
|
|
317
|
+
// the integer-like-key ambiguity is a property of the authored value,
|
|
318
|
+
// not of which side owns the key, and must throw either way.
|
|
319
|
+
(0, utils_1.assertNoIntegerLikeSpreadKey)(target[key], projectConfigSourceMap
|
|
320
|
+
? `Object at "${targetIdentifier}.${key}"`
|
|
321
|
+
: 'Object');
|
|
307
322
|
result[key] =
|
|
308
323
|
key in mergeBase
|
|
309
324
|
? mergeBase[key]
|
|
@@ -381,13 +396,20 @@ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, s
|
|
|
381
396
|
processKey(key);
|
|
382
397
|
}
|
|
383
398
|
}
|
|
384
|
-
// Update
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
399
|
+
// Update the node key once after the loop. Ownership follows identity: this
|
|
400
|
+
// merge claims `targets.<name>` only when it changed the target's identity
|
|
401
|
+
// (a new/different executor or command, or an incompatible replace — whose
|
|
402
|
+
// key purge above empties the slot anyway). A plugin that only layers fields
|
|
403
|
+
// onto an existing target leaves the node with its creator; weak
|
|
404
|
+
// target-defaults stamps are always reclaimable.
|
|
389
405
|
if (projectConfigSourceMap) {
|
|
390
|
-
|
|
406
|
+
const identityChanged = !isCompatible ||
|
|
407
|
+
(target.executor !== undefined &&
|
|
408
|
+
target.executor !== baseTarget?.executor) ||
|
|
409
|
+
(target.command !== undefined &&
|
|
410
|
+
target.command !== baseTarget?.command) ||
|
|
411
|
+
suppliesNewOptionsIdentity(baseTarget, target);
|
|
412
|
+
(0, source_maps_1.recordTargetIdentitySourceMapInfo)(projectConfigSourceMap, targetIdentifier, sourceInformation, identityChanged);
|
|
391
413
|
}
|
|
392
414
|
// merge options if there are any
|
|
393
415
|
// if the targets aren't compatible, we simply discard the old options during the merge
|
|
@@ -443,6 +465,26 @@ function isCompatibleTarget(a, b) {
|
|
|
443
465
|
}
|
|
444
466
|
return true;
|
|
445
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Run-commands and run-script targets carry their runnable identity in
|
|
470
|
+
* `options` (see {@link isCompatibleTarget}). A layer that supplies that
|
|
471
|
+
* identity where the base had none changed what the target runs — the same
|
|
472
|
+
* identity change as setting an executor on a target that had none.
|
|
473
|
+
*/
|
|
474
|
+
function suppliesNewOptionsIdentity(baseTarget, target) {
|
|
475
|
+
const executor = target.executor ?? baseTarget?.executor;
|
|
476
|
+
if (executor === 'nx:run-commands') {
|
|
477
|
+
const baseCommand = baseTarget?.options?.command ??
|
|
478
|
+
baseTarget?.options?.commands?.join(' && ');
|
|
479
|
+
const targetCommand = target.options?.command ?? target.options?.commands?.join(' && ');
|
|
480
|
+
return !!targetCommand && targetCommand !== baseCommand;
|
|
481
|
+
}
|
|
482
|
+
if (executor === 'nx:run-script') {
|
|
483
|
+
const targetScript = target.options?.script;
|
|
484
|
+
return !!targetScript && targetScript !== baseTarget?.options?.script;
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
446
488
|
function resolveNxTokensInOptions(object, project, key) {
|
|
447
489
|
const result = Array.isArray(object) ? [...object] : { ...object };
|
|
448
490
|
for (let [opt, value] of Object.entries(object ?? {})) {
|
|
@@ -115,9 +115,22 @@ function validateAndNormalizeProjectRootMap(workspaceRoot, projectRootMap, nxJso
|
|
|
115
115
|
// We initially did this in the project.json plugin, but
|
|
116
116
|
// that resulted in project.json files without names causing
|
|
117
117
|
// the resulting project to change names from earlier plugins...
|
|
118
|
-
if (!project.name
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (!project.name) {
|
|
119
|
+
const projectJsonPath = (0, path_1.join)(workspaceRoot, project.root, 'project.json');
|
|
120
|
+
if ((0, node_fs_1.existsSync)(projectJsonPath)) {
|
|
121
|
+
// The project.json plugin may not have run (e.g. when a single
|
|
122
|
+
// plugin is run in isolation via `addPlugin` from a generator), so
|
|
123
|
+
// prefer the name declared in project.json before deriving one from
|
|
124
|
+
// the directory name.
|
|
125
|
+
let nameFromProjectJson;
|
|
126
|
+
try {
|
|
127
|
+
nameFromProjectJson =
|
|
128
|
+
(0, fileutils_1.readJsonFile)(projectJsonPath).name;
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
project.name =
|
|
132
|
+
nameFromProjectJson ?? (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
|
|
133
|
+
}
|
|
121
134
|
}
|
|
122
135
|
try {
|
|
123
136
|
validateProject(project, projects);
|
|
@@ -8,6 +8,21 @@ export declare const INTEGER_LIKE_KEY_PATTERN: RegExp;
|
|
|
8
8
|
export declare class IntegerLikeSpreadKeyError extends Error {
|
|
9
9
|
constructor(key: string, context: string);
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
|
|
13
|
+
* whose enumeration hoists an integer-like key ahead of the spread, making its
|
|
14
|
+
* authored position ambiguous.
|
|
15
|
+
*
|
|
16
|
+
* The ambiguity is a property of the authored value alone, not of any merge
|
|
17
|
+
* base. `mergeObjectWithSpread` runs this the moment it merges such a value —
|
|
18
|
+
* but a merge layer that lets the base win a key drops the incoming value
|
|
19
|
+
* without merging it, so the check must also be run eagerly at those
|
|
20
|
+
* base-owns-key shortcuts. Otherwise the error would surface or vanish
|
|
21
|
+
* depending on which side owns the key (e.g. it fires in the target-defaults
|
|
22
|
+
* staging merge but not the real merge), which is exactly the divergence that
|
|
23
|
+
* makes discarding staging errors unsafe.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertNoIntegerLikeSpreadKey(value: unknown, errorContext: string): void;
|
|
11
26
|
type SourceMapContext = {
|
|
12
27
|
sourceMap: Record<string, SourceInformation>;
|
|
13
28
|
key: string;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.IntegerLikeSpreadKeyError = exports.INTEGER_LIKE_KEY_PATTERN = exports.NX_SPREAD_TOKEN = void 0;
|
|
4
4
|
exports.uniqueKeysInObjects = uniqueKeysInObjects;
|
|
5
|
+
exports.assertNoIntegerLikeSpreadKey = assertNoIntegerLikeSpreadKey;
|
|
5
6
|
exports.getMergeValueResult = getMergeValueResult;
|
|
6
7
|
const source_maps_1 = require("./source-maps");
|
|
7
8
|
exports.NX_SPREAD_TOKEN = '...';
|
|
@@ -30,6 +31,29 @@ class IntegerLikeSpreadKeyError extends Error {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
exports.IntegerLikeSpreadKeyError = IntegerLikeSpreadKeyError;
|
|
34
|
+
/**
|
|
35
|
+
* Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
|
|
36
|
+
* whose enumeration hoists an integer-like key ahead of the spread, making its
|
|
37
|
+
* authored position ambiguous.
|
|
38
|
+
*
|
|
39
|
+
* The ambiguity is a property of the authored value alone, not of any merge
|
|
40
|
+
* base. `mergeObjectWithSpread` runs this the moment it merges such a value —
|
|
41
|
+
* but a merge layer that lets the base win a key drops the incoming value
|
|
42
|
+
* without merging it, so the check must also be run eagerly at those
|
|
43
|
+
* base-owns-key shortcuts. Otherwise the error would surface or vanish
|
|
44
|
+
* depending on which side owns the key (e.g. it fires in the target-defaults
|
|
45
|
+
* staging merge but not the real merge), which is exactly the divergence that
|
|
46
|
+
* makes discarding staging errors unsafe.
|
|
47
|
+
*/
|
|
48
|
+
function assertNoIntegerLikeSpreadKey(value, errorContext) {
|
|
49
|
+
if (!isObject(value) || value[exports.NX_SPREAD_TOKEN] !== true) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const keys = Object.keys(value);
|
|
53
|
+
if (keys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(keys[0])) {
|
|
54
|
+
throw new IntegerLikeSpreadKeyError(keys[0], errorContext);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
33
57
|
/**
|
|
34
58
|
* `"..."` in `newValue` (as an array element or a key set to `true`)
|
|
35
59
|
* expands the base at that position; otherwise `newValue` replaces
|
|
@@ -52,11 +76,25 @@ function getMergeValueResult(baseValue, newValue, sourceMapContext, deferSpreads
|
|
|
52
76
|
// plugin already set, purely as a merge guard — must not steal provenance
|
|
53
77
|
// from the layer that introduced the value. A genuinely new value (no base,
|
|
54
78
|
// or a different one) still attributes to the new layer.
|
|
55
|
-
|
|
79
|
+
//
|
|
80
|
+
// The exception cuts the other way too: when the *existing* attribution is a
|
|
81
|
+
// target-defaults stamp (TD merges before the plugin whose value it
|
|
82
|
+
// predicted, so its stamp lands first), a real plugin re-stating that value
|
|
83
|
+
// is the genuine author and reclaims the key.
|
|
84
|
+
if (!isUnchangedPrimitive(newValue, baseValue) ||
|
|
85
|
+
isReclaimableTargetDefaultsStamp(sourceMapContext)) {
|
|
56
86
|
writeTopLevelSourceMap(sourceMapContext);
|
|
57
87
|
}
|
|
58
88
|
return newValue;
|
|
59
89
|
}
|
|
90
|
+
// Whether the key's current attribution is a weak target-defaults stamp that
|
|
91
|
+
// the (non-target-defaults) writer should reclaim despite the value being
|
|
92
|
+
// unchanged.
|
|
93
|
+
function isReclaimableTargetDefaultsStamp(ctx) {
|
|
94
|
+
return (!!ctx &&
|
|
95
|
+
ctx.sourceMap[ctx.key]?.[1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME &&
|
|
96
|
+
ctx.sourceInformation[1] !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME);
|
|
97
|
+
}
|
|
60
98
|
// Whether `newValue` leaves a defined primitive base untouched. Objects fall
|
|
61
99
|
// through (always re-attributed) to preserve the existing replace semantics —
|
|
62
100
|
// the regression this guards is scalar executor/command re-stamping.
|
|
@@ -120,12 +158,10 @@ function mergeObjectWithSpread(baseValue, newValue, sourceMapContext, deferSprea
|
|
|
120
158
|
const errorContext = sourceMapContext?.key
|
|
121
159
|
? `Object at "${sourceMapContext.key}"`
|
|
122
160
|
: 'Object';
|
|
161
|
+
// Integer-like keys are hoisted to the front of enumeration, so one
|
|
162
|
+
// alongside `'...'` makes its authored position ambiguous.
|
|
163
|
+
assertNoIntegerLikeSpreadKey(newValue, errorContext);
|
|
123
164
|
const newKeys = Object.keys(newValue);
|
|
124
|
-
// Integer-like keys are hoisted to the front of enumeration, so if one
|
|
125
|
-
// exists alongside `'...'` it must be newKeys[0].
|
|
126
|
-
if (newKeys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(newKeys[0])) {
|
|
127
|
-
throw new IntegerLikeSpreadKeyError(newKeys[0], errorContext);
|
|
128
|
-
}
|
|
129
165
|
// Base per-key sources captured lazily — only for shared keys the new
|
|
130
166
|
// object overwrites before `'...'`, since writing their new source
|
|
131
167
|
// clobbers the base entry the spread will need to restore.
|
|
@@ -60,19 +60,22 @@ export type MergeError = AggregateCreateNodesError | MergeNodesError | ProjectsW
|
|
|
60
60
|
/**
|
|
61
61
|
* Merges create nodes results into a single rootMap.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* `'...'` spreads deferred) so that synthesis can read each layer's
|
|
66
|
-
* contribution without re-running the merge. The synthetic result from
|
|
67
|
-
* `createTargetDefaultsResults` is then merged into the manager, and
|
|
68
|
-
* the staged intermediate is replayed on top — that replay is where
|
|
69
|
-
* deferred spreads expand against the final (specified + synth) base.
|
|
63
|
+
* Every layer merges into the manager through the same source-map-aware
|
|
64
|
+
* merge, in precedence order:
|
|
70
65
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
66
|
+
* specified plugins → synthetic target defaults → default plugins
|
|
67
|
+
*
|
|
68
|
+
* so field-level provenance is decided by the merge itself for all three
|
|
69
|
+
* layers — whichever layer a field's final value came from owns its
|
|
70
|
+
* attribution, and `'...'` spreads in default-plugin configs resolve against
|
|
71
|
+
* the accumulated specified + target-defaults base.
|
|
72
|
+
*
|
|
73
|
+
* Target-default synthesis needs the *merged* shape of the default layer
|
|
74
|
+
* (to predict each target's eventual executor/command) before that layer
|
|
75
|
+
* merges into the manager. To get it, default results are first staged into
|
|
76
|
+
* a throwaway intermediate rootMap with unresolvable `'...'` spreads
|
|
77
|
+
* deferred. The staging output feeds only `createTargetDefaultsResults`; the
|
|
78
|
+
* default plugins then merge into the manager from their original results.
|
|
76
79
|
*/
|
|
77
80
|
export declare function mergeCreateNodesResults(specifiedResults: CreateNodesResultEntry[][], defaultResults: CreateNodesResultEntry[][], nxJsonConfiguration: NxJsonConfiguration, workspaceRoot: string, errors: MergeError[]): {
|
|
78
81
|
projectRootMap: Record<string, ProjectConfiguration>;
|