flatlock 1.1.0 → 1.3.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 +95 -1
- package/bin/flatcover.js +398 -0
- package/bin/flatlock-cmp.js +71 -45
- package/bin/flatlock.js +158 -0
- package/package.json +21 -8
- package/src/compare.js +385 -28
- package/src/detect.js +3 -4
- package/src/index.js +9 -2
- package/src/parsers/index.js +24 -4
- package/src/parsers/npm.js +144 -14
- package/src/parsers/pnpm/detect.js +198 -0
- package/src/parsers/pnpm/index.js +359 -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 +271 -36
- package/src/parsers/yarn-classic.js +81 -21
- package/src/set.js +1307 -0
- package/dist/compare.d.ts +0 -63
- package/dist/compare.d.ts.map +0 -1
- package/dist/detect.d.ts +0 -33
- package/dist/detect.d.ts.map +0 -1
- package/dist/index.d.ts +0 -70
- package/dist/index.d.ts.map +0 -1
- package/dist/parsers/index.d.ts +0 -5
- package/dist/parsers/index.d.ts.map +0 -1
- package/dist/parsers/npm.d.ts +0 -82
- package/dist/parsers/npm.d.ts.map +0 -1
- package/dist/parsers/pnpm.d.ts +0 -60
- package/dist/parsers/pnpm.d.ts.map +0 -1
- package/dist/parsers/yarn-berry.d.ts +0 -65
- package/dist/parsers/yarn-berry.d.ts.map +0 -1
- package/dist/parsers/yarn-classic.d.ts +0 -64
- package/dist/parsers/yarn-classic.d.ts.map +0 -1
- package/dist/result.d.ts +0 -12
- package/dist/result.d.ts.map +0 -1
|
@@ -1,39 +1,190 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
import { parseSyml } from '@yarnpkg/parsers';
|
|
2
4
|
|
|
5
|
+
/** @typedef {import('./types.js').Dependency} Dependency */
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
|
-
* @typedef {Object}
|
|
5
|
-
* @property {string} name
|
|
6
|
-
* @property {string} version
|
|
7
|
-
* @property {string} [
|
|
8
|
-
* @property {string} [
|
|
9
|
-
* @property {
|
|
8
|
+
* @typedef {Object} WorkspacePackage
|
|
9
|
+
* @property {string} name
|
|
10
|
+
* @property {string} version
|
|
11
|
+
* @property {Record<string, string>} [dependencies]
|
|
12
|
+
* @property {Record<string, string>} [devDependencies]
|
|
13
|
+
* @property {Record<string, string>} [optionalDependencies]
|
|
14
|
+
* @property {Record<string, string>} [peerDependencies]
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
18
|
+
* Extract package name from yarn berry resolution field.
|
|
19
|
+
*
|
|
20
|
+
* The resolution field is the CANONICAL identifier and should be used instead of the key.
|
|
21
|
+
* Keys can contain npm aliases (e.g., "string-width-cjs@npm:string-width@^4.2.0") while
|
|
22
|
+
* the resolution always contains the actual package name (e.g., "string-width@npm:4.2.3").
|
|
23
|
+
*
|
|
24
|
+
* @param {string} resolution - Resolution field from lockfile entry
|
|
25
|
+
* @returns {string | null} Package name or null if parsing fails
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Unscoped npm package
|
|
29
|
+
* parseResolution('lodash@npm:4.17.21')
|
|
30
|
+
* // => 'lodash'
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Scoped npm package
|
|
34
|
+
* parseResolution('@babel/core@npm:7.24.0')
|
|
35
|
+
* // => '@babel/core'
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Aliased package - resolution shows the REAL package name
|
|
39
|
+
* // (key was "string-width-cjs@npm:string-width@^4.2.0")
|
|
40
|
+
* parseResolution('string-width@npm:4.2.3')
|
|
41
|
+
* // => 'string-width'
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Scoped aliased package - resolution shows the REAL package name
|
|
45
|
+
* // (key was "@babel-baseline/core@npm:@babel/core@7.24.4")
|
|
46
|
+
* parseResolution('@babel/core@npm:7.24.4')
|
|
47
|
+
* // => '@babel/core'
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Patch protocol (nested protocols)
|
|
51
|
+
* parseResolution('pkg@patch:pkg@npm:1.0.0#./patch')
|
|
52
|
+
* // => 'pkg'
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Scoped package with patch protocol
|
|
56
|
+
* parseResolution('@scope/pkg@patch:@scope/pkg@npm:1.0.0#./fix.patch')
|
|
57
|
+
* // => '@scope/pkg'
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // Workspace protocol
|
|
61
|
+
* parseResolution('my-pkg@workspace:packages/my-pkg')
|
|
62
|
+
* // => 'my-pkg'
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Scoped workspace package
|
|
66
|
+
* parseResolution('@myorg/utils@workspace:packages/utils')
|
|
67
|
+
* // => '@myorg/utils'
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // Git protocol
|
|
71
|
+
* parseResolution('my-lib@git:github.com/user/repo#commit-hash')
|
|
72
|
+
* // => 'my-lib'
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // Null/empty input
|
|
76
|
+
* parseResolution(null)
|
|
77
|
+
* // => null
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Empty string
|
|
81
|
+
* parseResolution('')
|
|
82
|
+
* // => null
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Portal protocol (symlink to external package)
|
|
86
|
+
* parseResolution('@scope/external@portal:../external-pkg')
|
|
87
|
+
* // => '@scope/external'
|
|
88
|
+
*/
|
|
89
|
+
export function parseResolution(resolution) {
|
|
90
|
+
if (!resolution) return null;
|
|
91
|
+
|
|
92
|
+
// Resolution format: name@protocol:version or @scope/name@protocol:version
|
|
93
|
+
// Examples:
|
|
94
|
+
// "lodash@npm:4.17.21"
|
|
95
|
+
// "@babel/core@npm:7.24.0"
|
|
96
|
+
// "pkg@patch:pkg@npm:1.0.0#./patch"
|
|
97
|
+
|
|
98
|
+
// Handle scoped packages: @scope/name@protocol:version
|
|
99
|
+
if (resolution.startsWith('@')) {
|
|
100
|
+
const slashIndex = resolution.indexOf('/');
|
|
101
|
+
if (slashIndex !== -1) {
|
|
102
|
+
// Find the @ after the scope/name
|
|
103
|
+
const afterSlash = resolution.indexOf('@', slashIndex);
|
|
104
|
+
if (afterSlash !== -1) {
|
|
105
|
+
return resolution.slice(0, afterSlash);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle unscoped packages: name@protocol:version
|
|
111
|
+
const atIndex = resolution.indexOf('@');
|
|
112
|
+
if (atIndex !== -1) {
|
|
113
|
+
return resolution.slice(0, atIndex);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extract package name from yarn berry key (fallback for when resolution is unavailable).
|
|
121
|
+
*
|
|
122
|
+
* WARNING: Keys can contain npm aliases. Prefer parseResolution() when possible.
|
|
123
|
+
* The key may return an alias name instead of the real package name.
|
|
34
124
|
*
|
|
35
125
|
* @param {string} key - Lockfile entry key
|
|
36
|
-
* @returns {string} Package name
|
|
126
|
+
* @returns {string} Package name (may be alias name, not canonical name)
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // Simple unscoped package
|
|
130
|
+
* parseLockfileKey('lodash@npm:^4.17.21')
|
|
131
|
+
* // => 'lodash'
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* // Scoped package
|
|
135
|
+
* parseLockfileKey('@babel/core@npm:^7.24.0')
|
|
136
|
+
* // => '@babel/core'
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Multiple version ranges (comma-separated) - takes first entry
|
|
140
|
+
* parseLockfileKey('@types/node@npm:^18.0.0, @types/node@npm:^20.0.0')
|
|
141
|
+
* // => '@types/node'
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* // npm alias - returns the ALIAS name (not real package)
|
|
145
|
+
* // Use parseResolution() for the real package name
|
|
146
|
+
* parseLockfileKey('string-width-cjs@npm:string-width@^4.2.0')
|
|
147
|
+
* // => 'string-width-cjs'
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* // Scoped npm alias
|
|
151
|
+
* parseLockfileKey('@babel-baseline/core@npm:@babel/core@7.24.4')
|
|
152
|
+
* // => '@babel-baseline/core'
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* // Workspace protocol
|
|
156
|
+
* parseLockfileKey('my-pkg@workspace:packages/my-pkg')
|
|
157
|
+
* // => 'my-pkg'
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* // Scoped workspace package
|
|
161
|
+
* parseLockfileKey('@myorg/utils@workspace:.')
|
|
162
|
+
* // => '@myorg/utils'
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* // Portal protocol
|
|
166
|
+
* parseLockfileKey('external-pkg@portal:../some/path')
|
|
167
|
+
* // => 'external-pkg'
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Link protocol
|
|
171
|
+
* parseLockfileKey('linked-pkg@link:./local')
|
|
172
|
+
* // => 'linked-pkg'
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* // Patch protocol (complex nested format)
|
|
176
|
+
* parseLockfileKey('pkg@patch:pkg@npm:1.0.0#./patches/fix.patch')
|
|
177
|
+
* // => 'pkg'
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Scoped patch
|
|
181
|
+
* parseLockfileKey('@scope/pkg@patch:@scope/pkg@npm:1.0.0#./fix.patch')
|
|
182
|
+
* // => '@scope/pkg'
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* // File protocol
|
|
186
|
+
* parseLockfileKey('local-pkg@file:../local-package')
|
|
187
|
+
* // => 'local-pkg'
|
|
37
188
|
*/
|
|
38
189
|
export function parseLockfileKey(key) {
|
|
39
190
|
// Keys can have multiple comma-separated entries, take the first one
|
|
@@ -73,29 +224,37 @@ export function parseLockfileKey(key) {
|
|
|
73
224
|
|
|
74
225
|
/**
|
|
75
226
|
* Parse yarn.lock v2+ (berry)
|
|
76
|
-
* @param {string}
|
|
227
|
+
* @param {string | object} input - Lockfile content string or pre-parsed object
|
|
77
228
|
* @param {Object} [_options] - Parser options (unused, reserved for future use)
|
|
78
229
|
* @returns {Generator<Dependency>}
|
|
79
230
|
*/
|
|
80
|
-
export function* fromYarnBerryLock(
|
|
81
|
-
const lockfile = parseSyml(
|
|
231
|
+
export function* fromYarnBerryLock(input, _options = {}) {
|
|
232
|
+
const lockfile = typeof input === 'string' ? parseSyml(input) : input;
|
|
82
233
|
|
|
83
234
|
for (const [key, pkg] of Object.entries(lockfile)) {
|
|
84
235
|
// Skip metadata
|
|
85
236
|
if (key === '__metadata') continue;
|
|
86
237
|
|
|
87
|
-
const name = parseLockfileKey(key);
|
|
88
238
|
const { version, checksum, resolution } = pkg;
|
|
89
239
|
|
|
90
|
-
// Check if this is a
|
|
240
|
+
// Check if this is a local/workspace entry (workspace:, portal:, or link: protocol)
|
|
241
|
+
// The protocol appears after @ in both key and resolution: "pkg@workspace:..."
|
|
91
242
|
const link =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
243
|
+
key.includes('@workspace:') ||
|
|
244
|
+
key.includes('@portal:') ||
|
|
245
|
+
key.includes('@link:') ||
|
|
246
|
+
resolution?.includes('@workspace:') ||
|
|
247
|
+
resolution?.includes('@portal:') ||
|
|
248
|
+
resolution?.includes('@link:');
|
|
95
249
|
|
|
96
250
|
// Skip workspace/link entries - flatlock only cares about external dependencies
|
|
97
251
|
if (link) continue;
|
|
98
252
|
|
|
253
|
+
// Use the resolution field for the package name - it's the canonical identifier
|
|
254
|
+
// Keys can contain npm aliases (e.g., "string-width-cjs@npm:string-width@^4.2.0")
|
|
255
|
+
// but resolution always has the actual package name (e.g., "string-width@npm:4.2.3")
|
|
256
|
+
const name = parseResolution(resolution) || parseLockfileKey(key);
|
|
257
|
+
|
|
99
258
|
if (name && version) {
|
|
100
259
|
/** @type {Dependency} */
|
|
101
260
|
const dep = { name, version };
|
|
@@ -105,3 +264,79 @@ export function* fromYarnBerryLock(content, _options = {}) {
|
|
|
105
264
|
}
|
|
106
265
|
}
|
|
107
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Extract workspace paths from yarn berry lockfile.
|
|
270
|
+
*
|
|
271
|
+
* Yarn berry workspace entries use `@workspace:` protocol in keys.
|
|
272
|
+
* Keys can have multiple comma-separated descriptors.
|
|
273
|
+
*
|
|
274
|
+
* @param {string | object} input - Lockfile content string or pre-parsed object
|
|
275
|
+
* @returns {string[]} Array of workspace paths (e.g., ['packages/foo', 'packages/bar'])
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* extractWorkspacePaths(lockfile)
|
|
279
|
+
* // => ['packages/babel-core', 'packages/babel-parser', ...]
|
|
280
|
+
*/
|
|
281
|
+
export function extractWorkspacePaths(input) {
|
|
282
|
+
const lockfile = typeof input === 'string' ? parseSyml(input) : input;
|
|
283
|
+
const paths = new Set();
|
|
284
|
+
|
|
285
|
+
for (const key of Object.keys(lockfile)) {
|
|
286
|
+
if (key === '__metadata') continue;
|
|
287
|
+
if (!key.includes('@workspace:')) continue;
|
|
288
|
+
|
|
289
|
+
// Keys can have multiple comma-separated descriptors:
|
|
290
|
+
// "@babel/types@workspace:*, @babel/types@workspace:^, @babel/types@workspace:packages/babel-types"
|
|
291
|
+
const descriptors = key.split(', ');
|
|
292
|
+
for (const desc of descriptors) {
|
|
293
|
+
if (!desc.includes('@workspace:')) continue;
|
|
294
|
+
|
|
295
|
+
const wsIndex = desc.indexOf('@workspace:');
|
|
296
|
+
const path = desc.slice(wsIndex + '@workspace:'.length);
|
|
297
|
+
|
|
298
|
+
// Skip wildcards (*, ^) and root workspace (.)
|
|
299
|
+
if (path && path !== '.' && path !== '*' && path !== '^' && path.includes('/')) {
|
|
300
|
+
paths.add(path);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return [...paths];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build workspace packages map by reading package.json files.
|
|
310
|
+
*
|
|
311
|
+
* @param {string | object} input - Lockfile content string or pre-parsed object
|
|
312
|
+
* @param {string} repoDir - Path to repository root
|
|
313
|
+
* @returns {Promise<Record<string, WorkspacePackage>>} Map of workspace path to package info
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* const workspaces = await buildWorkspacePackages(lockfile, '/path/to/repo');
|
|
317
|
+
* // => { 'packages/foo': { name: '@scope/foo', version: '1.0.0', dependencies: {...} } }
|
|
318
|
+
*/
|
|
319
|
+
export async function buildWorkspacePackages(input, repoDir) {
|
|
320
|
+
const paths = extractWorkspacePaths(input);
|
|
321
|
+
/** @type {Record<string, WorkspacePackage>} */
|
|
322
|
+
const workspacePackages = {};
|
|
323
|
+
|
|
324
|
+
for (const wsPath of paths) {
|
|
325
|
+
const pkgJsonPath = join(repoDir, wsPath, 'package.json');
|
|
326
|
+
try {
|
|
327
|
+
const pkg = JSON.parse(await readFile(pkgJsonPath, 'utf8'));
|
|
328
|
+
workspacePackages[wsPath] = {
|
|
329
|
+
name: pkg.name,
|
|
330
|
+
version: pkg.version || '0.0.0',
|
|
331
|
+
dependencies: pkg.dependencies,
|
|
332
|
+
devDependencies: pkg.devDependencies,
|
|
333
|
+
optionalDependencies: pkg.optionalDependencies,
|
|
334
|
+
peerDependencies: pkg.peerDependencies
|
|
335
|
+
};
|
|
336
|
+
} catch {
|
|
337
|
+
// Skip workspaces with missing or invalid package.json
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return workspacePackages;
|
|
342
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import yarnLockfile from '@yarnpkg/lockfile';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** @typedef {import('./types.js').Dependency} Dependency */
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @typedef {Object}
|
|
7
|
-
* @property {
|
|
8
|
-
* @property {string}
|
|
9
|
-
* @property {string} [integrity] - Integrity hash
|
|
10
|
-
* @property {string} [resolved] - Resolution URL
|
|
11
|
-
* @property {boolean} [link] - True if this is a symlink
|
|
6
|
+
* @typedef {Object} YarnClassicParseResult
|
|
7
|
+
* @property {'success' | 'merge' | 'conflict'} type - Parse result type
|
|
8
|
+
* @property {Record<string, any>} object - Parsed lockfile object
|
|
12
9
|
*/
|
|
13
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The yarn classic parse function (handles CJS/ESM interop)
|
|
13
|
+
* @type {(content: string) => YarnClassicParseResult}
|
|
14
|
+
*/
|
|
15
|
+
export const parseYarnClassic = yarnLockfile.default?.parse || yarnLockfile.parse;
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
16
19
|
* !! WARNING: DO NOT MODIFY THIS FUNCTION !!
|
|
@@ -27,14 +30,68 @@ const { parse } = yarnLockfile;
|
|
|
27
30
|
*
|
|
28
31
|
* Extract package name from yarn classic key.
|
|
29
32
|
*
|
|
30
|
-
* Examples:
|
|
31
|
-
* "lodash@^4.17.21" → "lodash"
|
|
32
|
-
* "@babel/core@^7.0.0" → "@babel/core"
|
|
33
|
-
* "lodash@^4.17.21, lodash@^4.0.0" → "lodash" (multiple version ranges)
|
|
34
|
-
* "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3" → "@babel/traverse--for-generate-function-map"
|
|
35
|
-
*
|
|
36
33
|
* @param {string} key - Lockfile entry key
|
|
37
34
|
* @returns {string} Package name
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // Simple unscoped package with semver range
|
|
38
|
+
* parseLockfileKey('lodash@^4.17.21')
|
|
39
|
+
* // => 'lodash'
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Scoped package
|
|
43
|
+
* parseLockfileKey('@babel/core@^7.0.0')
|
|
44
|
+
* // => '@babel/core'
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Multiple version ranges (comma-separated) - takes first entry
|
|
48
|
+
* parseLockfileKey('lodash@^4.17.21, lodash@^4.0.0')
|
|
49
|
+
* // => 'lodash'
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Multiple ranges for scoped package
|
|
53
|
+
* parseLockfileKey('@types/node@^18.0.0, @types/node@^20.0.0')
|
|
54
|
+
* // => '@types/node'
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // npm: alias protocol - returns the ALIAS name
|
|
58
|
+
* parseLockfileKey('@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3')
|
|
59
|
+
* // => '@babel/traverse--for-generate-function-map'
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Unscoped alias
|
|
63
|
+
* parseLockfileKey('string-width-cjs@npm:string-width@^4.2.0')
|
|
64
|
+
* // => 'string-width-cjs'
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // Exact version
|
|
68
|
+
* parseLockfileKey('typescript@5.3.3')
|
|
69
|
+
* // => 'typescript'
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Git URL specifier
|
|
73
|
+
* parseLockfileKey('my-lib@github:user/repo#v1.0.0')
|
|
74
|
+
* // => 'my-lib'
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Tarball URL
|
|
78
|
+
* parseLockfileKey('custom-pkg@https://example.com/pkg.tgz')
|
|
79
|
+
* // => 'custom-pkg'
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Package with prerelease version
|
|
83
|
+
* parseLockfileKey('@next/env@^14.0.0-canary.0')
|
|
84
|
+
* // => '@next/env'
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // Package without @ (bare name, edge case)
|
|
88
|
+
* parseLockfileKey('lodash')
|
|
89
|
+
* // => 'lodash'
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // Deeply scoped alias pointing to scoped package
|
|
93
|
+
* parseLockfileKey('@myorg/my-alias@npm:@original/package@^1.0.0')
|
|
94
|
+
* // => '@myorg/my-alias'
|
|
38
95
|
*/
|
|
39
96
|
export function parseLockfileKey(key) {
|
|
40
97
|
// Keys can have multiple version ranges: "pkg@^1.0.0, pkg@^2.0.0"
|
|
@@ -72,19 +129,22 @@ export function parseLockfileKey(key) {
|
|
|
72
129
|
|
|
73
130
|
/**
|
|
74
131
|
* Parse yarn.lock v1 (classic)
|
|
75
|
-
* @param {string}
|
|
132
|
+
* @param {string | object} input - Lockfile content string or pre-parsed object
|
|
76
133
|
* @param {Object} [_options] - Parser options (unused, reserved for future use)
|
|
77
134
|
* @returns {Generator<Dependency>}
|
|
78
135
|
*/
|
|
79
|
-
export function* fromYarnClassicLock(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
136
|
+
export function* fromYarnClassicLock(input, _options = {}) {
|
|
137
|
+
let lockfile;
|
|
138
|
+
if (typeof input === 'string') {
|
|
139
|
+
const result = parseYarnClassic(input);
|
|
140
|
+
if (result.type !== 'success' && result.type !== 'merge') {
|
|
141
|
+
throw new Error('Failed to parse yarn.lock');
|
|
142
|
+
}
|
|
143
|
+
lockfile = result.object;
|
|
144
|
+
} else {
|
|
145
|
+
lockfile = input;
|
|
84
146
|
}
|
|
85
147
|
|
|
86
|
-
const lockfile = parsed.object;
|
|
87
|
-
|
|
88
148
|
for (const [key, pkg] of Object.entries(lockfile)) {
|
|
89
149
|
const name = parseLockfileKey(key);
|
|
90
150
|
const { version, integrity, resolved } = pkg;
|