@antongolub/lockfile 0.0.0-snapshot.49 → 0.0.0-snapshot.50

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 (46) hide show
  1. package/README.md +66 -46
  2. package/dist/{_npm-flat-types-BtjAgSy6.d.ts → _npm-flat-types-6ABSGUI9.d.ts} +1 -1
  3. package/dist/{_pnpm-flat-core-u9QE3m8R.d.ts → _pnpm-flat-core-CGSknThP.d.ts} +1 -1
  4. package/dist/{_yarn-berry-core-tgctDDH2.d.ts → _yarn-berry-core-E1kzf3tA.d.ts} +1 -1
  5. package/dist/complete.d.ts +2 -2
  6. package/dist/formats/bun-text.d.ts +1 -1
  7. package/dist/formats/bun-text.js +40 -14
  8. package/dist/formats/npm-1.d.ts +1 -1
  9. package/dist/formats/npm-1.js +45 -2
  10. package/dist/formats/npm-2.d.ts +2 -2
  11. package/dist/formats/npm-2.js +45 -15
  12. package/dist/formats/npm-3.d.ts +2 -2
  13. package/dist/formats/npm-3.js +43 -14
  14. package/dist/formats/pnpm-v5.d.ts +1 -1
  15. package/dist/formats/pnpm-v5.js +47 -18
  16. package/dist/formats/pnpm-v6.d.ts +2 -2
  17. package/dist/formats/pnpm-v6.js +43 -14
  18. package/dist/formats/pnpm-v9.d.ts +2 -2
  19. package/dist/formats/pnpm-v9.js +43 -14
  20. package/dist/formats/yarn-berry-v10.d.ts +2 -2
  21. package/dist/formats/yarn-berry-v10.js +42 -102
  22. package/dist/formats/yarn-berry-v4.d.ts +2 -2
  23. package/dist/formats/yarn-berry-v4.js +42 -102
  24. package/dist/formats/yarn-berry-v5.d.ts +2 -2
  25. package/dist/formats/yarn-berry-v5.js +42 -102
  26. package/dist/formats/yarn-berry-v6.d.ts +2 -2
  27. package/dist/formats/yarn-berry-v6.js +42 -102
  28. package/dist/formats/yarn-berry-v7.d.ts +2 -2
  29. package/dist/formats/yarn-berry-v7.js +42 -102
  30. package/dist/formats/yarn-berry-v8.d.ts +2 -2
  31. package/dist/formats/yarn-berry-v8.js +42 -102
  32. package/dist/formats/yarn-berry-v9.d.ts +2 -2
  33. package/dist/formats/yarn-berry-v9.js +42 -102
  34. package/dist/formats/yarn-classic.d.ts +1 -1
  35. package/dist/formats/yarn-classic.js +61 -24
  36. package/dist/{graph-DlYNIpXt.d.ts → graph-DL9MNz81.d.ts} +12 -2
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +134 -141
  39. package/dist/{modify-DJj58soJ.d.ts → modify-CT0MxdsI.d.ts} +2 -2
  40. package/dist/modify.d.ts +3 -3
  41. package/dist/{optimize-eaNonOKo.d.ts → optimize-DVIavJ_L.d.ts} +1 -1
  42. package/dist/optimize.d.ts +2 -2
  43. package/dist/registry.d.ts +3 -3
  44. package/dist/registry.js +35 -2
  45. package/dist/{types-Ci06KZkZ.d.ts → types-BJ6TTw2m.d.ts} +4 -2
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -5,10 +5,9 @@
5
5
  <p><img alt="@antongolub/lockfile — universal lockfile model and converter for npm, yarn, pnpm, bun" src="./pics/lockfile.svg" align="right" width="300">
6
6
 
7
7
  Each package manager brings its own philosophy of how to describe, store and
8
- control project dependencies. It looks acceptable to a developer staring at a
9
- single repo, but it becomes a real headache for IS, DevOps and release
10
- engineers and impossible for any tool that needs to reason about
11
- dependency graphs across the ecosystem.
8
+ control project dependencies. Inside a single repo it's invisible to the
9
+ developer but it becomes a recurring cost for InfoSec, DevOps and release
10
+ engineers, and makes consistent policy unenforceable across the enterprise.
12
11
 
13
12
  This library models the dependency graph independent of any specific
14
13
  package manager, then projects it back into the format you need.
@@ -19,11 +18,23 @@ license filtering) is the headline.
19
18
 
20
19
  ## Status
21
20
 
22
- 🔒 **Contract preview, implementation in progress.** The public API
23
- (`parse` / `stringify`) is locked. Adapter implementations are landing
24
- incrementally see [SCHEMAS.md](./SCHEMAS.md) for what's recognised.
25
- Not yet published; `npm install` will appear once the first adapters
26
- ship end-to-end.
21
+ **Snapshot preview.** Every format below parses and stringifies; conversion is
22
+ *semantically equivalent*, not byte-identical (see [Concept](#concept)).
23
+ Published as `0.0.0-snapshot.*` builds the first stable release is pending.
24
+ [SCHEMAS.md](./SCHEMAS.md) maps each format id to the package-manager versions
25
+ that emit it.
26
+
27
+ | Format | `detect` | `parse` | `stringify` |
28
+ |--------|:-:|:-:|:-:|
29
+ | `npm-1` · `npm-2` · `npm-3` | ✓ | ✓ | ✓ |
30
+ | `yarn-classic` | ✓ | ✓ | ✓ |
31
+ | `yarn-berry-v4` … `yarn-berry-v10`| ✓ | ✓ | ✓ |
32
+ | `pnpm-v5` · `pnpm-v6` · `pnpm-v9` | ✓ | ✓ | ✓ |
33
+ | `bun-text` | ✓ | ✓ | ✓ |
34
+
35
+ Graph-level operations apply to **any** parsed graph, regardless of source
36
+ format: `convert` (parse any → stringify any), `modify` (audit-fix,
37
+ override-pin, license-filter), and `optimize` (orphan GC / dedup).
27
38
 
28
39
  ## Concept
29
40
 
@@ -49,66 +60,74 @@ signatures) are the exception — they are never silently lost.
49
60
  ## API
50
61
 
51
62
  ```ts
52
- import { parse, stringify } from '@antongolub/lockfile'
63
+ import { parse, stringify, convert } from '@antongolub/lockfile'
64
+
65
+ // explicit format (it's always the first argument):
66
+ const graph = parse('pnpm-v9', raw)
67
+ const out = stringify('npm-3', graph)
53
68
 
54
- const lf = parse(rawLockfileBytes)
55
- const str = stringify(lf, { format: 'npm-3' })
69
+ // or one step, auto-detecting the source:
70
+ const npm = convert(raw, { to: 'npm-3' })
56
71
  ```
57
72
 
58
- Two top-level operations, modelled on `JSON.parse` / `JSON.stringify`:
73
+ The format is **explicit**, never implicit — `parse` and `stringify` both take
74
+ it as the first argument; `detect` sniffs it from the bytes when you don't know
75
+ it. Round-tripping is a choice the caller makes, not a default.
59
76
 
60
77
  ```ts
61
- parse(input: string | Uint8Array, options?: ParseOptions): Lockfile
62
-
63
- stringify(lockfile: Lockfile, options: StringifyOptions): string
78
+ detect(input: string): FormatId | undefined
79
+ check(format: FormatId, input: string): boolean
80
+ parse(format: FormatId, input: string, opts?: ParseOptions): Graph
81
+ stringify(format: FormatId, graph: Graph, opts?: StringifyOptions): string
82
+ convert(input: string, opts: ConvertOptions): string // parse(from) → stringify(to)
64
83
  ```
65
84
 
66
- - `parse` auto-detects the format by content sniffing. Pass
67
- `{ format: '<id>' }` to skip detection. Pass `{ manifests }` for
68
- formats that need extra workspace context (notably `yarn-classic`).
69
- - `stringify`'s `options.format` is **required** — there is no implicit
70
- "same as parsed". Round-tripping is an explicit choice the caller makes.
85
+ `Graph` is the canonical, package-manager-independent model; `FormatId` is a
86
+ string-literal union (the [Status](#status) table lists every id, and
87
+ [SCHEMAS.md](./SCHEMAS.md) maps each to the package-manager versions behind it).
88
+
89
+ ### Operating on the graph
90
+
91
+ The graph is where the value lives — `modify` and `optimize` transform it,
92
+ format-agnostically:
71
93
 
72
- `Lockfile` is the public alias for the internal canonical-graph type.
73
- `FormatId` is a string-literal union see
74
- [SCHEMAS.md](./SCHEMAS.md) for the full list.
94
+ - **`modify`** applies a `Primitive[]` `replaceVersion`, `pinOverride`,
95
+ `addDependency`, `removeDependency`, `applyPatch`, `filterLicense`the
96
+ building blocks of audit-fix, override-pinning and license filtering.
97
+ - **`optimize`** runs orphan GC / dedup over the graph.
98
+ - **`overridesOf(graph)`** reads the canonical overrides back out.
75
99
 
76
100
  ### Options
77
101
 
78
102
  ```ts
79
103
  type ParseOptions = {
80
- format?: FormatId // skip auto-detect
81
- manifests?: Manifests // package.jsons keyed by workspace path
82
- pmConfig?: PmConfig // .npmrc / .yarnrc.yml / pnpm-workspace.yaml / bunfig.toml
83
- installDir?: string // path to node_modules / .pnp.cjs (refinement)
84
- cache?: CacheAdapter // PM cache (refinement)
85
- registry?: RegistryAdapter // network access (opt-in)
104
+ workspaceRoot?: string // FS root for out-of-lockfile reads (patch bytes, manifests)
105
+ manifests?: Record<string, Manifest> // package.jsons keyed by workspace path
106
+ onDiagnostic?: (d: Diagnostic) => void
86
107
  }
87
108
 
88
109
  type StringifyOptions = {
89
- format: FormatId // required
90
- manifests?: Manifests
91
- pmConfig?: PmConfig
92
- installDir?: string
93
- cache?: CacheAdapter
94
- registry?: RegistryAdapter
110
+ lineEnding?: 'lf' | 'crlf'
111
+ cacheKey?: string // yarn-berry cache-key prefix
112
+ overrides?: OverrideConstraint[] // canonical overrides → native projection
95
113
  onDiagnostic?: (d: Diagnostic) => void
96
114
  }
97
115
  ```
98
116
 
99
- `pmConfig` / `installDir` / `cache` / `registry` are progressive
100
- **refinement opt-ins**: each unlocks more information at higher cost. The
101
- default succeeds offline, against the lockfile bytes (and `manifests`)
102
- alone.
117
+ `manifests` supplies the workspace/override context the lockfile bytes cannot
118
+ carry on their own (notably for `yarn-classic` monorepos); everything else
119
+ succeeds offline against the bytes alone. Registry- and cache-backed refinement
120
+ ships as opt-in adapters (see [Sub-imports](#sub-imports)).
103
121
 
104
122
  ### Sub-imports
105
123
 
106
124
  | Surface | Importable as | Contains |
107
125
  |---------|---------------|----------|
108
- | Root | `@antongolub/lockfile` | `parse`, `stringify`, plus types: `Lockfile`, `FormatId`, `ParseOptions`, `StringifyOptions`, `Manifest`, `Manifests` |
109
- | Modifiers | `@antongolub/lockfile/modify` | audit-fix, override-pin, license-filter |
110
- | Registry | `@antongolub/lockfile/registry` | adapters for live npm, file cache, frozen-from-lockfile |
111
- | Per-format | `@antongolub/lockfile/formats/<id>` | direct access to a single adapter (test surface; not a primary user API) |
126
+ | Root | `@antongolub/lockfile` | `detect`, `check`, `parse`, `stringify`, `convert`, `modify`, `optimize`, `overridesOf`, plus types `Graph`, `FormatId`, `ParseOptions`, `StringifyOptions`, `ConvertOptions`, `Manifest` |
127
+ | Modifiers | `@antongolub/lockfile/modify` | the individual `Primitive` functions behind `modify` (audit-fix, override-pin, license-filter) |
128
+ | Optimize | `@antongolub/lockfile/optimize` | the individual GC passes behind `optimize` |
129
+ | Registry | `@antongolub/lockfile/registry` | `frozenRegistry`, `liveRegistry`, `fsCache`, `npmCache`, `pnpmCache` |
130
+ | Per-format | `@antongolub/lockfile/formats/<id>` | a single adapter directly (test surface; not a primary user API) |
112
131
 
113
132
  ### Errors
114
133
 
@@ -117,8 +136,9 @@ alone.
117
136
 
118
137
  ```ts
119
138
  'PARSE_FAILED' | 'FORMAT_DETECT_FAILED' | 'FORMAT_MISMATCH'
120
- | 'CAPABILITY_LACK' | 'MISSING_MANIFEST'
121
- | 'IRREDUCIBLE_LOSS' | 'INVARIANT_VIOLATION'
139
+ | 'CAPABILITY_LACK' | 'MISSING_MANIFEST' | 'MISSING_REQUIRED_FIELD'
140
+ | 'INVALID_INPUT' | 'ENRICH_REQUIRED' | 'IRREDUCIBLE_LOSS'
141
+ | 'PIPELINE_DIVERGED' | 'REGISTRY_UNREACHABLE' | 'INVARIANT_VIOLATION'
122
142
  ```
123
143
 
124
144
  Reducible losses (e.g. dropped patches when emitting `npm-1` from a
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic, O as OverrideConstraint } from './graph-DlYNIpXt.js';
1
+ import { D as Diagnostic, O as OverrideConstraint } from './graph-DL9MNz81.js';
2
2
 
3
3
  interface NpmFamilyParseOptions {
4
4
  }
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic, O as OverrideConstraint } from './graph-DlYNIpXt.js';
1
+ import { D as Diagnostic, O as OverrideConstraint } from './graph-DL9MNz81.js';
2
2
 
3
3
  interface PnpmFamilyParseOptions {
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic } from './graph-DlYNIpXt.js';
1
+ import { D as Diagnostic } from './graph-DL9MNz81.js';
2
2
 
3
3
  interface YarnBerryFamilyParseOptions {
4
4
  workspaceRoot?: string;
@@ -1,5 +1,5 @@
1
- import { N as NodeId, D as Diagnostic, G as Graph, E as EdgeTriple, a as Node, b as EdgeKind } from './graph-DlYNIpXt.js';
2
- import { R as RegistryAdapter } from './types-Ci06KZkZ.js';
1
+ import { N as NodeId, D as Diagnostic, G as Graph, E as EdgeTriple, a as Node, b as EdgeKind } from './graph-DL9MNz81.js';
2
+ import { R as RegistryAdapter } from './types-BJ6TTw2m.js';
3
3
 
4
4
  interface CompletionSeed {
5
5
  /** NodeIds the modifier added in the just-completed mutate phase. */
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic, G as Graph } from '../graph-DlYNIpXt.js';
1
+ import { D as Diagnostic, G as Graph } from '../graph-DL9MNz81.js';
2
2
 
3
3
  interface BunTextParseOptions {
4
4
  }
@@ -214,6 +214,7 @@ function validate(s) {
214
214
  const inc = s.incoming.get(id) ?? [];
215
215
  for (const edge of inc) {
216
216
  if (s.nodes.get(edge.src)?.workspacePath !== void 0) continue;
217
+ if (s.tarballs.get(stripPeerContextFromNodeId(edge.src))?.resolution?.type === "directory") continue;
217
218
  if (isPublishedSelfLink(edge)) {
218
219
  s.diagnostics.push({
219
220
  code: "SEAL_PUBLISHED_SELF_LINK",
@@ -634,16 +635,41 @@ function newBuilder() {
634
635
  }
635
636
 
636
637
  // src/main/ts/recipe/integrity.ts
637
- var CANONICAL_RE = /^sha512-[A-Za-z0-9+/]{86}==$/;
638
- function isCanonical(s) {
639
- if (!CANONICAL_RE.test(s)) return false;
640
- const b64 = s.slice("sha512-".length);
641
- const buf = Buffer.from(b64, "base64");
642
- if (buf.length !== 64) return false;
643
- return buf.toString("base64") === b64;
638
+ var SRI_ALGO_BYTES = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
639
+ var SRI_MEMBER_RE = /^([a-z][a-z0-9]*)-([A-Za-z0-9+/]+={0,2})(?:\?.*)?$/;
640
+ function hexToBase64(hex) {
641
+ return Buffer.from(hex, "hex").toString("base64");
642
+ }
643
+ function isEmptyIntegrity(i) {
644
+ return i.hashes.length === 0;
645
+ }
646
+ function isTarballOrigin(origin) {
647
+ return origin !== "berry-zip";
648
+ }
649
+ function tarballHashes(i) {
650
+ return i.hashes.filter((h) => isTarballOrigin(h.origin));
651
+ }
652
+ function parseSri(raw, origin = "sri") {
653
+ const hashes = [];
654
+ for (const member of raw.trim().split(/\s+/)) {
655
+ if (member === "") continue;
656
+ const m = SRI_MEMBER_RE.exec(member);
657
+ if (!m) continue;
658
+ const algorithm = m[1];
659
+ const bytes = Buffer.from(m[2], "base64");
660
+ const expected = SRI_ALGO_BYTES[algorithm];
661
+ if (expected !== void 0) {
662
+ if (bytes.length !== expected) continue;
663
+ } else if (bytes.length < 16) {
664
+ continue;
665
+ }
666
+ hashes.push({ algorithm, digest: bytes.toString("hex"), origin });
667
+ }
668
+ return { hashes };
644
669
  }
645
- function validateCanonical(raw) {
646
- return isCanonical(raw) ? raw : void 0;
670
+ function emitSri(i) {
671
+ const members = tarballHashes(i).map((h) => `${h.algorithm}-${hexToBase64(h.digest)}`);
672
+ return members.length > 0 ? members.join(" ") : void 0;
647
673
  }
648
674
 
649
675
  // src/main/ts/recipe/diagnostics.ts
@@ -652,7 +678,7 @@ function invalidIntegrityDiagnostic(prefix, subject, rawIntegrity) {
652
678
  code: `${prefix}_INVALID_INTEGRITY`,
653
679
  severity: "warning",
654
680
  subject,
655
- message: `integrity ${JSON.stringify(rawIntegrity)} is not a canonical sha512 SRI; dropping`
681
+ message: `integrity ${JSON.stringify(rawIntegrity)} has no parseable hash; dropping`
656
682
  };
657
683
  }
658
684
  function emitDropped(nodeId, feature, reason, onDiagnostic) {
@@ -857,11 +883,11 @@ function parse(input, _options = {}) {
857
883
  peerContext: []
858
884
  });
859
885
  if (integrity !== void 0) {
860
- const canonical = validateCanonical(integrity);
861
- if (canonical === void 0) {
886
+ const parsed2 = parseSri(integrity, "sri");
887
+ if (isEmptyIntegrity(parsed2)) {
862
888
  diagnostics.push(invalidIntegrityDiagnostic("BUN_TEXT", nodeId, integrity));
863
889
  } else {
864
- builder.setTarball({ name, version }, { integrity: canonical });
890
+ builder.setTarball({ name, version }, { integrity: parsed2 });
865
891
  }
866
892
  }
867
893
  }
@@ -962,7 +988,7 @@ function stringify(graph, options = {}) {
962
988
  emittedNameVersion.add(nameVersion);
963
989
  const inner = buildInnerBlock(graph, node, sidecar);
964
990
  const integritySrc = graph.tarballOf(node.id)?.integrity;
965
- const integrity = integritySrc ?? "";
991
+ const integrity = (integritySrc && emitSri(integritySrc)) ?? "";
966
992
  const key = chooseNodeEmitKey(node, sidecar, packagesBlock);
967
993
  packagesBlock[key] = [`${node.name}@${node.version}`, "", inner, integrity];
968
994
  }
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic, G as Graph } from '../graph-DlYNIpXt.js';
1
+ import { D as Diagnostic, G as Graph } from '../graph-DL9MNz81.js';
2
2
 
3
3
  interface Npm1ParseOptions {
4
4
  }
@@ -215,6 +215,7 @@ function validate(s) {
215
215
  const inc = s.incoming.get(id) ?? [];
216
216
  for (const edge of inc) {
217
217
  if (s.nodes.get(edge.src)?.workspacePath !== void 0) continue;
218
+ if (s.tarballs.get(stripPeerContextFromNodeId(edge.src))?.resolution?.type === "directory") continue;
218
219
  if (isPublishedSelfLink(edge)) {
219
220
  s.diagnostics.push({
220
221
  code: "SEAL_PUBLISHED_SELF_LINK",
@@ -634,6 +635,44 @@ function newBuilder() {
634
635
  };
635
636
  }
636
637
 
638
+ // src/main/ts/recipe/integrity.ts
639
+ var SRI_ALGO_BYTES = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
640
+ var SRI_MEMBER_RE = /^([a-z][a-z0-9]*)-([A-Za-z0-9+/]+={0,2})(?:\?.*)?$/;
641
+ function hexToBase64(hex) {
642
+ return Buffer.from(hex, "hex").toString("base64");
643
+ }
644
+ function isEmptyIntegrity(i) {
645
+ return i.hashes.length === 0;
646
+ }
647
+ function isTarballOrigin(origin) {
648
+ return origin !== "berry-zip";
649
+ }
650
+ function tarballHashes(i) {
651
+ return i.hashes.filter((h) => isTarballOrigin(h.origin));
652
+ }
653
+ function parseSri(raw, origin = "sri") {
654
+ const hashes = [];
655
+ for (const member of raw.trim().split(/\s+/)) {
656
+ if (member === "") continue;
657
+ const m = SRI_MEMBER_RE.exec(member);
658
+ if (!m) continue;
659
+ const algorithm = m[1];
660
+ const bytes = Buffer.from(m[2], "base64");
661
+ const expected = SRI_ALGO_BYTES[algorithm];
662
+ if (expected !== void 0) {
663
+ if (bytes.length !== expected) continue;
664
+ } else if (bytes.length < 16) {
665
+ continue;
666
+ }
667
+ hashes.push({ algorithm, digest: bytes.toString("hex"), origin });
668
+ }
669
+ return { hashes };
670
+ }
671
+ function emitSri(i) {
672
+ const members = tarballHashes(i).map((h) => `${h.algorithm}-${hexToBase64(h.digest)}`);
673
+ return members.length > 0 ? members.join(" ") : void 0;
674
+ }
675
+
637
676
  // src/main/ts/formats/_npm-flat-types.ts
638
677
  var cmpStr2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
639
678
  function sortRecord(record) {
@@ -971,7 +1010,10 @@ function parse2(input, _options = {}) {
971
1010
  if (resolved !== void 0) node.resolution = resolved;
972
1011
  builder.addNode(node);
973
1012
  const payload = {};
974
- if (entry.integrity !== void 0) payload.integrity = entry.integrity;
1013
+ if (entry.integrity !== void 0) {
1014
+ const integrity = parseSri(entry.integrity, "sri");
1015
+ if (!isEmptyIntegrity(integrity)) payload.integrity = integrity;
1016
+ }
975
1017
  if (resolved !== void 0) {
976
1018
  const canonical = parse(resolved, { sourceKind: "npm-resolved" });
977
1019
  if (canonical.type === "unknown") {
@@ -1439,7 +1481,8 @@ function buildEntry(node, installPath, graph, sidecar, treeByParent) {
1439
1481
  }
1440
1482
  }
1441
1483
  if (tarball?.integrity !== void 0 && !/^(git[+:]|github:)/.test(entry.version ?? "")) {
1442
- entry.integrity = tarball.integrity;
1484
+ const sri = emitSri(tarball.integrity);
1485
+ if (sri !== void 0) entry.integrity = sri;
1443
1486
  }
1444
1487
  if (nodeSide?.dev === true) entry.dev = true;
1445
1488
  if (nodeSide?.optional === true) entry.optional = true;
@@ -1,5 +1,5 @@
1
- import { G as Graph, D as Diagnostic } from '../graph-DlYNIpXt.js';
2
- import { N as NpmFamilyEnrichOptions, a as NpmFamilyOptimizeOptions, b as NpmFamilyParseOptions, c as NpmFamilyStringifyOptions } from '../_npm-flat-types-BtjAgSy6.js';
1
+ import { G as Graph, D as Diagnostic } from '../graph-DL9MNz81.js';
2
+ import { N as NpmFamilyEnrichOptions, a as NpmFamilyOptimizeOptions, b as NpmFamilyParseOptions, c as NpmFamilyStringifyOptions } from '../_npm-flat-types-6ABSGUI9.js';
3
3
 
4
4
  interface Npm2ParseOptions extends NpmFamilyParseOptions {
5
5
  }
@@ -228,6 +228,7 @@ function validate(s) {
228
228
  const inc = s.incoming.get(id) ?? [];
229
229
  for (const edge of inc) {
230
230
  if (s.nodes.get(edge.src)?.workspacePath !== void 0) continue;
231
+ if (s.tarballs.get(stripPeerContextFromNodeId(edge.src))?.resolution?.type === "directory") continue;
231
232
  if (isPublishedSelfLink(edge)) {
232
233
  s.diagnostics.push({
233
234
  code: "SEAL_PUBLISHED_SELF_LINK",
@@ -648,16 +649,41 @@ function newBuilder() {
648
649
  }
649
650
 
650
651
  // src/main/ts/recipe/integrity.ts
651
- var CANONICAL_RE = /^sha512-[A-Za-z0-9+/]{86}==$/;
652
- function isCanonical(s) {
653
- if (!CANONICAL_RE.test(s)) return false;
654
- const b64 = s.slice("sha512-".length);
655
- const buf = Buffer.from(b64, "base64");
656
- if (buf.length !== 64) return false;
657
- return buf.toString("base64") === b64;
652
+ var SRI_ALGO_BYTES = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
653
+ var SRI_MEMBER_RE = /^([a-z][a-z0-9]*)-([A-Za-z0-9+/]+={0,2})(?:\?.*)?$/;
654
+ function hexToBase64(hex) {
655
+ return Buffer.from(hex, "hex").toString("base64");
656
+ }
657
+ function isEmptyIntegrity(i) {
658
+ return i.hashes.length === 0;
659
+ }
660
+ function isTarballOrigin(origin) {
661
+ return origin !== "berry-zip";
662
+ }
663
+ function tarballHashes(i) {
664
+ return i.hashes.filter((h) => isTarballOrigin(h.origin));
665
+ }
666
+ function parseSri(raw, origin = "sri") {
667
+ const hashes = [];
668
+ for (const member of raw.trim().split(/\s+/)) {
669
+ if (member === "") continue;
670
+ const m = SRI_MEMBER_RE.exec(member);
671
+ if (!m) continue;
672
+ const algorithm = m[1];
673
+ const bytes = Buffer.from(m[2], "base64");
674
+ const expected = SRI_ALGO_BYTES[algorithm];
675
+ if (expected !== void 0) {
676
+ if (bytes.length !== expected) continue;
677
+ } else if (bytes.length < 16) {
678
+ continue;
679
+ }
680
+ hashes.push({ algorithm, digest: bytes.toString("hex"), origin });
681
+ }
682
+ return { hashes };
658
683
  }
659
- function validateCanonical(raw) {
660
- return isCanonical(raw) ? raw : void 0;
684
+ function emitSri(i) {
685
+ const members = tarballHashes(i).map((h) => `${h.algorithm}-${hexToBase64(h.digest)}`);
686
+ return members.length > 0 ? members.join(" ") : void 0;
661
687
  }
662
688
 
663
689
  // src/main/ts/recipe/diagnostics.ts
@@ -666,7 +692,7 @@ function invalidIntegrityDiagnostic(prefix, subject, rawIntegrity) {
666
692
  code: `${prefix}_INVALID_INTEGRITY`,
667
693
  severity: "warning",
668
694
  subject,
669
- message: `integrity ${JSON.stringify(rawIntegrity)} is not a canonical sha512 SRI; dropping`
695
+ message: `integrity ${JSON.stringify(rawIntegrity)} has no parseable hash; dropping`
670
696
  };
671
697
  }
672
698
  function emitDropped(nodeId, feature, reason, onDiagnostic) {
@@ -1616,11 +1642,11 @@ function hasTarballPayload(entry) {
1616
1642
  function tarballPayloadOf(entry, subject, diagnostics) {
1617
1643
  const payload = {};
1618
1644
  if (entry.integrity !== void 0) {
1619
- const canonical = validateCanonical(entry.integrity);
1620
- if (canonical === void 0) {
1645
+ const integrity = parseSri(entry.integrity, "sri");
1646
+ if (isEmptyIntegrity(integrity)) {
1621
1647
  diagnostics.push(invalidIntegrityDiagnostic("NPM", subject, entry.integrity));
1622
1648
  } else {
1623
- payload.integrity = canonical;
1649
+ payload.integrity = integrity;
1624
1650
  }
1625
1651
  }
1626
1652
  if (entry.engines !== void 0) payload.engines = { ...entry.engines };
@@ -1861,7 +1887,10 @@ function buildNodeModulesEntry(graph, node, nodeSide, config, emitDiagnostic = (
1861
1887
  const tarball = graph.tarballOf(node.id);
1862
1888
  const resolved = node.resolution ?? config.hooks?.recoverResolvedForNode?.(graph, node) ?? deriveResolvedFromCanonical(tarball?.resolution);
1863
1889
  if (resolved !== void 0) body.resolved = resolved;
1864
- if (tarball?.integrity !== void 0) body.integrity = tarball.integrity;
1890
+ if (tarball?.integrity !== void 0) {
1891
+ const sri = emitSri(tarball.integrity);
1892
+ if (sri !== void 0) body.integrity = sri;
1893
+ }
1865
1894
  if (nodeSide?.dev === true) body.dev = true;
1866
1895
  if (nodeSide?.optional === true) body.optional = true;
1867
1896
  if (nodeSide?.peer === true) body.peer = true;
@@ -2169,7 +2198,8 @@ function buildLegacyNodeEntry(ctx, node, parentInstallPrefix) {
2169
2198
  }
2170
2199
  }
2171
2200
  if (tarball?.integrity !== void 0 && entry.from === void 0) {
2172
- entry.integrity = tarball.integrity;
2201
+ const sri = emitSri(tarball.integrity);
2202
+ if (sri !== void 0) entry.integrity = sri;
2173
2203
  }
2174
2204
  const nodeSide = ctx.sidecar?.nodes.get(node.id);
2175
2205
  if (nodeSide?.dev === true) entry.dev = true;
@@ -1,5 +1,5 @@
1
- import { G as Graph, D as Diagnostic } from '../graph-DlYNIpXt.js';
2
- import { N as NpmFamilyEnrichOptions, a as NpmFamilyOptimizeOptions, b as NpmFamilyParseOptions, c as NpmFamilyStringifyOptions } from '../_npm-flat-types-BtjAgSy6.js';
1
+ import { G as Graph, D as Diagnostic } from '../graph-DL9MNz81.js';
2
+ import { N as NpmFamilyEnrichOptions, a as NpmFamilyOptimizeOptions, b as NpmFamilyParseOptions, c as NpmFamilyStringifyOptions } from '../_npm-flat-types-6ABSGUI9.js';
3
3
 
4
4
  interface Npm3ParseOptions extends NpmFamilyParseOptions {
5
5
  }
@@ -228,6 +228,7 @@ function validate(s) {
228
228
  const inc = s.incoming.get(id) ?? [];
229
229
  for (const edge of inc) {
230
230
  if (s.nodes.get(edge.src)?.workspacePath !== void 0) continue;
231
+ if (s.tarballs.get(stripPeerContextFromNodeId(edge.src))?.resolution?.type === "directory") continue;
231
232
  if (isPublishedSelfLink(edge)) {
232
233
  s.diagnostics.push({
233
234
  code: "SEAL_PUBLISHED_SELF_LINK",
@@ -648,16 +649,41 @@ function newBuilder() {
648
649
  }
649
650
 
650
651
  // src/main/ts/recipe/integrity.ts
651
- var CANONICAL_RE = /^sha512-[A-Za-z0-9+/]{86}==$/;
652
- function isCanonical(s) {
653
- if (!CANONICAL_RE.test(s)) return false;
654
- const b64 = s.slice("sha512-".length);
655
- const buf = Buffer.from(b64, "base64");
656
- if (buf.length !== 64) return false;
657
- return buf.toString("base64") === b64;
652
+ var SRI_ALGO_BYTES = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
653
+ var SRI_MEMBER_RE = /^([a-z][a-z0-9]*)-([A-Za-z0-9+/]+={0,2})(?:\?.*)?$/;
654
+ function hexToBase64(hex) {
655
+ return Buffer.from(hex, "hex").toString("base64");
656
+ }
657
+ function isEmptyIntegrity(i) {
658
+ return i.hashes.length === 0;
659
+ }
660
+ function isTarballOrigin(origin) {
661
+ return origin !== "berry-zip";
662
+ }
663
+ function tarballHashes(i) {
664
+ return i.hashes.filter((h) => isTarballOrigin(h.origin));
665
+ }
666
+ function parseSri(raw, origin = "sri") {
667
+ const hashes = [];
668
+ for (const member of raw.trim().split(/\s+/)) {
669
+ if (member === "") continue;
670
+ const m = SRI_MEMBER_RE.exec(member);
671
+ if (!m) continue;
672
+ const algorithm = m[1];
673
+ const bytes = Buffer.from(m[2], "base64");
674
+ const expected = SRI_ALGO_BYTES[algorithm];
675
+ if (expected !== void 0) {
676
+ if (bytes.length !== expected) continue;
677
+ } else if (bytes.length < 16) {
678
+ continue;
679
+ }
680
+ hashes.push({ algorithm, digest: bytes.toString("hex"), origin });
681
+ }
682
+ return { hashes };
658
683
  }
659
- function validateCanonical(raw) {
660
- return isCanonical(raw) ? raw : void 0;
684
+ function emitSri(i) {
685
+ const members = tarballHashes(i).map((h) => `${h.algorithm}-${hexToBase64(h.digest)}`);
686
+ return members.length > 0 ? members.join(" ") : void 0;
661
687
  }
662
688
 
663
689
  // src/main/ts/recipe/diagnostics.ts
@@ -666,7 +692,7 @@ function invalidIntegrityDiagnostic(prefix, subject, rawIntegrity) {
666
692
  code: `${prefix}_INVALID_INTEGRITY`,
667
693
  severity: "warning",
668
694
  subject,
669
- message: `integrity ${JSON.stringify(rawIntegrity)} is not a canonical sha512 SRI; dropping`
695
+ message: `integrity ${JSON.stringify(rawIntegrity)} has no parseable hash; dropping`
670
696
  };
671
697
  }
672
698
  function emitDropped(nodeId, feature, reason, onDiagnostic) {
@@ -1616,11 +1642,11 @@ function hasTarballPayload(entry) {
1616
1642
  function tarballPayloadOf(entry, subject, diagnostics) {
1617
1643
  const payload = {};
1618
1644
  if (entry.integrity !== void 0) {
1619
- const canonical = validateCanonical(entry.integrity);
1620
- if (canonical === void 0) {
1645
+ const integrity = parseSri(entry.integrity, "sri");
1646
+ if (isEmptyIntegrity(integrity)) {
1621
1647
  diagnostics.push(invalidIntegrityDiagnostic("NPM", subject, entry.integrity));
1622
1648
  } else {
1623
- payload.integrity = canonical;
1649
+ payload.integrity = integrity;
1624
1650
  }
1625
1651
  }
1626
1652
  if (entry.engines !== void 0) payload.engines = { ...entry.engines };
@@ -1861,7 +1887,10 @@ function buildNodeModulesEntry(graph, node, nodeSide, config, emitDiagnostic = (
1861
1887
  const tarball = graph.tarballOf(node.id);
1862
1888
  const resolved = node.resolution ?? config.hooks?.recoverResolvedForNode?.(graph, node) ?? deriveResolvedFromCanonical(tarball?.resolution);
1863
1889
  if (resolved !== void 0) body.resolved = resolved;
1864
- if (tarball?.integrity !== void 0) body.integrity = tarball.integrity;
1890
+ if (tarball?.integrity !== void 0) {
1891
+ const sri = emitSri(tarball.integrity);
1892
+ if (sri !== void 0) body.integrity = sri;
1893
+ }
1865
1894
  if (nodeSide?.dev === true) body.dev = true;
1866
1895
  if (nodeSide?.optional === true) body.optional = true;
1867
1896
  if (nodeSide?.peer === true) body.peer = true;
@@ -1,4 +1,4 @@
1
- import { D as Diagnostic, G as Graph } from '../graph-DlYNIpXt.js';
1
+ import { D as Diagnostic, G as Graph } from '../graph-DL9MNz81.js';
2
2
 
3
3
  interface PnpmV5ParseOptions {
4
4
  }