flatlock 1.5.0 → 1.5.1

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.
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.js"],"names":[],"mappings":"AAuGA;;;;;;;;;;;;;;GAcG;AACH,+CALG;IAAyB,IAAI;IACJ,OAAO;CAChC,GAAU,YAAY,CAgDxB;AA/JD;;GAEG;AAEH;;GAEG;AACH;;;;;GAKG;2BAXU,KAAK,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY"}
@@ -1,5 +1,5 @@
1
1
  export { buildWorkspacePackages as buildNpmWorkspacePackages, extractWorkspacePaths as extractNpmWorkspacePaths, fromPackageLock, parseLockfileKey as parseNpmKey } from "./npm.js";
2
- export { buildWorkspacePackages as buildPnpmWorkspacePackages, extractWorkspacePaths as extractPnpmWorkspacePaths, fromPnpmLock, parseLockfileKey as parsePnpmKey } from "./pnpm.js";
2
+ export { buildWorkspacePackages as buildPnpmWorkspacePackages, extractWorkspacePaths as extractPnpmWorkspacePaths, fromPnpmLock, parseLockfileKey as parsePnpmKey, parsePnpmYaml } from "./pnpm.js";
3
3
  export { buildWorkspacePackages as buildYarnBerryWorkspacePackages, extractWorkspacePaths as extractYarnBerryWorkspacePaths, fromYarnBerryLock, parseLockfileKey as parseYarnBerryKey, parseResolution as parseYarnBerryResolution } from "./yarn-berry.js";
4
4
  export { fromYarnClassicLock, parseLockfileKey as parseYarnClassicKey, parseYarnClassic } from "./yarn-classic.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -98,6 +98,17 @@ export function parseSpec(spec: string): {
98
98
  * parseLockfileKey('/lodash/4.17.21') // => 'lodash'
99
99
  */
100
100
  export function parseLockfileKey(key: string): string | null;
101
+ /**
102
+ * Parse pnpm YAML content, tolerating truncated files.
103
+ *
104
+ * pnpm lockfiles use inline flow collections like `{integrity: sha512-...}`
105
+ * which cause js-yaml to throw if the file is truncated mid-entry. When that
106
+ * happens, we progressively trim trailing lines until parsing succeeds.
107
+ *
108
+ * @param {string} content - YAML content
109
+ * @returns {Record<string, any>} Parsed lockfile object
110
+ */
111
+ export function parsePnpmYaml(content: string): Record<string, any>;
101
112
  /**
102
113
  * Parse pnpm lockfile (shrinkwrap.yaml, pnpm-lock.yaml v5.x, v6, v9)
103
114
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/parsers/pnpm/index.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,gCAzEW,MAAM,GACJ;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA+H3D;AAED;;;;;;;;;;GAUG;AACH,sCAPW,MAAM,GACJ,MAAM,GAAG,IAAI,CAQzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,oCAbW,MAAM,GAAG,MAAM,aACf,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CAoGjC;AAED;;;;;;;;;;;;GAYG;AACH,6CAPW,MAAM,GAAG,MAAM,GACb,MAAM,EAAE,CAapB;AAED;;;;;;;;;;GAUG;AACH,8CARW,MAAM,GAAG,MAAM,WACf,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CA6BrD;;yBAhVa,OAAO,aAAa,EAAE,UAAU;;UAIhC,MAAM;aACN,MAAM;mBACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;sBACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;2BACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;uBACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/parsers/pnpm/index.js"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AACH,gCAzEW,MAAM,GACJ;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA+H3D;AAED;;;;;;;;;;GAUG;AACH,sCAPW,MAAM,GACJ,MAAM,GAAG,IAAI,CAQzB;AAED;;;;;;;;;GASG;AACH,uCAHW,MAAM,GACJ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAoB/B;AAED;;;;;;;;;;;;;;;GAeG;AACH,oCAbW,MAAM,GAAG,MAAM,aACf,MAAM,GACJ,SAAS,CAAC,UAAU,CAAC,CAoGjC;AAED;;;;;;;;;;;;GAYG;AACH,6CAPW,MAAM,GAAG,MAAM,GACb,MAAM,EAAE,CAapB;AAED;;;;;;;;;;GAUG;AACH,8CARW,MAAM,GAAG,MAAM,WACf,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CA6BrD;;yBA9Wa,OAAO,aAAa,EAAE,UAAU;;UAIhC,MAAM;aACN,MAAM;mBACN,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;sBACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;2BACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;uBACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatlock",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "The Matlock of lockfile parsers - extracts packages without building dependency graphs",
5
5
  "keywords": [
6
6
  "lockfile",
@@ -47,8 +47,11 @@
47
47
  "bin/flatcover.js"
48
48
  ],
49
49
  "scripts": {
50
- "test": "node --test ./test/*.test.js ./test/**/*.test.js",
51
- "test:coverage": "c8 node --test ./test/*.test.js",
50
+ "pretest": "node test/fixtures/decode.js",
51
+ "pretest:coverage": "node test/fixtures/decode.js",
52
+ "test": "node --test ./test/*.test.js ./test/parsers/*.test.js",
53
+ "test:coverage": "c8 node --test ./test/*.test.js ./test/parsers/*.test.js",
54
+ "test:all": "node --test ./test/*.test.js ./test/**/*.test.js",
52
55
  "build:types": "tsc",
53
56
  "lint": "biome lint src test",
54
57
  "lint:fix": "biome lint --write src test",
package/src/detect.js CHANGED
@@ -61,25 +61,35 @@ function tryParseYarnClassic(content) {
61
61
 
62
62
  const result = parseYarnClassic(content);
63
63
  // Must parse successfully and NOT have __metadata (that's berry)
64
- // Must have at least one package entry (not empty object)
65
64
  const isValidResult = result.type === 'success' || result.type === 'merge';
66
- const hasEntries = result.object && Object.keys(result.object).length > 0;
67
- const notBerry = !('__metadata' in result.object);
65
+ const hasObject = result.object && typeof result.object === 'object';
66
+ const notBerry = hasObject && !('__metadata' in result.object);
67
+ // Must have entries OR start with the yarn lockfile header comment
68
+ const hasEntries = hasObject && Object.keys(result.object).length > 0;
69
+ const firstLines = content.split('\n', 5).join('\n');
70
+ const hasYarnHeader = /^# yarn lockfile v1/m.test(firstLines);
68
71
 
69
- return isValidResult && hasEntries && notBerry;
72
+ return isValidResult && hasObject && notBerry && (hasEntries || hasYarnHeader);
70
73
  } catch {
71
74
  return false;
72
75
  }
73
76
  }
74
77
 
75
78
  /**
76
- * Try to parse content as pnpm lockfile
79
+ * Try to parse content as pnpm lockfile.
80
+ *
81
+ * Only parses the YAML header (first 20 lines) to check for lockfileVersion.
82
+ * This avoids failures on truncated lockfiles where the body is incomplete
83
+ * but the header is valid.
84
+ *
77
85
  * @param {string} content
78
86
  * @returns {boolean}
79
87
  */
80
88
  function tryParsePnpm(content) {
81
89
  try {
82
- const parsed = yaml.load(content);
90
+ // Parse only the header to tolerate truncated lockfiles
91
+ const header = content.split('\n', 20).join('\n');
92
+ const parsed = yaml.load(header);
83
93
  // Must have lockfileVersion at root and NOT have __metadata
84
94
  // biome-ignore format: preserve multiline logical expression
85
95
  return !!(parsed
@@ -12,7 +12,8 @@ export {
12
12
  buildWorkspacePackages as buildPnpmWorkspacePackages,
13
13
  extractWorkspacePaths as extractPnpmWorkspacePaths,
14
14
  fromPnpmLock,
15
- parseLockfileKey as parsePnpmKey
15
+ parseLockfileKey as parsePnpmKey,
16
+ parsePnpmYaml
16
17
  } from './pnpm.js';
17
18
  export {
18
19
  buildWorkspacePackages as buildYarnBerryWorkspacePackages,
@@ -193,6 +193,36 @@ export function parseLockfileKey(key) {
193
193
  return parseSpec(key).name;
194
194
  }
195
195
 
196
+ /**
197
+ * Parse pnpm YAML content, tolerating truncated files.
198
+ *
199
+ * pnpm lockfiles use inline flow collections like `{integrity: sha512-...}`
200
+ * which cause js-yaml to throw if the file is truncated mid-entry. When that
201
+ * happens, we progressively trim trailing lines until parsing succeeds.
202
+ *
203
+ * @param {string} content - YAML content
204
+ * @returns {Record<string, any>} Parsed lockfile object
205
+ */
206
+ export function parsePnpmYaml(content) {
207
+ try {
208
+ return /** @type {Record<string, any>} */ (yaml.load(content));
209
+ } catch {
210
+ // Truncated file — trim lines from the end until yaml.load succeeds.
211
+ // Most truncations break an incomplete flow collection near the end,
212
+ // so we only need to trim a handful of lines.
213
+ const lines = content.split('\n');
214
+ for (let trim = 1; trim < Math.min(20, lines.length); trim++) {
215
+ try {
216
+ return /** @type {Record<string, any>} */ (yaml.load(lines.slice(0, -trim).join('\n')));
217
+ } catch {
218
+ // keep trimming
219
+ }
220
+ }
221
+ // If trimming didn't help, re-throw the original error
222
+ throw yaml.load(content);
223
+ }
224
+ }
225
+
196
226
  /**
197
227
  * Parse pnpm lockfile (shrinkwrap.yaml, pnpm-lock.yaml v5.x, v6, v9)
198
228
  *
@@ -211,7 +241,7 @@ export function parseLockfileKey(key) {
211
241
  */
212
242
  export function* fromPnpmLock(input, _options = {}) {
213
243
  const lockfile = /** @type {Record<string, any>} */ (
214
- typeof input === 'string' ? yaml.load(input) : input
244
+ typeof input === 'string' ? parsePnpmYaml(input) : input
215
245
  );
216
246
 
217
247
  // Detect version to determine where to look for packages
@@ -250,7 +280,7 @@ export function* fromPnpmLock(input, _options = {}) {
250
280
  if (seen.has(key)) continue;
251
281
  seen.add(key);
252
282
 
253
- const resolution = pkg.resolution || {};
283
+ const resolution = pkg?.resolution || {};
254
284
  const integrity = resolution.integrity;
255
285
  const resolved = resolution.tarball;
256
286
  const link = spec.startsWith('link:') || resolution.type === 'directory';
@@ -315,7 +345,7 @@ export function* fromPnpmLock(input, _options = {}) {
315
345
  */
316
346
  export function extractWorkspacePaths(input) {
317
347
  const lockfile = /** @type {Record<string, any>} */ (
318
- typeof input === 'string' ? yaml.load(input) : input
348
+ typeof input === 'string' ? parsePnpmYaml(input) : input
319
349
  );
320
350
 
321
351
  const importers = lockfile.importers || {};
package/src/set.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { parseSyml } from '@yarnpkg/parsers';
3
- import yaml from 'js-yaml';
4
3
  import { detectType, Type } from './detect.js';
5
4
  import {
6
5
  buildNpmWorkspacePackages,
@@ -13,6 +12,7 @@ import {
13
12
  fromPnpmLock,
14
13
  fromYarnBerryLock,
15
14
  fromYarnClassicLock,
15
+ parsePnpmYaml,
16
16
  parseYarnBerryKey,
17
17
  parseYarnClassic,
18
18
  parseYarnClassicKey
@@ -195,7 +195,7 @@ export class FlatlockSet {
195
195
  }
196
196
  case Type.PNPM: {
197
197
  /** @type {any} */
198
- const lockfile = yaml.load(content);
198
+ const lockfile = parsePnpmYaml(content);
199
199
  packages = lockfile.packages || {};
200
200
  importers = lockfile.importers || null;
201
201
  snapshots = lockfile.snapshots || null;