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.
Files changed (49) hide show
  1. package/README.md +54 -1
  2. package/bin/flatlock-cmp.js +71 -45
  3. package/dist/compare.d.ts +25 -3
  4. package/dist/compare.d.ts.map +1 -1
  5. package/dist/detect.d.ts.map +1 -1
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/parsers/index.d.ts +2 -2
  9. package/dist/parsers/npm.d.ts +64 -37
  10. package/dist/parsers/npm.d.ts.map +1 -1
  11. package/dist/parsers/pnpm/detect.d.ts +136 -0
  12. package/dist/parsers/pnpm/detect.d.ts.map +1 -0
  13. package/dist/parsers/pnpm/index.d.ts +120 -0
  14. package/dist/parsers/pnpm/index.d.ts.map +1 -0
  15. package/dist/parsers/pnpm/internal.d.ts +5 -0
  16. package/dist/parsers/pnpm/internal.d.ts.map +1 -0
  17. package/dist/parsers/pnpm/shrinkwrap.d.ts +129 -0
  18. package/dist/parsers/pnpm/shrinkwrap.d.ts.map +1 -0
  19. package/dist/parsers/pnpm/v5.d.ts +139 -0
  20. package/dist/parsers/pnpm/v5.d.ts.map +1 -0
  21. package/dist/parsers/pnpm/v6plus.d.ts +212 -0
  22. package/dist/parsers/pnpm/v6plus.d.ts.map +1 -0
  23. package/dist/parsers/pnpm.d.ts +1 -59
  24. package/dist/parsers/pnpm.d.ts.map +1 -1
  25. package/dist/parsers/types.d.ts +23 -0
  26. package/dist/parsers/types.d.ts.map +1 -0
  27. package/dist/parsers/yarn-berry.d.ts +141 -52
  28. package/dist/parsers/yarn-berry.d.ts.map +1 -1
  29. package/dist/parsers/yarn-classic.d.ts +79 -33
  30. package/dist/parsers/yarn-classic.d.ts.map +1 -1
  31. package/dist/set.d.ts +189 -0
  32. package/dist/set.d.ts.map +1 -0
  33. package/package.json +7 -5
  34. package/src/compare.js +385 -28
  35. package/src/detect.js +3 -4
  36. package/src/index.js +9 -2
  37. package/src/parsers/index.js +10 -2
  38. package/src/parsers/npm.js +64 -16
  39. package/src/parsers/pnpm/detect.js +198 -0
  40. package/src/parsers/pnpm/index.js +289 -0
  41. package/src/parsers/pnpm/internal.js +41 -0
  42. package/src/parsers/pnpm/shrinkwrap.js +241 -0
  43. package/src/parsers/pnpm/v5.js +225 -0
  44. package/src/parsers/pnpm/v6plus.js +290 -0
  45. package/src/parsers/pnpm.js +11 -89
  46. package/src/parsers/types.js +10 -0
  47. package/src/parsers/yarn-berry.js +183 -36
  48. package/src/parsers/yarn-classic.js +81 -21
  49. package/src/set.js +618 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ Most lockfile parsers (like `@npmcli/arborist` or `snyk-nodejs-lockfile-parser`)
11
11
  **flatlock** takes a different approach: it extracts a flat stream of packages from any lockfile format. No trees, no graphs, no edges - just packages.
12
12
 
13
13
  ```javascript
14
- import * as `flatlock`from 'flatlock';
14
+ import * as flatlock from 'flatlock';
15
15
 
16
16
  // Stream packages from any lockfile
17
17
  for await (const pkg of flatlock.fromPath('./package-lock.json')) {
@@ -89,6 +89,59 @@ Each yielded package has:
89
89
  }
90
90
  ```
91
91
 
92
+ ## FlatlockSet
93
+
94
+ For more advanced use cases, `FlatlockSet` provides Set-like operations on lockfile dependencies:
95
+
96
+ ```javascript
97
+ import { FlatlockSet } from 'flatlock';
98
+
99
+ // Create from lockfile
100
+ const set = await FlatlockSet.fromPath('./package-lock.json');
101
+ console.log(set.size); // 1234
102
+ console.log(set.has('lodash@4.17.21')); // true
103
+
104
+ // Set operations (immutable - return new sets)
105
+ const other = await FlatlockSet.fromPath('./other-lock.json');
106
+ const common = set.intersection(other); // packages in both
107
+ const added = other.difference(set); // packages only in other
108
+ const all = set.union(other); // packages in either
109
+
110
+ // Predicates
111
+ set.isSubsetOf(other); // true if all packages in set are in other
112
+ set.isSupersetOf(other); // true if set contains all packages in other
113
+ set.isDisjointFrom(other); // true if no packages in common
114
+
115
+ // Iterate like a Set
116
+ for (const dep of set) {
117
+ console.log(dep.name, dep.version);
118
+ }
119
+ ```
120
+
121
+ ### Workspace-Specific SBOMs
122
+
123
+ For monorepos, use `dependenciesOf()` to get only the dependencies of a specific workspace:
124
+
125
+ ```javascript
126
+ import { readFile } from 'node:fs/promises';
127
+ import { FlatlockSet } from 'flatlock';
128
+
129
+ const lockfile = await FlatlockSet.fromPath('./package-lock.json');
130
+ const pkg = JSON.parse(await readFile('./packages/api/package.json', 'utf8'));
131
+
132
+ // Get only dependencies reachable from this workspace
133
+ const subset = lockfile.dependenciesOf(pkg, {
134
+ workspacePath: 'packages/api', // for correct resolution in monorepos
135
+ dev: false, // exclude devDependencies
136
+ optional: true, // include optionalDependencies
137
+ peer: false // exclude peerDependencies
138
+ });
139
+
140
+ console.log(`${pkg.name} has ${subset.size} production dependencies`);
141
+ ```
142
+
143
+ **Note:** Sets created via `union()`, `intersection()`, or `difference()` cannot use `dependenciesOf()` because they lack the raw lockfile data needed for traversal. Check `set.canTraverse` before calling.
144
+
92
145
  ## License
93
146
 
94
147
  Apache-2.0
@@ -5,18 +5,6 @@ import { join } from 'node:path';
5
5
  import { parseArgs } from 'node:util';
6
6
  import * as flatlock from '../src/index.js';
7
7
 
8
- /**
9
- * Get comparison parser name for type
10
- */
11
- function getComparisonName(type) {
12
- switch (type) {
13
- case 'npm': return '@npmcli/arborist';
14
- case 'yarn-classic': return '@yarnpkg/lockfile';
15
- case 'yarn-berry': return '@yarnpkg/parsers';
16
- case 'pnpm': return 'js-yaml';
17
- default: return 'unknown';
18
- }
19
- }
20
8
 
21
9
  /**
22
10
  * Convert glob pattern to regex
@@ -59,18 +47,17 @@ async function processFile(filepath, baseDir) {
59
47
  try {
60
48
  const result = await flatlock.compare(filepath);
61
49
  const rel = baseDir ? filepath.replace(baseDir + '/', '') : filepath;
62
- const comparisonName = getComparisonName(result.type);
63
50
 
64
- if (result.identical === null) {
51
+ if (result.equinumerous === null) {
65
52
  // Unsupported type or no comparison available
66
53
  return {
67
54
  type: result.type,
68
55
  path: rel,
69
- comparisonName,
56
+ source: result.source || 'unknown',
70
57
  flatlockCount: result.flatlockCount,
71
58
  comparisonCount: null,
72
59
  workspaceCount: 0,
73
- identical: null,
60
+ equinumerous: null,
74
61
  onlyInFlatlock: null,
75
62
  onlyInComparison: null
76
63
  };
@@ -79,11 +66,11 @@ async function processFile(filepath, baseDir) {
79
66
  return {
80
67
  type: result.type,
81
68
  path: rel,
82
- comparisonName,
69
+ source: result.source,
83
70
  flatlockCount: result.flatlockCount,
84
71
  comparisonCount: result.comparisonCount,
85
72
  workspaceCount: result.workspaceCount,
86
- identical: result.identical,
73
+ equinumerous: result.equinumerous,
87
74
  onlyInFlatlock: result.onlyInFlatlock,
88
75
  onlyInComparison: result.onlyInComparison
89
76
  };
@@ -118,10 +105,21 @@ Options:
118
105
  -h, --help Show this help
119
106
 
120
107
  Comparison parsers (workspace/link entries excluded from all):
121
- npm: @npmcli/arborist (loadVirtual)
108
+ npm: @npmcli/arborist (preferred) or @cyclonedx/cyclonedx-npm
122
109
  yarn-classic: @yarnpkg/lockfile
123
110
  yarn-berry: @yarnpkg/parsers
124
- pnpm: js-yaml
111
+ pnpm: @pnpm/lockfile.fs (preferred) or js-yaml
112
+
113
+ Result types:
114
+ ✓ equinumerous Same packages in both (exact match)
115
+ ⊃ SUPERSET flatlock found MORE packages (expected for pnpm)
116
+ ❌ MISMATCH Unexpected difference (comparison found packages flatlock missed)
117
+
118
+ Note on pnpm supersets:
119
+ flatlock performs full reachability analysis on lockfiles, finding all
120
+ transitive dependencies. pnpm's official tools don't enumerate all reachable
121
+ packages - they omit some transitive deps from their API output. When flatlock
122
+ finds MORE packages than pnpm, this is expected and correct behavior.
125
123
 
126
124
  Examples:
127
125
  flatlock-cmp package-lock.json
@@ -153,6 +151,7 @@ Examples:
153
151
  let fileCount = 0;
154
152
  let errorCount = 0;
155
153
  let matchCount = 0;
154
+ let supersetCount = 0; // flatlock found more (expected for pnpm reachability)
156
155
  let mismatchCount = 0;
157
156
 
158
157
  for (const file of files) {
@@ -175,44 +174,61 @@ Examples:
175
174
  if (!values.quiet) {
176
175
  console.log(`\n⚠️ ${result.path}`);
177
176
  console.log(` flatlock: ${result.flatlockCount} packages`);
178
- console.log(` ${result.comparisonName}: unavailable`);
177
+ console.log(` ${result.source}: unavailable`);
179
178
  }
180
179
  continue;
181
180
  }
182
181
 
183
182
  totalComparison += result.comparisonCount;
184
183
 
185
- if (result.identical) {
184
+ if (result.equinumerous) {
186
185
  matchCount++;
187
186
  if (!values.quiet) {
188
187
  const wsNote = result.workspaceCount > 0 ? ` (${result.workspaceCount} workspaces excluded)` : '';
189
188
  console.log(`✓ ${result.path}${wsNote}`);
190
- console.log(` count: flatlock=${result.flatlockCount} ${result.comparisonName}=${result.comparisonCount}`);
191
- console.log(` sets: identical`);
189
+ console.log(` count: flatlock=${result.flatlockCount} ${result.source}=${result.comparisonCount}`);
190
+ console.log(` sets: equinumerous`);
192
191
  }
193
192
  } else {
194
- mismatchCount++;
195
- console.log(`\n❌ ${result.path}`);
196
- console.log(` count: flatlock=${result.flatlockCount} ${result.comparisonName}=${result.comparisonCount}`);
197
- console.log(` sets: MISMATCH`);
198
-
199
- if (result.onlyInFlatlock.length > 0) {
200
- console.log(` only in flatlock (${result.onlyInFlatlock.length}):`);
201
- for (const pkg of result.onlyInFlatlock.slice(0, 10)) {
202
- console.log(` + ${pkg}`);
203
- }
204
- if (result.onlyInFlatlock.length > 10) {
205
- console.log(` ... and ${result.onlyInFlatlock.length - 10} more`);
193
+ // Determine if this is a "superset" (flatlock found more, expected for pnpm)
194
+ // or a true "mismatch" (comparison found packages flatlock missed)
195
+ const isPnpm = result.type === 'pnpm' || result.path.includes('pnpm-lock');
196
+ const isSuperset = result.onlyInFlatlock.length > 0 && result.onlyInComparison.length === 0;
197
+
198
+ if (isPnpm && isSuperset) {
199
+ // Expected behavior: flatlock's reachability analysis found more packages
200
+ supersetCount++;
201
+ if (!values.quiet) {
202
+ const wsNote = result.workspaceCount > 0 ? ` (${result.workspaceCount} workspaces excluded)` : '';
203
+ console.log(`⊃ ${result.path}${wsNote}`);
204
+ console.log(` count: flatlock=${result.flatlockCount} ${result.source}=${result.comparisonCount}`);
205
+ console.log(` sets: SUPERSET (+${result.onlyInFlatlock.length} reachable deps)`);
206
+ console.log(` note: flatlock's reachability analysis found transitive deps pnpm omits`);
206
207
  }
207
- }
208
+ } else {
209
+ mismatchCount++;
210
+ console.log(`\n❌ ${result.path}`);
211
+ console.log(` count: flatlock=${result.flatlockCount} ${result.source}=${result.comparisonCount}`);
212
+ console.log(` sets: MISMATCH`);
208
213
 
209
- if (result.onlyInComparison.length > 0) {
210
- console.log(` only in ${result.comparisonName} (${result.onlyInComparison.length}):`);
211
- for (const pkg of result.onlyInComparison.slice(0, 10)) {
212
- console.log(` - ${pkg}`);
214
+ if (result.onlyInFlatlock.length > 0) {
215
+ console.log(` only in flatlock (${result.onlyInFlatlock.length}):`);
216
+ for (const pkg of result.onlyInFlatlock.slice(0, 10)) {
217
+ console.log(` + ${pkg}`);
218
+ }
219
+ if (result.onlyInFlatlock.length > 10) {
220
+ console.log(` ... and ${result.onlyInFlatlock.length - 10} more`);
221
+ }
213
222
  }
214
- if (result.onlyInComparison.length > 10) {
215
- console.log(` ... and ${result.onlyInComparison.length - 10} more`);
223
+
224
+ if (result.onlyInComparison.length > 0) {
225
+ console.log(` only in ${result.source} (${result.onlyInComparison.length}):`);
226
+ for (const pkg of result.onlyInComparison.slice(0, 10)) {
227
+ console.log(` - ${pkg}`);
228
+ }
229
+ if (result.onlyInComparison.length > 10) {
230
+ console.log(` ... and ${result.onlyInComparison.length - 10} more`);
231
+ }
216
232
  }
217
233
  }
218
234
  }
@@ -220,7 +236,13 @@ Examples:
220
236
 
221
237
  // Summary
222
238
  console.log('\n' + '='.repeat(70));
223
- console.log(`SUMMARY: ${fileCount} files, ${matchCount} identical, ${mismatchCount} mismatches, ${errorCount} errors`);
239
+ const summaryParts = [`${fileCount} files`, `${matchCount} equinumerous`];
240
+ if (supersetCount > 0) {
241
+ summaryParts.push(`${supersetCount} supersets`);
242
+ }
243
+ summaryParts.push(`${mismatchCount} mismatches`, `${errorCount} errors`);
244
+ console.log(`SUMMARY: ${summaryParts.join(', ')}`);
245
+
224
246
  console.log(` flatlock total: ${totalFlatlock.toString().padStart(8)} packages`);
225
247
  if (totalComparison > 0) {
226
248
  console.log(` comparison total: ${totalComparison.toString().padStart(8)} packages`);
@@ -228,8 +250,12 @@ Examples:
228
250
  if (totalWorkspaces > 0) {
229
251
  console.log(` workspaces: ${totalWorkspaces.toString().padStart(8)} excluded (local/workspace refs)`);
230
252
  }
253
+ if (supersetCount > 0) {
254
+ console.log(` supersets: ${supersetCount.toString().padStart(8)} (flatlock found more via reachability)`);
255
+ }
231
256
 
232
- // Exit with error if any mismatches
257
+ // Exit with error only for true mismatches (not supersets)
258
+ // Supersets are expected: flatlock's reachability analysis is more thorough
233
259
  if (mismatchCount > 0) {
234
260
  process.exit(1);
235
261
  }
package/dist/compare.d.ts CHANGED
@@ -14,11 +14,25 @@ export function compare(filepath: string, options?: CompareOptions): Promise<Com
14
14
  export function compareAll(filepaths: string[], options?: CompareOptions): AsyncGenerator<ComparisonResult & {
15
15
  filepath: string;
16
16
  }>;
17
+ /**
18
+ * Check which optional comparison parsers are available
19
+ * @returns {Promise<{ arborist: boolean, cyclonedx: boolean, pnpmLockfileFs: boolean, yarnCore: boolean }>}
20
+ */
21
+ export function getAvailableParsers(): Promise<{
22
+ arborist: boolean;
23
+ cyclonedx: boolean;
24
+ pnpmLockfileFs: boolean;
25
+ yarnCore: boolean;
26
+ }>;
17
27
  export type CompareOptions = {
18
28
  /**
19
- * - Temp directory for Arborist (npm only)
29
+ * - Temp directory for Arborist/CycloneDX (npm only)
20
30
  */
21
31
  tmpDir?: string;
32
+ /**
33
+ * - Workspace paths for CycloneDX (-w flag)
34
+ */
35
+ workspace?: string[];
22
36
  };
23
37
  export type ComparisonResult = {
24
38
  /**
@@ -26,9 +40,13 @@ export type ComparisonResult = {
26
40
  */
27
41
  type: string;
28
42
  /**
29
- * - Whether flatlock matches comparison parser
43
+ * - Comparison source used (e.g., '@npmcli/arborist', '@cyclonedx/cyclonedx-npm')
30
44
  */
31
- identical: boolean | null;
45
+ source?: string;
46
+ /**
47
+ * - Whether flatlock and comparison have same cardinality
48
+ */
49
+ equinumerous: boolean | null;
32
50
  /**
33
51
  * - Number of packages found by flatlock
34
52
  */
@@ -59,5 +77,9 @@ export type PackagesResult = {
59
77
  * - Number of workspace packages skipped
60
78
  */
61
79
  workspaceCount: number;
80
+ /**
81
+ * - Comparison source used
82
+ */
83
+ source: string;
62
84
  };
63
85
  //# sourceMappingURL=compare.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../src/compare.js"],"names":[],"mappings":"AAyMA;;;;;GAKG;AACH,kCAJW,MAAM,YACN,cAAc,GACZ,OAAO,CAAC,gBAAgB,CAAC,CA6CrC;AAED;;;;;GAKG;AACH,sCAJW,MAAM,EAAE,YACR,cAAc,GACZ,cAAc,CAAC,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAMnE;;;;;aAzPa,MAAM;;;;;;UAKN,MAAM;;;;eACN,OAAO,GAAG,IAAI;;;;mBACd,MAAM;;;;sBACN,MAAM;;;;qBACN,MAAM;;;;qBACN,MAAM,EAAE;;;;uBACR,MAAM,EAAE;;;;;;cAKR,GAAG,CAAC,MAAM,CAAC;;;;oBACX,MAAM"}
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../src/compare.js"],"names":[],"mappings":"AAyhBA;;;;;GAKG;AACH,kCAJW,MAAM,YACN,cAAc,GACZ,OAAO,CAAC,gBAAgB,CAAC,CA8CrC;AAED;;;;;GAKG;AACH,sCAJW,MAAM,EAAE,YACR,cAAc,GACZ,cAAc,CAAC,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAMnE;AAED;;;GAGG;AACH,uCAFa,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAgB1G;;;;;aAnhBa,MAAM;;;;gBACN,MAAM,EAAE;;;;;;UAKR,MAAM;;;;aACN,MAAM;;;;kBACN,OAAO,GAAG,IAAI;;;;mBACd,MAAM;;;;sBACN,MAAM;;;;qBACN,MAAM;;;;qBACN,MAAM,EAAE;;;;uBACR,MAAM,EAAE;;;;;;cAKR,GAAG,CAAC,MAAM,CAAC;;;;oBACX,MAAM;;;;YACN,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.js"],"names":[],"mappings":"AA8FA;;;;;;;;;;;;;;GAcG;AACH,+CALG;IAAyB,IAAI;IACJ,OAAO;CAChC,GAAU,YAAY,CAgDxB;AAtJD;;GAEG;AAEH;;GAEG;AACH;;;;;GAKG;2BAXU,KAAK,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.js"],"names":[],"mappings":"AA6FA;;;;;;;;;;;;;;GAcG;AACH,+CALG;IAAyB,IAAI;IACJ,OAAO;CAChC,GAAU,YAAY,CAgDxB;AArJD;;GAEG;AAEH;;GAEG;AACH;;;;;GAKG;2BAXU,KAAK,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY"}
package/dist/index.d.ts CHANGED
@@ -65,6 +65,8 @@ import { fromPackageLock } from './parsers/index.js';
65
65
  import { fromPnpmLock } from './parsers/index.js';
66
66
  import { fromYarnClassicLock } from './parsers/index.js';
67
67
  import { fromYarnBerryLock } from './parsers/index.js';
68
- export { Type, detectType, Ok, Err, fromPackageLock, fromPnpmLock, fromYarnClassicLock, fromYarnBerryLock };
68
+ import { FlatlockSet } from './set.js';
69
+ export { Type, detectType, Ok, Err, fromPackageLock, fromPnpmLock, fromYarnClassicLock, fromYarnBerryLock, FlatlockSet };
70
+ export { compare, compareAll, getAvailableParsers } from "./compare.js";
69
71
  export { parseNpmKey, parsePnpmKey, parseYarnBerryKey, parseYarnClassicKey } from "./parsers/index.js";
70
72
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA4BA;;;;;GAKG;AACH,+BAJW,MAAM,YACN,MAAM,GACJ,cAAc,CAAC,UAAU,CAAC,CAOtC;AAED;;;;;;;GAOG;AACH,oCANW,MAAM,YAEd;IAAyB,IAAI;IACE,IAAI;CACnC,GAAU,SAAS,CAAC,UAAU,CAAC,CA0BjC;AAED;;;;;GAKG;AACH,kCAJW,MAAM,YACN,YAAY,GACV,OAAO,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAS7E;AAED;;;;;GAKG;AACH,uCAJW,MAAM,YACN,YAAY,GACV,OAAO,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAW/D;AAED;;;;;GAKG;AACH,sCAJW,MAAM,YACN,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CAUjC;AAED;;;;;GAKG;AACH,uCAJW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,UAAU,EAAE,CAAC,CAmBjC;2BAxIa,OAAO,aAAa,EAAE,YAAY;yBAClC,OAAO,kBAAkB,EAAE,UAAU;;;;;WAIrC,MAAM;;;;WACN,YAAY;;qBAfO,aAAa;2BAAb,aAAa;mBAOtB,aAAa;oBAAb,aAAa;gCAD9B,oBAAoB;6BAApB,oBAAoB;oCAApB,oBAAoB;kCAApB,oBAAoB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAgCA;;;;;GAKG;AACH,+BAJW,MAAM,YACN,MAAM,GACJ,cAAc,CAAC,UAAU,CAAC,CAOtC;AAED;;;;;;;GAOG;AACH,oCANW,MAAM,YAEd;IAAyB,IAAI;IACE,IAAI;CACnC,GAAU,SAAS,CAAC,UAAU,CAAC,CA0BjC;AAED;;;;;GAKG;AACH,kCAJW,MAAM,YACN,YAAY,GACV,OAAO,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAS7E;AAED;;;;;GAKG;AACH,uCAJW,MAAM,YACN,YAAY,GACV,OAAO,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAW/D;AAED;;;;;GAKG;AACH,sCAJW,MAAM,YACN,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CAUjC;AAED;;;;;GAKG;AACH,uCAJW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,UAAU,EAAE,CAAC,CAmBjC;2BA3Ia,OAAO,aAAa,EAAE,YAAY;yBAClC,OAAO,kBAAkB,EAAE,UAAU;;;;;WAIrC,MAAM;;;;WACN,YAAY;;qBAhBO,aAAa;2BAAb,aAAa;mBAOtB,aAAa;oBAAb,aAAa;gCAD9B,oBAAoB;6BAApB,oBAAoB;oCAApB,oBAAoB;kCAApB,oBAAoB;4BAEC,UAAU"}
@@ -1,5 +1,5 @@
1
1
  export { fromPackageLock, parseLockfileKey as parseNpmKey } from "./npm.js";
2
2
  export { fromPnpmLock, parseLockfileKey as parsePnpmKey } from "./pnpm.js";
3
- export { fromYarnBerryLock, parseLockfileKey as parseYarnBerryKey } from "./yarn-berry.js";
4
- export { fromYarnClassicLock, parseLockfileKey as parseYarnClassicKey } from "./yarn-classic.js";
3
+ export { fromYarnBerryLock, parseLockfileKey as parseYarnBerryKey, parseResolution as parseYarnBerryResolution } from "./yarn-berry.js";
4
+ export { fromYarnClassicLock, parseLockfileKey as parseYarnClassicKey, parseYarnClassic } from "./yarn-classic.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,11 +1,4 @@
1
- /**
2
- * @typedef {Object} Dependency
3
- * @property {string} name - Package name
4
- * @property {string} version - Resolved version
5
- * @property {string} [integrity] - Integrity hash
6
- * @property {string} [resolved] - Resolution URL
7
- * @property {boolean} [link] - True if this is a symlink
8
- */
1
+ /** @typedef {import('./types.js').Dependency} Dependency */
9
2
  /**
10
3
  * LIMITATION: Workspace symlinks are not yielded
11
4
  *
@@ -41,42 +34,76 @@
41
34
  * pkg := name (unscoped)
42
35
  * | @scope/name (scoped)
43
36
  *
44
- * Examples:
45
- * - node_modules/lodash → "lodash"
46
- * - node_modules/@babel/core → "@babel/core"
47
- * - node_modules/foo/node_modules/@scope/bar → "@scope/bar"
48
- *
49
37
  * @param {string} path - Lockfile path key
50
38
  * @returns {string} Package name
39
+ *
40
+ * @example
41
+ * // Simple unscoped package
42
+ * parseLockfileKey('node_modules/lodash')
43
+ * // => 'lodash'
44
+ *
45
+ * @example
46
+ * // Scoped package
47
+ * parseLockfileKey('node_modules/@babel/core')
48
+ * // => '@babel/core'
49
+ *
50
+ * @example
51
+ * // Nested dependency (hoisted conflict resolution)
52
+ * parseLockfileKey('node_modules/foo/node_modules/bar')
53
+ * // => 'bar'
54
+ *
55
+ * @example
56
+ * // Nested scoped dependency
57
+ * parseLockfileKey('node_modules/foo/node_modules/@scope/bar')
58
+ * // => '@scope/bar'
59
+ *
60
+ * @example
61
+ * // Deeply nested dependency
62
+ * parseLockfileKey('node_modules/a/node_modules/b/node_modules/c')
63
+ * // => 'c'
64
+ *
65
+ * @example
66
+ * // Deeply nested scoped dependency
67
+ * parseLockfileKey('node_modules/a/node_modules/@types/node')
68
+ * // => '@types/node'
69
+ *
70
+ * @example
71
+ * // Workspace package path (definition)
72
+ * parseLockfileKey('packages/my-lib')
73
+ * // => 'my-lib'
74
+ *
75
+ * @example
76
+ * // Workspace nested dependency
77
+ * parseLockfileKey('packages/my-lib/node_modules/lodash')
78
+ * // => 'lodash'
79
+ *
80
+ * @example
81
+ * // Workspace nested scoped dependency
82
+ * parseLockfileKey('packages/my-lib/node_modules/@types/react')
83
+ * // => '@types/react'
84
+ *
85
+ * @example
86
+ * // Package with hyphenated name
87
+ * parseLockfileKey('node_modules/string-width')
88
+ * // => 'string-width'
89
+ *
90
+ * @example
91
+ * // Scoped package with hyphenated name
92
+ * parseLockfileKey('node_modules/@emotion/styled')
93
+ * // => '@emotion/styled'
94
+ *
95
+ * @example
96
+ * // Complex nested path
97
+ * parseLockfileKey('node_modules/@babel/core/node_modules/@babel/helper-compilation-targets')
98
+ * // => '@babel/helper-compilation-targets'
51
99
  */
52
100
  export function parseLockfileKey(path: string): string;
53
101
  /**
54
102
  * Parse npm package-lock.json (v1, v2, v3)
55
- * @param {string} content - Lockfile content
103
+ * @param {string | object} input - Lockfile content string or pre-parsed object
56
104
  * @param {Object} [_options] - Parser options (unused, reserved for future use)
57
105
  * @returns {Generator<Dependency>}
58
106
  */
59
- export function fromPackageLock(content: string, _options?: Object): Generator<Dependency>;
60
- export type Dependency = {
61
- /**
62
- * - Package name
63
- */
64
- name: string;
65
- /**
66
- * - Resolved version
67
- */
68
- version: string;
69
- /**
70
- * - Integrity hash
71
- */
72
- integrity?: string;
73
- /**
74
- * - Resolution URL
75
- */
76
- resolved?: string;
77
- /**
78
- * - True if this is a symlink
79
- */
80
- link?: boolean;
81
- };
107
+ export function fromPackageLock(input: string | object, _options?: Object): Generator<Dependency>;
108
+ export type Dependency = import("./types.js").Dependency;
82
109
  //# sourceMappingURL=npm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../src/parsers/npm.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,uCAHW,MAAM,GACJ,MAAM,CAQlB;AAED;;;;;GAKG;AACH,yCAJW,MAAM,aACN,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CA6BjC;;;;;UA5Fa,MAAM;;;;aACN,MAAM;;;;gBACN,MAAM;;;;eACN,MAAM;;;;WACN,OAAO"}
1
+ {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../src/parsers/npm.js"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,uCA/DW,MAAM,GACJ,MAAM,CAoElB;AAED;;;;;GAKG;AACH,uCAJW,MAAM,GAAG,MAAM,aACf,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CA6BjC;yBA9Ia,OAAO,YAAY,EAAE,UAAU"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @fileoverview Version detection for pnpm lockfiles
3
+ *
4
+ * Detects the era and version of pnpm lockfiles including:
5
+ * - shrinkwrap.yaml (v3/v4) from 2016-2019
6
+ * - pnpm-lock.yaml v5.x (2019-2022)
7
+ * - pnpm-lock.yaml v6.0 (2023)
8
+ * - pnpm-lock.yaml v9.0 (2024+)
9
+ *
10
+ * @module flatlock/parsers/pnpm/detect
11
+ */
12
+ /**
13
+ * @typedef {'shrinkwrap'|'v5'|'v5-inline'|'v6'|'v9'|'unknown'} LockfileEra
14
+ */
15
+ /**
16
+ * @typedef {Object} DetectedVersion
17
+ * @property {LockfileEra} era - The lockfile era/format family
18
+ * @property {string|number} version - The raw version from the lockfile
19
+ * @property {boolean} isShrinkwrap - True if this is a shrinkwrap.yaml file
20
+ */
21
+ /**
22
+ * Detect the version and era of a pnpm lockfile.
23
+ *
24
+ * Version detection rules:
25
+ * - If `shrinkwrapVersion` exists: shrinkwrap era (v3/v4)
26
+ * - If `lockfileVersion` is a number: v5 era
27
+ * - If `lockfileVersion` is '5.4-inlineSpecifiers': v5-inline era
28
+ * - If `lockfileVersion` starts with '6': v6 era
29
+ * - If `lockfileVersion` starts with '9': v9 era
30
+ *
31
+ * @param {Object} lockfile - Parsed pnpm lockfile object
32
+ * @param {string|number} [lockfile.lockfileVersion] - The lockfile version field
33
+ * @param {number} [lockfile.shrinkwrapVersion] - The shrinkwrap version field (v3/v4)
34
+ * @param {number} [lockfile.shrinkwrapMinorVersion] - Minor version for shrinkwrap
35
+ * @returns {DetectedVersion} The detected version information
36
+ *
37
+ * @example
38
+ * // shrinkwrap.yaml v3
39
+ * detectVersion({ shrinkwrapVersion: 3 })
40
+ * // => { era: 'shrinkwrap', version: 3, isShrinkwrap: true }
41
+ *
42
+ * @example
43
+ * // pnpm-lock.yaml v5.4
44
+ * detectVersion({ lockfileVersion: 5.4 })
45
+ * // => { era: 'v5', version: 5.4, isShrinkwrap: false }
46
+ *
47
+ * @example
48
+ * // pnpm-lock.yaml v6.0
49
+ * detectVersion({ lockfileVersion: '6.0' })
50
+ * // => { era: 'v6', version: '6.0', isShrinkwrap: false }
51
+ *
52
+ * @example
53
+ * // pnpm-lock.yaml v9.0
54
+ * detectVersion({ lockfileVersion: '9.0' })
55
+ * // => { era: 'v9', version: '9.0', isShrinkwrap: false }
56
+ */
57
+ export function detectVersion(lockfile: {
58
+ lockfileVersion?: string | number | undefined;
59
+ shrinkwrapVersion?: number | undefined;
60
+ shrinkwrapMinorVersion?: number | undefined;
61
+ }): DetectedVersion;
62
+ /**
63
+ * Check if a lockfile uses the v6+ package key format (name@version).
64
+ *
65
+ * v5 and earlier use: /name/version or /@scope/name/version
66
+ * v6+ use: /name@version or /@scope/name@version
67
+ * v9+ use: name@version (no leading slash)
68
+ *
69
+ * @param {DetectedVersion} detected - The detected version info
70
+ * @returns {boolean} True if the lockfile uses @ separator for name@version
71
+ *
72
+ * @example
73
+ * usesAtSeparator({ era: 'v5', version: 5.4 }) // => false
74
+ * usesAtSeparator({ era: 'v6', version: '6.0' }) // => true
75
+ * usesAtSeparator({ era: 'v9', version: '9.0' }) // => true
76
+ */
77
+ export function usesAtSeparator(detected: DetectedVersion): boolean;
78
+ /**
79
+ * Check if a lockfile uses the packages/snapshots split (v9+).
80
+ *
81
+ * v9 separates package metadata (packages) from dependency relationships (snapshots).
82
+ *
83
+ * @param {DetectedVersion} detected - The detected version info
84
+ * @returns {boolean} True if the lockfile has packages/snapshots split
85
+ *
86
+ * @example
87
+ * usesSnapshotsSplit({ era: 'v6', version: '6.0' }) // => false
88
+ * usesSnapshotsSplit({ era: 'v9', version: '9.0' }) // => true
89
+ */
90
+ export function usesSnapshotsSplit(detected: DetectedVersion): boolean;
91
+ /**
92
+ * Check if a lockfile uses inline specifiers.
93
+ *
94
+ * v5.4-inlineSpecifiers and v6+ use inline specifiers in importers.
95
+ * Earlier versions have a separate `specifiers` block.
96
+ *
97
+ * @param {DetectedVersion} detected - The detected version info
98
+ * @returns {boolean} True if specifiers are inlined
99
+ *
100
+ * @example
101
+ * usesInlineSpecifiers({ era: 'v5', version: 5.4 }) // => false
102
+ * usesInlineSpecifiers({ era: 'v5-inline', version: '5.4-inlineSpecifiers' }) // => true
103
+ * usesInlineSpecifiers({ era: 'v6', version: '6.0' }) // => true
104
+ */
105
+ export function usesInlineSpecifiers(detected: DetectedVersion): boolean;
106
+ /**
107
+ * Check if package keys have a leading slash.
108
+ *
109
+ * v5 and v6 use leading slash: /name/version or /name@version
110
+ * v9 omits leading slash: name@version
111
+ *
112
+ * @param {DetectedVersion} detected - The detected version info
113
+ * @returns {boolean} True if package keys have leading slash
114
+ *
115
+ * @example
116
+ * hasLeadingSlash({ era: 'v5', version: 5.4 }) // => true
117
+ * hasLeadingSlash({ era: 'v6', version: '6.0' }) // => true
118
+ * hasLeadingSlash({ era: 'v9', version: '9.0' }) // => false
119
+ */
120
+ export function hasLeadingSlash(detected: DetectedVersion): boolean;
121
+ export type LockfileEra = "shrinkwrap" | "v5" | "v5-inline" | "v6" | "v9" | "unknown";
122
+ export type DetectedVersion = {
123
+ /**
124
+ * - The lockfile era/format family
125
+ */
126
+ era: LockfileEra;
127
+ /**
128
+ * - The raw version from the lockfile
129
+ */
130
+ version: string | number;
131
+ /**
132
+ * - True if this is a shrinkwrap.yaml file
133
+ */
134
+ isShrinkwrap: boolean;
135
+ };
136
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/parsers/pnpm/detect.js"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;GAEG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wCAzBG;IAAiC,eAAe;IACtB,iBAAiB;IACjB,sBAAsB;CAChD,GAAU,eAAe,CAyF3B;AAED;;;;;;;;;;;;;;GAcG;AACH,0CARW,eAAe,GACb,OAAO,CASnB;AAED;;;;;;;;;;;GAWG;AACH,6CAPW,eAAe,GACb,OAAO,CAQnB;AAED;;;;;;;;;;;;;GAaG;AACH,+CARW,eAAe,GACb,OAAO,CASnB;AAED;;;;;;;;;;;;;GAaG;AACH,0CARW,eAAe,GACb,OAAO,CASnB;0BAxLY,YAAY,GAAC,IAAI,GAAC,WAAW,GAAC,IAAI,GAAC,IAAI,GAAC,SAAS;;;;;SAKhD,WAAW;;;;aACX,MAAM,GAAC,MAAM;;;;kBACb,OAAO"}