nx 23.1.0 → 23.2.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.
- package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
- package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
- package/dist/src/command-line/add/add.js +8 -1
- package/dist/src/command-line/import/import.js +1 -1
- package/dist/src/command-line/init/configure-plugins.js +3 -2
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
- package/dist/src/command-line/init/implementation/angular/index.js +4 -2
- package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
- package/dist/src/command-line/init/implementation/utils.js +22 -3
- package/dist/src/command-line/migrate/migrate.d.ts +13 -0
- package/dist/src/command-line/migrate/migrate.js +45 -0
- package/dist/src/command-line/release/utils/git.js +6 -2
- package/dist/src/command-line/show/show-target/info.js +11 -2
- package/dist/src/daemon/client/client.d.ts +0 -2
- package/dist/src/daemon/client/client.js +17 -19
- package/dist/src/devkit-internals.d.ts +2 -0
- package/dist/src/devkit-internals.js +7 -1
- package/dist/src/native/index.d.ts +1 -1
- 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/npm-parser.js +71 -27
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +46 -15
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
- package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
- package/dist/src/plugins/js/utils/register.d.ts +18 -0
- package/dist/src/plugins/js/utils/register.js +81 -0
- package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
- package/dist/src/plugins/js/utils/typescript.js +15 -0
- package/dist/src/project-graph/error-types.js +41 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
- package/dist/src/project-graph/plugins/transpiler.js +4 -0
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +9 -12
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +77 -61
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +1 -1
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +1 -1
- package/dist/src/project-graph/utils/project-configuration-utils.js +4 -6
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +2 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +2 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.js +11 -2
- package/dist/src/tasks-runner/run-command.js +5 -3
- package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
- package/dist/src/utils/acknowledge-build-scripts.js +100 -0
- package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
- package/dist/src/utils/catalog/manager-utils.js +22 -5
- package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
- package/dist/src/utils/catalog/pnpm-manager.js +3 -15
- package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
- package/dist/src/utils/catalog/yarn-manager.js +3 -15
- package/dist/src/utils/git-utils.d.ts +2 -3
- package/dist/src/utils/git-utils.js +101 -47
- package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
- package/dist/src/utils/provenance.js +12 -2
- package/migrations.json +7 -1
- package/package.json +11 -11
- package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
- package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
|
@@ -133,6 +133,22 @@ function findV3Version(snapshot, packageName) {
|
|
|
133
133
|
}
|
|
134
134
|
function getDependencies(data, keyMap, ctx) {
|
|
135
135
|
const dependencies = [];
|
|
136
|
+
// Memoizes semver `satisfies(version, range)` for this dependency walk. The
|
|
137
|
+
// same (version, range) pairs recur across many edges, so the range parse
|
|
138
|
+
// happens once per distinct pair instead of once per edge. Scoped to the walk
|
|
139
|
+
// (not module-global) so V8 collects it when dependency creation finishes
|
|
140
|
+
// rather than retaining it for the daemon's lifetime.
|
|
141
|
+
const versionSatisfiesCache = new Map();
|
|
142
|
+
const cachedSatisfies = (version, range) => {
|
|
143
|
+
const key = `${version}\n${range}`;
|
|
144
|
+
const cached = versionSatisfiesCache.get(key);
|
|
145
|
+
if (cached !== undefined) {
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
const result = (0, semver_1.satisfies)(version, range);
|
|
149
|
+
versionSatisfiesCache.set(key, result);
|
|
150
|
+
return result;
|
|
151
|
+
};
|
|
136
152
|
if (data.lockfileVersion > 1) {
|
|
137
153
|
Object.entries(data.packages).forEach(([path, snapshot]) => {
|
|
138
154
|
// we are skipping workspaces packages
|
|
@@ -147,7 +163,7 @@ function getDependencies(data, keyMap, ctx) {
|
|
|
147
163
|
].forEach((section) => {
|
|
148
164
|
if (section) {
|
|
149
165
|
Object.entries(section).forEach(([name, versionRange]) => {
|
|
150
|
-
const target = findTarget(path, keyMap, name, versionRange);
|
|
166
|
+
const target = findTarget(path, keyMap, name, versionRange, cachedSatisfies);
|
|
151
167
|
if (target) {
|
|
152
168
|
const dep = {
|
|
153
169
|
source: sourceName,
|
|
@@ -164,12 +180,12 @@ function getDependencies(data, keyMap, ctx) {
|
|
|
164
180
|
}
|
|
165
181
|
else {
|
|
166
182
|
Object.entries(data.dependencies).forEach(([packageName, snapshot]) => {
|
|
167
|
-
addV1NodeDependencies(`node_modules/${packageName}`, snapshot, dependencies, keyMap, ctx);
|
|
183
|
+
addV1NodeDependencies(`node_modules/${packageName}`, snapshot, dependencies, keyMap, ctx, cachedSatisfies);
|
|
168
184
|
});
|
|
169
185
|
}
|
|
170
186
|
return dependencies;
|
|
171
187
|
}
|
|
172
|
-
function findTarget(sourcePath, keyMap, targetName, versionRange,
|
|
188
|
+
function findTarget(sourcePath, keyMap, targetName, versionRange, cachedSatisfies,
|
|
173
189
|
// When a package is found at a path but its version doesn't satisfy the
|
|
174
190
|
// range (e.g. due to npm overrides), we keep it as a fallback. npm already
|
|
175
191
|
// resolved this dependency to that location, so it is the correct target
|
|
@@ -186,12 +202,13 @@ fallback) {
|
|
|
186
202
|
versionRange.startsWith('npm:')) {
|
|
187
203
|
const nodeVersion = child.data.version.slice(child.data.version.indexOf('@', 5) + 1);
|
|
188
204
|
const depVersion = versionRange.slice(versionRange.indexOf('@', 5) + 1);
|
|
189
|
-
if (nodeVersion === depVersion ||
|
|
205
|
+
if (nodeVersion === depVersion ||
|
|
206
|
+
cachedSatisfies(nodeVersion, depVersion)) {
|
|
190
207
|
return child;
|
|
191
208
|
}
|
|
192
209
|
}
|
|
193
210
|
else if (child.data.version === versionRange ||
|
|
194
|
-
(
|
|
211
|
+
cachedSatisfies(child.data.version, versionRange)) {
|
|
195
212
|
return child;
|
|
196
213
|
}
|
|
197
214
|
// Version mismatch — save as fallback (could be an npm override)
|
|
@@ -203,13 +220,17 @@ fallback) {
|
|
|
203
220
|
if (!sourcePath) {
|
|
204
221
|
return fallback;
|
|
205
222
|
}
|
|
206
|
-
|
|
223
|
+
// Walk one level up the nesting chain by dropping the trailing
|
|
224
|
+
// `node_modules/<pkg>` segment. Slash-index arithmetic avoids the
|
|
225
|
+
// split/slice/join array allocation on every hop.
|
|
226
|
+
const lastNodeModules = sourcePath.lastIndexOf('node_modules/');
|
|
227
|
+
return findTarget(lastNodeModules === -1 ? '' : sourcePath.substring(0, lastNodeModules), keyMap, targetName, versionRange, cachedSatisfies, fallback);
|
|
207
228
|
}
|
|
208
|
-
function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx) {
|
|
229
|
+
function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx, cachedSatisfies) {
|
|
209
230
|
if (keyMap.has(path) && snapshot.requires) {
|
|
210
231
|
const source = keyMap.get(path).name;
|
|
211
232
|
Object.entries(snapshot.requires).forEach(([name, versionRange]) => {
|
|
212
|
-
const target = findTarget(path, keyMap, name, versionRange);
|
|
233
|
+
const target = findTarget(path, keyMap, name, versionRange, cachedSatisfies);
|
|
213
234
|
if (target) {
|
|
214
235
|
const dep = {
|
|
215
236
|
source: source,
|
|
@@ -223,14 +244,14 @@ function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx) {
|
|
|
223
244
|
}
|
|
224
245
|
if (snapshot.dependencies) {
|
|
225
246
|
Object.entries(snapshot.dependencies).forEach(([depName, depSnapshot]) => {
|
|
226
|
-
addV1NodeDependencies(`${path}/node_modules/${depName}`, depSnapshot, dependencies, keyMap, ctx);
|
|
247
|
+
addV1NodeDependencies(`${path}/node_modules/${depName}`, depSnapshot, dependencies, keyMap, ctx, cachedSatisfies);
|
|
227
248
|
});
|
|
228
249
|
}
|
|
229
250
|
const { peerDependencies } = getPeerDependencies(path);
|
|
230
251
|
if (peerDependencies) {
|
|
231
252
|
const node = keyMap.get(path);
|
|
232
253
|
Object.entries(peerDependencies).forEach(([depName, depSpec]) => {
|
|
233
|
-
const target = findTarget(path, keyMap, depName, depSpec);
|
|
254
|
+
const target = findTarget(path, keyMap, depName, depSpec, cachedSatisfies);
|
|
234
255
|
if (target) {
|
|
235
256
|
const dep = {
|
|
236
257
|
source: node.name,
|
|
@@ -358,10 +379,11 @@ function mapSnapshots(rootLockFile, graph) {
|
|
|
358
379
|
const nestedNodes = new Set();
|
|
359
380
|
const visitedNodes = new Map();
|
|
360
381
|
const remappedPackages = new Map();
|
|
382
|
+
const packageIndex = buildV3Index(rootLockFile.packages);
|
|
361
383
|
// add first level children
|
|
362
384
|
Object.values(graph.externalNodes).forEach((node) => {
|
|
363
385
|
if (node.name === `npm:${node.data.packageName}`) {
|
|
364
|
-
const mappedPackage = mapPackage(rootLockFile, node.data.packageName, node.data.version);
|
|
386
|
+
const mappedPackage = mapPackage(rootLockFile, packageIndex, node.data.packageName, node.data.version);
|
|
365
387
|
remappedPackages.set(mappedPackage.path, mappedPackage);
|
|
366
388
|
visitedNodes.set(node, {
|
|
367
389
|
packagePaths: new Set([mappedPackage.path]),
|
|
@@ -375,7 +397,7 @@ function mapSnapshots(rootLockFile, graph) {
|
|
|
375
397
|
let remappedPackagesArray;
|
|
376
398
|
if (nestedNodes.size) {
|
|
377
399
|
const invertedGraph = (0, operators_1.reverse)(graph);
|
|
378
|
-
nestMappedPackages(invertedGraph, remappedPackages, nestedNodes, visitedNodes, rootLockFile);
|
|
400
|
+
nestMappedPackages(invertedGraph, remappedPackages, nestedNodes, visitedNodes, rootLockFile, packageIndex);
|
|
379
401
|
// initially we naively map package paths to topParent/../parent/child
|
|
380
402
|
// but some of those should be nested higher up the tree
|
|
381
403
|
remappedPackagesArray = elevateNestedPaths(remappedPackages);
|
|
@@ -385,14 +407,14 @@ function mapSnapshots(rootLockFile, graph) {
|
|
|
385
407
|
}
|
|
386
408
|
return remappedPackagesArray.sort((a, b) => a.path.localeCompare(b.path));
|
|
387
409
|
}
|
|
388
|
-
function mapPackage(rootLockFile, packageName, version, parentPath = '') {
|
|
410
|
+
function mapPackage(rootLockFile, packageIndex, packageName, version, parentPath = '') {
|
|
389
411
|
const lockfileVersion = rootLockFile.lockfileVersion;
|
|
390
412
|
let valueV3, valueV1;
|
|
391
413
|
if (lockfileVersion < 3) {
|
|
392
414
|
valueV1 = findMatchingPackageV1(rootLockFile.dependencies, packageName, version);
|
|
393
415
|
}
|
|
394
416
|
if (lockfileVersion > 1) {
|
|
395
|
-
valueV3 = findMatchingPackageV3(
|
|
417
|
+
valueV3 = findMatchingPackageV3(packageIndex, packageName, version);
|
|
396
418
|
}
|
|
397
419
|
return {
|
|
398
420
|
path: parentPath + `node_modules/${packageName}`,
|
|
@@ -401,7 +423,7 @@ function mapPackage(rootLockFile, packageName, version, parentPath = '') {
|
|
|
401
423
|
valueV3,
|
|
402
424
|
};
|
|
403
425
|
}
|
|
404
|
-
function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile) {
|
|
426
|
+
function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile, packageIndex) {
|
|
405
427
|
const initialSize = nestedNodes.size;
|
|
406
428
|
if (!initialSize) {
|
|
407
429
|
return;
|
|
@@ -421,7 +443,7 @@ function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, ro
|
|
|
421
443
|
if (visitedNodes.has(targetNode) &&
|
|
422
444
|
!visitedNodes.get(targetNode).unresolvedParents.size) {
|
|
423
445
|
visitedNodes.get(targetNode).packagePaths.forEach((path) => {
|
|
424
|
-
const mappedPackage = mapPackage(rootLockFile, node.data.packageName, node.data.version, path + '/');
|
|
446
|
+
const mappedPackage = mapPackage(rootLockFile, packageIndex, node.data.packageName, node.data.version, path + '/');
|
|
425
447
|
result.set(mappedPackage.path, mappedPackage);
|
|
426
448
|
visitedNodes.get(node).packagePaths.add(mappedPackage.path);
|
|
427
449
|
visitedNodes.get(node).unresolvedParents.delete(target);
|
|
@@ -439,7 +461,7 @@ function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, ro
|
|
|
439
461
|
].join('\n'));
|
|
440
462
|
}
|
|
441
463
|
else {
|
|
442
|
-
nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile);
|
|
464
|
+
nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile, packageIndex);
|
|
443
465
|
}
|
|
444
466
|
}
|
|
445
467
|
// sort paths by number of segments and then alphabetically
|
|
@@ -495,16 +517,38 @@ function elevateNestedPaths(remappedPackages) {
|
|
|
495
517
|
});
|
|
496
518
|
return Array.from(result.values());
|
|
497
519
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
520
|
+
// Bucket packages by their trailing "node_modules/<name>" segment so a lookup
|
|
521
|
+
// scans only that name's copies instead of every package (was O(nodes *
|
|
522
|
+
// allPackages)). Mirrors the old `key.endsWith(node_modules/<name>)` match:
|
|
523
|
+
// the name is whatever follows the last "node_modules/" in the key.
|
|
524
|
+
function buildV3Index(packages) {
|
|
525
|
+
const index = new Map();
|
|
526
|
+
if (!packages)
|
|
527
|
+
return index;
|
|
528
|
+
const marker = 'node_modules/';
|
|
529
|
+
for (const key of Object.keys(packages)) {
|
|
530
|
+
const i = key.lastIndexOf(marker);
|
|
531
|
+
if (i === -1)
|
|
532
|
+
continue; // root "" / workspace paths never matched endsWith
|
|
533
|
+
const name = key.slice(i + marker.length);
|
|
534
|
+
let bucket = index.get(name);
|
|
535
|
+
if (!bucket)
|
|
536
|
+
index.set(name, (bucket = []));
|
|
537
|
+
bucket.push(packages[key]);
|
|
538
|
+
}
|
|
539
|
+
return index;
|
|
540
|
+
}
|
|
541
|
+
function findMatchingPackageV3(packageIndex, name, version) {
|
|
542
|
+
const bucket = packageIndex.get(name);
|
|
543
|
+
if (!bucket)
|
|
544
|
+
return undefined;
|
|
545
|
+
for (const { dev, peer, ...snapshot } of bucket) {
|
|
546
|
+
if ([
|
|
547
|
+
snapshot.version,
|
|
548
|
+
snapshot.resolved,
|
|
549
|
+
`npm:${snapshot.name}@${snapshot.version}`,
|
|
550
|
+
].includes(version)) {
|
|
551
|
+
return snapshot;
|
|
508
552
|
}
|
|
509
553
|
}
|
|
510
554
|
}
|
|
@@ -142,7 +142,12 @@ function getNodes(data, isV5) {
|
|
|
142
142
|
if (data.patchedDependencies) {
|
|
143
143
|
for (const specifier of Object.keys(data.patchedDependencies)) {
|
|
144
144
|
const patchInfo = data.patchedDependencies[specifier];
|
|
145
|
-
|
|
145
|
+
const patchHash = typeof patchInfo === 'string'
|
|
146
|
+
? patchInfo
|
|
147
|
+
: patchInfo && typeof patchInfo === 'object' && 'hash' in patchInfo
|
|
148
|
+
? patchInfo.hash
|
|
149
|
+
: undefined;
|
|
150
|
+
if (patchHash) {
|
|
146
151
|
const packageName = extractNameFromKey(specifier, false);
|
|
147
152
|
const versionSpecifier = getVersion(specifier, packageName) || null;
|
|
148
153
|
if (!patchEntriesByPackage.has(packageName)) {
|
|
@@ -150,7 +155,7 @@ function getNodes(data, isV5) {
|
|
|
150
155
|
}
|
|
151
156
|
patchEntriesByPackage.get(packageName).push({
|
|
152
157
|
versionSpecifier,
|
|
153
|
-
hash:
|
|
158
|
+
hash: patchHash,
|
|
154
159
|
});
|
|
155
160
|
}
|
|
156
161
|
}
|
|
@@ -391,9 +396,12 @@ function parseBaseVersion(rawVersion, isV5) {
|
|
|
391
396
|
}
|
|
392
397
|
function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspaceRoot) {
|
|
393
398
|
const data = (0, pnpm_normalizer_1.parseAndNormalizePnpmLockfile)(rootLockFileContent);
|
|
394
|
-
const { lockfileVersion,
|
|
395
|
-
|
|
396
|
-
const
|
|
399
|
+
const { lockfileVersion, importers } = data;
|
|
400
|
+
// pnpm omits the packages block for workspace-only lockfiles (no external deps)
|
|
401
|
+
const packages = data.packages ?? {};
|
|
402
|
+
const packageIndex = indexPackagesByName(packages, +lockfileVersion);
|
|
403
|
+
const { snapshot: rootSnapshot, importers: requiredImporters } = mapRootSnapshot(packageJson, importers, packages, packageIndex, graph, +lockfileVersion, workspaceRoot);
|
|
404
|
+
const snapshots = mapSnapshots(packages, packageIndex, graph.externalNodes, +lockfileVersion);
|
|
397
405
|
const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
|
|
398
406
|
// Walk transitive workspace deps so every package copy-workspace-modules
|
|
399
407
|
// writes to disk has a matching importer block. Without this, pnpm errors
|
|
@@ -455,12 +463,10 @@ function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspac
|
|
|
455
463
|
};
|
|
456
464
|
return (0, pnpm_normalizer_1.stringifyToPnpmYaml)(output);
|
|
457
465
|
}
|
|
458
|
-
function mapSnapshots(packages, nodes, lockfileVersion) {
|
|
466
|
+
function mapSnapshots(packages, packageIndex, nodes, lockfileVersion) {
|
|
459
467
|
const result = {};
|
|
460
468
|
Object.values(nodes).forEach((node) => {
|
|
461
|
-
const matchedKeys = findOriginalKeys(packages, node, lockfileVersion, {
|
|
462
|
-
returnFullKey: true,
|
|
463
|
-
});
|
|
469
|
+
const matchedKeys = findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey: true });
|
|
464
470
|
// the package manager doesn't check for types of dependencies
|
|
465
471
|
// so we can safely set all to prod
|
|
466
472
|
matchedKeys.forEach(([key, snapshot]) => {
|
|
@@ -500,10 +506,35 @@ function remapDependencies(snapshot) {
|
|
|
500
506
|
}
|
|
501
507
|
});
|
|
502
508
|
}
|
|
503
|
-
|
|
504
|
-
|
|
509
|
+
// Bucket package keys by their package name so a node only scans its own name's
|
|
510
|
+
// versions instead of every key (was O(nodes * allPackages)). v5 is excluded
|
|
511
|
+
// below and keeps the full scan: its standard keys use a "/" separator while
|
|
512
|
+
// tarball keys use "@", so a single name index would misfile v5 tarballs.
|
|
513
|
+
function indexPackagesByName(packages, lockfileVersion) {
|
|
514
|
+
const isV5 = lockfileVersion < 6;
|
|
515
|
+
const index = new Map();
|
|
505
516
|
for (const key of Object.keys(packages)) {
|
|
506
|
-
const
|
|
517
|
+
const name = extractNameFromKey(key, isV5);
|
|
518
|
+
let bucket = index.get(name);
|
|
519
|
+
if (!bucket)
|
|
520
|
+
index.set(name, (bucket = []));
|
|
521
|
+
bucket.push([key, packages[key]]);
|
|
522
|
+
}
|
|
523
|
+
return index;
|
|
524
|
+
}
|
|
525
|
+
// npm alias version is "npm:<name>@<ver>"; extract <name> the same way
|
|
526
|
+
// versionIsAlias does so the index lookup matches the alias branch below.
|
|
527
|
+
function aliasTargetName(version) {
|
|
528
|
+
return version.slice('npm:'.length, version.indexOf('@', 'npm:'.length + 1));
|
|
529
|
+
}
|
|
530
|
+
const NO_CANDIDATES = [];
|
|
531
|
+
function findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey } = {}) {
|
|
532
|
+
const { data: { packageName, version }, } = node;
|
|
533
|
+
const candidates = lockfileVersion >= 6
|
|
534
|
+
? (packageIndex.get(version.startsWith('npm:') ? aliasTargetName(version) : packageName) ?? NO_CANDIDATES)
|
|
535
|
+
: Object.entries(packages);
|
|
536
|
+
const matchedKeys = [];
|
|
537
|
+
for (const [key, snapshot] of candidates) {
|
|
507
538
|
// tarball package
|
|
508
539
|
if (key.startsWith(`${packageName}@${version}`) &&
|
|
509
540
|
snapshot.resolution?.['tarball']) {
|
|
@@ -562,10 +593,11 @@ function versionIsAlias(key, versionExpr, lockfileVersion) {
|
|
|
562
593
|
? key.startsWith(`${packageName}/${version}`)
|
|
563
594
|
: key.startsWith(`${packageName}@${version}`);
|
|
564
595
|
}
|
|
565
|
-
function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVersion, workspaceRoot) {
|
|
596
|
+
function mapRootSnapshot(packageJson, rootImporters, packages, packageIndex, graph, lockfileVersion, workspaceRoot) {
|
|
566
597
|
const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
|
|
567
598
|
const snapshot = { specifiers: {} };
|
|
568
599
|
const importers = {};
|
|
600
|
+
const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
|
|
569
601
|
[
|
|
570
602
|
'dependencies',
|
|
571
603
|
'optionalDependencies',
|
|
@@ -575,7 +607,6 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
|
|
|
575
607
|
if (packageJson[depType]) {
|
|
576
608
|
Object.keys(packageJson[depType]).forEach((packageName) => {
|
|
577
609
|
let version = packageJson[depType][packageName];
|
|
578
|
-
const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
|
|
579
610
|
if (manager?.isCatalogReference(version)) {
|
|
580
611
|
version = manager.resolveCatalogReference(workspaceRoot, packageName, version);
|
|
581
612
|
if (!version) {
|
|
@@ -612,7 +643,7 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
|
|
|
612
643
|
// peer dependencies are mapped to dependencies
|
|
613
644
|
let section = depType === 'peerDependencies' ? 'dependencies' : depType;
|
|
614
645
|
snapshot[section] = snapshot[section] || {};
|
|
615
|
-
snapshot[section][packageName] = findOriginalKeys(packages, node, lockfileVersion)[0][0];
|
|
646
|
+
snapshot[section][packageName] = findOriginalKeys(packages, packageIndex, node, lockfileVersion)[0][0];
|
|
616
647
|
}
|
|
617
648
|
});
|
|
618
649
|
}
|
|
@@ -38,9 +38,9 @@ function normalizeDependencies(packageJson, graph, workspacePackages, workspaceR
|
|
|
38
38
|
...optionalDependencies,
|
|
39
39
|
...peerDependencies,
|
|
40
40
|
};
|
|
41
|
+
const manager = (0, catalog_1.getCatalogManager)(workspaceRootPath);
|
|
41
42
|
Object.entries(combinedDependencies).forEach(([packageName, versionRange]) => {
|
|
42
43
|
let resolvedVersionRange = versionRange;
|
|
43
|
-
const manager = (0, catalog_1.getCatalogManager)(workspaceRootPath);
|
|
44
44
|
if (manager?.isCatalogReference(versionRange)) {
|
|
45
45
|
resolvedVersionRange = manager.resolveCatalogReference(workspaceRootPath, packageName, versionRange);
|
|
46
46
|
if (!resolvedVersionRange) {
|
|
@@ -314,9 +314,10 @@ function mapSnapshots(rootDependencies, nodes, packageJson, workspaceModules, is
|
|
|
314
314
|
};
|
|
315
315
|
// yarn classic splits keys when parsing so we need to stich them back together
|
|
316
316
|
const groupedDependencies = groupDependencies(rootDependencies, isBerry);
|
|
317
|
+
const keyIndex = buildYarnKeyIndex(groupedDependencies);
|
|
317
318
|
// collect snapshots and their matching keys
|
|
318
319
|
Object.values(nodes).forEach((node) => {
|
|
319
|
-
const foundOriginalKeys = findOriginalKeys(groupedDependencies, node, workspaceModules);
|
|
320
|
+
const foundOriginalKeys = findOriginalKeys(groupedDependencies, keyIndex, node, workspaceModules);
|
|
320
321
|
if (!foundOriginalKeys) {
|
|
321
322
|
throw new Error(`Original key(s) not found for "${node.data.packageName}@${node.data.version}" while pruning yarn.lock.`);
|
|
322
323
|
}
|
|
@@ -349,7 +350,7 @@ function mapSnapshots(rootDependencies, nodes, packageJson, workspaceModules, is
|
|
|
349
350
|
}
|
|
350
351
|
if (isBerry) {
|
|
351
352
|
// look for patched versions
|
|
352
|
-
const patch = findPatchedKeys(groupedDependencies, node, resolutions[node.data.packageName]);
|
|
353
|
+
const patch = findPatchedKeys(groupedDependencies, keyIndex, node, resolutions[node.data.packageName]);
|
|
353
354
|
if (patch) {
|
|
354
355
|
const [matchedKeys, snapshot] = patch;
|
|
355
356
|
snapshotMap.set(snapshot, new Set(matchedKeys));
|
|
@@ -424,8 +425,34 @@ function isClassicAlias(node, keys) {
|
|
|
424
425
|
return (node.data.version.startsWith('npm:') &&
|
|
425
426
|
keys.some((k) => k === `${node.data.packageName}@${node.data.version}`));
|
|
426
427
|
}
|
|
427
|
-
|
|
428
|
+
// Bucket grouped-dependency key expressions by the package names they contain
|
|
429
|
+
// so a node scans only its own name's entries (was O(nodes * allEntries)). A
|
|
430
|
+
// key like "foo@npm:1.0" indexes under "foo"; the inner match checks below are
|
|
431
|
+
// unchanged, so candidates are exactly the entries the old full scan would not
|
|
432
|
+
// have skipped and the result is identical.
|
|
433
|
+
function extractYarnKeyName(key) {
|
|
434
|
+
const at = key.indexOf('@', 1);
|
|
435
|
+
return at === -1 ? key : key.slice(0, at);
|
|
436
|
+
}
|
|
437
|
+
function buildYarnKeyIndex(dependencies) {
|
|
438
|
+
const index = new Map();
|
|
428
439
|
for (const keyExpr of Object.keys(dependencies)) {
|
|
440
|
+
const seen = new Set();
|
|
441
|
+
for (const k of keyExpr.split(', ')) {
|
|
442
|
+
const name = extractYarnKeyName(k);
|
|
443
|
+
if (seen.has(name))
|
|
444
|
+
continue;
|
|
445
|
+
seen.add(name);
|
|
446
|
+
let bucket = index.get(name);
|
|
447
|
+
if (!bucket)
|
|
448
|
+
index.set(name, (bucket = []));
|
|
449
|
+
bucket.push(keyExpr);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return index;
|
|
453
|
+
}
|
|
454
|
+
function findOriginalKeys(dependencies, keyIndex, node, workspaceModules) {
|
|
455
|
+
for (const keyExpr of keyIndex.get(node.data.packageName) ?? []) {
|
|
429
456
|
const snapshot = dependencies[keyExpr];
|
|
430
457
|
const keys = keyExpr.split(', ');
|
|
431
458
|
if (!keys.some((k) => k.startsWith(`${node.data.packageName}@`))) {
|
|
@@ -450,8 +477,8 @@ function findOriginalKeys(dependencies, node, workspaceModules) {
|
|
|
450
477
|
}
|
|
451
478
|
}
|
|
452
479
|
}
|
|
453
|
-
function findPatchedKeys(dependencies, node, resolutionVersion) {
|
|
454
|
-
for (const keyExpr of
|
|
480
|
+
function findPatchedKeys(dependencies, keyIndex, node, resolutionVersion) {
|
|
481
|
+
for (const keyExpr of keyIndex.get(node.data.packageName) ?? []) {
|
|
455
482
|
const snapshot = dependencies[keyExpr];
|
|
456
483
|
const keys = keyExpr.split(', ');
|
|
457
484
|
if (!keys[0].startsWith(`${node.data.packageName}@patch:`)) {
|
|
@@ -96,6 +96,24 @@ export declare function ensureNodeNextEsmResolverRegistered(): void;
|
|
|
96
96
|
* unaffected when both files exist. Idempotent on repeat calls.
|
|
97
97
|
*/
|
|
98
98
|
export declare function ensureCjsResolverPatched(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Make Node's module resolver honor the given export conditions for the rest of
|
|
101
|
+
* the process, via `module.registerHooks()`. Used when a local plugin is loaded
|
|
102
|
+
* from source in-process (no child process to pass `--conditions` to at spawn):
|
|
103
|
+
* the hook appends the conditions to every resolution so the plugin's transitive
|
|
104
|
+
* workspace imports resolve to source the same way the plugin entry did.
|
|
105
|
+
*
|
|
106
|
+
* No-op when:
|
|
107
|
+
* - `conditions` is empty (nothing to inject);
|
|
108
|
+
* - every target condition is already active at startup (a spawned plugin
|
|
109
|
+
* worker or daemon was launched with the full `--conditions` set, so Node's
|
|
110
|
+
* resolver already honors them and the per-resolve hook would be redundant);
|
|
111
|
+
* - `module.registerHooks` is unavailable (Node < 22.15 / < 23.5). Those
|
|
112
|
+
* runtimes keep the `NODE_OPTIONS=--conditions` escape hatch.
|
|
113
|
+
*
|
|
114
|
+
* Idempotent and best-effort.
|
|
115
|
+
*/
|
|
116
|
+
export declare function ensureResolveConditionsInjected(conditions: string[]): void;
|
|
99
117
|
/**
|
|
100
118
|
* Whether Nx will defer to Node's native TypeScript stripping for the next
|
|
101
119
|
* `.ts` load. Mirrors the gate used by `loadTsFile`/`registerTsProject` so
|
|
@@ -5,6 +5,7 @@ exports.forceRegisterEsmLoader = forceRegisterEsmLoader;
|
|
|
5
5
|
exports.nodeNextEsmResolveHook = nodeNextEsmResolveHook;
|
|
6
6
|
exports.ensureNodeNextEsmResolverRegistered = ensureNodeNextEsmResolverRegistered;
|
|
7
7
|
exports.ensureCjsResolverPatched = ensureCjsResolverPatched;
|
|
8
|
+
exports.ensureResolveConditionsInjected = ensureResolveConditionsInjected;
|
|
8
9
|
exports.isNativeStripPreferred = isNativeStripPreferred;
|
|
9
10
|
exports.registerTsProject = registerTsProject;
|
|
10
11
|
exports.getSwcTranspiler = getSwcTranspiler;
|
|
@@ -388,6 +389,86 @@ const isInvokedByTsx = (() => {
|
|
|
388
389
|
return (requireArgs.some((a) => isTsxPath(a)) ||
|
|
389
390
|
importArgs.some((a) => isTsxPath(a)));
|
|
390
391
|
})();
|
|
392
|
+
let resolveConditionsInjected = false;
|
|
393
|
+
/**
|
|
394
|
+
* Make Node's module resolver honor the given export conditions for the rest of
|
|
395
|
+
* the process, via `module.registerHooks()`. Used when a local plugin is loaded
|
|
396
|
+
* from source in-process (no child process to pass `--conditions` to at spawn):
|
|
397
|
+
* the hook appends the conditions to every resolution so the plugin's transitive
|
|
398
|
+
* workspace imports resolve to source the same way the plugin entry did.
|
|
399
|
+
*
|
|
400
|
+
* No-op when:
|
|
401
|
+
* - `conditions` is empty (nothing to inject);
|
|
402
|
+
* - every target condition is already active at startup (a spawned plugin
|
|
403
|
+
* worker or daemon was launched with the full `--conditions` set, so Node's
|
|
404
|
+
* resolver already honors them and the per-resolve hook would be redundant);
|
|
405
|
+
* - `module.registerHooks` is unavailable (Node < 22.15 / < 23.5). Those
|
|
406
|
+
* runtimes keep the `NODE_OPTIONS=--conditions` escape hatch.
|
|
407
|
+
*
|
|
408
|
+
* Idempotent and best-effort.
|
|
409
|
+
*/
|
|
410
|
+
function ensureResolveConditionsInjected(conditions) {
|
|
411
|
+
if (resolveConditionsInjected)
|
|
412
|
+
return;
|
|
413
|
+
resolveConditionsInjected = true;
|
|
414
|
+
if (!conditions.length)
|
|
415
|
+
return;
|
|
416
|
+
// Skip only when Node already honors every target condition (e.g. a worker or
|
|
417
|
+
// daemon spawned with the full `--conditions` set); a partial overlap still
|
|
418
|
+
// needs the hook to add the missing ones.
|
|
419
|
+
const activeConditions = getConditionsActiveAtStartup();
|
|
420
|
+
if (conditions.every((condition) => activeConditions.has(condition)))
|
|
421
|
+
return;
|
|
422
|
+
const module = require('node:module');
|
|
423
|
+
const registerHooks = module.registerHooks;
|
|
424
|
+
if (typeof registerHooks !== 'function')
|
|
425
|
+
return;
|
|
426
|
+
try {
|
|
427
|
+
registerHooks.call(module, {
|
|
428
|
+
resolve(specifier, context, nextResolve) {
|
|
429
|
+
const merged = context.conditions
|
|
430
|
+
? [...context.conditions, ...conditions]
|
|
431
|
+
: conditions;
|
|
432
|
+
return nextResolve(specifier, { ...context, conditions: merged });
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Best-effort: leave Node's native resolution in place rather than failing.
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Export conditions this process was started with, parsed from `--conditions`
|
|
442
|
+
* (or its `-C` alias) in `process.execArgv` and `NODE_OPTIONS`. Node's resolver
|
|
443
|
+
* already honors these, so the injected hook only needs to cover target
|
|
444
|
+
* conditions not present here.
|
|
445
|
+
*/
|
|
446
|
+
function getConditionsActiveAtStartup() {
|
|
447
|
+
const active = new Set();
|
|
448
|
+
const collect = (tokens) => {
|
|
449
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
450
|
+
const token = tokens[i];
|
|
451
|
+
if (token === '--conditions' || token === '-C') {
|
|
452
|
+
const value = tokens[i + 1];
|
|
453
|
+
if (value) {
|
|
454
|
+
active.add(value);
|
|
455
|
+
i++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else if (token.startsWith('--conditions=')) {
|
|
459
|
+
active.add(token.slice('--conditions='.length));
|
|
460
|
+
}
|
|
461
|
+
else if (token.startsWith('-C=')) {
|
|
462
|
+
active.add(token.slice('-C='.length));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
collect(process.execArgv ?? []);
|
|
467
|
+
const nodeOptions = process.env.NODE_OPTIONS;
|
|
468
|
+
if (nodeOptions)
|
|
469
|
+
collect(nodeOptions.split(/\s+/).filter(Boolean));
|
|
470
|
+
return active;
|
|
471
|
+
}
|
|
391
472
|
/**
|
|
392
473
|
* Whether the current Node.js runtime exposes native TypeScript type
|
|
393
474
|
* stripping. This is the authoritative gate - it correctly handles every
|
|
@@ -20,4 +20,13 @@ export declare function getRootTsConfigCustomConditions(root?: string): string[]
|
|
|
20
20
|
* `migrate-development-custom-condition` (21.5).
|
|
21
21
|
*/
|
|
22
22
|
export declare function getRootTsConfigResolveExportsConditions(root?: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Node `--conditions <name>` CLI args for spawning a plugin worker or the daemon
|
|
25
|
+
* with the plugin-resolution conditions active at startup. Mirrors the set Nx
|
|
26
|
+
* uses to resolve the plugin entry (`getRootTsConfigResolveExportsConditions`)
|
|
27
|
+
* so the entry and the plugin's transitive workspace imports resolve the same
|
|
28
|
+
* way; Node's own resolver otherwise ignores TypeScript `customConditions` and a
|
|
29
|
+
* source-loaded plugin's imports land on their unbuilt `dist`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getPluginResolveConditionNodeArgs(root?: string): string[];
|
|
23
32
|
export declare function findNodes(node: Node, kind: SyntaxKind | SyntaxKind[], max?: number): Node[];
|
|
@@ -8,6 +8,7 @@ exports.getRootTsConfigFileName = getRootTsConfigFileName;
|
|
|
8
8
|
exports.getRootTsConfigPath = getRootTsConfigPath;
|
|
9
9
|
exports.getRootTsConfigCustomConditions = getRootTsConfigCustomConditions;
|
|
10
10
|
exports.getRootTsConfigResolveExportsConditions = getRootTsConfigResolveExportsConditions;
|
|
11
|
+
exports.getPluginResolveConditionNodeArgs = getPluginResolveConditionNodeArgs;
|
|
11
12
|
exports.findNodes = findNodes;
|
|
12
13
|
const workspace_root_1 = require("../../../utils/workspace-root");
|
|
13
14
|
const fs_1 = require("fs");
|
|
@@ -113,6 +114,20 @@ function getRootTsConfigResolveExportsConditions(root = workspace_root_1.workspa
|
|
|
113
114
|
? conditions
|
|
114
115
|
: [...conditions, 'development'];
|
|
115
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Node `--conditions <name>` CLI args for spawning a plugin worker or the daemon
|
|
119
|
+
* with the plugin-resolution conditions active at startup. Mirrors the set Nx
|
|
120
|
+
* uses to resolve the plugin entry (`getRootTsConfigResolveExportsConditions`)
|
|
121
|
+
* so the entry and the plugin's transitive workspace imports resolve the same
|
|
122
|
+
* way; Node's own resolver otherwise ignores TypeScript `customConditions` and a
|
|
123
|
+
* source-loaded plugin's imports land on their unbuilt `dist`.
|
|
124
|
+
*/
|
|
125
|
+
function getPluginResolveConditionNodeArgs(root = workspace_root_1.workspaceRoot) {
|
|
126
|
+
return getRootTsConfigResolveExportsConditions(root).flatMap((c) => [
|
|
127
|
+
'--conditions',
|
|
128
|
+
c,
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
116
131
|
function findNodes(node, kind, max = Infinity) {
|
|
117
132
|
if (!node || max == 0) {
|
|
118
133
|
return [];
|
|
@@ -242,9 +242,48 @@ class AggregateCreateNodesError extends Error {
|
|
|
242
242
|
errorTuple.length === 2)) {
|
|
243
243
|
throw new Error('AggregateCreateNodesError must be constructed with an array of tuples where the first element is a filename or undefined and the second element is the underlying error.');
|
|
244
244
|
}
|
|
245
|
+
// Plugins pass through whatever value they caught, which is not
|
|
246
|
+
// guaranteed to be an Error. Coerce so formatting can rely on
|
|
247
|
+
// message and stack being present.
|
|
248
|
+
for (const errorTuple of errors) {
|
|
249
|
+
errorTuple[1] = coerceToError(errorTuple[1]);
|
|
250
|
+
}
|
|
245
251
|
}
|
|
246
252
|
}
|
|
247
253
|
exports.AggregateCreateNodesError = AggregateCreateNodesError;
|
|
254
|
+
function coerceToError(value) {
|
|
255
|
+
if (value instanceof Error) {
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
let message;
|
|
259
|
+
let stack;
|
|
260
|
+
if (typeof value === 'object' && value !== null) {
|
|
261
|
+
const candidate = value;
|
|
262
|
+
if (typeof candidate.message === 'string') {
|
|
263
|
+
message = candidate.message;
|
|
264
|
+
if (typeof candidate.stack === 'string') {
|
|
265
|
+
stack = candidate.stack;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
try {
|
|
270
|
+
message = JSON.stringify(value);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Circular structures cannot be stringified.
|
|
274
|
+
message = String(value);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
message = String(value);
|
|
280
|
+
}
|
|
281
|
+
const error = new Error(message);
|
|
282
|
+
// A synthesized stack would point at this coercion site rather than
|
|
283
|
+
// the original failure, so prefer the original stack or just the message.
|
|
284
|
+
error.stack = stack ?? message;
|
|
285
|
+
return error;
|
|
286
|
+
}
|
|
248
287
|
function formatAggregateCreateNodesError(error, pluginName) {
|
|
249
288
|
const errorCount = error.errors.length > 1 ? `${error.errors.length} errors` : 'An error';
|
|
250
289
|
const pluginLocation = error.pluginIndex
|
|
@@ -270,7 +309,8 @@ function formatAggregateCreateNodesError(error, pluginName) {
|
|
|
270
309
|
}
|
|
271
310
|
for (const e of errors) {
|
|
272
311
|
const messageLines = e.message.split('\n');
|
|
273
|
-
|
|
312
|
+
// Errors deserialized from a plugin worker may arrive without a stack.
|
|
313
|
+
const stackLines = (e.stack ?? e.message).split('\n');
|
|
274
314
|
if (file) {
|
|
275
315
|
errorBodyLines.push(...messageLines.map((line) => ` ${line}`));
|
|
276
316
|
errorStackLines.push(...stackLines.map((line) => ` ${line}`));
|
|
@@ -7,6 +7,7 @@ const path = require("path");
|
|
|
7
7
|
const logger_1 = require("../../../daemon/logger");
|
|
8
8
|
const socket_utils_1 = require("../../../daemon/socket-utils");
|
|
9
9
|
const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
|
|
10
|
+
const typescript_1 = require("../../../plugins/js/utils/typescript");
|
|
10
11
|
const installation_directory_1 = require("../../../utils/installation-directory");
|
|
11
12
|
const logger_2 = require("../../../utils/logger");
|
|
12
13
|
const progress_topics_1 = require("../../../utils/progress-topics");
|
|
@@ -353,6 +354,9 @@ async function startPluginWorker(name) {
|
|
|
353
354
|
};
|
|
354
355
|
const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)([process.pid, global.nxPluginWorkerCount++, performance.now()].join('-'));
|
|
355
356
|
const worker = (0, child_process_1.spawn)(process.execPath, [
|
|
357
|
+
// Spawn the worker with the same resolve conditions Nx uses for plugin
|
|
358
|
+
// entries so the plugin's transitive workspace imports resolve to source.
|
|
359
|
+
...(0, typescript_1.getPluginResolveConditionNodeArgs)(),
|
|
356
360
|
...(isWorkerTypescript ? ['--require', 'ts-node/register'] : []),
|
|
357
361
|
workerPath,
|
|
358
362
|
ipcPath,
|