flatlock 1.1.0 → 1.2.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/README.md +54 -1
- package/bin/flatlock-cmp.js +71 -45
- package/dist/compare.d.ts +25 -3
- package/dist/compare.d.ts.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/parsers/index.d.ts +2 -2
- package/dist/parsers/npm.d.ts +64 -37
- package/dist/parsers/npm.d.ts.map +1 -1
- package/dist/parsers/pnpm/detect.d.ts +136 -0
- package/dist/parsers/pnpm/detect.d.ts.map +1 -0
- package/dist/parsers/pnpm/index.d.ts +120 -0
- package/dist/parsers/pnpm/index.d.ts.map +1 -0
- package/dist/parsers/pnpm/internal.d.ts +5 -0
- package/dist/parsers/pnpm/internal.d.ts.map +1 -0
- package/dist/parsers/pnpm/shrinkwrap.d.ts +129 -0
- package/dist/parsers/pnpm/shrinkwrap.d.ts.map +1 -0
- package/dist/parsers/pnpm/v5.d.ts +139 -0
- package/dist/parsers/pnpm/v5.d.ts.map +1 -0
- package/dist/parsers/pnpm/v6plus.d.ts +212 -0
- package/dist/parsers/pnpm/v6plus.d.ts.map +1 -0
- package/dist/parsers/pnpm.d.ts +1 -59
- package/dist/parsers/pnpm.d.ts.map +1 -1
- package/dist/parsers/types.d.ts +23 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/yarn-berry.d.ts +141 -52
- package/dist/parsers/yarn-berry.d.ts.map +1 -1
- package/dist/parsers/yarn-classic.d.ts +79 -33
- package/dist/parsers/yarn-classic.d.ts.map +1 -1
- package/dist/set.d.ts +189 -0
- package/dist/set.d.ts.map +1 -0
- package/package.json +7 -5
- package/src/compare.js +385 -28
- package/src/detect.js +3 -4
- package/src/index.js +9 -2
- package/src/parsers/index.js +10 -2
- package/src/parsers/npm.js +64 -16
- package/src/parsers/pnpm/detect.js +198 -0
- package/src/parsers/pnpm/index.js +289 -0
- package/src/parsers/pnpm/internal.js +41 -0
- package/src/parsers/pnpm/shrinkwrap.js +241 -0
- package/src/parsers/pnpm/v5.js +225 -0
- package/src/parsers/pnpm/v6plus.js +290 -0
- package/src/parsers/pnpm.js +11 -89
- package/src/parsers/types.js +10 -0
- package/src/parsers/yarn-berry.js +183 -36
- package/src/parsers/yarn-classic.js +81 -21
- package/src/set.js +618 -0
package/src/set.js
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { parseSyml } from '@yarnpkg/parsers';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { detectType, Type } from './detect.js';
|
|
5
|
+
import {
|
|
6
|
+
fromPackageLock,
|
|
7
|
+
fromPnpmLock,
|
|
8
|
+
fromYarnBerryLock,
|
|
9
|
+
fromYarnClassicLock,
|
|
10
|
+
parseYarnBerryKey,
|
|
11
|
+
parseYarnClassic,
|
|
12
|
+
parseYarnClassicKey
|
|
13
|
+
} from './parsers/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('./parsers/npm.js').Dependency} Dependency
|
|
17
|
+
* @typedef {import('./detect.js').LockfileType} LockfileType
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} DependenciesOfOptions
|
|
22
|
+
* @property {string} [workspacePath] - Path to workspace (e.g., 'packages/foo')
|
|
23
|
+
* @property {boolean} [dev=false] - Include devDependencies
|
|
24
|
+
* @property {boolean} [optional=true] - Include optionalDependencies
|
|
25
|
+
* @property {boolean} [peer=false] - Include peerDependencies (default false: peers are provided by consumer)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} FromStringOptions
|
|
30
|
+
* @property {string} [path] - Path hint for type detection
|
|
31
|
+
* @property {LockfileType} [type] - Explicit lockfile type
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} PackageJson
|
|
36
|
+
* @property {Record<string, string>} [dependencies]
|
|
37
|
+
* @property {Record<string, string>} [devDependencies]
|
|
38
|
+
* @property {Record<string, string>} [optionalDependencies]
|
|
39
|
+
* @property {Record<string, string>} [peerDependencies]
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {Record<string, any>} LockfilePackages
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Record<string, any>} LockfileImporters
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** Symbol to prevent direct construction */
|
|
51
|
+
const INTERNAL = Symbol('FlatlockSet.internal');
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A Set-like container for lockfile dependencies.
|
|
55
|
+
*
|
|
56
|
+
* Identity is determined by name@version. Two dependencies with the same
|
|
57
|
+
* name and version are considered equal, regardless of integrity or resolved URL.
|
|
58
|
+
*
|
|
59
|
+
* All set operations return new FlatlockSet instances (immutable pattern).
|
|
60
|
+
*
|
|
61
|
+
* NOTE: Set operations (union, intersection, difference) return sets that
|
|
62
|
+
* cannot use dependenciesOf() because they lack lockfile traversal data.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const set = await FlatlockSet.fromPath('./package-lock.json');
|
|
66
|
+
* console.log(set.size); // 1234
|
|
67
|
+
* console.log(set.has('lodash@4.17.21')); // true
|
|
68
|
+
*
|
|
69
|
+
* // Get dependencies for a specific workspace
|
|
70
|
+
* const pkg = JSON.parse(await readFile('./packages/foo/package.json'));
|
|
71
|
+
* const subset = set.dependenciesOf(pkg, { workspacePath: 'packages/foo' });
|
|
72
|
+
*
|
|
73
|
+
* // Set operations
|
|
74
|
+
* const other = await FlatlockSet.fromPath('./other-lock.json');
|
|
75
|
+
* const common = set.intersection(other);
|
|
76
|
+
*/
|
|
77
|
+
export class FlatlockSet {
|
|
78
|
+
/** @type {Map<string, Dependency>} */
|
|
79
|
+
#deps = new Map();
|
|
80
|
+
|
|
81
|
+
/** @type {LockfilePackages | null} Raw lockfile packages for traversal */
|
|
82
|
+
#packages = null;
|
|
83
|
+
|
|
84
|
+
/** @type {LockfileImporters | null} Workspace importers (pnpm) */
|
|
85
|
+
#importers = null;
|
|
86
|
+
|
|
87
|
+
/** @type {LockfileType | null} */
|
|
88
|
+
#type = null;
|
|
89
|
+
|
|
90
|
+
/** @type {boolean} Whether this set supports dependenciesOf */
|
|
91
|
+
#canTraverse = false;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {symbol} internal - Must be INTERNAL symbol
|
|
95
|
+
* @param {Map<string, Dependency>} deps
|
|
96
|
+
* @param {LockfilePackages | null} packages
|
|
97
|
+
* @param {LockfileImporters | null} importers
|
|
98
|
+
* @param {LockfileType | null} type
|
|
99
|
+
*/
|
|
100
|
+
constructor(internal, deps, packages, importers, type) {
|
|
101
|
+
if (internal !== INTERNAL) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
'FlatlockSet cannot be constructed directly. Use FlatlockSet.fromPath() or FlatlockSet.fromString()'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
this.#deps = deps;
|
|
107
|
+
this.#packages = packages;
|
|
108
|
+
this.#importers = importers;
|
|
109
|
+
this.#type = type;
|
|
110
|
+
this.#canTraverse = packages !== null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create FlatlockSet from lockfile path (auto-detect type)
|
|
115
|
+
* @param {string} path - Path to lockfile
|
|
116
|
+
* @param {FromStringOptions} [options] - Parser options
|
|
117
|
+
* @returns {Promise<FlatlockSet>}
|
|
118
|
+
*/
|
|
119
|
+
static async fromPath(path, options = {}) {
|
|
120
|
+
const content = await readFile(path, 'utf8');
|
|
121
|
+
return FlatlockSet.fromString(content, { ...options, path });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create FlatlockSet from lockfile string
|
|
126
|
+
* @param {string} content - Lockfile content
|
|
127
|
+
* @param {FromStringOptions} [options] - Parser options
|
|
128
|
+
* @returns {FlatlockSet}
|
|
129
|
+
*/
|
|
130
|
+
static fromString(content, options = {}) {
|
|
131
|
+
const type = options.type || detectType({ path: options.path, content });
|
|
132
|
+
|
|
133
|
+
if (!type) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'Unable to detect lockfile type. ' +
|
|
136
|
+
'Provide options.type explicitly or ensure content is a valid lockfile format.'
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Parse once, extract both deps and raw data
|
|
141
|
+
const { deps, packages, importers } = FlatlockSet.#parseAll(content, type, options);
|
|
142
|
+
|
|
143
|
+
return new FlatlockSet(INTERNAL, deps, packages, importers, type);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Parse lockfile once, returning both processed deps and raw data
|
|
148
|
+
* @param {string} content
|
|
149
|
+
* @param {LockfileType} type
|
|
150
|
+
* @param {FromStringOptions} options
|
|
151
|
+
* @returns {{ deps: Map<string, Dependency>, packages: LockfilePackages, importers: LockfileImporters | null }}
|
|
152
|
+
*/
|
|
153
|
+
static #parseAll(content, type, options) {
|
|
154
|
+
/** @type {Map<string, Dependency>} */
|
|
155
|
+
const deps = new Map();
|
|
156
|
+
/** @type {LockfilePackages} */
|
|
157
|
+
let packages = {};
|
|
158
|
+
/** @type {LockfileImporters | null} */
|
|
159
|
+
let importers = null;
|
|
160
|
+
|
|
161
|
+
switch (type) {
|
|
162
|
+
case Type.NPM: {
|
|
163
|
+
const lockfile = JSON.parse(content);
|
|
164
|
+
packages = lockfile.packages || {};
|
|
165
|
+
// Pass pre-parsed lockfile object to avoid re-parsing
|
|
166
|
+
for (const dep of fromPackageLock(lockfile, options)) {
|
|
167
|
+
deps.set(`${dep.name}@${dep.version}`, dep);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case Type.PNPM: {
|
|
172
|
+
/** @type {any} */
|
|
173
|
+
const lockfile = yaml.load(content);
|
|
174
|
+
packages = lockfile.packages || {};
|
|
175
|
+
importers = lockfile.importers || null;
|
|
176
|
+
// Pass pre-parsed lockfile object to avoid re-parsing
|
|
177
|
+
for (const dep of fromPnpmLock(lockfile, options)) {
|
|
178
|
+
deps.set(`${dep.name}@${dep.version}`, dep);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case Type.YARN_CLASSIC: {
|
|
183
|
+
const result = parseYarnClassic(content);
|
|
184
|
+
packages = result.object || {};
|
|
185
|
+
// Pass pre-parsed lockfile object to avoid re-parsing
|
|
186
|
+
for (const dep of fromYarnClassicLock(packages, options)) {
|
|
187
|
+
deps.set(`${dep.name}@${dep.version}`, dep);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case Type.YARN_BERRY: {
|
|
192
|
+
packages = parseSyml(content);
|
|
193
|
+
// Pass pre-parsed lockfile object to avoid re-parsing
|
|
194
|
+
for (const dep of fromYarnBerryLock(packages, options)) {
|
|
195
|
+
deps.set(`${dep.name}@${dep.version}`, dep);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { deps, packages, importers };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @returns {number} */
|
|
205
|
+
get size() {
|
|
206
|
+
return this.#deps.size;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** @returns {LockfileType | null} */
|
|
210
|
+
get type() {
|
|
211
|
+
return this.#type;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @returns {boolean} */
|
|
215
|
+
get canTraverse() {
|
|
216
|
+
return this.#canTraverse;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check if a dependency exists
|
|
221
|
+
* @param {string} nameAtVersion - e.g., "lodash@4.17.21"
|
|
222
|
+
* @returns {boolean}
|
|
223
|
+
*/
|
|
224
|
+
has(nameAtVersion) {
|
|
225
|
+
return this.#deps.has(nameAtVersion);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get a dependency by name@version
|
|
230
|
+
* @param {string} nameAtVersion
|
|
231
|
+
* @returns {Dependency | undefined}
|
|
232
|
+
*/
|
|
233
|
+
get(nameAtVersion) {
|
|
234
|
+
return this.#deps.get(nameAtVersion);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** @returns {IterableIterator<Dependency>} */
|
|
238
|
+
[Symbol.iterator]() {
|
|
239
|
+
return this.#deps.values();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** @returns {IterableIterator<Dependency>} */
|
|
243
|
+
values() {
|
|
244
|
+
return this.#deps.values();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** @returns {IterableIterator<string>} */
|
|
248
|
+
keys() {
|
|
249
|
+
return this.#deps.keys();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** @returns {IterableIterator<[string, Dependency]>} */
|
|
253
|
+
entries() {
|
|
254
|
+
return this.#deps.entries();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Execute a callback for each dependency
|
|
259
|
+
* @param {(dep: Dependency, key: string, set: FlatlockSet) => void} callback
|
|
260
|
+
* @param {any} [thisArg]
|
|
261
|
+
*/
|
|
262
|
+
forEach(callback, thisArg) {
|
|
263
|
+
for (const [key, dep] of this.#deps) {
|
|
264
|
+
callback.call(thisArg, dep, key, this);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Union of this set with another
|
|
270
|
+
* @param {FlatlockSet} other
|
|
271
|
+
* @returns {FlatlockSet}
|
|
272
|
+
*/
|
|
273
|
+
union(other) {
|
|
274
|
+
const deps = new Map(this.#deps);
|
|
275
|
+
for (const [key, dep] of other.#deps) {
|
|
276
|
+
if (!deps.has(key)) {
|
|
277
|
+
deps.set(key, dep);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return new FlatlockSet(INTERNAL, deps, null, null, null);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Intersection of this set with another
|
|
285
|
+
* @param {FlatlockSet} other
|
|
286
|
+
* @returns {FlatlockSet}
|
|
287
|
+
*/
|
|
288
|
+
intersection(other) {
|
|
289
|
+
const deps = new Map();
|
|
290
|
+
for (const [key, dep] of this.#deps) {
|
|
291
|
+
if (other.has(key)) {
|
|
292
|
+
deps.set(key, dep);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return new FlatlockSet(INTERNAL, deps, null, null, null);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Difference: elements in this set but not in other
|
|
300
|
+
* @param {FlatlockSet} other
|
|
301
|
+
* @returns {FlatlockSet}
|
|
302
|
+
*/
|
|
303
|
+
difference(other) {
|
|
304
|
+
const deps = new Map();
|
|
305
|
+
for (const [key, dep] of this.#deps) {
|
|
306
|
+
if (!other.has(key)) {
|
|
307
|
+
deps.set(key, dep);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return new FlatlockSet(INTERNAL, deps, null, null, null);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check if this set is a subset of another
|
|
315
|
+
* @param {FlatlockSet} other
|
|
316
|
+
* @returns {boolean}
|
|
317
|
+
*/
|
|
318
|
+
isSubsetOf(other) {
|
|
319
|
+
for (const key of this.#deps.keys()) {
|
|
320
|
+
if (!other.has(key)) return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if this set is a superset of another
|
|
327
|
+
* @param {FlatlockSet} other
|
|
328
|
+
* @returns {boolean}
|
|
329
|
+
*/
|
|
330
|
+
isSupersetOf(other) {
|
|
331
|
+
return other.isSubsetOf(this);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if this set has no elements in common with another
|
|
336
|
+
* @param {FlatlockSet} other
|
|
337
|
+
* @returns {boolean}
|
|
338
|
+
*/
|
|
339
|
+
isDisjointFrom(other) {
|
|
340
|
+
for (const key of this.#deps.keys()) {
|
|
341
|
+
if (other.has(key)) return false;
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get transitive dependencies of a package.json
|
|
348
|
+
*
|
|
349
|
+
* For monorepos, provide workspacePath to get correct resolution.
|
|
350
|
+
* Without workspacePath, assumes root package (hoisted deps only).
|
|
351
|
+
*
|
|
352
|
+
* NOTE: This method is only available on sets created directly from
|
|
353
|
+
* fromPath/fromString. Sets created via union/intersection/difference
|
|
354
|
+
* cannot use this method (canTraverse will be false).
|
|
355
|
+
*
|
|
356
|
+
* @param {PackageJson} packageJson - Parsed package.json
|
|
357
|
+
* @param {DependenciesOfOptions} [options]
|
|
358
|
+
* @returns {FlatlockSet}
|
|
359
|
+
* @throws {Error} If called on a set that cannot traverse
|
|
360
|
+
*/
|
|
361
|
+
dependenciesOf(packageJson, options = {}) {
|
|
362
|
+
if (!packageJson || typeof packageJson !== 'object') {
|
|
363
|
+
throw new TypeError('packageJson must be a non-null object');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!this.#canTraverse) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
'dependenciesOf() requires lockfile data. ' +
|
|
369
|
+
'This set was created via set operations and cannot traverse dependencies. ' +
|
|
370
|
+
'Use dependenciesOf() on the original set before set operations.'
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const { workspacePath, dev = false, optional = true, peer = false } = options;
|
|
375
|
+
|
|
376
|
+
// Collect seed dependencies from package.json
|
|
377
|
+
const seeds = this.#collectSeeds(packageJson, { dev, optional, peer });
|
|
378
|
+
|
|
379
|
+
// If pnpm with workspacePath, use importers to get resolved versions
|
|
380
|
+
if (this.#type === Type.PNPM && workspacePath && this.#importers) {
|
|
381
|
+
return this.#dependenciesOfPnpm(seeds, workspacePath, { dev, optional, peer });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// BFS traversal for npm/yarn (hoisted resolution)
|
|
385
|
+
return this.#dependenciesOfHoisted(seeds, workspacePath);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Collect seed dependency names from package.json
|
|
390
|
+
* @param {PackageJson} packageJson
|
|
391
|
+
* @param {{ dev: boolean, optional: boolean, peer: boolean }} options
|
|
392
|
+
* @returns {Set<string>}
|
|
393
|
+
*/
|
|
394
|
+
#collectSeeds(packageJson, { dev, optional, peer }) {
|
|
395
|
+
const seeds = new Set();
|
|
396
|
+
|
|
397
|
+
for (const name of Object.keys(packageJson.dependencies || {})) {
|
|
398
|
+
seeds.add(name);
|
|
399
|
+
}
|
|
400
|
+
if (dev) {
|
|
401
|
+
for (const name of Object.keys(packageJson.devDependencies || {})) {
|
|
402
|
+
seeds.add(name);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (optional) {
|
|
406
|
+
for (const name of Object.keys(packageJson.optionalDependencies || {})) {
|
|
407
|
+
seeds.add(name);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (peer) {
|
|
411
|
+
for (const name of Object.keys(packageJson.peerDependencies || {})) {
|
|
412
|
+
seeds.add(name);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return seeds;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* pnpm-specific resolution using importers
|
|
421
|
+
* @param {Set<string>} seeds
|
|
422
|
+
* @param {string} workspacePath
|
|
423
|
+
* @param {{ dev: boolean, optional: boolean, peer: boolean }} options
|
|
424
|
+
* @returns {FlatlockSet}
|
|
425
|
+
*/
|
|
426
|
+
#dependenciesOfPnpm(seeds, workspacePath, { dev, optional, peer }) {
|
|
427
|
+
/** @type {Map<string, Dependency>} */
|
|
428
|
+
const result = new Map();
|
|
429
|
+
/** @type {Set<string>} */
|
|
430
|
+
const visited = new Set();
|
|
431
|
+
/** @type {string[]} */
|
|
432
|
+
const queue = [...seeds];
|
|
433
|
+
|
|
434
|
+
// Get resolved versions from importers
|
|
435
|
+
const importer = this.#importers?.[workspacePath] || this.#importers?.['.'] || {};
|
|
436
|
+
const resolvedDeps = {
|
|
437
|
+
...importer.dependencies,
|
|
438
|
+
...(dev ? importer.devDependencies : {}),
|
|
439
|
+
...(optional ? importer.optionalDependencies : {}),
|
|
440
|
+
...(peer ? importer.peerDependencies : {})
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
while (queue.length > 0) {
|
|
444
|
+
const name = /** @type {string} */ (queue.shift());
|
|
445
|
+
if (visited.has(name)) continue;
|
|
446
|
+
visited.add(name);
|
|
447
|
+
|
|
448
|
+
// Get resolved version from importer or find in deps
|
|
449
|
+
const version = resolvedDeps[name];
|
|
450
|
+
let dep;
|
|
451
|
+
|
|
452
|
+
if (version) {
|
|
453
|
+
// pnpm stores version directly or as specifier
|
|
454
|
+
dep = this.get(`${name}@${version}`) || this.#findByName(name);
|
|
455
|
+
} else {
|
|
456
|
+
dep = this.#findByName(name);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!dep) continue;
|
|
460
|
+
|
|
461
|
+
const key = `${dep.name}@${dep.version}`;
|
|
462
|
+
result.set(key, dep);
|
|
463
|
+
|
|
464
|
+
// Get transitive deps
|
|
465
|
+
const pkgKey = `/${dep.name}@${dep.version}`;
|
|
466
|
+
const pkgEntry = this.#packages?.[pkgKey];
|
|
467
|
+
if (pkgEntry) {
|
|
468
|
+
for (const transName of Object.keys(pkgEntry.dependencies || {})) {
|
|
469
|
+
if (!visited.has(transName)) queue.push(transName);
|
|
470
|
+
}
|
|
471
|
+
if (optional) {
|
|
472
|
+
for (const transName of Object.keys(pkgEntry.optionalDependencies || {})) {
|
|
473
|
+
if (!visited.has(transName)) queue.push(transName);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return new FlatlockSet(INTERNAL, result, null, null, this.#type);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* npm/yarn resolution with hoisting
|
|
484
|
+
* @param {Set<string>} seeds
|
|
485
|
+
* @param {string} [workspacePath]
|
|
486
|
+
* @returns {FlatlockSet}
|
|
487
|
+
*/
|
|
488
|
+
#dependenciesOfHoisted(seeds, workspacePath) {
|
|
489
|
+
/** @type {Map<string, Dependency>} */
|
|
490
|
+
const result = new Map();
|
|
491
|
+
/** @type {Set<string>} */
|
|
492
|
+
const visited = new Set();
|
|
493
|
+
/** @type {string[]} */
|
|
494
|
+
const queue = [...seeds];
|
|
495
|
+
|
|
496
|
+
while (queue.length > 0) {
|
|
497
|
+
const name = /** @type {string} */ (queue.shift());
|
|
498
|
+
if (visited.has(name)) continue;
|
|
499
|
+
visited.add(name);
|
|
500
|
+
|
|
501
|
+
// Find package: check workspace-local first, then hoisted
|
|
502
|
+
const dep = this.#findPackage(name, workspacePath);
|
|
503
|
+
if (!dep) continue;
|
|
504
|
+
|
|
505
|
+
const key = `${dep.name}@${dep.version}`;
|
|
506
|
+
result.set(key, dep);
|
|
507
|
+
|
|
508
|
+
// Get transitive deps from raw lockfile
|
|
509
|
+
const entry = this.#getPackageEntry(dep.name, dep.version, workspacePath);
|
|
510
|
+
if (entry) {
|
|
511
|
+
for (const transName of Object.keys(entry.dependencies || {})) {
|
|
512
|
+
if (!visited.has(transName)) queue.push(transName);
|
|
513
|
+
}
|
|
514
|
+
for (const transName of Object.keys(entry.optionalDependencies || {})) {
|
|
515
|
+
if (!visited.has(transName)) queue.push(transName);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return new FlatlockSet(INTERNAL, result, null, null, this.#type);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Find a package by name, checking workspace-local then hoisted
|
|
525
|
+
* @param {string} name
|
|
526
|
+
* @param {string} [workspacePath]
|
|
527
|
+
* @returns {Dependency | undefined}
|
|
528
|
+
*/
|
|
529
|
+
#findPackage(name, workspacePath) {
|
|
530
|
+
if (this.#type === Type.NPM && workspacePath) {
|
|
531
|
+
// Check workspace-local node_modules first
|
|
532
|
+
const localKey = `${workspacePath}/node_modules/${name}`;
|
|
533
|
+
const localEntry = this.#packages?.[localKey];
|
|
534
|
+
if (localEntry?.version) {
|
|
535
|
+
return this.get(`${name}@${localEntry.version}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Fall back to hoisted (root node_modules)
|
|
540
|
+
return this.#findByName(name);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Find a dependency by name (returns hoisted/first match)
|
|
545
|
+
* @param {string} name
|
|
546
|
+
* @returns {Dependency | undefined}
|
|
547
|
+
*/
|
|
548
|
+
#findByName(name) {
|
|
549
|
+
// For npm, check root node_modules path first
|
|
550
|
+
if (this.#type === Type.NPM) {
|
|
551
|
+
const rootKey = `node_modules/${name}`;
|
|
552
|
+
const entry = this.#packages?.[rootKey];
|
|
553
|
+
if (entry?.version) {
|
|
554
|
+
return this.get(`${name}@${entry.version}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Fallback: iterate deps (may return arbitrary version if multiple)
|
|
559
|
+
for (const dep of this.#deps.values()) {
|
|
560
|
+
if (dep.name === name) return dep;
|
|
561
|
+
}
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Get raw package entry for transitive dep lookup
|
|
567
|
+
* @param {string} name
|
|
568
|
+
* @param {string} version
|
|
569
|
+
* @param {string} [workspacePath]
|
|
570
|
+
* @returns {any}
|
|
571
|
+
*/
|
|
572
|
+
#getPackageEntry(name, version, workspacePath) {
|
|
573
|
+
if (!this.#packages) return null;
|
|
574
|
+
|
|
575
|
+
switch (this.#type) {
|
|
576
|
+
case Type.NPM: {
|
|
577
|
+
// Check workspace-local first
|
|
578
|
+
if (workspacePath) {
|
|
579
|
+
const localKey = `${workspacePath}/node_modules/${name}`;
|
|
580
|
+
if (this.#packages[localKey]) return this.#packages[localKey];
|
|
581
|
+
}
|
|
582
|
+
// Fall back to hoisted
|
|
583
|
+
return this.#packages[`node_modules/${name}`] || null;
|
|
584
|
+
}
|
|
585
|
+
case Type.PNPM: {
|
|
586
|
+
return this.#packages[`/${name}@${version}`] || null;
|
|
587
|
+
}
|
|
588
|
+
case Type.YARN_CLASSIC: {
|
|
589
|
+
for (const [key, entry] of Object.entries(this.#packages)) {
|
|
590
|
+
if (entry.version === version) {
|
|
591
|
+
const keyName = parseYarnClassicKey(key);
|
|
592
|
+
if (keyName === name) return entry;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
case Type.YARN_BERRY: {
|
|
598
|
+
for (const [key, entry] of Object.entries(this.#packages)) {
|
|
599
|
+
if (entry.version === version) {
|
|
600
|
+
const keyName = parseYarnBerryKey(key);
|
|
601
|
+
if (keyName === name) return entry;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
default:
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Convert to array
|
|
613
|
+
* @returns {Dependency[]}
|
|
614
|
+
*/
|
|
615
|
+
toArray() {
|
|
616
|
+
return [...this.#deps.values()];
|
|
617
|
+
}
|
|
618
|
+
}
|