nx 21.4.0-canary.20250725-0359f0d → 21.4.0-canary.20250729-c2d0ecf
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/.eslintrc.json +2 -0
- package/bin/nx.js +6 -6
- package/bin/post-install.js +8 -22
- package/package.json +12 -11
- package/schemas/nx-schema.json +77 -0
- package/src/command-line/init/implementation/utils.d.ts.map +1 -1
- package/src/command-line/init/implementation/utils.js +1 -2
- package/src/command-line/nx-cloud/connect/command-object.d.ts.map +1 -1
- package/src/command-line/nx-cloud/connect/command-object.js +2 -1
- package/src/command-line/nx-cloud/connect/connect-to-nx-cloud.d.ts +1 -0
- package/src/command-line/nx-cloud/connect/connect-to-nx-cloud.d.ts.map +1 -1
- package/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +14 -2
- package/src/command-line/nx-cloud/connect/view-logs.d.ts.map +1 -1
- package/src/command-line/nx-cloud/connect/view-logs.js +3 -5
- package/src/command-line/release/changelog.d.ts.map +1 -1
- package/src/command-line/release/changelog.js +6 -2
- package/src/command-line/release/command-object.d.ts +1 -0
- package/src/command-line/release/command-object.d.ts.map +1 -1
- package/src/command-line/release/command-object.js +4 -0
- package/src/command-line/release/config/config.d.ts +10 -3
- package/src/command-line/release/config/config.d.ts.map +1 -1
- package/src/command-line/release/config/config.js +103 -0
- package/src/command-line/release/config/filter-release-groups.d.ts +5 -1
- package/src/command-line/release/config/filter-release-groups.d.ts.map +1 -1
- package/src/command-line/release/release.d.ts.map +1 -1
- package/src/command-line/release/release.js +6 -1
- package/src/command-line/release/utils/shared.d.ts +9 -0
- package/src/command-line/release/utils/shared.d.ts.map +1 -1
- package/src/command-line/release/utils/shared.js +25 -4
- package/src/command-line/release/version/project-logger.d.ts.map +1 -1
- package/src/command-line/release/version/project-logger.js +1 -0
- package/src/command-line/release/version/release-group-processor.d.ts +6 -1
- package/src/command-line/release/version/release-group-processor.d.ts.map +1 -1
- package/src/command-line/release/version/release-group-processor.js +73 -3
- package/src/command-line/release/version/version-actions.d.ts +21 -0
- package/src/command-line/release/version/version-actions.d.ts.map +1 -1
- package/src/command-line/release/version/version-actions.js +32 -1
- package/src/command-line/release/version.d.ts.map +1 -1
- package/src/command-line/release/version.js +42 -11
- package/src/command-line/sync/sync.d.ts.map +1 -1
- package/src/command-line/sync/sync.js +8 -2
- package/src/config/nx-json.d.ts +58 -0
- package/src/config/nx-json.d.ts.map +1 -1
- package/src/config/workspace-json-project-json.d.ts +2 -1
- package/src/config/workspace-json-project-json.d.ts.map +1 -1
- package/src/core/graph/main.js +1 -1
- package/src/daemon/server/handle-get-sync-generator-changes.d.ts.map +1 -1
- package/src/daemon/server/handle-get-sync-generator-changes.js +1 -0
- package/src/native/index.d.ts +1 -0
- package/src/native/nx.wasm32-wasi.wasm +0 -0
- package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.d.ts +1 -1
- package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.d.ts.map +1 -1
- package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.js +5 -3
- package/src/nx-cloud/utilities/onboarding.d.ts.map +1 -1
- package/src/nx-cloud/utilities/onboarding.js +1 -1
- package/src/nx-cloud/utilities/url-shorten.d.ts +1 -2
- package/src/nx-cloud/utilities/url-shorten.d.ts.map +1 -1
- package/src/nx-cloud/utilities/url-shorten.js +17 -21
- package/src/plugins/js/index.d.ts.map +1 -1
- package/src/plugins/js/index.js +10 -16
- package/src/plugins/js/lock-file/bun-parser.d.ts +15 -0
- package/src/plugins/js/lock-file/bun-parser.d.ts.map +1 -0
- package/src/plugins/js/lock-file/bun-parser.js +964 -0
- package/src/plugins/js/lock-file/lock-file.d.ts +2 -2
- package/src/plugins/js/lock-file/lock-file.d.ts.map +1 -1
- package/src/plugins/js/lock-file/lock-file.js +40 -17
- package/src/plugins/js/lock-file/pnpm-parser.js +2 -1
- package/src/project-graph/utils/normalize-project-nodes.d.ts +5 -5
- package/src/project-graph/utils/normalize-project-nodes.d.ts.map +1 -1
- package/src/project-graph/utils/normalize-project-nodes.js +35 -6
- package/src/tasks-runner/life-cycle.d.ts +2 -0
- package/src/tasks-runner/life-cycle.d.ts.map +1 -1
- package/src/tasks-runner/life-cycle.js +7 -0
- package/src/tasks-runner/task-orchestrator.d.ts.map +1 -1
- package/src/tasks-runner/task-orchestrator.js +5 -0
- package/src/tasks-runner/tasks-schedule.d.ts +1 -0
- package/src/tasks-runner/tasks-schedule.d.ts.map +1 -1
- package/src/tasks-runner/tasks-schedule.js +3 -0
- package/src/utils/ab-testing.d.ts +1 -1
- package/src/utils/ab-testing.d.ts.map +1 -1
- package/src/utils/ab-testing.js +1 -1
- package/src/utils/git-utils.d.ts +6 -5
- package/src/utils/git-utils.d.ts.map +1 -1
- package/src/utils/git-utils.js +73 -41
- package/src/utils/sync-generators.d.ts +3 -1
- package/src/utils/sync-generators.d.ts.map +1 -1
- package/src/utils/sync-generators.js +7 -1
@@ -0,0 +1,964 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.BunLockfileParseError = exports.BUN_TEXT_LOCK_FILE = exports.BUN_LOCK_FILE = void 0;
|
4
|
+
exports.readBunLockFile = readBunLockFile;
|
5
|
+
exports.getBunTextLockfileDependencies = getBunTextLockfileDependencies;
|
6
|
+
exports.clearCache = clearCache;
|
7
|
+
exports.getBunTextLockfileNodes = getBunTextLockfileNodes;
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
9
|
+
const node_fs_1 = require("node:fs");
|
10
|
+
const semver_1 = require("semver");
|
11
|
+
const project_graph_1 = require("../../../config/project-graph");
|
12
|
+
const file_hasher_1 = require("../../../hasher/file-hasher");
|
13
|
+
const project_graph_builder_1 = require("../../../project-graph/project-graph-builder");
|
14
|
+
const json_1 = require("../../../utils/json");
|
15
|
+
const DEPENDENCY_TYPES = [
|
16
|
+
'dependencies',
|
17
|
+
'devDependencies',
|
18
|
+
'optionalDependencies',
|
19
|
+
'peerDependencies',
|
20
|
+
];
|
21
|
+
exports.BUN_LOCK_FILE = 'bun.lockb';
|
22
|
+
exports.BUN_TEXT_LOCK_FILE = 'bun.lock';
|
23
|
+
let currentLockFileHash;
|
24
|
+
let cachedParsedLockFile;
|
25
|
+
const keyMap = new Map();
|
26
|
+
const packageVersions = new Map();
|
27
|
+
const specParseCache = new Map();
|
28
|
+
// Structured error types for better error handling
|
29
|
+
class BunLockfileParseError extends Error {
|
30
|
+
constructor(message, cause) {
|
31
|
+
super(message);
|
32
|
+
this.cause = cause;
|
33
|
+
this.name = 'BunLockfileParseError';
|
34
|
+
}
|
35
|
+
}
|
36
|
+
exports.BunLockfileParseError = BunLockfileParseError;
|
37
|
+
function readBunLockFile(lockFilePath) {
|
38
|
+
if (lockFilePath.endsWith(exports.BUN_TEXT_LOCK_FILE)) {
|
39
|
+
return (0, node_fs_1.readFileSync)(lockFilePath, { encoding: 'utf-8' });
|
40
|
+
}
|
41
|
+
return (0, node_child_process_1.execSync)(`bun ${lockFilePath}`, {
|
42
|
+
encoding: 'utf-8',
|
43
|
+
maxBuffer: 1024 * 1024 * 10,
|
44
|
+
windowsHide: false,
|
45
|
+
});
|
46
|
+
}
|
47
|
+
function getBunTextLockfileDependencies(lockFileContent, lockFileHash, ctx) {
|
48
|
+
try {
|
49
|
+
const lockFile = parseLockFile(lockFileContent, lockFileHash);
|
50
|
+
const dependencies = [];
|
51
|
+
const workspacePackages = new Set(Object.keys(ctx.projects));
|
52
|
+
if (!lockFile.workspaces || Object.keys(lockFile.workspaces).length === 0) {
|
53
|
+
return dependencies;
|
54
|
+
}
|
55
|
+
// Pre-compute workspace collections for performance
|
56
|
+
const workspacePackageNames = getWorkspacePackageNames(lockFile);
|
57
|
+
const workspacePaths = getWorkspacePaths(lockFile);
|
58
|
+
const packageDeps = processPackageToPackageDependencies(lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths);
|
59
|
+
dependencies.push(...packageDeps);
|
60
|
+
return dependencies;
|
61
|
+
}
|
62
|
+
catch (error) {
|
63
|
+
if (error instanceof Error) {
|
64
|
+
throw error;
|
65
|
+
}
|
66
|
+
throw new Error(`Failed to get Bun lockfile dependencies: ${error.message}`);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
/** @internal */
|
70
|
+
function clearCache() {
|
71
|
+
currentLockFileHash = undefined;
|
72
|
+
cachedParsedLockFile = undefined;
|
73
|
+
keyMap.clear();
|
74
|
+
packageVersions.clear();
|
75
|
+
specParseCache.clear();
|
76
|
+
}
|
77
|
+
// ===== UTILITY FUNCTIONS =====
|
78
|
+
function getCachedSpecInfo(resolvedSpec) {
|
79
|
+
if (!specParseCache.has(resolvedSpec)) {
|
80
|
+
const { name, version } = parseResolvedSpec(resolvedSpec);
|
81
|
+
const protocol = getProtocolFromResolvedSpec(resolvedSpec);
|
82
|
+
specParseCache.set(resolvedSpec, { name, version, protocol });
|
83
|
+
}
|
84
|
+
return specParseCache.get(resolvedSpec);
|
85
|
+
}
|
86
|
+
function getProtocolFromResolvedSpec(resolvedSpec) {
|
87
|
+
// Handle scoped packages properly
|
88
|
+
let protocolAndSpec;
|
89
|
+
if (resolvedSpec.startsWith('@')) {
|
90
|
+
// For scoped packages, find the second @ which separates name from protocol
|
91
|
+
const secondAtIndex = resolvedSpec.indexOf('@', 1);
|
92
|
+
if (secondAtIndex === -1) {
|
93
|
+
return 'npm'; // Default fallback
|
94
|
+
}
|
95
|
+
protocolAndSpec = resolvedSpec.substring(secondAtIndex + 1);
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
// For non-scoped packages, find the first @ which separates name from protocol
|
99
|
+
const firstAtIndex = resolvedSpec.indexOf('@');
|
100
|
+
if (firstAtIndex === -1) {
|
101
|
+
return 'npm'; // Default fallback
|
102
|
+
}
|
103
|
+
protocolAndSpec = resolvedSpec.substring(firstAtIndex + 1);
|
104
|
+
}
|
105
|
+
const colonIndex = protocolAndSpec.indexOf(':');
|
106
|
+
if (colonIndex === -1) {
|
107
|
+
return 'npm'; // No protocol specified, default to npm
|
108
|
+
}
|
109
|
+
return protocolAndSpec.substring(0, colonIndex);
|
110
|
+
}
|
111
|
+
function parseResolvedSpec(resolvedSpec) {
|
112
|
+
// Handle different resolution formats:
|
113
|
+
// - "package-name@npm:1.0.0"
|
114
|
+
// - "@scope/package-name@npm:1.0.0"
|
115
|
+
// - "package-name@github:user/repo#commit"
|
116
|
+
// - "package-name@file:./path"
|
117
|
+
// - "package-name@https://example.com/package.tgz"
|
118
|
+
// - "alias-name@npm:actual-package@version" (ALIAS FORMAT)
|
119
|
+
// Handle scoped packages properly - they have an extra @ at the beginning
|
120
|
+
// Format: @scope/package@protocol:spec
|
121
|
+
let name;
|
122
|
+
let protocolAndSpec;
|
123
|
+
if (resolvedSpec.startsWith('@')) {
|
124
|
+
// For scoped packages, find the second @ which separates name from protocol
|
125
|
+
const secondAtIndex = resolvedSpec.indexOf('@', 1);
|
126
|
+
if (secondAtIndex === -1) {
|
127
|
+
return { name: '', version: '' };
|
128
|
+
}
|
129
|
+
name = resolvedSpec.substring(0, secondAtIndex);
|
130
|
+
protocolAndSpec = resolvedSpec.substring(secondAtIndex + 1);
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
// For non-scoped packages, find the first @ which separates name from protocol
|
134
|
+
const firstAtIndex = resolvedSpec.indexOf('@');
|
135
|
+
if (firstAtIndex === -1) {
|
136
|
+
return { name: '', version: '' };
|
137
|
+
}
|
138
|
+
name = resolvedSpec.substring(0, firstAtIndex);
|
139
|
+
protocolAndSpec = resolvedSpec.substring(firstAtIndex + 1);
|
140
|
+
}
|
141
|
+
// Parse protocol and spec
|
142
|
+
const colonIndex = protocolAndSpec.indexOf(':');
|
143
|
+
// Handle specs without protocol prefix (e.g., "package@1.0.0" instead of "package@npm:1.0.0")
|
144
|
+
if (colonIndex === -1) {
|
145
|
+
// No protocol specified, treat as npm package with direct version
|
146
|
+
return { name, version: protocolAndSpec };
|
147
|
+
}
|
148
|
+
const protocol = protocolAndSpec.substring(0, colonIndex);
|
149
|
+
const spec = protocolAndSpec.substring(colonIndex + 1);
|
150
|
+
if (protocol === 'npm') {
|
151
|
+
// For npm protocol, spec should always be in format: package@version
|
152
|
+
// Examples:
|
153
|
+
// - Regular: "package@npm:package@1.0.0" -> version: "1.0.0"
|
154
|
+
// - Alias: "alias@npm:real-package@1.0.0" -> version: "npm:real-package@1.0.0"
|
155
|
+
// Extract the package name and version from the spec
|
156
|
+
const atIndex = spec.lastIndexOf('@');
|
157
|
+
if (atIndex === -1) {
|
158
|
+
// Malformed spec, return as-is
|
159
|
+
return { name, version: spec };
|
160
|
+
}
|
161
|
+
const specPackageName = spec.substring(0, atIndex);
|
162
|
+
const specVersion = spec.substring(atIndex + 1);
|
163
|
+
if (specPackageName === name) {
|
164
|
+
// Regular npm package: "package@npm:package@1.0.0" -> version: "1.0.0"
|
165
|
+
return { name, version: specVersion };
|
166
|
+
}
|
167
|
+
else {
|
168
|
+
// Alias package: "alias@npm:real-package@1.0.0" -> version: "npm:real-package@1.0.0"
|
169
|
+
return { name, version: `npm:${spec}` };
|
170
|
+
}
|
171
|
+
}
|
172
|
+
else if (protocol === 'workspace') {
|
173
|
+
// Workspace dependencies use the workspace path as version
|
174
|
+
return { name, version: `workspace:${spec}` };
|
175
|
+
}
|
176
|
+
else if (protocol === 'github' || protocol === 'git') {
|
177
|
+
// Extract commit hash from GitHub/Git reference
|
178
|
+
// Format: user/repo#commit-hash or repo-url#commit-hash
|
179
|
+
const gitMatch = spec.match(/^(.+?)#(.+)$/);
|
180
|
+
if (gitMatch) {
|
181
|
+
const [, repo, commit] = gitMatch;
|
182
|
+
return { name, version: `${protocol}:${repo}#${commit}` };
|
183
|
+
}
|
184
|
+
else {
|
185
|
+
return { name, version: `${protocol}:${spec}` };
|
186
|
+
}
|
187
|
+
}
|
188
|
+
else if (protocol === 'file' || protocol === 'link') {
|
189
|
+
// File/Link dependencies use the file path as version
|
190
|
+
return { name, version: `${protocol}:${spec}` };
|
191
|
+
}
|
192
|
+
else if (protocol === 'https' || protocol === 'http') {
|
193
|
+
// Tarball dependencies use the full URL as version
|
194
|
+
return { name, version: `${protocol}:${spec}` };
|
195
|
+
}
|
196
|
+
else {
|
197
|
+
// For any other protocols, use the original spec as version
|
198
|
+
return { name, version: resolvedSpec };
|
199
|
+
}
|
200
|
+
}
|
201
|
+
function calculatePackageHash(packageData, lockFile, name, version) {
|
202
|
+
const [resolvedSpec, tarballUrl, metadata, hash] = packageData;
|
203
|
+
// For NPM packages (4 elements), use the integrity hash
|
204
|
+
if (packageData.length === 4 && hash && typeof hash === 'string') {
|
205
|
+
// Use better hash from manifests if available
|
206
|
+
if (lockFile.manifests && lockFile.manifests[`${name}@${version}`]) {
|
207
|
+
const manifest = lockFile.manifests[`${name}@${version}`];
|
208
|
+
if (manifest.dist && manifest.dist.shasum) {
|
209
|
+
return manifest.dist.shasum;
|
210
|
+
}
|
211
|
+
}
|
212
|
+
return hash;
|
213
|
+
}
|
214
|
+
// For other package types, calculate hash from available data
|
215
|
+
const hashData = [resolvedSpec];
|
216
|
+
if (tarballUrl && typeof tarballUrl === 'string')
|
217
|
+
hashData.push(tarballUrl);
|
218
|
+
if (metadata)
|
219
|
+
hashData.push(JSON.stringify(metadata));
|
220
|
+
if (hash && typeof hash === 'string')
|
221
|
+
hashData.push(hash);
|
222
|
+
return (0, file_hasher_1.hashArray)(hashData);
|
223
|
+
}
|
224
|
+
/**
|
225
|
+
* Determines if a package is an alias by comparing the package key with the resolved spec name
|
226
|
+
* In Bun lockfiles, aliases are identified when the package key differs from the resolved package name
|
227
|
+
*/
|
228
|
+
function isAliasPackage(packageKey, resolvedPackageName) {
|
229
|
+
return packageKey !== resolvedPackageName;
|
230
|
+
}
|
231
|
+
function parseLockFile(lockFileContent, lockFileHash) {
|
232
|
+
if (currentLockFileHash === lockFileHash) {
|
233
|
+
return cachedParsedLockFile;
|
234
|
+
}
|
235
|
+
clearCache();
|
236
|
+
try {
|
237
|
+
const result = (0, json_1.parseJson)(lockFileContent, {
|
238
|
+
allowTrailingComma: true,
|
239
|
+
expectComments: true,
|
240
|
+
});
|
241
|
+
// Validate basic structure
|
242
|
+
if (!result || typeof result !== 'object') {
|
243
|
+
throw new Error('Lockfile root must be an object');
|
244
|
+
}
|
245
|
+
// Validate lockfile version
|
246
|
+
if (result.lockfileVersion !== undefined) {
|
247
|
+
if (typeof result.lockfileVersion !== 'number') {
|
248
|
+
throw new Error(`Lockfile version must be a number, got ${typeof result.lockfileVersion}`);
|
249
|
+
}
|
250
|
+
const supportedVersions = [0, 1];
|
251
|
+
if (!supportedVersions.includes(result.lockfileVersion)) {
|
252
|
+
throw new Error(`Unsupported lockfile version ${result.lockfileVersion}. Supported versions: ${supportedVersions.join(', ')}`);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
if (!result.packages || typeof result.packages !== 'object') {
|
256
|
+
throw new Error('Lockfile packages section must be an object');
|
257
|
+
}
|
258
|
+
if (!result.workspaces || typeof result.workspaces !== 'object') {
|
259
|
+
throw new Error('Lockfile workspaces section must be an object');
|
260
|
+
}
|
261
|
+
// Validate optional sections
|
262
|
+
if (result.patches && typeof result.patches !== 'object') {
|
263
|
+
throw new Error('Lockfile patches section must be an object');
|
264
|
+
}
|
265
|
+
if (result.manifests && typeof result.manifests !== 'object') {
|
266
|
+
throw new Error('Lockfile manifests section must be an object');
|
267
|
+
}
|
268
|
+
if (result.workspacePackages &&
|
269
|
+
typeof result.workspacePackages !== 'object') {
|
270
|
+
throw new Error('Lockfile workspacePackages section must be an object');
|
271
|
+
}
|
272
|
+
// Validate structure of patches entries
|
273
|
+
if (result.patches) {
|
274
|
+
for (const [packageName, patchInfo] of Object.entries(result.patches)) {
|
275
|
+
if (!patchInfo || typeof patchInfo !== 'object') {
|
276
|
+
throw new Error(`Invalid patch entry for package "${packageName}": must be an object`);
|
277
|
+
}
|
278
|
+
if (!patchInfo.path || typeof patchInfo.path !== 'string') {
|
279
|
+
throw new Error(`Invalid patch entry for package "${packageName}": path must be a string`);
|
280
|
+
}
|
281
|
+
}
|
282
|
+
}
|
283
|
+
// Validate structure of workspace packages entries
|
284
|
+
if (result.workspacePackages) {
|
285
|
+
for (const [packageName, packageInfo] of Object.entries(result.workspacePackages)) {
|
286
|
+
if (!packageInfo || typeof packageInfo !== 'object') {
|
287
|
+
throw new Error(`Invalid workspace package entry for "${packageName}": must be an object`);
|
288
|
+
}
|
289
|
+
if (!packageInfo.name || typeof packageInfo.name !== 'string') {
|
290
|
+
throw new Error(`Invalid workspace package entry for "${packageName}": name must be a string`);
|
291
|
+
}
|
292
|
+
if (!packageInfo.version || typeof packageInfo.version !== 'string') {
|
293
|
+
throw new Error(`Invalid workspace package entry for "${packageName}": version must be a string`);
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
cachedParsedLockFile = result;
|
298
|
+
currentLockFileHash = lockFileHash;
|
299
|
+
return result;
|
300
|
+
}
|
301
|
+
catch (error) {
|
302
|
+
// Handle JSON parsing errors
|
303
|
+
if (error.message.includes('JSON') ||
|
304
|
+
error.message.includes('InvalidSymbol')) {
|
305
|
+
throw new BunLockfileParseError('Failed to parse Bun lockfile: Invalid JSON syntax. Please check for syntax errors or regenerate the lockfile.', error);
|
306
|
+
}
|
307
|
+
// Re-throw parsing errors as-is (for validation errors)
|
308
|
+
if (error instanceof Error) {
|
309
|
+
throw error;
|
310
|
+
}
|
311
|
+
// Handle unknown errors
|
312
|
+
throw new BunLockfileParseError(`Failed to parse Bun lockfile: ${error.message}`, error);
|
313
|
+
}
|
314
|
+
}
|
315
|
+
// ===== MAIN EXPORT FUNCTIONS =====
|
316
|
+
function getBunTextLockfileNodes(lockFileContent, lockFileHash) {
|
317
|
+
try {
|
318
|
+
const lockFile = parseLockFile(lockFileContent, lockFileHash);
|
319
|
+
const nodes = {};
|
320
|
+
const packageVersions = new Map();
|
321
|
+
if (!lockFile.packages || Object.keys(lockFile.packages).length === 0) {
|
322
|
+
return nodes;
|
323
|
+
}
|
324
|
+
// Pre-compute workspace collections for performance
|
325
|
+
const workspacePaths = getWorkspacePaths(lockFile);
|
326
|
+
const workspacePackageNames = getWorkspacePackageNames(lockFile);
|
327
|
+
const packageEntries = Object.entries(lockFile.packages);
|
328
|
+
for (const [packageKey, packageData] of packageEntries) {
|
329
|
+
const result = processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes, packageVersions);
|
330
|
+
if (result.shouldContinue) {
|
331
|
+
continue;
|
332
|
+
}
|
333
|
+
}
|
334
|
+
createHoistedNodes(packageVersions, lockFile, keyMap, nodes, workspacePaths, workspacePackageNames);
|
335
|
+
return nodes;
|
336
|
+
}
|
337
|
+
catch (error) {
|
338
|
+
if (error instanceof Error) {
|
339
|
+
throw error;
|
340
|
+
}
|
341
|
+
throw new Error(`Failed to get Bun lockfile nodes: ${error.message}`);
|
342
|
+
}
|
343
|
+
}
|
344
|
+
function processPackageEntry(packageKey, packageData, lockFile, keyMap, nodes, packageVersions) {
|
345
|
+
try {
|
346
|
+
if (!Array.isArray(packageData) ||
|
347
|
+
packageData.length < 1 ||
|
348
|
+
packageData.length > 4) {
|
349
|
+
console.warn(`Lockfile contains invalid package entry '${packageKey}'. Try regenerating the lockfile with 'bun install --force'.\nDebug: expected 1-4 elements, got ${JSON.stringify(packageData)}`);
|
350
|
+
return { shouldContinue: true };
|
351
|
+
}
|
352
|
+
const [resolvedSpec] = packageData;
|
353
|
+
if (typeof resolvedSpec !== 'string') {
|
354
|
+
console.warn(`Lockfile contains corrupted package entry '${packageKey}'. Try regenerating the lockfile with 'bun install --force'.\nDebug: expected string, got ${typeof resolvedSpec}`);
|
355
|
+
return { shouldContinue: true };
|
356
|
+
}
|
357
|
+
const { name, version, protocol } = getCachedSpecInfo(resolvedSpec);
|
358
|
+
if (!name || !version) {
|
359
|
+
console.warn(`Lockfile contains unrecognized package format. Try regenerating the lockfile with 'bun install --force'.\nDebug: could not parse resolved spec '${resolvedSpec}'`);
|
360
|
+
return { shouldContinue: true };
|
361
|
+
}
|
362
|
+
if (isWorkspacePackage(name, lockFile)) {
|
363
|
+
return { shouldContinue: true };
|
364
|
+
}
|
365
|
+
if (lockFile.patches && lockFile.patches[name]) {
|
366
|
+
return { shouldContinue: true };
|
367
|
+
}
|
368
|
+
if (protocol === 'workspace') {
|
369
|
+
return { shouldContinue: true };
|
370
|
+
}
|
371
|
+
const isWorkspaceSpecific = isNestedPackageKey(packageKey, lockFile);
|
372
|
+
if (!isWorkspaceSpecific && isAliasPackage(packageKey, name)) {
|
373
|
+
const aliasName = packageKey;
|
374
|
+
const actualPackageName = name;
|
375
|
+
const actualVersion = version;
|
376
|
+
const aliasNodeKey = `npm:${aliasName}`;
|
377
|
+
if (!keyMap.has(aliasNodeKey)) {
|
378
|
+
const aliasNode = {
|
379
|
+
type: 'npm',
|
380
|
+
name: aliasNodeKey,
|
381
|
+
data: {
|
382
|
+
version: `npm:${actualPackageName}@${actualVersion}`,
|
383
|
+
packageName: aliasName,
|
384
|
+
hash: calculatePackageHash(packageData, lockFile, aliasName, `npm:${actualPackageName}@${actualVersion}`),
|
385
|
+
},
|
386
|
+
};
|
387
|
+
keyMap.set(aliasNodeKey, aliasNode);
|
388
|
+
nodes[aliasNodeKey] = aliasNode;
|
389
|
+
}
|
390
|
+
const targetNodeKey = `npm:${actualPackageName}@${actualVersion}`;
|
391
|
+
if (!keyMap.has(targetNodeKey)) {
|
392
|
+
const targetNode = {
|
393
|
+
type: 'npm',
|
394
|
+
name: targetNodeKey,
|
395
|
+
data: {
|
396
|
+
version: actualVersion,
|
397
|
+
packageName: actualPackageName,
|
398
|
+
hash: calculatePackageHash(packageData, lockFile, actualPackageName, actualVersion),
|
399
|
+
},
|
400
|
+
};
|
401
|
+
keyMap.set(targetNodeKey, targetNode);
|
402
|
+
nodes[targetNodeKey] = targetNode;
|
403
|
+
}
|
404
|
+
if (!packageVersions.has(aliasName)) {
|
405
|
+
packageVersions.set(aliasName, new Set());
|
406
|
+
}
|
407
|
+
packageVersions
|
408
|
+
.get(aliasName)
|
409
|
+
.add(`npm:${actualPackageName}@${actualVersion}`);
|
410
|
+
if (!packageVersions.has(actualPackageName)) {
|
411
|
+
packageVersions.set(actualPackageName, new Set());
|
412
|
+
}
|
413
|
+
packageVersions.get(actualPackageName).add(actualVersion);
|
414
|
+
}
|
415
|
+
else {
|
416
|
+
if (!packageVersions.has(name)) {
|
417
|
+
packageVersions.set(name, new Set());
|
418
|
+
}
|
419
|
+
packageVersions.get(name).add(version);
|
420
|
+
const nodeKey = `npm:${name}@${version}`;
|
421
|
+
if (keyMap.has(nodeKey)) {
|
422
|
+
nodes[nodeKey] = keyMap.get(nodeKey);
|
423
|
+
return { shouldContinue: false };
|
424
|
+
}
|
425
|
+
const nodeHash = calculatePackageHash(packageData, lockFile, name, version);
|
426
|
+
const node = {
|
427
|
+
type: 'npm',
|
428
|
+
name: nodeKey,
|
429
|
+
data: {
|
430
|
+
version,
|
431
|
+
packageName: name,
|
432
|
+
hash: nodeHash,
|
433
|
+
},
|
434
|
+
};
|
435
|
+
keyMap.set(nodeKey, node);
|
436
|
+
nodes[nodeKey] = node;
|
437
|
+
}
|
438
|
+
return { shouldContinue: false };
|
439
|
+
}
|
440
|
+
catch (error) {
|
441
|
+
console.warn(`Unable to process package '${packageKey}'. The lockfile may be corrupted. Try regenerating with 'bun install --force'.\nDebug: ${error.message}`);
|
442
|
+
return { shouldContinue: true };
|
443
|
+
}
|
444
|
+
}
|
445
|
+
function isWorkspaceOrPatchedPackage(packageName, lockFile, workspacePackages, workspacePackageNames) {
|
446
|
+
return (workspacePackages.has(packageName) ||
|
447
|
+
workspacePackageNames.has(packageName) ||
|
448
|
+
isWorkspacePackage(packageName, lockFile) ||
|
449
|
+
(lockFile.patches && !!lockFile.patches[packageName]));
|
450
|
+
}
|
451
|
+
function resolveAliasTarget(versionSpec) {
|
452
|
+
if (!versionSpec.startsWith('npm:'))
|
453
|
+
return null;
|
454
|
+
const actualSpec = versionSpec.substring(4);
|
455
|
+
const actualAtIndex = actualSpec.lastIndexOf('@');
|
456
|
+
return {
|
457
|
+
packageName: actualSpec.substring(0, actualAtIndex),
|
458
|
+
version: actualSpec.substring(actualAtIndex + 1),
|
459
|
+
};
|
460
|
+
}
|
461
|
+
function getAllWorkspaceDependencies(workspace) {
|
462
|
+
return {
|
463
|
+
...workspace.dependencies,
|
464
|
+
...workspace.devDependencies,
|
465
|
+
...workspace.optionalDependencies,
|
466
|
+
...workspace.peerDependencies,
|
467
|
+
};
|
468
|
+
}
|
469
|
+
function processPackageToPackageDependencies(lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths) {
|
470
|
+
const dependencies = [];
|
471
|
+
if (!lockFile.packages || Object.keys(lockFile.packages).length === 0) {
|
472
|
+
return dependencies;
|
473
|
+
}
|
474
|
+
const packageEntries = Object.entries(lockFile.packages);
|
475
|
+
for (const [packageKey, packageData] of packageEntries) {
|
476
|
+
try {
|
477
|
+
const packageDeps = processPackageForDependencies(packageKey, packageData, lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths);
|
478
|
+
dependencies.push(...packageDeps);
|
479
|
+
}
|
480
|
+
catch (error) {
|
481
|
+
continue;
|
482
|
+
}
|
483
|
+
}
|
484
|
+
return dependencies;
|
485
|
+
}
|
486
|
+
function processPackageForDependencies(packageKey, packageData, lockFile, ctx, workspacePackages, workspacePackageNames, workspacePaths) {
|
487
|
+
if (isWorkspacePackage(packageKey, lockFile) ||
|
488
|
+
isNestedPackageKey(packageKey, lockFile, workspacePaths, workspacePackageNames)) {
|
489
|
+
return [];
|
490
|
+
}
|
491
|
+
if (!Array.isArray(packageData) || packageData.length < 1) {
|
492
|
+
return [];
|
493
|
+
}
|
494
|
+
const [resolvedSpec] = packageData;
|
495
|
+
if (typeof resolvedSpec !== 'string') {
|
496
|
+
return [];
|
497
|
+
}
|
498
|
+
const { name: sourcePackageName, version: sourceVersion } = getCachedSpecInfo(resolvedSpec);
|
499
|
+
if (!sourcePackageName || !sourceVersion) {
|
500
|
+
return [];
|
501
|
+
}
|
502
|
+
if (lockFile.patches && lockFile.patches[sourcePackageName]) {
|
503
|
+
return [];
|
504
|
+
}
|
505
|
+
const sourceNodeName = `npm:${sourcePackageName}@${sourceVersion}`;
|
506
|
+
if (!ctx.externalNodes[sourceNodeName]) {
|
507
|
+
return [];
|
508
|
+
}
|
509
|
+
const packageDependencies = extractPackageDependencies(packageData);
|
510
|
+
if (!packageDependencies) {
|
511
|
+
return [];
|
512
|
+
}
|
513
|
+
const dependencies = [];
|
514
|
+
for (const depType of DEPENDENCY_TYPES) {
|
515
|
+
const deps = packageDependencies[depType];
|
516
|
+
if (!deps || typeof deps !== 'object')
|
517
|
+
continue;
|
518
|
+
const depDependencies = processDependencyEntries(deps, sourceNodeName, lockFile, ctx, workspacePackages, workspacePackageNames);
|
519
|
+
dependencies.push(...depDependencies);
|
520
|
+
}
|
521
|
+
return dependencies;
|
522
|
+
}
|
523
|
+
function extractPackageDependencies(packageData) {
|
524
|
+
if (packageData.length >= 3 &&
|
525
|
+
packageData[2] &&
|
526
|
+
typeof packageData[2] === 'object') {
|
527
|
+
return packageData[2];
|
528
|
+
}
|
529
|
+
if (packageData.length >= 2 &&
|
530
|
+
packageData[1] &&
|
531
|
+
typeof packageData[1] === 'object') {
|
532
|
+
return packageData[1];
|
533
|
+
}
|
534
|
+
return undefined;
|
535
|
+
}
|
536
|
+
function processDependencyEntries(deps, sourceNodeName, lockFile, ctx, workspacePackages, workspacePackageNames) {
|
537
|
+
const dependencies = [];
|
538
|
+
const depsEntries = Object.entries(deps);
|
539
|
+
for (const [packageName, versionSpec] of depsEntries) {
|
540
|
+
try {
|
541
|
+
const dependency = processSingleDependency(packageName, versionSpec, sourceNodeName, lockFile, ctx, workspacePackages, workspacePackageNames);
|
542
|
+
if (dependency) {
|
543
|
+
dependencies.push(dependency);
|
544
|
+
}
|
545
|
+
}
|
546
|
+
catch (error) {
|
547
|
+
continue;
|
548
|
+
}
|
549
|
+
}
|
550
|
+
return dependencies;
|
551
|
+
}
|
552
|
+
function processSingleDependency(packageName, versionSpec, sourceNodeName, lockFile, ctx, workspacePackages, workspacePackageNames) {
|
553
|
+
if (typeof packageName !== 'string' || typeof versionSpec !== 'string') {
|
554
|
+
return null;
|
555
|
+
}
|
556
|
+
if (isWorkspaceOrPatchedPackage(packageName, lockFile, workspacePackages, workspacePackageNames)) {
|
557
|
+
return null;
|
558
|
+
}
|
559
|
+
if (versionSpec.startsWith('workspace:')) {
|
560
|
+
return null;
|
561
|
+
}
|
562
|
+
let targetPackageName = packageName;
|
563
|
+
let targetVersion = versionSpec;
|
564
|
+
const aliasTarget = resolveAliasTarget(versionSpec);
|
565
|
+
if (aliasTarget) {
|
566
|
+
targetPackageName = aliasTarget.packageName;
|
567
|
+
targetVersion = aliasTarget.version;
|
568
|
+
}
|
569
|
+
else {
|
570
|
+
const resolvedVersion = findResolvedVersion(packageName, versionSpec, lockFile.packages, lockFile.manifests);
|
571
|
+
if (!resolvedVersion) {
|
572
|
+
return null;
|
573
|
+
}
|
574
|
+
targetVersion = resolvedVersion;
|
575
|
+
}
|
576
|
+
const targetNodeName = resolveTargetNodeName(targetPackageName, targetVersion, ctx);
|
577
|
+
if (!targetNodeName) {
|
578
|
+
return null;
|
579
|
+
}
|
580
|
+
const dependency = {
|
581
|
+
source: sourceNodeName,
|
582
|
+
target: targetNodeName,
|
583
|
+
type: project_graph_1.DependencyType.static,
|
584
|
+
};
|
585
|
+
try {
|
586
|
+
(0, project_graph_builder_1.validateDependency)(dependency, ctx);
|
587
|
+
return dependency;
|
588
|
+
}
|
589
|
+
catch (e) {
|
590
|
+
return null;
|
591
|
+
}
|
592
|
+
}
|
593
|
+
function resolveTargetNodeName(targetPackageName, targetVersion, ctx) {
|
594
|
+
const hoistedNodeName = `npm:${targetPackageName}`;
|
595
|
+
const versionedNodeName = `npm:${targetPackageName}@${targetVersion}`;
|
596
|
+
if (ctx.externalNodes[versionedNodeName]) {
|
597
|
+
return versionedNodeName;
|
598
|
+
}
|
599
|
+
if (ctx.externalNodes[hoistedNodeName]) {
|
600
|
+
return hoistedNodeName;
|
601
|
+
}
|
602
|
+
return null;
|
603
|
+
}
|
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
|
+
// ===== HOISTING-RELATED FUNCTIONS =====
|
663
|
+
function createHoistedNodes(packageVersions, lockFile, keyMap, nodes, workspacePaths, workspacePackageNames) {
|
664
|
+
for (const [packageName, versions] of packageVersions.entries()) {
|
665
|
+
const hoistedNodeKey = `npm:${packageName}`;
|
666
|
+
if (shouldCreateHoistedNode(packageName, lockFile, workspacePaths, workspacePackageNames)) {
|
667
|
+
const hoistedVersion = getHoistedVersion(packageName, versions, lockFile);
|
668
|
+
if (hoistedVersion) {
|
669
|
+
const versionedNodeKey = `npm:${packageName}@${hoistedVersion}`;
|
670
|
+
const versionedNode = keyMap.get(versionedNodeKey);
|
671
|
+
if (versionedNode && !keyMap.has(hoistedNodeKey)) {
|
672
|
+
const hoistedNode = {
|
673
|
+
type: 'npm',
|
674
|
+
name: hoistedNodeKey,
|
675
|
+
data: {
|
676
|
+
version: hoistedVersion,
|
677
|
+
packageName: packageName,
|
678
|
+
hash: versionedNode.data.hash,
|
679
|
+
},
|
680
|
+
};
|
681
|
+
keyMap.set(hoistedNodeKey, hoistedNode);
|
682
|
+
nodes[hoistedNodeKey] = hoistedNode;
|
683
|
+
}
|
684
|
+
}
|
685
|
+
}
|
686
|
+
}
|
687
|
+
}
|
688
|
+
/**
|
689
|
+
* Checks if a package key represents a workspace-specific or nested dependency entry
|
690
|
+
* These entries should not become external nodes, they are used only for resolution
|
691
|
+
*
|
692
|
+
* Examples of workspace-specific/nested entries:
|
693
|
+
* - "@quz/pkg1/lodash" (workspace-specific)
|
694
|
+
* - "is-even/is-odd" (dependency nesting)
|
695
|
+
* - "@quz/pkg2/is-even/is-odd" (workspace-specific nested)
|
696
|
+
*/
|
697
|
+
function isNestedPackageKey(packageKey, lockFile, workspacePaths, workspacePackageNames) {
|
698
|
+
// If the key doesn't contain '/', it's a direct package entry
|
699
|
+
if (!packageKey.includes('/')) {
|
700
|
+
return false;
|
701
|
+
}
|
702
|
+
// Get workspace paths and package names for comparison
|
703
|
+
const computedWorkspacePaths = workspacePaths || getWorkspacePaths(lockFile);
|
704
|
+
const computedWorkspacePackageNames = workspacePackageNames || getWorkspacePackageNames(lockFile);
|
705
|
+
// Check if this looks like a workspace-specific or nested entry
|
706
|
+
const parts = packageKey.split('/');
|
707
|
+
// For multi-part keys, check if the prefix is a workspace path or package name
|
708
|
+
if (parts.length >= 2) {
|
709
|
+
const prefix = parts.slice(0, -1).join('/');
|
710
|
+
// Check against known workspace paths
|
711
|
+
if (computedWorkspacePaths.has(prefix)) {
|
712
|
+
return true;
|
713
|
+
}
|
714
|
+
// Check against workspace package names (scoped packages)
|
715
|
+
if (computedWorkspacePackageNames.has(prefix)) {
|
716
|
+
return true;
|
717
|
+
}
|
718
|
+
// Check for scoped workspace packages (e.g., "@quz/pkg1")
|
719
|
+
if (prefix.startsWith('@') && prefix.includes('/')) {
|
720
|
+
return true;
|
721
|
+
}
|
722
|
+
// This could be dependency nesting (e.g., "is-even/is-odd")
|
723
|
+
// These should also be filtered out as they're not direct packages
|
724
|
+
return true;
|
725
|
+
}
|
726
|
+
return false;
|
727
|
+
}
|
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
|
+
/**
|
761
|
+
* Determines if a package should have a hoisted node created
|
762
|
+
* A package should be hoisted if:
|
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
|
770
|
+
*/
|
771
|
+
function shouldCreateHoistedNode(packageName, lockFile, workspacePaths, workspacePackageNames) {
|
772
|
+
if (!lockFile.workspaces || !lockFile.packages)
|
773
|
+
return false;
|
774
|
+
// First check if the package has a direct entry in the packages section
|
775
|
+
// Direct entries should always be hoisted (they represent the canonical version)
|
776
|
+
if (lockFile.packages[packageName]) {
|
777
|
+
return true;
|
778
|
+
}
|
779
|
+
// For packages without direct entries, check if they appear in workspace dependencies
|
780
|
+
// and don't have workspace-specific variants (which would cause conflicts)
|
781
|
+
let appearsInWorkspace = false;
|
782
|
+
for (const workspace of Object.values(lockFile.workspaces)) {
|
783
|
+
const allDeps = getAllWorkspaceDependencies(workspace);
|
784
|
+
if (allDeps[packageName]) {
|
785
|
+
appearsInWorkspace = true;
|
786
|
+
break;
|
787
|
+
}
|
788
|
+
}
|
789
|
+
if (appearsInWorkspace &&
|
790
|
+
!hasWorkspaceSpecificVariant(packageName, lockFile, workspacePaths, workspacePackageNames)) {
|
791
|
+
return true; // Found in workspace deps and no conflicts
|
792
|
+
}
|
793
|
+
return false;
|
794
|
+
}
|
795
|
+
/**
|
796
|
+
* Gets the version that should be used for a hoisted package
|
797
|
+
* For truly hoisted packages, we look up the version from the main package entry
|
798
|
+
*/
|
799
|
+
function getHoistedVersion(packageName, availableVersions, lockFile) {
|
800
|
+
if (!lockFile.packages)
|
801
|
+
return null;
|
802
|
+
// Look for the main package entry (not workspace-specific)
|
803
|
+
const mainPackageData = lockFile.packages[packageName];
|
804
|
+
if (mainPackageData &&
|
805
|
+
Array.isArray(mainPackageData) &&
|
806
|
+
mainPackageData.length > 0) {
|
807
|
+
const resolvedSpec = mainPackageData[0];
|
808
|
+
if (typeof resolvedSpec === 'string') {
|
809
|
+
const { version } = getCachedSpecInfo(resolvedSpec);
|
810
|
+
if (version && availableVersions.has(version)) {
|
811
|
+
return version;
|
812
|
+
}
|
813
|
+
}
|
814
|
+
}
|
815
|
+
// Fallback: return the first available version
|
816
|
+
return availableVersions.size > 0 ? Array.from(availableVersions)[0] : null;
|
817
|
+
}
|
818
|
+
/**
|
819
|
+
* Finds the resolved version for a package given its version specification
|
820
|
+
*
|
821
|
+
* 1. Fast path: Check manifests for exact version match
|
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
|
826
|
+
*/
|
827
|
+
function findResolvedVersion(packageName, versionSpec, packages, manifests) {
|
828
|
+
// Look for matching packages and collect all versions
|
829
|
+
const candidateVersions = [];
|
830
|
+
const packageEntries = Object.entries(packages);
|
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) {
|
868
|
+
// Try to find in manifests as fallback
|
869
|
+
if (manifests) {
|
870
|
+
const manifestKey = Object.keys(manifests).find((key) => key.startsWith(`${packageName}@`));
|
871
|
+
if (manifestKey) {
|
872
|
+
const manifest = manifests[manifestKey];
|
873
|
+
const version = manifest.version;
|
874
|
+
if (version) {
|
875
|
+
candidateVersions.push({
|
876
|
+
version,
|
877
|
+
packageKey: manifestKey,
|
878
|
+
manifest,
|
879
|
+
});
|
880
|
+
}
|
881
|
+
}
|
882
|
+
}
|
883
|
+
if (candidateVersions.length === 0) {
|
884
|
+
return null;
|
885
|
+
}
|
886
|
+
}
|
887
|
+
// Handle different version specification patterns with enhanced logic
|
888
|
+
const bestMatch = findBestVersionMatch(packageName, versionSpec, candidateVersions);
|
889
|
+
return bestMatch ? bestMatch.version : null;
|
890
|
+
}
|
891
|
+
/**
|
892
|
+
* 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
|
898
|
+
*/
|
899
|
+
function findBestVersionMatch(packageName, versionSpec, candidates) {
|
900
|
+
// For exact matches, return immediately
|
901
|
+
const exactMatch = candidates.find((c) => c.version === versionSpec);
|
902
|
+
if (exactMatch) {
|
903
|
+
return exactMatch;
|
904
|
+
}
|
905
|
+
// Handle union ranges (||)
|
906
|
+
if (versionSpec.includes('||')) {
|
907
|
+
const ranges = versionSpec.split('||').map((r) => r.trim());
|
908
|
+
for (const range of ranges) {
|
909
|
+
const match = findBestVersionMatch(packageName, range, candidates);
|
910
|
+
if (match) {
|
911
|
+
return match;
|
912
|
+
}
|
913
|
+
}
|
914
|
+
return null;
|
915
|
+
}
|
916
|
+
// Handle non-semver versions (git, file, etc.)
|
917
|
+
const nonSemverVersions = candidates.filter((c) => !c.version.match(/^\d+\.\d+\.\d+/));
|
918
|
+
if (nonSemverVersions.length > 0) {
|
919
|
+
// For non-semver versions, use the first match or exact match
|
920
|
+
const nonSemverMatch = nonSemverVersions.find((c) => c.version === versionSpec);
|
921
|
+
if (nonSemverMatch) {
|
922
|
+
return nonSemverMatch;
|
923
|
+
}
|
924
|
+
// If no exact match, return the first non-semver candidate
|
925
|
+
return nonSemverVersions[0];
|
926
|
+
}
|
927
|
+
// Handle semver versions
|
928
|
+
const semverVersions = candidates.filter((c) => c.version.match(/^\d+\.\d+\.\d+/));
|
929
|
+
if (semverVersions.length === 0) {
|
930
|
+
return candidates[0]; // Fallback to any available version
|
931
|
+
}
|
932
|
+
// Find all versions that satisfy the spec
|
933
|
+
const satisfyingVersions = semverVersions.filter((candidate) => {
|
934
|
+
try {
|
935
|
+
return (0, semver_1.satisfies)(candidate.version, versionSpec);
|
936
|
+
}
|
937
|
+
catch (error) {
|
938
|
+
// If semver fails, fall back to string comparison
|
939
|
+
return candidate.version === versionSpec;
|
940
|
+
}
|
941
|
+
});
|
942
|
+
if (satisfyingVersions.length === 0) {
|
943
|
+
// No satisfying versions found, return the first candidate as fallback
|
944
|
+
return semverVersions[0];
|
945
|
+
}
|
946
|
+
// Return the highest satisfying version (similar to npm behavior)
|
947
|
+
// Sort versions in descending order and return the first one
|
948
|
+
const sortedVersions = satisfyingVersions.sort((a, b) => {
|
949
|
+
try {
|
950
|
+
// Use semver comparison if possible
|
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, {
|
954
|
+
numeric: true,
|
955
|
+
sensitivity: 'base',
|
956
|
+
});
|
957
|
+
}
|
958
|
+
catch {
|
959
|
+
// Fallback to string comparison
|
960
|
+
return b.version.localeCompare(a.version);
|
961
|
+
}
|
962
|
+
});
|
963
|
+
return sortedVersions[0];
|
964
|
+
}
|