nx 22.2.0-canary.20251203-2e442d5 → 22.2.0-canary.20251204-0f24bf5
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/executors.json +16 -16
- package/generators.json +13 -13
- package/migrations.json +138 -138
- package/package.json +11 -14
- package/presets/npm.json +4 -4
- package/schemas/nx-schema.json +1286 -1286
- package/schemas/project-schema.json +359 -359
- package/schemas/workspace-schema.json +165 -165
- package/src/ai/set-up-ai-agents/schema.json +31 -31
- package/src/executors/noop/schema.json +8 -8
- package/src/executors/run-commands/schema.json +187 -187
- package/src/executors/run-script/schema.json +25 -25
- package/src/nx-cloud/generators/connect-to-nx-cloud/schema.json +38 -38
- package/src/nx-cloud/utilities/url-shorten.d.ts +1 -1
- package/src/nx-cloud/utilities/url-shorten.d.ts.map +1 -1
- package/src/nx-cloud/utilities/url-shorten.js +2 -2
- package/src/plugins/js/lock-file/bun-parser.d.ts.map +1 -1
- package/src/plugins/js/lock-file/bun-parser.js +236 -264
- package/src/utils/git-utils.d.ts +1 -1
- package/src/utils/git-utils.d.ts.map +1 -1
- package/src/utils/git-utils.js +2 -1
|
@@ -22,6 +22,7 @@ exports.BUN_LOCK_FILE = 'bun.lockb';
|
|
|
22
22
|
exports.BUN_TEXT_LOCK_FILE = 'bun.lock';
|
|
23
23
|
let currentLockFileHash;
|
|
24
24
|
let cachedParsedLockFile;
|
|
25
|
+
let cachedPackageIndex;
|
|
25
26
|
const keyMap = new Map();
|
|
26
27
|
const packageVersions = new Map();
|
|
27
28
|
const specParseCache = new Map();
|
|
@@ -46,16 +47,13 @@ function readBunLockFile(lockFilePath) {
|
|
|
46
47
|
}
|
|
47
48
|
function getBunTextLockfileDependencies(lockFileContent, lockFileHash, ctx) {
|
|
48
49
|
try {
|
|
49
|
-
const lockFile = parseLockFile(lockFileContent, lockFileHash);
|
|
50
|
+
const { lockFile, index } = parseLockFile(lockFileContent, lockFileHash);
|
|
50
51
|
const dependencies = [];
|
|
51
52
|
const workspacePackages = new Set(Object.keys(ctx.projects));
|
|
52
53
|
if (!lockFile.workspaces || Object.keys(lockFile.workspaces).length === 0) {
|
|
53
54
|
return dependencies;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
const workspacePackageNames = getWorkspacePackageNames(lockFile);
|
|
57
|
-
const workspacePaths = getWorkspacePaths(lockFile);
|
|
58
|
-
const packageDeps = processPackageToPackageDependencies(lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths);
|
|
56
|
+
const packageDeps = processPackageToPackageDependencies(lockFile, index, ctx, workspacePackages);
|
|
59
57
|
dependencies.push(...packageDeps);
|
|
60
58
|
return dependencies;
|
|
61
59
|
}
|
|
@@ -70,18 +68,21 @@ function getBunTextLockfileDependencies(lockFileContent, lockFileHash, ctx) {
|
|
|
70
68
|
function clearCache() {
|
|
71
69
|
currentLockFileHash = undefined;
|
|
72
70
|
cachedParsedLockFile = undefined;
|
|
71
|
+
cachedPackageIndex = undefined;
|
|
73
72
|
keyMap.clear();
|
|
74
73
|
packageVersions.clear();
|
|
75
74
|
specParseCache.clear();
|
|
76
75
|
}
|
|
77
76
|
// ===== UTILITY FUNCTIONS =====
|
|
78
77
|
function getCachedSpecInfo(resolvedSpec) {
|
|
79
|
-
|
|
78
|
+
let cached = specParseCache.get(resolvedSpec);
|
|
79
|
+
if (!cached) {
|
|
80
80
|
const { name, version } = parseResolvedSpec(resolvedSpec);
|
|
81
81
|
const protocol = getProtocolFromResolvedSpec(resolvedSpec);
|
|
82
|
-
|
|
82
|
+
cached = { name, version, protocol };
|
|
83
|
+
specParseCache.set(resolvedSpec, cached);
|
|
83
84
|
}
|
|
84
|
-
return
|
|
85
|
+
return cached;
|
|
85
86
|
}
|
|
86
87
|
function getProtocolFromResolvedSpec(resolvedSpec) {
|
|
87
88
|
// Handle scoped packages properly
|
|
@@ -228,9 +229,112 @@ function calculatePackageHash(packageData, lockFile, name, version) {
|
|
|
228
229
|
function isAliasPackage(packageKey, resolvedPackageName) {
|
|
229
230
|
return packageKey !== resolvedPackageName;
|
|
230
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Build a pre-computed index for O(1) lookups
|
|
234
|
+
* This is the key optimization - we do one pass through all packages
|
|
235
|
+
* and build indexes that can be queried in O(1) time later
|
|
236
|
+
*/
|
|
237
|
+
function buildPackageIndex(lockFile) {
|
|
238
|
+
const byName = new Map();
|
|
239
|
+
const workspaceNames = new Set();
|
|
240
|
+
const workspacePaths = new Set();
|
|
241
|
+
const packagesWithWorkspaceVariants = new Set();
|
|
242
|
+
const patchedPackages = new Set();
|
|
243
|
+
// Collect workspace paths
|
|
244
|
+
if (lockFile.workspaces) {
|
|
245
|
+
for (const workspacePath of Object.keys(lockFile.workspaces)) {
|
|
246
|
+
if (workspacePath !== '') {
|
|
247
|
+
workspacePaths.add(workspacePath);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Collect workspace package names from workspacePackages field
|
|
252
|
+
if (lockFile.workspacePackages) {
|
|
253
|
+
for (const packageInfo of Object.values(lockFile.workspacePackages)) {
|
|
254
|
+
workspaceNames.add(packageInfo.name);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Collect patched package names
|
|
258
|
+
if (lockFile.patches) {
|
|
259
|
+
for (const packageName of Object.keys(lockFile.patches)) {
|
|
260
|
+
patchedPackages.add(packageName);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Collect workspace package names from workspace dependencies
|
|
264
|
+
if (lockFile.workspaces) {
|
|
265
|
+
for (const workspace of Object.values(lockFile.workspaces)) {
|
|
266
|
+
const allDeps = {
|
|
267
|
+
...workspace.dependencies,
|
|
268
|
+
...workspace.devDependencies,
|
|
269
|
+
...workspace.optionalDependencies,
|
|
270
|
+
...workspace.peerDependencies,
|
|
271
|
+
};
|
|
272
|
+
for (const [depName, depVersion] of Object.entries(allDeps)) {
|
|
273
|
+
if (depVersion.startsWith('workspace:') ||
|
|
274
|
+
depVersion.startsWith('file:')) {
|
|
275
|
+
workspaceNames.add(depName);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Single pass through all packages to build indexes
|
|
281
|
+
if (lockFile.packages) {
|
|
282
|
+
for (const [packageKey, packageData] of Object.entries(lockFile.packages)) {
|
|
283
|
+
if (!Array.isArray(packageData) || packageData.length < 1) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const resolvedSpec = packageData[0];
|
|
287
|
+
if (typeof resolvedSpec !== 'string') {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const { name, version, protocol } = getCachedSpecInfo(resolvedSpec);
|
|
291
|
+
if (!name || !version) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
// Track workspace packages from packages section
|
|
295
|
+
if (protocol === 'workspace' || protocol === 'file') {
|
|
296
|
+
workspaceNames.add(name);
|
|
297
|
+
}
|
|
298
|
+
// Check if this is a workspace-specific variant (e.g., "@quz/pkg1/lodash")
|
|
299
|
+
if (packageKey.includes('/') && packageKey !== name) {
|
|
300
|
+
// Check if it ends with a package name pattern
|
|
301
|
+
const lastSlash = packageKey.lastIndexOf('/');
|
|
302
|
+
if (lastSlash > 0) {
|
|
303
|
+
const possiblePackageName = packageKey.substring(lastSlash + 1);
|
|
304
|
+
const prefix = packageKey.substring(0, lastSlash);
|
|
305
|
+
// If the prefix is a workspace path or scoped package pattern
|
|
306
|
+
// For scoped packages, require prefix to contain '/' (e.g., "@scope/pkg")
|
|
307
|
+
// to avoid incorrectly matching just "@scope"
|
|
308
|
+
if (workspacePaths.has(prefix) ||
|
|
309
|
+
(prefix.startsWith('@') && prefix.includes('/'))) {
|
|
310
|
+
packagesWithWorkspaceVariants.add(possiblePackageName);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Build the byName index
|
|
315
|
+
let entries = byName.get(name);
|
|
316
|
+
if (!entries) {
|
|
317
|
+
entries = [];
|
|
318
|
+
byName.set(name, entries);
|
|
319
|
+
}
|
|
320
|
+
// Calculate hash once
|
|
321
|
+
const hash = calculatePackageHash(packageData, lockFile, name, version);
|
|
322
|
+
entries.push({ version, packageKey, hash });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
byName,
|
|
327
|
+
workspaceNames,
|
|
328
|
+
workspacePaths,
|
|
329
|
+
packagesWithWorkspaceVariants,
|
|
330
|
+
patchedPackages,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
231
333
|
function parseLockFile(lockFileContent, lockFileHash) {
|
|
232
|
-
if (currentLockFileHash === lockFileHash
|
|
233
|
-
|
|
334
|
+
if (currentLockFileHash === lockFileHash &&
|
|
335
|
+
cachedParsedLockFile &&
|
|
336
|
+
cachedPackageIndex) {
|
|
337
|
+
return { lockFile: cachedParsedLockFile, index: cachedPackageIndex };
|
|
234
338
|
}
|
|
235
339
|
clearCache();
|
|
236
340
|
try {
|
|
@@ -296,7 +400,9 @@ function parseLockFile(lockFileContent, lockFileHash) {
|
|
|
296
400
|
}
|
|
297
401
|
cachedParsedLockFile = result;
|
|
298
402
|
currentLockFileHash = lockFileHash;
|
|
299
|
-
|
|
403
|
+
// Build the optimized index
|
|
404
|
+
cachedPackageIndex = buildPackageIndex(result);
|
|
405
|
+
return { lockFile: result, index: cachedPackageIndex };
|
|
300
406
|
}
|
|
301
407
|
catch (error) {
|
|
302
408
|
// Handle JSON parsing errors
|
|
@@ -315,23 +421,20 @@ function parseLockFile(lockFileContent, lockFileHash) {
|
|
|
315
421
|
// ===== MAIN EXPORT FUNCTIONS =====
|
|
316
422
|
function getBunTextLockfileNodes(lockFileContent, lockFileHash) {
|
|
317
423
|
try {
|
|
318
|
-
const lockFile = parseLockFile(lockFileContent, lockFileHash);
|
|
424
|
+
const { lockFile, index } = parseLockFile(lockFileContent, lockFileHash);
|
|
319
425
|
const nodes = {};
|
|
320
|
-
const
|
|
426
|
+
const localPackageVersions = new Map();
|
|
321
427
|
if (!lockFile.packages || Object.keys(lockFile.packages).length === 0) {
|
|
322
428
|
return nodes;
|
|
323
429
|
}
|
|
324
|
-
// Pre-compute workspace collections for performance
|
|
325
|
-
const workspacePaths = getWorkspacePaths(lockFile);
|
|
326
|
-
const workspacePackageNames = getWorkspacePackageNames(lockFile);
|
|
327
430
|
const packageEntries = Object.entries(lockFile.packages);
|
|
328
431
|
for (const [packageKey, packageData] of packageEntries) {
|
|
329
|
-
const result = processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes,
|
|
432
|
+
const result = processPackageEntry(packageKey, packageData, lockFile, index, keyMap, nodes, localPackageVersions);
|
|
330
433
|
if (result.shouldContinue) {
|
|
331
434
|
continue;
|
|
332
435
|
}
|
|
333
436
|
}
|
|
334
|
-
createHoistedNodes(
|
|
437
|
+
createHoistedNodes(localPackageVersions, lockFile, index, keyMap, nodes);
|
|
335
438
|
return nodes;
|
|
336
439
|
}
|
|
337
440
|
catch (error) {
|
|
@@ -341,7 +444,7 @@ function getBunTextLockfileNodes(lockFileContent, lockFileHash) {
|
|
|
341
444
|
throw new Error(`Failed to get Bun lockfile nodes: ${error.message}`);
|
|
342
445
|
}
|
|
343
446
|
}
|
|
344
|
-
function processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes, packageVersions) {
|
|
447
|
+
function processPackageEntry(packageKey, packageData, lockFile, index, keyMap, nodes, packageVersions) {
|
|
345
448
|
try {
|
|
346
449
|
if (!Array.isArray(packageData) ||
|
|
347
450
|
packageData.length < 1 ||
|
|
@@ -359,16 +462,17 @@ function processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes, p
|
|
|
359
462
|
console.warn(`Lockfile contains unrecognized package format. Try regenerating the lockfile with 'bun install --force'.\nDebug: could not parse resolved spec '${resolvedSpec}'`);
|
|
360
463
|
return { shouldContinue: true };
|
|
361
464
|
}
|
|
362
|
-
|
|
465
|
+
// O(1) lookups using index
|
|
466
|
+
if (index.workspaceNames.has(name)) {
|
|
363
467
|
return { shouldContinue: true };
|
|
364
468
|
}
|
|
365
|
-
if (
|
|
469
|
+
if (index.patchedPackages.has(name)) {
|
|
366
470
|
return { shouldContinue: true };
|
|
367
471
|
}
|
|
368
472
|
if (protocol === 'workspace') {
|
|
369
473
|
return { shouldContinue: true };
|
|
370
474
|
}
|
|
371
|
-
const isWorkspaceSpecific = isNestedPackageKey(packageKey,
|
|
475
|
+
const isWorkspaceSpecific = isNestedPackageKey(packageKey, index);
|
|
372
476
|
if (!isWorkspaceSpecific && isAliasPackage(packageKey, name)) {
|
|
373
477
|
const aliasName = packageKey;
|
|
374
478
|
const actualPackageName = name;
|
|
@@ -442,11 +546,10 @@ function processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes, p
|
|
|
442
546
|
return { shouldContinue: true };
|
|
443
547
|
}
|
|
444
548
|
}
|
|
445
|
-
function isWorkspaceOrPatchedPackage(packageName,
|
|
549
|
+
function isWorkspaceOrPatchedPackage(packageName, index, workspacePackages) {
|
|
446
550
|
return (workspacePackages.has(packageName) ||
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
(lockFile.patches && !!lockFile.patches[packageName]));
|
|
551
|
+
index.workspaceNames.has(packageName) ||
|
|
552
|
+
index.patchedPackages.has(packageName));
|
|
450
553
|
}
|
|
451
554
|
function resolveAliasTarget(versionSpec) {
|
|
452
555
|
if (!versionSpec.startsWith('npm:'))
|
|
@@ -458,15 +561,7 @@ function resolveAliasTarget(versionSpec) {
|
|
|
458
561
|
version: actualSpec.substring(actualAtIndex + 1),
|
|
459
562
|
};
|
|
460
563
|
}
|
|
461
|
-
function
|
|
462
|
-
return {
|
|
463
|
-
...workspace.dependencies,
|
|
464
|
-
...workspace.devDependencies,
|
|
465
|
-
...workspace.optionalDependencies,
|
|
466
|
-
...workspace.peerDependencies,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
function processPackageToPackageDependencies(lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths) {
|
|
564
|
+
function processPackageToPackageDependencies(lockFile, index, ctx, workspacePackages) {
|
|
470
565
|
const dependencies = [];
|
|
471
566
|
if (!lockFile.packages || Object.keys(lockFile.packages).length === 0) {
|
|
472
567
|
return dependencies;
|
|
@@ -474,7 +569,7 @@ function processPackageToPackageDependencies(lockFile, ctx, workspacePackages, w
|
|
|
474
569
|
const packageEntries = Object.entries(lockFile.packages);
|
|
475
570
|
for (const [packageKey, packageData] of packageEntries) {
|
|
476
571
|
try {
|
|
477
|
-
const packageDeps = processPackageForDependencies(packageKey, packageData, lockFile, ctx, workspacePackages
|
|
572
|
+
const packageDeps = processPackageForDependencies(packageKey, packageData, lockFile, index, ctx, workspacePackages);
|
|
478
573
|
dependencies.push(...packageDeps);
|
|
479
574
|
}
|
|
480
575
|
catch (error) {
|
|
@@ -483,9 +578,10 @@ function processPackageToPackageDependencies(lockFile, ctx, workspacePackages, w
|
|
|
483
578
|
}
|
|
484
579
|
return dependencies;
|
|
485
580
|
}
|
|
486
|
-
function processPackageForDependencies(packageKey, packageData, lockFile, ctx, workspacePackages
|
|
487
|
-
|
|
488
|
-
|
|
581
|
+
function processPackageForDependencies(packageKey, packageData, lockFile, index, ctx, workspacePackages) {
|
|
582
|
+
// O(1) checks using index
|
|
583
|
+
if (index.workspaceNames.has(packageKey) ||
|
|
584
|
+
isNestedPackageKey(packageKey, index)) {
|
|
489
585
|
return [];
|
|
490
586
|
}
|
|
491
587
|
if (!Array.isArray(packageData) || packageData.length < 1) {
|
|
@@ -499,7 +595,7 @@ function processPackageForDependencies(packageKey, packageData, lockFile, ctx, w
|
|
|
499
595
|
if (!sourcePackageName || !sourceVersion) {
|
|
500
596
|
return [];
|
|
501
597
|
}
|
|
502
|
-
if (
|
|
598
|
+
if (index.patchedPackages.has(sourcePackageName)) {
|
|
503
599
|
return [];
|
|
504
600
|
}
|
|
505
601
|
const sourceNodeName = `npm:${sourcePackageName}@${sourceVersion}`;
|
|
@@ -515,7 +611,7 @@ function processPackageForDependencies(packageKey, packageData, lockFile, ctx, w
|
|
|
515
611
|
const deps = packageDependencies[depType];
|
|
516
612
|
if (!deps || typeof deps !== 'object')
|
|
517
613
|
continue;
|
|
518
|
-
const depDependencies = processDependencyEntries(deps, sourceNodeName,
|
|
614
|
+
const depDependencies = processDependencyEntries(deps, sourceNodeName, index, ctx, workspacePackages, lockFile.manifests);
|
|
519
615
|
dependencies.push(...depDependencies);
|
|
520
616
|
}
|
|
521
617
|
return dependencies;
|
|
@@ -533,12 +629,12 @@ function extractPackageDependencies(packageData) {
|
|
|
533
629
|
}
|
|
534
630
|
return undefined;
|
|
535
631
|
}
|
|
536
|
-
function processDependencyEntries(deps, sourceNodeName,
|
|
632
|
+
function processDependencyEntries(deps, sourceNodeName, index, ctx, workspacePackages, manifests) {
|
|
537
633
|
const dependencies = [];
|
|
538
634
|
const depsEntries = Object.entries(deps);
|
|
539
635
|
for (const [packageName, versionSpec] of depsEntries) {
|
|
540
636
|
try {
|
|
541
|
-
const dependency = processSingleDependency(packageName, versionSpec, sourceNodeName,
|
|
637
|
+
const dependency = processSingleDependency(packageName, versionSpec, sourceNodeName, index, ctx, workspacePackages, manifests);
|
|
542
638
|
if (dependency) {
|
|
543
639
|
dependencies.push(dependency);
|
|
544
640
|
}
|
|
@@ -549,11 +645,12 @@ function processDependencyEntries(deps, sourceNodeName, lockFile, ctx, workspace
|
|
|
549
645
|
}
|
|
550
646
|
return dependencies;
|
|
551
647
|
}
|
|
552
|
-
function processSingleDependency(packageName, versionSpec, sourceNodeName,
|
|
648
|
+
function processSingleDependency(packageName, versionSpec, sourceNodeName, index, ctx, workspacePackages, manifests) {
|
|
553
649
|
if (typeof packageName !== 'string' || typeof versionSpec !== 'string') {
|
|
554
650
|
return null;
|
|
555
651
|
}
|
|
556
|
-
|
|
652
|
+
// O(1) check using index
|
|
653
|
+
if (isWorkspaceOrPatchedPackage(packageName, index, workspacePackages)) {
|
|
557
654
|
return null;
|
|
558
655
|
}
|
|
559
656
|
if (versionSpec.startsWith('workspace:')) {
|
|
@@ -567,7 +664,8 @@ function processSingleDependency(packageName, versionSpec, sourceNodeName, lockF
|
|
|
567
664
|
targetVersion = aliasTarget.version;
|
|
568
665
|
}
|
|
569
666
|
else {
|
|
570
|
-
|
|
667
|
+
// O(1) lookup using index instead of O(n) scan
|
|
668
|
+
const resolvedVersion = findResolvedVersion(packageName, versionSpec, index, manifests);
|
|
571
669
|
if (!resolvedVersion) {
|
|
572
670
|
return null;
|
|
573
671
|
}
|
|
@@ -601,70 +699,12 @@ function resolveTargetNodeName(targetPackageName, targetVersion, ctx) {
|
|
|
601
699
|
}
|
|
602
700
|
return null;
|
|
603
701
|
}
|
|
604
|
-
// ===== WORKSPACE-RELATED FUNCTIONS =====
|
|
605
|
-
function getWorkspacePackageNames(lockFile) {
|
|
606
|
-
const workspacePackageNames = new Set();
|
|
607
|
-
if (lockFile.workspacePackages) {
|
|
608
|
-
for (const packageInfo of Object.values(lockFile.workspacePackages)) {
|
|
609
|
-
workspacePackageNames.add(packageInfo.name);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
return workspacePackageNames;
|
|
613
|
-
}
|
|
614
|
-
function getWorkspacePaths(lockFile) {
|
|
615
|
-
const workspacePaths = new Set();
|
|
616
|
-
if (lockFile.workspaces) {
|
|
617
|
-
for (const workspacePath of Object.keys(lockFile.workspaces)) {
|
|
618
|
-
if (workspacePath !== '') {
|
|
619
|
-
workspacePaths.add(workspacePath);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return workspacePaths;
|
|
624
|
-
}
|
|
625
|
-
function isWorkspacePackage(packageName, lockFile) {
|
|
626
|
-
// Check if package is in workspacePackages field
|
|
627
|
-
if (lockFile.workspacePackages && lockFile.workspacePackages[packageName]) {
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
// Check if package is defined in any workspace dependencies with workspace: prefix
|
|
631
|
-
// or if it's a file dependency in workspace dependencies
|
|
632
|
-
if (lockFile.workspaces) {
|
|
633
|
-
for (const workspace of Object.values(lockFile.workspaces)) {
|
|
634
|
-
const allDeps = getAllWorkspaceDependencies(workspace);
|
|
635
|
-
if (allDeps[packageName]?.startsWith('workspace:')) {
|
|
636
|
-
return true;
|
|
637
|
-
}
|
|
638
|
-
// Check if this is a file dependency defined in workspace dependencies
|
|
639
|
-
// Always filter out file dependencies as they represent workspace packages
|
|
640
|
-
if (allDeps[packageName]?.startsWith('file:')) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
// Check if package appears in packages section with workspace: or file: protocol
|
|
646
|
-
if (lockFile.packages) {
|
|
647
|
-
for (const packageData of Object.values(lockFile.packages)) {
|
|
648
|
-
if (Array.isArray(packageData) && packageData.length > 0) {
|
|
649
|
-
const resolvedSpec = packageData[0];
|
|
650
|
-
if (typeof resolvedSpec === 'string') {
|
|
651
|
-
const { name, protocol } = getCachedSpecInfo(resolvedSpec);
|
|
652
|
-
if (name === packageName &&
|
|
653
|
-
(protocol === 'workspace' || protocol === 'file')) {
|
|
654
|
-
return true;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return false;
|
|
661
|
-
}
|
|
662
702
|
// ===== HOISTING-RELATED FUNCTIONS =====
|
|
663
|
-
function createHoistedNodes(packageVersions, lockFile, keyMap, nodes
|
|
703
|
+
function createHoistedNodes(packageVersions, lockFile, index, keyMap, nodes) {
|
|
664
704
|
for (const [packageName, versions] of packageVersions.entries()) {
|
|
665
705
|
const hoistedNodeKey = `npm:${packageName}`;
|
|
666
|
-
if (shouldCreateHoistedNode(packageName, lockFile,
|
|
667
|
-
const hoistedVersion = getHoistedVersion(packageName, versions,
|
|
706
|
+
if (shouldCreateHoistedNode(packageName, lockFile, index)) {
|
|
707
|
+
const hoistedVersion = getHoistedVersion(packageName, versions, index);
|
|
668
708
|
if (hoistedVersion) {
|
|
669
709
|
const versionedNodeKey = `npm:${packageName}@${hoistedVersion}`;
|
|
670
710
|
const versionedNode = keyMap.get(versionedNodeKey);
|
|
@@ -689,86 +729,49 @@ function createHoistedNodes(packageVersions, lockFile, keyMap, nodes, workspaceP
|
|
|
689
729
|
* Checks if a package key represents a workspace-specific or nested dependency entry
|
|
690
730
|
* These entries should not become external nodes, they are used only for resolution
|
|
691
731
|
*
|
|
692
|
-
*
|
|
693
|
-
* - "@quz/pkg1/lodash" (workspace-specific)
|
|
694
|
-
* - "is-even/is-odd" (dependency nesting)
|
|
695
|
-
* - "@quz/pkg2/is-even/is-odd" (workspace-specific nested)
|
|
732
|
+
* O(1) lookup using pre-computed index
|
|
696
733
|
*/
|
|
697
|
-
function isNestedPackageKey(packageKey,
|
|
734
|
+
function isNestedPackageKey(packageKey, index) {
|
|
698
735
|
// If the key doesn't contain '/', it's a direct package entry
|
|
699
736
|
if (!packageKey.includes('/')) {
|
|
700
737
|
return false;
|
|
701
738
|
}
|
|
702
|
-
// Get workspace paths and package names for comparison
|
|
703
|
-
const computedWorkspacePaths = workspacePaths || getWorkspacePaths(lockFile);
|
|
704
|
-
const computedWorkspacePackageNames = workspacePackageNames || getWorkspacePackageNames(lockFile);
|
|
705
739
|
// Check if this looks like a workspace-specific or nested entry
|
|
706
740
|
const parts = packageKey.split('/');
|
|
707
741
|
// For multi-part keys, check if the prefix is a workspace path or package name
|
|
708
742
|
if (parts.length >= 2) {
|
|
709
743
|
const prefix = parts.slice(0, -1).join('/');
|
|
710
|
-
//
|
|
711
|
-
if (
|
|
744
|
+
// O(1) check against known workspace paths
|
|
745
|
+
if (index.workspacePaths.has(prefix)) {
|
|
712
746
|
return true;
|
|
713
747
|
}
|
|
714
|
-
//
|
|
715
|
-
if (
|
|
748
|
+
// O(1) check against workspace package names (scoped packages)
|
|
749
|
+
if (index.workspaceNames.has(prefix)) {
|
|
716
750
|
return true;
|
|
717
751
|
}
|
|
718
|
-
// Check for scoped workspace packages (e.g., "@quz/pkg1")
|
|
752
|
+
// Check for scoped workspace packages (e.g., "@quz/pkg1/lodash")
|
|
753
|
+
// The prefix must contain '/' to be a scoped package (e.g., "@scope/pkg")
|
|
754
|
+
// A prefix like just "@scope" without '/' is not a scoped package
|
|
719
755
|
if (prefix.startsWith('@') && prefix.includes('/')) {
|
|
720
756
|
return true;
|
|
721
757
|
}
|
|
758
|
+
// If the key looks like a simple scoped package (e.g., "@custom/lodash")
|
|
759
|
+
// where parts.length === 2 and first part starts with '@', it's likely
|
|
760
|
+
// a scoped package alias, not a nested dependency
|
|
761
|
+
if (parts.length === 2 && parts[0].startsWith('@')) {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
722
764
|
// This could be dependency nesting (e.g., "is-even/is-odd")
|
|
723
|
-
// These should
|
|
765
|
+
// These should be filtered out as they're not direct packages
|
|
724
766
|
return true;
|
|
725
767
|
}
|
|
726
768
|
return false;
|
|
727
769
|
}
|
|
728
|
-
/**
|
|
729
|
-
* Checks if a package has workspace-specific variants in the lockfile
|
|
730
|
-
* Workspace-specific variants indicate the package should NOT be hoisted
|
|
731
|
-
* Example: "@quz/pkg1/lodash" indicates lodash should not be hoisted for the @quz/pkg1 workspace
|
|
732
|
-
*
|
|
733
|
-
* This should NOT match dependency nesting like "is-even/is-odd" which represents
|
|
734
|
-
* is-odd as a dependency of is-even, not a workspace-specific variant.
|
|
735
|
-
*/
|
|
736
|
-
function hasWorkspaceSpecificVariant(packageName, lockFile, workspacePaths, workspacePackageNames) {
|
|
737
|
-
if (!lockFile.packages)
|
|
738
|
-
return false;
|
|
739
|
-
// Get list of known workspace paths to distinguish workspace-specific variants
|
|
740
|
-
// from dependency nesting
|
|
741
|
-
const computedWorkspacePaths = workspacePaths || getWorkspacePaths(lockFile);
|
|
742
|
-
const computedWorkspacePackageNames = workspacePackageNames || getWorkspacePackageNames(lockFile);
|
|
743
|
-
// Check if any package key follows pattern: "workspace/packageName"
|
|
744
|
-
for (const packageKey of Object.keys(lockFile.packages)) {
|
|
745
|
-
if (packageKey.includes('/') && packageKey.endsWith(`/${packageName}`)) {
|
|
746
|
-
const prefix = packageKey.substring(0, packageKey.lastIndexOf(`/${packageName}`));
|
|
747
|
-
// Check if prefix is a known workspace path or workspace package name
|
|
748
|
-
if (computedWorkspacePaths.has(prefix) ||
|
|
749
|
-
computedWorkspacePackageNames.has(prefix)) {
|
|
750
|
-
return true;
|
|
751
|
-
}
|
|
752
|
-
// Also check for scoped workspace packages (e.g., "@quz/pkg1/lodash")
|
|
753
|
-
if (prefix.startsWith('@') && prefix.includes('/')) {
|
|
754
|
-
return true;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
770
|
/**
|
|
761
771
|
* Determines if a package should have a hoisted node created
|
|
762
|
-
*
|
|
763
|
-
* 1. It has a direct entry in the packages section (key matches package name exactly), OR
|
|
764
|
-
* 2. It appears as a direct dependency in any workspace AND no workspace-specific variants exist
|
|
765
|
-
*
|
|
766
|
-
* This handles both cases:
|
|
767
|
-
* - Packages with direct entries (like transitive deps) should be hoisted
|
|
768
|
-
* - Packages in workspace deps without conflicts should be hoisted
|
|
769
|
-
* - Packages with both direct entries and workspace-specific variants get both
|
|
772
|
+
* O(1) lookup using pre-computed index
|
|
770
773
|
*/
|
|
771
|
-
function shouldCreateHoistedNode(packageName, lockFile,
|
|
774
|
+
function shouldCreateHoistedNode(packageName, lockFile, index) {
|
|
772
775
|
if (!lockFile.workspaces || !lockFile.packages)
|
|
773
776
|
return false;
|
|
774
777
|
// First check if the package has a direct entry in the packages section
|
|
@@ -780,154 +783,126 @@ function shouldCreateHoistedNode(packageName, lockFile, workspacePaths, workspac
|
|
|
780
783
|
// and don't have workspace-specific variants (which would cause conflicts)
|
|
781
784
|
let appearsInWorkspace = false;
|
|
782
785
|
for (const workspace of Object.values(lockFile.workspaces)) {
|
|
783
|
-
const allDeps =
|
|
786
|
+
const allDeps = {
|
|
787
|
+
...workspace.dependencies,
|
|
788
|
+
...workspace.devDependencies,
|
|
789
|
+
...workspace.optionalDependencies,
|
|
790
|
+
...workspace.peerDependencies,
|
|
791
|
+
};
|
|
784
792
|
if (allDeps[packageName]) {
|
|
785
793
|
appearsInWorkspace = true;
|
|
786
794
|
break;
|
|
787
795
|
}
|
|
788
796
|
}
|
|
797
|
+
// O(1) check using pre-computed index
|
|
789
798
|
if (appearsInWorkspace &&
|
|
790
|
-
!
|
|
791
|
-
return true;
|
|
799
|
+
!index.packagesWithWorkspaceVariants.has(packageName)) {
|
|
800
|
+
return true;
|
|
792
801
|
}
|
|
793
802
|
return false;
|
|
794
803
|
}
|
|
795
804
|
/**
|
|
796
805
|
* Gets the version that should be used for a hoisted package
|
|
797
|
-
*
|
|
806
|
+
* O(1) lookup using pre-computed index
|
|
798
807
|
*/
|
|
799
|
-
function getHoistedVersion(packageName, availableVersions,
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
return version;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
808
|
+
function getHoistedVersion(packageName, availableVersions, index) {
|
|
809
|
+
// Use the index to find the main package version
|
|
810
|
+
const candidates = index.byName.get(packageName);
|
|
811
|
+
if (!candidates || candidates.length === 0) {
|
|
812
|
+
return availableVersions.size > 0
|
|
813
|
+
? availableVersions.values().next().value
|
|
814
|
+
: null;
|
|
815
|
+
}
|
|
816
|
+
// Look for the main package entry (the one where packageKey === packageName)
|
|
817
|
+
const mainEntry = candidates.find((c) => c.packageKey === packageName);
|
|
818
|
+
if (mainEntry && availableVersions.has(mainEntry.version)) {
|
|
819
|
+
return mainEntry.version;
|
|
814
820
|
}
|
|
815
821
|
// Fallback: return the first available version
|
|
816
|
-
return availableVersions.size > 0
|
|
822
|
+
return availableVersions.size > 0
|
|
823
|
+
? availableVersions.values().next().value
|
|
824
|
+
: null;
|
|
817
825
|
}
|
|
818
826
|
/**
|
|
819
827
|
* Finds the resolved version for a package given its version specification
|
|
820
828
|
*
|
|
821
|
-
* 1
|
|
822
|
-
* 2. Scan all packages to find candidates with matching names
|
|
823
|
-
* 3. Include alias packages where the target matches our package name
|
|
824
|
-
* 4. Fallback: Search manifests for any matching package entries
|
|
825
|
-
* 5. Use findBestVersionMatch to select the optimal version from candidates
|
|
829
|
+
* O(1) lookup using pre-computed index instead of O(n) scan through all packages
|
|
826
830
|
*/
|
|
827
|
-
function findResolvedVersion(packageName, versionSpec,
|
|
828
|
-
//
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
// Early manifest lookup for exact matches
|
|
832
|
-
// Avoids expensive package scanning when exact version is available
|
|
833
|
-
if (manifests) {
|
|
834
|
-
const exactManifestKey = `${packageName}@${versionSpec}`;
|
|
835
|
-
if (manifests[exactManifestKey]) {
|
|
836
|
-
return versionSpec;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
for (const [packageKey, packageData] of packageEntries) {
|
|
840
|
-
const [resolvedSpec] = packageData;
|
|
841
|
-
// Skip non-string specs early
|
|
842
|
-
if (typeof resolvedSpec !== 'string') {
|
|
843
|
-
continue;
|
|
844
|
-
}
|
|
845
|
-
// Use cached spec parsing to avoid repeated string operations
|
|
846
|
-
const { name, version } = getCachedSpecInfo(resolvedSpec);
|
|
847
|
-
if (name === packageName) {
|
|
848
|
-
// Include manifest information if available
|
|
849
|
-
const manifest = manifests?.[`${name}@${version}`];
|
|
850
|
-
candidateVersions.push({ version, packageKey, manifest });
|
|
851
|
-
// Early termination if we find an exact version match
|
|
852
|
-
if (version === versionSpec) {
|
|
853
|
-
return version;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
// Check for alias packages where this package might be the target
|
|
857
|
-
if (isAliasPackage(packageKey, name) && name === packageName) {
|
|
858
|
-
// This alias points to the package we're looking for
|
|
859
|
-
const manifest = manifests?.[`${name}@${version}`];
|
|
860
|
-
candidateVersions.push({ version, packageKey, manifest });
|
|
861
|
-
// Early termination if we find an exact version match
|
|
862
|
-
if (version === versionSpec) {
|
|
863
|
-
return version;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
if (candidateVersions.length === 0) {
|
|
831
|
+
function findResolvedVersion(packageName, versionSpec, index, manifests) {
|
|
832
|
+
// O(1) lookup
|
|
833
|
+
const candidates = index.byName.get(packageName);
|
|
834
|
+
if (!candidates || candidates.length === 0) {
|
|
868
835
|
// Try to find in manifests as fallback
|
|
869
836
|
if (manifests) {
|
|
837
|
+
const exactManifestKey = `${packageName}@${versionSpec}`;
|
|
838
|
+
if (manifests[exactManifestKey]) {
|
|
839
|
+
return versionSpec;
|
|
840
|
+
}
|
|
870
841
|
const manifestKey = Object.keys(manifests).find((key) => key.startsWith(`${packageName}@`));
|
|
871
842
|
if (manifestKey) {
|
|
872
843
|
const manifest = manifests[manifestKey];
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
candidateVersions.push({
|
|
876
|
-
version,
|
|
877
|
-
packageKey: manifestKey,
|
|
878
|
-
manifest,
|
|
879
|
-
});
|
|
844
|
+
if (manifest.version) {
|
|
845
|
+
return manifest.version;
|
|
880
846
|
}
|
|
881
847
|
}
|
|
882
848
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
// Check for exact version match first (most common case)
|
|
852
|
+
const exactMatch = candidates.find((c) => c.version === versionSpec);
|
|
853
|
+
if (exactMatch) {
|
|
854
|
+
return exactMatch.version;
|
|
886
855
|
}
|
|
887
|
-
// Handle different version specification patterns
|
|
888
|
-
|
|
889
|
-
return bestMatch ? bestMatch.version : null;
|
|
856
|
+
// Handle different version specification patterns
|
|
857
|
+
return findBestVersionMatch(versionSpec, candidates);
|
|
890
858
|
}
|
|
891
859
|
/**
|
|
892
860
|
* Find the best version match for a given version specification
|
|
893
|
-
*
|
|
894
|
-
* 1. Check for exact version matches first (highest priority)
|
|
895
|
-
* 2. Handle union ranges (||) by recursively checking each range
|
|
896
|
-
* 3. For non-semver versions (git, file, etc.), prefer exact matches or return first candidate
|
|
897
|
-
* 4. For semver versions, use semver.satisfies() to find compatible versions
|
|
861
|
+
* Only operates on the pre-filtered candidates array (usually 1-3 items)
|
|
898
862
|
*/
|
|
899
|
-
function findBestVersionMatch(
|
|
900
|
-
|
|
863
|
+
function findBestVersionMatch(versionSpec, candidates) {
|
|
864
|
+
if (candidates.length === 0) {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
// For exact matches, return immediately (already checked above but defensive)
|
|
901
868
|
const exactMatch = candidates.find((c) => c.version === versionSpec);
|
|
902
869
|
if (exactMatch) {
|
|
903
|
-
return exactMatch;
|
|
870
|
+
return exactMatch.version;
|
|
904
871
|
}
|
|
905
872
|
// Handle union ranges (||)
|
|
906
873
|
if (versionSpec.includes('||')) {
|
|
907
874
|
const ranges = versionSpec.split('||').map((r) => r.trim());
|
|
908
875
|
for (const range of ranges) {
|
|
909
|
-
const match = findBestVersionMatch(
|
|
876
|
+
const match = findBestVersionMatch(range, candidates);
|
|
910
877
|
if (match) {
|
|
911
878
|
return match;
|
|
912
879
|
}
|
|
913
880
|
}
|
|
914
881
|
return null;
|
|
915
882
|
}
|
|
883
|
+
// Separate semver and non-semver versions
|
|
884
|
+
const semverVersions = [];
|
|
885
|
+
const nonSemverVersions = [];
|
|
886
|
+
for (const c of candidates) {
|
|
887
|
+
if (/^\d+\.\d+\.\d+/.test(c.version)) {
|
|
888
|
+
semverVersions.push(c);
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
nonSemverVersions.push(c);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
916
894
|
// Handle non-semver versions (git, file, etc.)
|
|
917
|
-
const nonSemverVersions = candidates.filter((c) => !c.version.match(/^\d+\.\d+\.\d+/));
|
|
918
895
|
if (nonSemverVersions.length > 0) {
|
|
919
|
-
// For non-semver versions, use the first match or exact match
|
|
920
896
|
const nonSemverMatch = nonSemverVersions.find((c) => c.version === versionSpec);
|
|
921
897
|
if (nonSemverMatch) {
|
|
922
|
-
return nonSemverMatch;
|
|
898
|
+
return nonSemverMatch.version;
|
|
923
899
|
}
|
|
924
|
-
// If no exact match
|
|
925
|
-
return nonSemverVersions[0];
|
|
900
|
+
// If no exact match for non-semver, continue to semver matching
|
|
926
901
|
}
|
|
927
902
|
// Handle semver versions
|
|
928
|
-
const semverVersions = candidates.filter((c) => c.version.match(/^\d+\.\d+\.\d+/));
|
|
929
903
|
if (semverVersions.length === 0) {
|
|
930
|
-
|
|
904
|
+
// No semver versions, return first non-semver if available
|
|
905
|
+
return nonSemverVersions.length > 0 ? nonSemverVersions[0].version : null;
|
|
931
906
|
}
|
|
932
907
|
// Find all versions that satisfy the spec
|
|
933
908
|
const satisfyingVersions = semverVersions.filter((candidate) => {
|
|
@@ -940,17 +915,14 @@ function findBestVersionMatch(packageName, versionSpec, candidates) {
|
|
|
940
915
|
}
|
|
941
916
|
});
|
|
942
917
|
if (satisfyingVersions.length === 0) {
|
|
943
|
-
// No satisfying versions found, return the first candidate as fallback
|
|
944
|
-
return semverVersions[0];
|
|
918
|
+
// No satisfying versions found, return the first semver candidate as fallback
|
|
919
|
+
return semverVersions[0].version;
|
|
945
920
|
}
|
|
946
921
|
// Return the highest satisfying version (similar to npm behavior)
|
|
947
922
|
// Sort versions in descending order and return the first one
|
|
948
923
|
const sortedVersions = satisfyingVersions.sort((a, b) => {
|
|
949
924
|
try {
|
|
950
|
-
|
|
951
|
-
const aVersion = a.version.match(/^\d+\.\d+\.\d+/) ? a.version : '0.0.0';
|
|
952
|
-
const bVersion = b.version.match(/^\d+\.\d+\.\d+/) ? b.version : '0.0.0';
|
|
953
|
-
return aVersion.localeCompare(bVersion, undefined, {
|
|
925
|
+
return b.version.localeCompare(a.version, undefined, {
|
|
954
926
|
numeric: true,
|
|
955
927
|
sensitivity: 'base',
|
|
956
928
|
});
|
|
@@ -960,5 +932,5 @@ function findBestVersionMatch(packageName, versionSpec, candidates) {
|
|
|
960
932
|
return b.version.localeCompare(a.version);
|
|
961
933
|
}
|
|
962
934
|
});
|
|
963
|
-
return sortedVersions[0];
|
|
935
|
+
return sortedVersions[0].version;
|
|
964
936
|
}
|