@antongolub/lockfile 0.0.0-snapshot.69 → 0.0.0-snapshot.70

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.
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2836,6 +2860,22 @@ function isRegistryRange(range) {
2836
2860
  if (range.startsWith("npm:")) return true;
2837
2861
  return !hasExplicitProtocol(range);
2838
2862
  }
2863
+ function splitOptionalDeps(deps, meta) {
2864
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2865
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2866
+ let regular;
2867
+ let optional;
2868
+ for (const [name, range] of Object.entries(deps)) {
2869
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2870
+ else (regular ?? (regular = {}))[name] = range;
2871
+ }
2872
+ return { regular, optional };
2873
+ }
2874
+ function isOptionalDepFlag(meta, name) {
2875
+ const entry = meta[name];
2876
+ if (entry === void 0 || typeof entry === "string") return false;
2877
+ return entry["optional"] === "true";
2878
+ }
2839
2879
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2840
2880
  var _a;
2841
2881
  if (!block) return;
@@ -2926,7 +2966,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2926
2966
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2927
2967
  const refs = unresolvedDeps.get(srcId) ?? [];
2928
2968
  refs.push({
2929
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2969
+ block: "dependencies",
2930
2970
  name: depName,
2931
2971
  range: depRange
2932
2972
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2441,7 +2463,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2441
2463
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2442
2464
  if (edges.length === 0) return void 0;
2443
2465
  const blockEntries = [];
2444
- const seen = /* @__PURE__ */ new Set();
2466
+ const seen = /* @__PURE__ */ new Map();
2445
2467
  for (const edge of edges) {
2446
2468
  const dst = graph.getNode(edge.dst);
2447
2469
  if (dst === void 0) {
@@ -2461,13 +2483,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2461
2483
  }
2462
2484
  const aliased = edge.attrs.alias !== void 0;
2463
2485
  const depName = aliased ? edge.attrs.alias : dst.name;
2464
- if (seen.has(depName)) {
2486
+ const prior = seen.get(depName);
2487
+ if (prior !== void 0) {
2488
+ if (prior === edge.dst) continue;
2465
2489
  throw new LockfileError({
2466
2490
  code: "INVARIANT_VIOLATION",
2467
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2491
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2468
2492
  });
2469
2493
  }
2470
- seen.add(depName);
2494
+ seen.set(depName, edge.dst);
2471
2495
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2472
2496
  }
2473
2497
  if (blockEntries.length === 0) return void 0;
@@ -2840,6 +2864,22 @@ function isRegistryRange(range) {
2840
2864
  if (range.startsWith("npm:")) return true;
2841
2865
  return !hasExplicitProtocol(range);
2842
2866
  }
2867
+ function splitOptionalDeps(deps, meta) {
2868
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2869
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2870
+ let regular;
2871
+ let optional;
2872
+ for (const [name, range] of Object.entries(deps)) {
2873
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2874
+ else (regular ?? (regular = {}))[name] = range;
2875
+ }
2876
+ return { regular, optional };
2877
+ }
2878
+ function isOptionalDepFlag(meta, name) {
2879
+ const entry = meta[name];
2880
+ if (entry === void 0 || typeof entry === "string") return false;
2881
+ return entry["optional"] === "true";
2882
+ }
2843
2883
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2844
2884
  var _a;
2845
2885
  if (!block) return;
@@ -2930,7 +2970,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2930
2970
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2931
2971
  const refs = unresolvedDeps.get(srcId) ?? [];
2932
2972
  refs.push({
2933
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2973
+ block: "dependencies",
2934
2974
  name: depName,
2935
2975
  range: depRange
2936
2976
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2835,6 +2859,22 @@ function isRegistryRange(range) {
2835
2859
  if (range.startsWith("npm:")) return true;
2836
2860
  return !hasExplicitProtocol(range);
2837
2861
  }
2862
+ function splitOptionalDeps(deps, meta) {
2863
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2864
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2865
+ let regular;
2866
+ let optional;
2867
+ for (const [name, range] of Object.entries(deps)) {
2868
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2869
+ else (regular ?? (regular = {}))[name] = range;
2870
+ }
2871
+ return { regular, optional };
2872
+ }
2873
+ function isOptionalDepFlag(meta, name) {
2874
+ const entry = meta[name];
2875
+ if (entry === void 0 || typeof entry === "string") return false;
2876
+ return entry["optional"] === "true";
2877
+ }
2838
2878
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2839
2879
  var _a;
2840
2880
  if (!block) return;
@@ -2925,7 +2965,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2925
2965
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2926
2966
  const refs = unresolvedDeps.get(srcId) ?? [];
2927
2967
  refs.push({
2928
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2968
+ block: "dependencies",
2929
2969
  name: depName,
2930
2970
  range: depRange
2931
2971
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2835,6 +2859,22 @@ function isRegistryRange(range) {
2835
2859
  if (range.startsWith("npm:")) return true;
2836
2860
  return !hasExplicitProtocol(range);
2837
2861
  }
2862
+ function splitOptionalDeps(deps, meta) {
2863
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2864
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2865
+ let regular;
2866
+ let optional;
2867
+ for (const [name, range] of Object.entries(deps)) {
2868
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2869
+ else (regular ?? (regular = {}))[name] = range;
2870
+ }
2871
+ return { regular, optional };
2872
+ }
2873
+ function isOptionalDepFlag(meta, name) {
2874
+ const entry = meta[name];
2875
+ if (entry === void 0 || typeof entry === "string") return false;
2876
+ return entry["optional"] === "true";
2877
+ }
2838
2878
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2839
2879
  var _a;
2840
2880
  if (!block) return;
@@ -2925,7 +2965,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2925
2965
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2926
2966
  const refs = unresolvedDeps.get(srcId) ?? [];
2927
2967
  refs.push({
2928
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2968
+ block: "dependencies",
2929
2969
  name: depName,
2930
2970
  range: depRange
2931
2971
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2835,6 +2859,22 @@ function isRegistryRange(range) {
2835
2859
  if (range.startsWith("npm:")) return true;
2836
2860
  return !hasExplicitProtocol(range);
2837
2861
  }
2862
+ function splitOptionalDeps(deps, meta) {
2863
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2864
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2865
+ let regular;
2866
+ let optional;
2867
+ for (const [name, range] of Object.entries(deps)) {
2868
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2869
+ else (regular ?? (regular = {}))[name] = range;
2870
+ }
2871
+ return { regular, optional };
2872
+ }
2873
+ function isOptionalDepFlag(meta, name) {
2874
+ const entry = meta[name];
2875
+ if (entry === void 0 || typeof entry === "string") return false;
2876
+ return entry["optional"] === "true";
2877
+ }
2838
2878
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2839
2879
  var _a;
2840
2880
  if (!block) return;
@@ -2925,7 +2965,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2925
2965
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2926
2966
  const refs = unresolvedDeps.get(srcId) ?? [];
2927
2967
  refs.push({
2928
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2968
+ block: "dependencies",
2929
2969
  name: depName,
2930
2970
  range: depRange
2931
2971
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2836,6 +2860,22 @@ function isRegistryRange(range) {
2836
2860
  if (range.startsWith("npm:")) return true;
2837
2861
  return !hasExplicitProtocol(range);
2838
2862
  }
2863
+ function splitOptionalDeps(deps, meta) {
2864
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2865
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2866
+ let regular;
2867
+ let optional;
2868
+ for (const [name, range] of Object.entries(deps)) {
2869
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2870
+ else (regular ?? (regular = {}))[name] = range;
2871
+ }
2872
+ return { regular, optional };
2873
+ }
2874
+ function isOptionalDepFlag(meta, name) {
2875
+ const entry = meta[name];
2876
+ if (entry === void 0 || typeof entry === "string") return false;
2877
+ return entry["optional"] === "true";
2878
+ }
2839
2879
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2840
2880
  var _a;
2841
2881
  if (!block) return;
@@ -2926,7 +2966,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2926
2966
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2927
2967
  const refs = unresolvedDeps.get(srcId) ?? [];
2928
2968
  refs.push({
2929
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2969
+ block: "dependencies",
2930
2970
  name: depName,
2931
2971
  range: depRange
2932
2972
  });
@@ -1823,7 +1823,9 @@ function parseFamily(input, options, config) {
1823
1823
  const srcId = entryIds.get(key);
1824
1824
  if (srcId === void 0) continue;
1825
1825
  const srcResolution = asString(value["resolution"]);
1826
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1826
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
1827
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1827
1829
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
1828
1830
  }
1829
1831
  diagnostics.sort((a, b) => {
@@ -2180,6 +2182,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
2180
2182
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
2181
2183
  return block === void 0 ? void 0 : coerceSymlMap(block);
2182
2184
  }
2185
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
2186
+ var _a;
2187
+ const optionalNames = [];
2188
+ for (const edge of graph.out(node.id, "optional")) {
2189
+ const dst = graph.getNode(edge.dst);
2190
+ if (dst === void 0) continue;
2191
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
2192
+ }
2193
+ if (optionalNames.length === 0) return verbatim;
2194
+ const merged = {};
2195
+ if (verbatim !== void 0) {
2196
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
2197
+ }
2198
+ for (const name of optionalNames) {
2199
+ if (name in merged) continue;
2200
+ merged[name] = { optional: "true" };
2201
+ }
2202
+ const sorted = {};
2203
+ for (const name of Object.keys(merged).sort(cmpStr3)) sorted[name] = merged[name];
2204
+ return sorted;
2205
+ }
2183
2206
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
2184
2207
  var _a, _b;
2185
2208
  const block = (_b = (_a = sidecarByGraph.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -2356,18 +2379,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
2356
2379
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
2357
2380
  };
2358
2381
  const dependencies = withUnresolvedDepRefs(
2359
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
2382
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
2360
2383
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
2361
2384
  );
2362
2385
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
2363
- const optionalDependencies = withUnresolvedDepRefs(
2364
- edgeBlockOfKinds(graph, node, ["optional"], config),
2365
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
2366
- );
2367
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
2368
2386
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
2369
2387
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
2370
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
2388
+ const dependenciesMeta = dependenciesMetaWithOptional(
2389
+ graph,
2390
+ node,
2391
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
2392
+ );
2371
2393
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
2372
2394
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
2373
2395
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -2436,7 +2458,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2436
2458
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
2437
2459
  if (edges.length === 0) return void 0;
2438
2460
  const blockEntries = [];
2439
- const seen = /* @__PURE__ */ new Set();
2461
+ const seen = /* @__PURE__ */ new Map();
2440
2462
  for (const edge of edges) {
2441
2463
  const dst = graph.getNode(edge.dst);
2442
2464
  if (dst === void 0) {
@@ -2456,13 +2478,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
2456
2478
  }
2457
2479
  const aliased = edge.attrs.alias !== void 0;
2458
2480
  const depName = aliased ? edge.attrs.alias : dst.name;
2459
- if (seen.has(depName)) {
2481
+ const prior = seen.get(depName);
2482
+ if (prior !== void 0) {
2483
+ if (prior === edge.dst) continue;
2460
2484
  throw new LockfileError({
2461
2485
  code: "INVARIANT_VIOLATION",
2462
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
2486
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
2463
2487
  });
2464
2488
  }
2465
- seen.add(depName);
2489
+ seen.set(depName, edge.dst);
2466
2490
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
2467
2491
  }
2468
2492
  if (blockEntries.length === 0) return void 0;
@@ -2836,6 +2860,22 @@ function isRegistryRange(range) {
2836
2860
  if (range.startsWith("npm:")) return true;
2837
2861
  return !hasExplicitProtocol(range);
2838
2862
  }
2863
+ function splitOptionalDeps(deps, meta) {
2864
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
2865
+ if (meta === void 0) return { regular: deps, optional: void 0 };
2866
+ let regular;
2867
+ let optional;
2868
+ for (const [name, range] of Object.entries(deps)) {
2869
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
2870
+ else (regular ?? (regular = {}))[name] = range;
2871
+ }
2872
+ return { regular, optional };
2873
+ }
2874
+ function isOptionalDepFlag(meta, name) {
2875
+ const entry = meta[name];
2876
+ if (entry === void 0 || typeof entry === "string") return false;
2877
+ return entry["optional"] === "true";
2878
+ }
2839
2879
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
2840
2880
  var _a;
2841
2881
  if (!block) return;
@@ -2926,7 +2966,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
2926
2966
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
2927
2967
  const refs = unresolvedDeps.get(srcId) ?? [];
2928
2968
  refs.push({
2929
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
2969
+ block: "dependencies",
2930
2970
  name: depName,
2931
2971
  range: depRange
2932
2972
  });
package/dist/index.js CHANGED
@@ -6637,7 +6637,9 @@ function parseFamily3(input, options, config) {
6637
6637
  const srcId = entryIds.get(key);
6638
6638
  if (srcId === void 0) continue;
6639
6639
  const srcResolution = asString(value["resolution"]);
6640
- addEdgesFromBlock(builder, srcId, asMap(value["dependencies"]), "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
6640
+ const { regular, optional } = splitOptionalDeps(asMap(value["dependencies"]), rawDependenciesMeta.get(srcId));
6641
+ addEdgesFromBlock(builder, srcId, regular, "dep", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
6642
+ addEdgesFromBlock(builder, srcId, optional, "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
6641
6643
  addEdgesFromBlock(builder, srcId, asMap(value["optionalDependencies"]), "optional", specIndex, patchDescriptorIndex, diagnostics, ladderCtx, srcResolution, rawUnresolvedDeps);
6642
6644
  }
6643
6645
  diagnostics.sort((a, b) => {
@@ -6794,6 +6796,27 @@ function rawDependenciesMetaBlockOfNode(graph, nodeId) {
6794
6796
  const block = (_b = (_a = sidecarByGraph6.get(graph)) == null ? void 0 : _a.dependenciesMeta) == null ? void 0 : _b.get(nodeId);
6795
6797
  return block === void 0 ? void 0 : coerceSymlMap(block);
6796
6798
  }
6799
+ function dependenciesMetaWithOptional(graph, node, verbatim) {
6800
+ var _a;
6801
+ const optionalNames = [];
6802
+ for (const edge of graph.out(node.id, "optional")) {
6803
+ const dst = graph.getNode(edge.dst);
6804
+ if (dst === void 0) continue;
6805
+ optionalNames.push(((_a = edge.attrs) == null ? void 0 : _a.alias) ?? dst.name);
6806
+ }
6807
+ if (optionalNames.length === 0) return verbatim;
6808
+ const merged = {};
6809
+ if (verbatim !== void 0) {
6810
+ for (const [name, meta] of Object.entries(verbatim)) merged[name] = meta;
6811
+ }
6812
+ for (const name of optionalNames) {
6813
+ if (name in merged) continue;
6814
+ merged[name] = { optional: "true" };
6815
+ }
6816
+ const sorted = {};
6817
+ for (const name of Object.keys(merged).sort(cmpStr5)) sorted[name] = merged[name];
6818
+ return sorted;
6819
+ }
6797
6820
  function rawPeerDependenciesMetaBlockOfNode(graph, nodeId) {
6798
6821
  var _a, _b;
6799
6822
  const block = (_b = (_a = sidecarByGraph6.get(graph)) == null ? void 0 : _a.peerDependenciesMeta) == null ? void 0 : _b.get(nodeId);
@@ -6970,18 +6993,17 @@ function entryOfNode(graph, node, config, emitDiagnostic, cacheKey) {
6970
6993
  resolution: resolutionOfNode(node, payload == null ? void 0 : payload.resolution, payload == null ? void 0 : payload.nativeResolution, emitDiagnostic)
6971
6994
  };
6972
6995
  const dependencies = withUnresolvedDepRefs(
6973
- edgeBlockOfKinds(graph, node, ["dep", "dev"], config),
6996
+ edgeBlockOfKinds(graph, node, ["dep", "dev", "optional"], config),
6974
6997
  unresolvedDepRefsOfNode(graph, node.id, "dependencies")
6975
6998
  );
6976
6999
  if (dependencies !== void 0) entry["dependencies"] = dependencies;
6977
- const optionalDependencies = withUnresolvedDepRefs(
6978
- edgeBlockOfKinds(graph, node, ["optional"], config),
6979
- unresolvedDepRefsOfNode(graph, node.id, "optionalDependencies")
6980
- );
6981
- if (optionalDependencies !== void 0) entry["optionalDependencies"] = optionalDependencies;
6982
7000
  const peerDependencies = extraBlockOfNode(node, "peerDependencies") ?? rawPeerDependenciesBlockOfNode(graph, node.id) ?? edgeBlockOfKinds(graph, node, ["peer"], config, { skipMissingRange: true });
6983
7001
  if (peerDependencies !== void 0) entry["peerDependencies"] = peerDependencies;
6984
- const dependenciesMeta = rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta");
7002
+ const dependenciesMeta = dependenciesMetaWithOptional(
7003
+ graph,
7004
+ node,
7005
+ rawDependenciesMetaBlockOfNode(graph, node.id) ?? extraBlockOfNode(node, "dependenciesMeta")
7006
+ );
6985
7007
  if (dependenciesMeta !== void 0) entry["dependenciesMeta"] = dependenciesMeta;
6986
7008
  const peerDependenciesMeta = peerDependenciesMetaOfNode(graph, node);
6987
7009
  if (peerDependenciesMeta !== void 0) entry["peerDependenciesMeta"] = peerDependenciesMeta;
@@ -7057,7 +7079,7 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
7057
7079
  const edges = kinds.flatMap((kind) => graph.out(node.id, kind));
7058
7080
  if (edges.length === 0) return void 0;
7059
7081
  const blockEntries = [];
7060
- const seen = /* @__PURE__ */ new Set();
7082
+ const seen = /* @__PURE__ */ new Map();
7061
7083
  for (const edge of edges) {
7062
7084
  const dst = graph.getNode(edge.dst);
7063
7085
  if (dst === void 0) {
@@ -7077,13 +7099,15 @@ function edgeBlockOfKinds(graph, node, kinds, config, options = {}) {
7077
7099
  }
7078
7100
  const aliased = edge.attrs.alias !== void 0;
7079
7101
  const depName = aliased ? edge.attrs.alias : dst.name;
7080
- if (seen.has(depName)) {
7102
+ const prior = seen.get(depName);
7103
+ if (prior !== void 0) {
7104
+ if (prior === edge.dst) continue;
7081
7105
  throw new LockfileError({
7082
7106
  code: "INVARIANT_VIOLATION",
7083
- message: `cannot emit duplicate ${edge.kind} dependency key ${depName} from ${node.id}`
7107
+ message: `cannot emit duplicate dependency key ${depName} from ${node.id} (targets ${prior} and ${edge.dst})`
7084
7108
  });
7085
7109
  }
7086
- seen.add(depName);
7110
+ seen.set(depName, edge.dst);
7087
7111
  blockEntries.push([depName, emittedRangeOfEdge(edge.kind, edge.attrs.range, config, aliased)]);
7088
7112
  }
7089
7113
  if (blockEntries.length === 0) return void 0;
@@ -7346,6 +7370,22 @@ function isRegistryRange(range) {
7346
7370
  if (range.startsWith("npm:")) return true;
7347
7371
  return !hasExplicitProtocol(range);
7348
7372
  }
7373
+ function splitOptionalDeps(deps, meta) {
7374
+ if (deps === void 0) return { regular: void 0, optional: void 0 };
7375
+ if (meta === void 0) return { regular: deps, optional: void 0 };
7376
+ let regular;
7377
+ let optional;
7378
+ for (const [name, range] of Object.entries(deps)) {
7379
+ if (isOptionalDepFlag(meta, name)) (optional ?? (optional = {}))[name] = range;
7380
+ else (regular ?? (regular = {}))[name] = range;
7381
+ }
7382
+ return { regular, optional };
7383
+ }
7384
+ function isOptionalDepFlag(meta, name) {
7385
+ const entry = meta[name];
7386
+ if (entry === void 0 || typeof entry === "string") return false;
7387
+ return entry["optional"] === "true";
7388
+ }
7349
7389
  function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIndex, diagnostics, ladder, srcResolution, unresolvedDeps) {
7350
7390
  var _a;
7351
7391
  if (!block) return;
@@ -7436,7 +7476,7 @@ function addEdgesFromBlock(builder, srcId, block, kind, index, patchDescriptorIn
7436
7476
  if (unresolvedDeps !== void 0 && (kind === "dep" || kind === "optional")) {
7437
7477
  const refs = unresolvedDeps.get(srcId) ?? [];
7438
7478
  refs.push({
7439
- block: kind === "optional" ? "optionalDependencies" : "dependencies",
7479
+ block: "dependencies",
7440
7480
  name: depName,
7441
7481
  range: depRange
7442
7482
  });
@@ -10813,6 +10853,14 @@ async function modify(graph, primitive, options) {
10813
10853
 
10814
10854
  // src/main/ts/optimize/diagnostics.ts
10815
10855
  function optimizeNodeRemoved(nodeId) {
10856
+ return {
10857
+ code: "OPTIMIZE_NODE_REMOVED",
10858
+ severity: "info",
10859
+ subject: nodeId,
10860
+ message: `removed orphan ${nodeLabel(nodeId)} (${nodeId})`
10861
+ };
10862
+ }
10863
+ function nodeLabel(nodeId) {
10816
10864
  const name = nameOf(nodeId);
10817
10865
  const tail = nodeId.slice(name.length + 1);
10818
10866
  let depth = 0;
@@ -10827,13 +10875,7 @@ function optimizeNodeRemoved(nodeId) {
10827
10875
  else if (c === ")") depth--;
10828
10876
  }
10829
10877
  const version3 = tail.slice(0, cut);
10830
- const label = name.length > 0 && version3.length > 0 ? `${name}@${version3}` : nodeId;
10831
- return {
10832
- code: "OPTIMIZE_NODE_REMOVED",
10833
- severity: "info",
10834
- subject: nodeId,
10835
- message: `removed orphan ${label} (${nodeId})`
10836
- };
10878
+ return name.length > 0 && version3.length > 0 ? `${name}@${version3}` : nodeId;
10837
10879
  }
10838
10880
  function optimizeNoop() {
10839
10881
  return {
@@ -1,5 +1,36 @@
1
1
  export { O as OptimizeOptions, a as OptimizeResult, o as optimize } from './optimize-B6TOjYyj.js';
2
- import { D as Diagnostic, N as NodeId } from './graph-CPo-SvS2.js';
2
+ import { D as Diagnostic, N as NodeId, G as Graph } from './graph-CPo-SvS2.js';
3
+
4
+ interface PruneOrphansOptions {
5
+ /** Stream diagnostics as they fire (mirrors the modify / optimize channel). */
6
+ onDiagnostic?: (d: Diagnostic) => void;
7
+ /** NodeIds to keep even at in-degree 0 (intentional roots; rare). */
8
+ preserve?: ReadonlySet<NodeId>;
9
+ /**
10
+ * Bound the sweep to a candidate set. When provided, ONLY these NodeIds (and
11
+ * the closure they transitively strand) are removal candidates — any OTHER
12
+ * pre-existing in-degree-0 node is left untouched. Pass the mutation's removed
13
+ * / orphaned NodeIds (e.g. `replaceVersion`'s `recentlyOrphaned`) for a purely
14
+ * differential GC. Omit for a whole-graph sweep.
15
+ */
16
+ seed?: ReadonlySet<NodeId>;
17
+ }
18
+ interface PruneOrphansResult {
19
+ graph: Graph;
20
+ /** NodeIds removed by this call, in removal order. */
21
+ removed: NodeId[];
22
+ /** All diagnostics this call emitted, in emission order. */
23
+ unresolved: Diagnostic[];
24
+ }
25
+ /**
26
+ * Remove every non-workspace node that has lost its last incoming edge, plus
27
+ * the closure such removals strand. See the module header for why this is
28
+ * reference-counting, not reachability (and how it differs from `optimize`).
29
+ *
30
+ * Synchronous + deterministic (worklist seeded in `graph.nodes()` content-sort
31
+ * order). Idempotent: a second call on the result removes nothing.
32
+ */
33
+ declare function pruneOrphans(graph: Graph, options?: PruneOrphansOptions): PruneOrphansResult;
3
34
 
4
35
  type OptimizeDiagnosticCode = 'OPTIMIZE_NODE_REMOVED' | 'OPTIMIZE_WORKSPACE_UNREACHABLE' | 'OPTIMIZE_NOOP' | 'OPTIMIZE_NO_ROOTS';
5
36
  interface OptimizeDiagnostic extends Diagnostic {
@@ -11,6 +42,14 @@ interface OptimizeDiagnostic extends Diagnostic {
11
42
  * rationale — the NodeId alone may carry a long peerContext suffix.
12
43
  */
13
44
  declare function optimizeNodeRemoved(nodeId: NodeId): OptimizeDiagnostic;
45
+ type PruneDiagnosticCode = 'PRUNE_NODE_REMOVED' | 'PRUNE_NOOP';
46
+ interface PruneDiagnostic extends Diagnostic {
47
+ code: PruneDiagnosticCode;
48
+ }
49
+ /** Fires once per node retired because it lost its last incoming edge. */
50
+ declare function pruneNodeRemoved(nodeId: NodeId): PruneDiagnostic;
51
+ /** Fires once per `pruneOrphans(graph)` call when nothing was removed. */
52
+ declare function pruneNoop(): PruneDiagnostic;
14
53
  declare function optimizeWorkspaceUnreachable(nodeId: NodeId): OptimizeDiagnostic;
15
54
  /**
16
55
  * Fires once per `optimize(graph)` call when `removed.length === 0`. The
@@ -21,4 +60,4 @@ declare function optimizeWorkspaceUnreachable(nodeId: NodeId): OptimizeDiagnosti
21
60
  */
22
61
  declare function optimizeNoop(): OptimizeDiagnostic;
23
62
 
24
- export { type OptimizeDiagnostic, type OptimizeDiagnosticCode, optimizeNodeRemoved, optimizeNoop, optimizeWorkspaceUnreachable };
63
+ export { type OptimizeDiagnostic, type OptimizeDiagnosticCode, type PruneDiagnostic, type PruneDiagnosticCode, type PruneOrphansOptions, type PruneOrphansResult, optimizeNodeRemoved, optimizeNoop, optimizeWorkspaceUnreachable, pruneNodeRemoved, pruneNoop, pruneOrphans };
package/dist/optimize.js CHANGED
@@ -18,6 +18,14 @@ function nameOf(id) {
18
18
 
19
19
  // src/main/ts/optimize/diagnostics.ts
20
20
  function optimizeNodeRemoved(nodeId) {
21
+ return {
22
+ code: "OPTIMIZE_NODE_REMOVED",
23
+ severity: "info",
24
+ subject: nodeId,
25
+ message: `removed orphan ${nodeLabel(nodeId)} (${nodeId})`
26
+ };
27
+ }
28
+ function nodeLabel(nodeId) {
21
29
  const name = nameOf(nodeId);
22
30
  const tail = nodeId.slice(name.length + 1);
23
31
  let depth = 0;
@@ -32,12 +40,22 @@ function optimizeNodeRemoved(nodeId) {
32
40
  else if (c === ")") depth--;
33
41
  }
34
42
  const version = tail.slice(0, cut);
35
- const label = name.length > 0 && version.length > 0 ? `${name}@${version}` : nodeId;
43
+ return name.length > 0 && version.length > 0 ? `${name}@${version}` : nodeId;
44
+ }
45
+ function pruneNodeRemoved(nodeId) {
36
46
  return {
37
- code: "OPTIMIZE_NODE_REMOVED",
47
+ code: "PRUNE_NODE_REMOVED",
38
48
  severity: "info",
39
49
  subject: nodeId,
40
- message: `removed orphan ${label} (${nodeId})`
50
+ message: `pruned unreferenced ${nodeLabel(nodeId)} (${nodeId})`
51
+ };
52
+ }
53
+ function pruneNoop() {
54
+ return {
55
+ code: "PRUNE_NOOP",
56
+ severity: "info",
57
+ subject: "graph",
58
+ message: "pruneOrphans: no unreferenced nodes to collect"
41
59
  };
42
60
  }
43
61
  function optimizeWorkspaceUnreachable(nodeId) {
@@ -147,4 +165,65 @@ function optimize(graph, options = {}) {
147
165
  }
148
166
  var EMPTY_PRESERVE = /* @__PURE__ */ new Set();
149
167
 
150
- export { optimize, optimizeNodeRemoved, optimizeNoop, optimizeWorkspaceUnreachable };
168
+ // src/main/ts/optimize/prune.ts
169
+ var EMPTY = /* @__PURE__ */ new Set();
170
+ function pruneOrphans(graph, options = {}) {
171
+ const preserve = options.preserve ?? EMPTY;
172
+ const onDiagnostic = options.onDiagnostic;
173
+ const removed = [];
174
+ const unresolved = [];
175
+ const emit = (d) => {
176
+ unresolved.push(d);
177
+ if (onDiagnostic !== void 0) onDiagnostic(d);
178
+ };
179
+ let current = graph;
180
+ const collectable = (id) => {
181
+ const node = current.getNode(id);
182
+ if (node === void 0) return false;
183
+ if (node.workspacePath !== void 0) return false;
184
+ if (preserve.has(id)) return false;
185
+ return current.in(id).length === 0;
186
+ };
187
+ const queue = [];
188
+ if (options.seed !== void 0) {
189
+ for (const id of options.seed) if (collectable(id)) queue.push(id);
190
+ } else {
191
+ for (const node of current.nodes()) if (collectable(node.id)) queue.push(node.id);
192
+ }
193
+ while (queue.length > 0) {
194
+ const id = queue.shift();
195
+ if (!collectable(id)) continue;
196
+ const node = current.getNode(id);
197
+ const children = current.out(id).map((e) => e.dst);
198
+ const tarballInputs = { name: node.name, version: node.version, patch: node.patch };
199
+ const diag = pruneNodeRemoved(id);
200
+ current = current.mutate((m) => {
201
+ m.removeNode(id);
202
+ if (current.tarball(tarballInputs) !== void 0 && !tarballSharedByOther(current, id, tarballInputs)) {
203
+ m.removeTarball(tarballInputs);
204
+ }
205
+ m.diagnostic(diag);
206
+ }).graph;
207
+ removed.push(id);
208
+ emit(diag);
209
+ for (const child of children) if (collectable(child)) queue.push(child);
210
+ }
211
+ if (removed.length === 0) {
212
+ const noop = pruneNoop();
213
+ current = current.mutate((m) => {
214
+ m.diagnostic(noop);
215
+ }).graph;
216
+ emit(noop);
217
+ }
218
+ return { graph: current, removed, unresolved };
219
+ }
220
+ function tarballSharedByOther(graph, removingId, key) {
221
+ for (const id of graph.byName(key.name)) {
222
+ if (id === removingId) continue;
223
+ const n = graph.getNode(id);
224
+ if (n !== void 0 && n.version === key.version && n.patch === key.patch) return true;
225
+ }
226
+ return false;
227
+ }
228
+
229
+ export { optimize, optimizeNodeRemoved, optimizeNoop, optimizeWorkspaceUnreachable, pruneNodeRemoved, pruneNoop, pruneOrphans };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antongolub/lockfile",
3
- "version": "0.0.0-snapshot.69",
3
+ "version": "0.0.0-snapshot.70",
4
4
  "private": false,
5
5
  "description": "Universal lockfile model and converter for npm, yarn, pnpm, bun",
6
6
  "type": "module",