@vltpkg/cli-js 1.0.7 → 1.0.8

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 (70) hide show
  1. package/{access-OKO4MXDT.js → access-FOYRQGJC.js} +1 -1
  2. package/{bugs-J56QQ3CP.js → bugs-ORVU4I5O.js} +19 -11
  3. package/{build-CQ2E2XRO.js → build-JLVAGEEU.js} +4 -4
  4. package/{cache-PPEXAQT7.js → cache-RIOSSQD3.js} +3 -3
  5. package/{chunk-CIKUT62L.js → chunk-3L2ERW54.js} +2 -2
  6. package/{chunk-RKXQ66T4.js → chunk-5CNTLC2U.js} +1 -1
  7. package/{chunk-SVO3HI2G.js → chunk-5QAFAGO5.js} +3 -3
  8. package/{chunk-3NGV7EC6.js → chunk-7D7SNVBV.js} +1177 -214
  9. package/{chunk-34VLICEH.js → chunk-ADLHAQRA.js} +5 -5
  10. package/{chunk-JWWDJYSU.js → chunk-AGHW2E24.js} +17 -14
  11. package/chunk-AQ3XJJUP.js +75 -0
  12. package/{chunk-ED2DDTQT.js → chunk-FG633ZKS.js} +31 -4
  13. package/{chunk-OUF2P2OD.js → chunk-JXVDCQZF.js} +14 -2
  14. package/{chunk-QWTGTFHN.js → chunk-MUG5K6X2.js} +1 -1
  15. package/{chunk-CHHGSDAP.js → chunk-NC3QYZPW.js} +3 -3
  16. package/chunk-OB73T23B.js +30 -0
  17. package/{chunk-SVBEF5PK.js → chunk-OWML62W5.js} +1 -1
  18. package/{chunk-JLKFIX2H.js → chunk-UYIA44HE.js} +4 -4
  19. package/{chunk-GVZRKITF.js → chunk-WY4PAM6D.js} +3 -3
  20. package/{chunk-A4EKZ32F.js → chunk-YCVPEZRM.js} +2 -2
  21. package/{chunk-3OPNZI6G.js → chunk-YOE3VZFX.js} +8 -7
  22. package/{chunk-ADYHZTEI.js → chunk-ZXIF4PVX.js} +13 -6
  23. package/{ci-WDQ4SQN6.js → ci-2666LKEC.js} +5 -5
  24. package/cli-package.json +1 -1
  25. package/{config-XY5CDGTD.js → config-3N7PBEXH.js} +4 -4
  26. package/{config-JMARDHLW.js → config-WMYY5J37.js} +4 -4
  27. package/{create-C24ZGR6S.js → create-AEQSO6KX.js} +9 -9
  28. package/{deprecate-HYHD5WQC.js → deprecate-MW76DAZ5.js} +4 -4
  29. package/{devtools-66MVSNF5.js → devtools-UEMPW5GG.js} +29 -29
  30. package/{dist-tag-RPQ5LZWM.js → dist-tag-XGQLYA6C.js} +1 -1
  31. package/{docs-UKQNAVM3.js → docs-7CCTL3OY.js} +19 -11
  32. package/{exec-FNZEVDGU.js → exec-GWCIDNOH.js} +9 -9
  33. package/{exec-cache-GRHS57DV.js → exec-cache-AML2HUTJ.js} +7 -7
  34. package/{exec-local-AHDW7CKQ.js → exec-local-JRTO2XG4.js} +7 -7
  35. package/{help-VDBXIWF3.js → help-4KSCV7GN.js} +1 -1
  36. package/{init-2ICZBLI2.js → init-OHVBHXB7.js} +5 -5
  37. package/{install-IFSVVRU5.js → install-TFDEHY5J.js} +6 -6
  38. package/{list-RCAPA3OE.js → list-UD4SDHVE.js} +16 -10
  39. package/{login-T42R75OV.js → login-B6TIQW62.js} +5 -5
  40. package/{logout-PVR6J2AK.js → logout-H3CXGLDN.js} +1 -1
  41. package/{pack-HPLDE4RB.js → pack-YYK4LKBQ.js} +6 -6
  42. package/package.json +1 -1
  43. package/{ping-N5U4XHUU.js → ping-G765RWLM.js} +1 -1
  44. package/{pkg-VNKZX5JC.js → pkg-D4S3SPUH.js} +6 -6
  45. package/{profile-N7UOXZ2X.js → profile-YDQGMWYP.js} +1 -1
  46. package/{publish-CRSF7NZP.js → publish-ANHSZPIM.js} +8 -8
  47. package/{query-MEAXXYOE.js → query-PZ6C6AZJ.js} +16 -10
  48. package/registry-client-package.json +1 -1
  49. package/registry-client-src-revalidate.js +17 -14
  50. package/{repo-IRTGGLIC.js → repo-ZA3TMERE.js} +19 -11
  51. package/{reporter-Y7RMSVN4.js → reporter-SDMDYYMJ.js} +72 -72
  52. package/{run-I6HWURSB.js → run-N4NK35UT.js} +7 -7
  53. package/{run-exec-BV3RUWQM.js → run-exec-N5FT4QO7.js} +7 -7
  54. package/security-archive-src-update-expired.js +6 -7
  55. package/{setup-U6DWTZBA.js → setup-MWWDSKQZ.js} +6 -6
  56. package/{src-44BBCHST.js → src-6PISL6QX.js} +1 -1
  57. package/{src-SXMLUOLI.js → src-6UF5YTTL.js} +9 -8
  58. package/{token-CTUMLTQP.js → token-FFUAFL43.js} +1 -1
  59. package/{uninstall-UUFXE47B.js → uninstall-A4PUG6RI.js} +14 -7
  60. package/{unpublish-G4NALOC6.js → unpublish-OJEGZDGQ.js} +1 -1
  61. package/{update-BGMQQNYE.js → update-ENR6ICMR.js} +5 -5
  62. package/{version-QVFPFL6M.js → version-K3BUGG7X.js} +5 -5
  63. package/{view-QFYMJVBE.js → view-SWVQLQCB.js} +4 -4
  64. package/vlr.js +1 -1
  65. package/vlrx.js +1 -1
  66. package/vlt.js +1 -1
  67. package/vlx.js +1 -1
  68. package/vlxl.js +1 -1
  69. package/{whoami-5D4XUOSP.js → whoami-MA733O2T.js} +1 -1
  70. package/chunk-NEFV2B5V.js +0 -75
@@ -13,17 +13,19 @@ import {
13
13
  AbortError,
14
14
  SecurityArchive,
15
15
  pRetry
16
- } from "./chunk-ED2DDTQT.js";
16
+ } from "./chunk-FG633ZKS.js";
17
17
  import {
18
18
  Monorepo,
19
19
  PackageJson,
20
20
  getUser,
21
21
  minimatch,
22
22
  platformCheck
23
- } from "./chunk-SVBEF5PK.js";
23
+ } from "./chunk-OWML62W5.js";
24
24
  import {
25
25
  asDepID,
26
+ baseDepID,
26
27
  getId,
28
+ getTuple,
27
29
  graphRun,
28
30
  hydrate,
29
31
  hydrate2,
@@ -33,7 +35,7 @@ import {
33
35
  joinExtra,
34
36
  splitDepID,
35
37
  splitExtra
36
- } from "./chunk-3OPNZI6G.js";
38
+ } from "./chunk-YOE3VZFX.js";
37
39
  import {
38
40
  Spec as Spec2
39
41
  } from "./chunk-XKVU23NC.js";
@@ -4755,6 +4757,34 @@ var Node = class _Node {
4755
4757
  break;
4756
4758
  }
4757
4759
  }
4760
+ /**
4761
+ * Update this node's DepID and peer suffix after canonicalization.
4762
+ * Resets name/location memos that were derived from the previous id.
4763
+ */
4764
+ setPeerIdentity(id2, peerSetHash) {
4765
+ const prevId = this.id;
4766
+ const prevName = this.name;
4767
+ const nameWasId = this.#name === prevId;
4768
+ const prevDefault = `./node_modules/.vlt/${prevId}/node_modules/${prevName}`;
4769
+ const locWasDefault = !this.#location || this.#location === prevDefault;
4770
+ this.id = id2;
4771
+ this.peerSetHash = peerSetHash;
4772
+ if (nameWasId) this.#name = void 0;
4773
+ const nextName = this.name;
4774
+ if (prevName !== nextName) {
4775
+ const oldSet = this.graph.nodesByName.get(prevName);
4776
+ oldSet?.delete(this);
4777
+ if (oldSet?.size === 0) {
4778
+ this.graph.nodesByName.delete(prevName);
4779
+ }
4780
+ const nbn = this.graph.nodesByName.get(nextName) ?? /* @__PURE__ */ new Set();
4781
+ nbn.add(this);
4782
+ this.graph.nodesByName.set(nextName, nbn);
4783
+ }
4784
+ if (locWasDefault) {
4785
+ this.location = `./node_modules/.vlt/${id2}/node_modules/${nextName}`;
4786
+ }
4787
+ }
4758
4788
  setDefaultLocation() {
4759
4789
  const def = `./node_modules/.vlt/${this.id}/node_modules/${this.name}`;
4760
4790
  if (!this.importer && (!this.#location || this.#location !== def && /^(?:\.\/)?node_modules\//.test(this.#location))) {
@@ -4825,6 +4855,13 @@ var Node = class _Node {
4825
4855
  return stringifyNode(this);
4826
4856
  }
4827
4857
  };
4858
+ var copyPackageMetadata = (to, from) => {
4859
+ to.integrity ??= from.integrity;
4860
+ to.resolved ??= from.resolved;
4861
+ if (from.resolvedFromLockfile && to.integrity && to.resolved) {
4862
+ to.resolvedFromLockfile = true;
4863
+ }
4864
+ };
4828
4865
  var isNode = (value) => {
4829
4866
  return typeof value === "object" && value != null && "id" in value && "manifest" in value && value[Symbol.toStringTag] === "@vltpkg/graph.Node";
4830
4867
  };
@@ -6765,17 +6802,22 @@ var asOutdatedKind = (value) => {
6765
6802
  }
6766
6803
  return value;
6767
6804
  };
6768
- var retrieveRemoteVersions = async (node, signal) => {
6805
+ var retrieveRemoteVersions = async (node, signal, getAuthHeader) => {
6769
6806
  const spec2 = hydrate(node.id, String(node.name), node.options);
6770
6807
  if (!spec2.registry || !node.name) {
6771
6808
  return [];
6772
6809
  }
6773
- const url = new URL(spec2.registry);
6774
- url.pathname = `/${node.name}`;
6810
+ const base = spec2.registry.endsWith("/") ? spec2.registry : `${spec2.registry}/`;
6811
+ const url = new URL(node.name, base);
6812
+ const headers = {
6813
+ Accept: "application/vnd.npm.install-v1+json"
6814
+ };
6815
+ const auth = await getAuthHeader?.(String(url));
6816
+ if (auth) {
6817
+ headers.authorization = auth;
6818
+ }
6775
6819
  const response = await fetch(String(url), {
6776
- headers: {
6777
- Accept: "application/vnd.npm.install-v1+json"
6778
- },
6820
+ headers,
6779
6821
  signal
6780
6822
  });
6781
6823
  if (response.status === 404) {
@@ -6806,7 +6848,7 @@ var parseInternals7 = (nodes) => {
6806
6848
  }
6807
6849
  return { kind };
6808
6850
  };
6809
- var queueNode2 = async (state, node, kind) => {
6851
+ var queueNode2 = async (state, node, kind, onError) => {
6810
6852
  if (!node.name || !node.version) {
6811
6853
  return node;
6812
6854
  }
@@ -6814,19 +6856,27 @@ var queueNode2 = async (state, node, kind) => {
6814
6856
  let versions;
6815
6857
  try {
6816
6858
  versions = await pRetry(
6817
- () => retrieveRemoteVersions(node, state.signal),
6859
+ () => retrieveRemoteVersions(
6860
+ node,
6861
+ state.signal,
6862
+ state.getAuthHeader
6863
+ ),
6818
6864
  {
6819
6865
  retries: state.retries,
6820
6866
  signal: state.signal
6821
6867
  }
6822
6868
  );
6823
6869
  } catch (err) {
6824
- console.warn(
6825
- error("Could not retrieve registry versions", {
6826
- name: node.name,
6827
- cause: err
6828
- })
6829
- );
6870
+ if (onError) {
6871
+ onError(node, err);
6872
+ } else {
6873
+ console.warn(
6874
+ error("Could not retrieve registry versions", {
6875
+ name: node.name,
6876
+ cause: err
6877
+ })
6878
+ );
6879
+ }
6830
6880
  versions = [];
6831
6881
  }
6832
6882
  const greaterVersions = versions.filter(
@@ -6899,12 +6949,20 @@ var outdated = async (state) => {
6899
6949
  }
6900
6950
  const { kind } = internals;
6901
6951
  const queue = [];
6952
+ const failures = [];
6902
6953
  for (const node of state.partial.nodes) {
6903
6954
  if (node.mainImporter || node.manifest?.private || splitDepID(node.id)[0] !== "registry") {
6904
6955
  removeNode(state, node);
6905
6956
  continue;
6906
6957
  }
6907
- queue.push(queueNode2(state, node, kind));
6958
+ queue.push(
6959
+ queueNode2(
6960
+ state,
6961
+ node,
6962
+ kind,
6963
+ (node2, err) => failures.push({ node: node2, err })
6964
+ )
6965
+ );
6908
6966
  }
6909
6967
  const removeNodeQueue = await Promise.all(queue);
6910
6968
  for (const node of removeNodeQueue) {
@@ -6912,6 +6970,14 @@ var outdated = async (state) => {
6912
6970
  removeNode(state, node);
6913
6971
  }
6914
6972
  }
6973
+ if (failures.length) {
6974
+ console.warn(
6975
+ error("Could not retrieve registry versions", {
6976
+ found: failures.map(({ node }) => String(node.name)).sort(),
6977
+ cause: failures[0]?.err
6978
+ })
6979
+ );
6980
+ }
6915
6981
  return state;
6916
6982
  };
6917
6983
 
@@ -8519,6 +8585,7 @@ var setMethodToJSON = (node) => {
8519
8585
  var Query = class {
8520
8586
  #cache;
8521
8587
  #diffFiles;
8588
+ #getAuthHeader;
8522
8589
  #edges;
8523
8590
  #nodes;
8524
8591
  #importers;
@@ -8562,10 +8629,12 @@ var Query = class {
8562
8629
  retries,
8563
8630
  securityArchive,
8564
8631
  hostContexts,
8565
- diffFiles
8632
+ diffFiles,
8633
+ getAuthHeader
8566
8634
  }) {
8567
8635
  this.#cache = /* @__PURE__ */ new Map();
8568
8636
  this.#diffFiles = diffFiles;
8637
+ this.#getAuthHeader = getAuthHeader;
8569
8638
  this.#edges = edges;
8570
8639
  this.#nodes = nodes;
8571
8640
  this.#importers = importers;
@@ -8791,6 +8860,7 @@ var Query = class {
8791
8860
  },
8792
8861
  comment: "",
8793
8862
  diffFiles: this.#diffFiles,
8863
+ getAuthHeader: this.#getAuthHeader,
8794
8864
  loose,
8795
8865
  importers,
8796
8866
  partial: { nodes, edges },
@@ -8882,6 +8952,7 @@ var asDependency = (obj) => {
8882
8952
  }
8883
8953
  return obj;
8884
8954
  };
8955
+ var addKey = (spec2) => spec2.name === "(unknown)" ? spec2.spec : spec2.name;
8885
8956
  var shorten = (typeLong, name, manifest) => {
8886
8957
  const shortName = dependencyTypes.get(typeLong);
8887
8958
  if (!shortName) {
@@ -8899,6 +8970,7 @@ var shorten = (typeLong, name, manifest) => {
8899
8970
  return "peer";
8900
8971
  };
8901
8972
  var isStringArray = (a) => Array.isArray(a) && !a.some((b) => typeof b !== "string");
8973
+ var shouldInstallDepType = (node, depType) => depType !== "devDependencies" || node.importer || node.id.startsWith("git");
8902
8974
  var getRawDependencies = (node) => {
8903
8975
  const dependencies = /* @__PURE__ */ new Map();
8904
8976
  const bundleDeps = node.manifest?.bundleDependencies ?? [];
@@ -8911,6 +8983,9 @@ var getRawDependencies = (node) => {
8911
8983
  if (obj) {
8912
8984
  for (const [name, bareSpec] of Object.entries(obj)) {
8913
8985
  if (bundled.has(name)) continue;
8986
+ if (depType === "peerDependencies" && dependencies.has(name)) {
8987
+ continue;
8988
+ }
8914
8989
  dependencies.set(name, {
8915
8990
  name,
8916
8991
  type: depType,
@@ -9082,20 +9157,20 @@ var lockfileData = ({
9082
9157
  const cleanRegistries = isRecordStringString(registries) ? removeDefaultItems(defaultRegistries, registries) : void 0;
9083
9158
  const cleanScopeRegistries = isRecordStringString(scopeRegistries) ? removeDefaultItems(defaultScopeRegistries, scopeRegistries) : void 0;
9084
9159
  const cleanJsrRegistries = isRecordStringString(jsrRegistries) ? removeDefaultItems(defaultJsrRegistries, jsrRegistries) : void 0;
9085
- const hasItems = (clean) => clean && Object.keys(clean).length;
9160
+ const hasItems2 = (clean) => clean && Object.keys(clean).length;
9086
9161
  return {
9087
9162
  lockfileVersion: LOCKFILE_VERSION,
9088
9163
  options: {
9089
- ...hasItems(cleanModifiers) ? { modifiers: cleanModifiers } : {},
9090
- ...hasItems(catalog) ? { catalog } : {},
9091
- ...hasItems(catalogs) ? { catalogs } : {},
9092
- ...hasItems(cleanScopeRegistries) ? { "scoped-registries": cleanScopeRegistries } : void 0,
9093
- ...hasItems(cleanJsrRegistries) ? { "jsr-registries": cleanJsrRegistries } : void 0,
9164
+ ...hasItems2(cleanModifiers) ? { modifiers: cleanModifiers } : {},
9165
+ ...hasItems2(catalog) ? { catalog } : {},
9166
+ ...hasItems2(catalogs) ? { catalogs } : {},
9167
+ ...hasItems2(cleanScopeRegistries) ? { "scoped-registries": cleanScopeRegistries } : void 0,
9168
+ ...hasItems2(cleanJsrRegistries) ? { "jsr-registries": cleanJsrRegistries } : void 0,
9094
9169
  ...registry2 !== void 0 ? { registry: registry2 } : void 0,
9095
9170
  ...defaultRegistryAlias !== void 0 && defaultRegistryAlias !== defaultRegistryName ? { "default-registry-alias": defaultRegistryAlias } : void 0,
9096
- ...hasItems(cleanRegistries) ? { registries: cleanRegistries } : void 0,
9097
- ...hasItems(cleanGitHosts) ? { "git-hosts": cleanGitHosts } : void 0,
9098
- ...hasItems(cleanGitHostArchives) ? { "git-host-archives": cleanGitHostArchives } : void 0
9171
+ ...hasItems2(cleanRegistries) ? { registries: cleanRegistries } : void 0,
9172
+ ...hasItems2(cleanGitHosts) ? { "git-hosts": cleanGitHosts } : void 0,
9173
+ ...hasItems2(cleanGitHostArchives) ? { "git-host-archives": cleanGitHostArchives } : void 0
9099
9174
  },
9100
9175
  nodes: formatNodes(
9101
9176
  graph.nodes.values(),
@@ -9238,10 +9313,35 @@ var Graph = class {
9238
9313
  * the ideal builder must reset edges and rebuild the graph.
9239
9314
  */
9240
9315
  optionsChanged = false;
9316
+ /**
9317
+ * The individual differences behind `optionsChanged`, in the order
9318
+ * they are reported to the user. Empty when nothing changed.
9319
+ */
9320
+ optionsChanges = [];
9321
+ /**
9322
+ * Whether the lockfile on disk no longer matches this graph even
9323
+ * though no node changed, e.g. an importer edge spec was rewritten to
9324
+ * the value saved to `package.json`, or the config options the graph
9325
+ * was built with differ from the ones stored in the lockfile. Reify
9326
+ * saves the lockfiles from its no-diff early return when set.
9327
+ */
9328
+ lockfileStale = false;
9329
+ /**
9330
+ * Lockfile edge targets captured immediately before `resetEdges()`.
9331
+ * Used by the ideal builder to reuse locked resolutions across a rebuild.
9332
+ */
9333
+ lockedResolutions;
9241
9334
  /**
9242
9335
  * Tracks the current peer context index.
9243
9336
  */
9244
9337
  currentPeerContextIndex = 0;
9338
+ /**
9339
+ * Count of structural writes: nodes placed or removed, edges reset,
9340
+ * created or re-pointed. Never reset; callers snapshot it and compare,
9341
+ * so the ideal builder can tell whether a rebuild touched anything the
9342
+ * peer identities depend on.
9343
+ */
9344
+ mutations = 0;
9245
9345
  constructor(options) {
9246
9346
  const { mainManifest, monorepo } = options;
9247
9347
  this.#options = options;
@@ -9318,39 +9418,89 @@ var Graph = class {
9318
9418
  * Delete all nodes and edges that are unreachable from the importers.
9319
9419
  * The collection of deleted nodes is returned.
9320
9420
  *
9321
- * NOTE: This can be extremely slow for large graphs, and is almost always
9322
- * unnecessary! Only call when it is known that some unreachable nodes may
9323
- * have been created, for example when deleting the unneeded subgraph when an
9421
+ * Marking is O(N+E) and always runs, but when nothing is unreachable
9422
+ * and every reachable node is registered under its own id, `nodes` is
9423
+ * left alone — no rebuild, no sort, and an empty map is returned.
9424
+ * Only call when it is known that some unreachable nodes may have been
9425
+ * created, for example when deleting the unneeded subgraph when an
9324
9426
  * optional node fails to resolve/install.
9325
9427
  */
9326
9428
  gc() {
9327
9429
  const { nodes } = this;
9328
9430
  this.edges.clear();
9329
- this.nodes = /* @__PURE__ */ new Map();
9330
9431
  const marked = new Set(this.importers);
9331
- for (const imp of marked) {
9332
- nodes.delete(imp.id);
9333
- this.nodes.set(imp.id, imp);
9334
- for (const edge of imp.edgesOut.values()) {
9432
+ for (const node of marked) {
9433
+ for (const edge of node.edgesOut.values()) {
9335
9434
  this.edges.add(edge);
9336
9435
  const { to } = edge;
9337
- if (!to || marked.has(to)) continue;
9338
- marked.add(to);
9339
- nodes.delete(to.id);
9340
- this.nodes.set(to.id, to);
9436
+ if (to) marked.add(to);
9341
9437
  }
9342
9438
  }
9439
+ let intact = marked.size === nodes.size;
9440
+ if (intact) {
9441
+ for (const node of marked) {
9442
+ if (nodes.get(node.id) !== node) {
9443
+ intact = false;
9444
+ break;
9445
+ }
9446
+ }
9447
+ }
9448
+ if (intact) return /* @__PURE__ */ new Map();
9449
+ this.nodes = /* @__PURE__ */ new Map();
9450
+ for (const node of marked) {
9451
+ nodes.delete(node.id);
9452
+ this.nodes.set(node.id, node);
9453
+ }
9343
9454
  for (const node of nodes.values()) {
9344
9455
  this.removeNode(node);
9345
9456
  }
9457
+ this.sortNodes();
9346
9458
  return nodes;
9347
9459
  }
9460
+ /**
9461
+ * Rebuild `nodes` so importers come first, then remaining nodes in
9462
+ * DepID order. Matches lockfile save so a built graph and a
9463
+ * save→load round-trip iterate the same way.
9464
+ *
9465
+ * A graph coming from the lockfile loader is already in that order, so
9466
+ * the pass below detects it and keeps the map as-is.
9467
+ */
9468
+ sortNodes() {
9469
+ const cmp = (a, b) => a.id.localeCompare(b.id, "en");
9470
+ const rest = [];
9471
+ const importers = this.importers.values();
9472
+ let seenImporters = 0;
9473
+ let sorted = true;
9474
+ let prev;
9475
+ for (const node of this.nodes.values()) {
9476
+ if (this.importers.has(node)) {
9477
+ if (rest.length || importers.next().value !== node) {
9478
+ sorted = false;
9479
+ }
9480
+ seenImporters++;
9481
+ continue;
9482
+ }
9483
+ if (sorted && prev && cmp(prev, node) > 0) sorted = false;
9484
+ prev = node;
9485
+ rest.push(node);
9486
+ }
9487
+ if (sorted && seenImporters === this.importers.size) return;
9488
+ rest.sort(cmp);
9489
+ const nodes = /* @__PURE__ */ new Map();
9490
+ for (const node of this.importers) nodes.set(node.id, node);
9491
+ for (const node of rest) nodes.set(node.id, node);
9492
+ this.nodes = nodes;
9493
+ }
9348
9494
  /**
9349
9495
  * Create a new edge between two nodes of the graph in case both exist,
9350
9496
  * in case the destination node does not exists, then a dangling edge,
9351
9497
  * pointing to nothing will be created to represent that missing dependency.
9352
9498
  */
9353
9499
  addEdge(type2, spec2, from, to) {
9500
+ if (to) {
9501
+ const toNode = to;
9502
+ toNode.detached = false;
9503
+ }
9354
9504
  if (spec2.name === "(unknown)") {
9355
9505
  if (to) {
9356
9506
  spec2.name = to.name || "(unknown)";
@@ -9368,6 +9518,7 @@ var Graph = class {
9368
9518
  const edge = existing;
9369
9519
  if (edge.type === type2 && edge.spec.bareSpec === spec2.bareSpec) {
9370
9520
  if (to && to !== edge.to) {
9521
+ this.mutations++;
9371
9522
  edge.to?.edgesIn.delete(edge);
9372
9523
  edge.to = to;
9373
9524
  edge.to.edgesIn.add(edge);
@@ -9376,6 +9527,7 @@ var Graph = class {
9376
9527
  }
9377
9528
  this.edges.delete(edge);
9378
9529
  }
9530
+ this.mutations++;
9379
9531
  const f = from;
9380
9532
  const edgeOut = f.addEdgesTo(
9381
9533
  resolveSaveType(from, spec2.name, type2),
@@ -9392,22 +9544,28 @@ var Graph = class {
9392
9544
  const f = spec2.final;
9393
9545
  const sf = getResolutionCacheKey(f, fromNode.location, extra);
9394
9546
  const cached = this.resolutions.get(sf);
9395
- if (cached) return cached;
9547
+ if (cached && !cached.detached) return cached;
9396
9548
  const nbn = this.nodesByName.get(f.name);
9397
9549
  if (!nbn) return void 0;
9550
+ const sat = (n) => satisfies2(
9551
+ n.id,
9552
+ f,
9553
+ fromNode.location,
9554
+ this.projectRoot,
9555
+ this.monorepo
9556
+ );
9557
+ let detached = cached?.detached && sat(cached) ? cached : void 0;
9398
9558
  for (const node of nbn) {
9399
- if (satisfies2(
9400
- node.id,
9401
- f,
9402
- fromNode.location,
9403
- this.projectRoot,
9404
- this.monorepo
9405
- )) {
9406
- this.resolutions.set(sf, node);
9407
- this.resolutionsReverse.get(node)?.add(sf);
9408
- return node;
9559
+ if (!sat(node)) continue;
9560
+ if (node.detached) {
9561
+ detached ??= node;
9562
+ continue;
9409
9563
  }
9564
+ this.resolutions.set(sf, node);
9565
+ this.resolutionsReverse.get(node)?.add(sf);
9566
+ return node;
9410
9567
  }
9568
+ return detached;
9411
9569
  }
9412
9570
  /**
9413
9571
  * Create a new node in the graph.
@@ -9425,7 +9583,7 @@ var Graph = class {
9425
9583
  const nbn = this.nodesByName.get(node.name) ?? /* @__PURE__ */ new Set();
9426
9584
  nbn.add(node);
9427
9585
  const newByNameSet = new Set(
9428
- [...nbn].sort((a, b) => a.id.localeCompare(b.id))
9586
+ [...nbn].sort((a, b) => a.id.localeCompare(b.id, "en"))
9429
9587
  );
9430
9588
  this.nodesByName.set(node.name, newByNameSet);
9431
9589
  if (manifest) {
@@ -9442,6 +9600,7 @@ var Graph = class {
9442
9600
  * consider using `addNode()` and `addEdge()` instead.
9443
9601
  */
9444
9602
  placePackage(fromNode, depType, spec2, manifest, id2, extra) {
9603
+ this.mutations++;
9445
9604
  if (!manifest && !id2) {
9446
9605
  this.addEdge(depType, spec2, fromNode);
9447
9606
  return;
@@ -9472,10 +9631,23 @@ var Graph = class {
9472
9631
  }
9473
9632
  return toFoundNode;
9474
9633
  }
9634
+ let samePackage;
9635
+ const base = baseDepID(depId);
9636
+ const nbn = this.nodesByName.get(manifest?.name ?? spec2.name);
9637
+ if (nbn) {
9638
+ for (const n of nbn) {
9639
+ if (baseDepID(n.id) !== base) continue;
9640
+ if (!n.integrity && !n.resolved) continue;
9641
+ samePackage = n;
9642
+ if (n.integrity) break;
9643
+ }
9644
+ }
9475
9645
  const toNode = this.addNode(depId, manifest, spec2);
9476
9646
  toNode.registry = spec2.registry;
9477
9647
  toNode.dev = flags.dev;
9478
9648
  toNode.optional = flags.optional;
9649
+ if (samePackage) copyPackageMetadata(toNode, samePackage);
9650
+ toNode.integrity ??= manifest?.dist?.integrity;
9479
9651
  if (extra) {
9480
9652
  const { modifier, peerSetHash } = splitExtra(extra);
9481
9653
  toNode.modifier = modifier;
@@ -9519,6 +9691,7 @@ var Graph = class {
9519
9691
  * if it is valid to do so.
9520
9692
  */
9521
9693
  removeNode(node, replacement, keepEdges) {
9694
+ this.mutations++;
9522
9695
  this.nodes.delete(node.id);
9523
9696
  const nbn = this.nodesByName.get(node.name);
9524
9697
  if (nbn?.size === 1) this.nodesByName.delete(node.name);
@@ -9552,6 +9725,7 @@ var Graph = class {
9552
9725
  * Removes the resolved node of a given edge.
9553
9726
  */
9554
9727
  removeEdgeResolution(edge, extra = "") {
9728
+ this.mutations++;
9555
9729
  const node = edge.to;
9556
9730
  const resolutionKey = getResolutionCacheKey(
9557
9731
  edge.spec,
@@ -9574,9 +9748,10 @@ var Graph = class {
9574
9748
  * This allows the graph to be reconstructed efficiently using the existing nodes.
9575
9749
  */
9576
9750
  resetEdges() {
9751
+ this.mutations++;
9577
9752
  this.edges.clear();
9578
9753
  for (const node of this.nodes.values()) {
9579
- node.detached = true;
9754
+ node.detached = !node.importer;
9580
9755
  node.edgesOut.clear();
9581
9756
  node.edgesIn.clear();
9582
9757
  }
@@ -9743,7 +9918,7 @@ var loadNodes = (graph, nodes, options, actual2, throwOnMissingManifest) => {
9743
9918
  );
9744
9919
  }
9745
9920
  let version;
9746
- if (type2 === "registry" && registrySpec && registrySpec.indexOf("@") > 0) {
9921
+ if (type2 === "registry" && registrySpec && registrySpec.indexOf("@", 1) > 0) {
9747
9922
  if (registryVersionCache) {
9748
9923
  const seenVersion = registryVersionCache.get(registrySpec);
9749
9924
  if (seenVersion) {
@@ -9836,8 +10011,8 @@ var removeDefaultItems2 = (defaultItems, items) => {
9836
10011
  }
9837
10012
  return res;
9838
10013
  };
10014
+ var hasItems = (o) => !!o && Object.keys(o).length > 0;
9839
10015
  var buildCurrentOptions = (options) => {
9840
- const hasItems = (o) => o && Object.keys(o).length > 0;
9841
10016
  const cleanModifiers = options.modifiers && isRecordStringString(options.modifiers.config) ? options.modifiers.config : void 0;
9842
10017
  const cleanRegistries = isRecordStringString(options.registries) ? removeDefaultItems2(defaultRegistries, options.registries) : void 0;
9843
10018
  const cleanScopeRegistries = isRecordStringString(options["scoped-registries"]) ? removeDefaultItems2(
@@ -9866,6 +10041,56 @@ var buildCurrentOptions = (options) => {
9866
10041
  ...hasItems(cleanGitHostArchives) ? { "git-host-archives": cleanGitHostArchives } : void 0
9867
10042
  };
9868
10043
  };
10044
+ var asRecord = (o) => o;
10045
+ var unionKeys = (a = {}, b = {}) => [.../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])].sort();
10046
+ var diffRecords = (section, from = {}, to = {}, changes, prefix) => {
10047
+ for (const key of unionKeys(from, to)) {
10048
+ if (from[key] !== to[key]) {
10049
+ changes.push({
10050
+ section,
10051
+ key: prefix === void 0 ? key : `${prefix} ${key}`,
10052
+ from: from[key],
10053
+ to: to[key]
10054
+ });
10055
+ }
10056
+ }
10057
+ };
10058
+ var diffLockfileOptions = (options, lockfileOptions = {}) => {
10059
+ const current = asRecord(buildCurrentOptions(options));
10060
+ const stored = asRecord(lockfileOptions);
10061
+ const changes = [];
10062
+ for (const section of unionKeys(stored, current)) {
10063
+ const from = stored[section];
10064
+ const to = current[section];
10065
+ if (typeof from === "string" || typeof to === "string") {
10066
+ if (from !== to) {
10067
+ changes.push({
10068
+ section,
10069
+ from,
10070
+ to
10071
+ });
10072
+ }
10073
+ } else if (section === "catalogs") {
10074
+ for (const name of unionKeys(from, to)) {
10075
+ diffRecords(
10076
+ section,
10077
+ from?.[name],
10078
+ to?.[name],
10079
+ changes,
10080
+ name
10081
+ );
10082
+ }
10083
+ } else {
10084
+ diffRecords(
10085
+ section,
10086
+ from,
10087
+ to,
10088
+ changes
10089
+ );
10090
+ }
10091
+ }
10092
+ return changes;
10093
+ };
9869
10094
  var loadObject = (options, lockfileData2) => {
9870
10095
  const version = lockfileData2.lockfileVersion;
9871
10096
  if (version == null) {
@@ -9902,13 +10127,17 @@ var loadObject = (options, lockfileData2) => {
9902
10127
  /* c8 ignore next */
9903
10128
  } = lockfileData2.options ?? {};
9904
10129
  const scopeRegistries = scopedRegistriesOption ?? lockfileData2.options["scope-registries"];
9905
- const currentOptions = buildCurrentOptions(options);
9906
10130
  const lockfileOptions = lockfileData2.options ?? {};
9907
- const optionsChanged = JSON.stringify(currentOptions) !== JSON.stringify(lockfileOptions);
10131
+ const optionsChanges = diffLockfileOptions(options, lockfileOptions);
9908
10132
  const mergedOptions = {
9909
10133
  ...options,
9910
- catalog,
9911
- catalogs,
10134
+ // catalog entries are indirections resolved at parse time: prefer
10135
+ // the current config so loading a lockfile (e.g. the hidden
10136
+ // lockfile cache) yields the same edge specs as a fresh scan of
10137
+ // the project; fall back to the stored values so a standalone
10138
+ // lockfile still parses when the config no longer defines them
10139
+ catalog: hasItems(options.catalog) ? options.catalog : catalog,
10140
+ catalogs: hasItems(options.catalogs) ? options.catalogs : catalogs,
9912
10141
  "scoped-registries": scopeRegistries ? { ...options["scoped-registries"], ...scopeRegistries } : options["scoped-registries"],
9913
10142
  registry: registry2 ?? options.registry,
9914
10143
  "default-registry-alias": defaultRegistryAlias ?? options["default-registry-alias"],
@@ -9921,7 +10150,8 @@ var loadObject = (options, lockfileData2) => {
9921
10150
  mainManifest,
9922
10151
  monorepo
9923
10152
  });
9924
- graph.optionsChanged = optionsChanged;
10153
+ graph.optionsChanges = optionsChanges;
10154
+ graph.optionsChanged = optionsChanges.length > 0;
9925
10155
  loadNodes(
9926
10156
  graph,
9927
10157
  lockfileData2.nodes,
@@ -9931,7 +10161,11 @@ var loadObject = (options, lockfileData2) => {
9931
10161
  );
9932
10162
  const specCache = options.modifiers || !options.specCache ? void 0 : {
9933
10163
  specCache: options.specCache,
9934
- prefix: JSON.stringify(lockfileOptions)
10164
+ prefix: JSON.stringify([
10165
+ lockfileOptions,
10166
+ mergedOptions.catalog,
10167
+ mergedOptions.catalogs
10168
+ ])
9935
10169
  };
9936
10170
  loadEdges(graph, lockfileData2.edges, mergedOptions, specCache);
9937
10171
  for (const node of graph.nodes.values()) {
@@ -9940,6 +10174,7 @@ var loadObject = (options, lockfileData2) => {
9940
10174
  node.registry = firstEdge.spec.registry;
9941
10175
  }
9942
10176
  }
10177
+ graph.sortNodes();
9943
10178
  return graph;
9944
10179
  };
9945
10180
 
@@ -10206,6 +10441,7 @@ var load4 = (options) => {
10206
10441
  });
10207
10442
  }
10208
10443
  }
10444
+ graph.sortNodes();
10209
10445
  done();
10210
10446
  return graph;
10211
10447
  };
@@ -11188,6 +11424,8 @@ var specificitySort = (breadcrumbs) => {
11188
11424
  };
11189
11425
 
11190
11426
  // ../../src/graph/src/modifiers.ts
11427
+ var matchesImporter = (item, importer) => item.importer && (item.value === ":project" || item.value === ":root" && importer.mainImporter || item.value === ":workspace" && importer.importer);
11428
+ var matchesQualifier = (item, spec2) => !!item?.comparator({ semver: spec2.semver });
11191
11429
  var GraphModifier = class _GraphModifier {
11192
11430
  /** The loaded modifiers configuration */
11193
11431
  #config;
@@ -11229,6 +11467,27 @@ var GraphModifier = class _GraphModifier {
11229
11467
  if (this.#config) return this.#config;
11230
11468
  return this.#config = load("modifiers", assertRecordStringString) ?? {};
11231
11469
  }
11470
+ /**
11471
+ * Whether a modifier governs the direct `importer -> spec.name` edge,
11472
+ * i.e. whether its value comes from config rather than from the
11473
+ * manifest that declares it. Only a breadcrumb whose whole scope is
11474
+ * that edge counts, and only if its qualifier accepts the spec:
11475
+ * `:root > #a > #b` names b but governs an edge under a, and
11476
+ * `#b:semver(^1)` leaves a `^2` edge alone, so in both cases the
11477
+ * root's own b edge is still the manifest's to validate and heal.
11478
+ */
11479
+ targetsImporterEdge(importer, spec2) {
11480
+ for (const { breadcrumb } of this.#modifiers) {
11481
+ const { last } = breadcrumb;
11482
+ if (last.name !== spec2.name) continue;
11483
+ if (!matchesQualifier(last, spec2)) continue;
11484
+ const { prev } = last;
11485
+ if (!prev) return true;
11486
+ if (prev.prev) continue;
11487
+ if (matchesImporter(prev, importer)) return true;
11488
+ }
11489
+ return false;
11490
+ }
11232
11491
  /**
11233
11492
  * Loads the modifiers defined in `vlt.json` into memory.
11234
11493
  */
@@ -11281,11 +11540,7 @@ var GraphModifier = class _GraphModifier {
11281
11540
  */
11282
11541
  tryImporter(importer) {
11283
11542
  for (const modifier of this.#modifiers) {
11284
- const { first } = modifier.breadcrumb;
11285
- const matchRoot = first.value === ":root" && importer.mainImporter;
11286
- const matchWorkspace = first.value === ":workspace" && importer.importer;
11287
- const matchAny = first.value === ":project" || matchRoot || matchWorkspace;
11288
- if (first.importer && matchAny) {
11543
+ if (matchesImporter(modifier.breadcrumb.first, importer)) {
11289
11544
  const active = this.newModifier(importer, modifier);
11290
11545
  const single = active.modifier.breadcrumb.single;
11291
11546
  if (!single) {
@@ -11311,7 +11566,7 @@ var GraphModifier = class _GraphModifier {
11311
11566
  * iteration.
11312
11567
  */
11313
11568
  tryNewDependency(from, spec2) {
11314
- const { name, semver } = spec2;
11569
+ const { name } = spec2;
11315
11570
  const all = /* @__PURE__ */ new Map();
11316
11571
  for (const modifier of this.#modifiers) {
11317
11572
  const entry = this.#activeEntries.get(modifier)?.get(name)?.get(from);
@@ -11338,9 +11593,7 @@ var GraphModifier = class _GraphModifier {
11338
11593
  return all.get(
11339
11594
  specificitySort(
11340
11595
  entries.filter(
11341
- (i) => i.interactiveBreadcrumb.current?.comparator({
11342
- semver
11343
- })
11596
+ (i) => matchesQualifier(i.interactiveBreadcrumb.current, spec2)
11344
11597
  ).map((i) => i.modifier.breadcrumb)
11345
11598
  )[0]
11346
11599
  );
@@ -11560,8 +11813,10 @@ var init = async ({
11560
11813
  // ../../src/graph/src/ideal/remove-satisfied-specs.ts
11561
11814
  var removeSatisfiedSpecs = ({
11562
11815
  add,
11563
- graph
11816
+ graph,
11817
+ modifiers
11564
11818
  }) => {
11819
+ const staleSpecs = /* @__PURE__ */ new Map();
11565
11820
  for (const [depID, dependencies] of add.entries()) {
11566
11821
  const importer = graph.nodes.get(depID);
11567
11822
  if (!importer) {
@@ -11584,6 +11839,9 @@ var removeSatisfiedSpecs = ({
11584
11839
  graph.projectRoot,
11585
11840
  graph.monorepo
11586
11841
  )) {
11842
+ if ((edge.spec.bareSpec !== dependency.spec.bareSpec || dependency.type !== "implicit" && edge.type !== dependency.type) && !modifiers?.targetsImporterEdge(importer, dependency.spec)) {
11843
+ staleSpecs.set(edge, dependency);
11844
+ }
11587
11845
  dependencies.delete(name);
11588
11846
  }
11589
11847
  }
@@ -11593,6 +11851,7 @@ var removeSatisfiedSpecs = ({
11593
11851
  add.delete(depID);
11594
11852
  }
11595
11853
  }
11854
+ return staleSpecs;
11596
11855
  };
11597
11856
 
11598
11857
  // ../../src/graph/src/ideal/get-importer-specs.ts
@@ -11623,29 +11882,35 @@ var getImporterSpecs = (options) => {
11623
11882
  removeResult.modifiedDependencies = true;
11624
11883
  }
11625
11884
  }
11885
+ const declared = /* @__PURE__ */ new Map();
11626
11886
  for (const depType of longDependencyTypes) {
11627
11887
  const deps = Object.entries(importer.manifest?.[depType] ?? {});
11628
11888
  for (const [depName, depSpec] of deps) {
11629
- const edge = importer.edgesOut.get(depName);
11630
- if (edge?.to && depSpec === edge.spec.bareSpec) continue;
11631
- const spec2 = Spec2.parse(depName, depSpec, options);
11632
- if (spec2.type === "workspace" && !edge?.to) {
11633
- const wsExists = [...graph.importers].some(
11634
- (n) => n.name === depName
11635
- );
11636
- if (!wsExists) {
11637
- removeDeps.add(depName);
11638
- removeResult.modifiedDependencies = true;
11639
- continue;
11640
- }
11889
+ if (depType === "peerDependencies" && declared.has(depName)) {
11890
+ continue;
11641
11891
  }
11642
- const dependency = asDependency({
11643
- spec: spec2,
11644
- type: shorten(depType, depName, importer.manifest)
11645
- });
11646
- addDeps.set(depName, dependency);
11892
+ declared.set(depName, [depType, depSpec]);
11647
11893
  }
11648
11894
  }
11895
+ for (const [depName, [depType, depSpec]] of declared) {
11896
+ const edge = importer.edgesOut.get(depName);
11897
+ const type2 = shorten(depType, depName, importer.manifest);
11898
+ if (edge?.to && edge.spec.bareSpec === depSpec && edge.type === type2) {
11899
+ continue;
11900
+ }
11901
+ const spec2 = Spec2.parse(depName, depSpec, options);
11902
+ if (spec2.type === "workspace" && !edge?.to) {
11903
+ const wsExists = [...graph.importers].some(
11904
+ (n) => n.name === depName
11905
+ );
11906
+ if (!wsExists) {
11907
+ removeDeps.add(depName);
11908
+ removeResult.modifiedDependencies = true;
11909
+ continue;
11910
+ }
11911
+ }
11912
+ addDeps.set(depName, asDependency({ spec: spec2, type: type2 }));
11913
+ }
11649
11914
  addResult.set(importer.id, addDeps);
11650
11915
  removeResult.set(importer.id, removeDeps);
11651
11916
  }
@@ -11670,7 +11935,7 @@ var getImporterSpecs = (options) => {
11670
11935
  const deps = Object.entries(manifest[depType] ?? {});
11671
11936
  for (const [depName, depSpec] of deps) {
11672
11937
  const edge = node.edgesOut.get(depName);
11673
- if (edge?.to && depSpec === edge.spec.bareSpec) continue;
11938
+ if (edge?.to && edge.spec.bareSpec === depSpec) continue;
11674
11939
  const dependency = asDependency({
11675
11940
  spec: Spec2.parse(depName, depSpec, options),
11676
11941
  type: shorten(depType, depName, manifest)
@@ -11720,10 +11985,16 @@ var getImporterSpecs = (options) => {
11720
11985
  removeResult.delete(key);
11721
11986
  }
11722
11987
  }
11723
- removeSatisfiedSpecs({
11988
+ const staleSpecs = removeSatisfiedSpecs({
11724
11989
  add: addResult,
11725
- graph
11990
+ graph,
11991
+ modifiers: options.modifiers
11726
11992
  });
11993
+ for (const edge of staleSpecs.keys()) {
11994
+ if (add.get(edge.from.id)?.has(edge.name)) {
11995
+ staleSpecs.delete(edge);
11996
+ }
11997
+ }
11727
11998
  for (const addDeps of addResult.values()) {
11728
11999
  if (addDeps.size > 0) {
11729
12000
  addResult.modifiedDependencies = true;
@@ -11733,11 +12004,409 @@ var getImporterSpecs = (options) => {
11733
12004
  return {
11734
12005
  add: addResult,
11735
12006
  remove: removeResult,
12007
+ staleSpecs,
11736
12008
  transientAdd,
11737
12009
  transientRemove
11738
12010
  };
11739
12011
  };
11740
12012
 
12013
+ // ../../src/graph/src/ideal/canonicalize-peer-ids.ts
12014
+ import { createHash } from "node:crypto";
12015
+ var NUL = "\0";
12016
+ var NL = "\n";
12017
+ var MISSING = "MISSING";
12018
+ var PEER_PREFIX = "peer.";
12019
+ var peerEnvDigest = (input, length = 16) => createHash("sha256").update(input).digest("hex").slice(0, length);
12020
+ var isProvisionalPeerSuffix = (suffix) => /^peer\.\d{1,15}$/.test(suffix);
12021
+ var hasProvisionalPeerIds = (graph) => {
12022
+ for (const node of graph.nodes.values()) {
12023
+ if (node.peerSetHash && isProvisionalPeerSuffix(node.peerSetHash)) {
12024
+ return true;
12025
+ }
12026
+ }
12027
+ return false;
12028
+ };
12029
+ var byteCompare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
12030
+ var canCarryPeerSuffix = (node) => {
12031
+ if (node.importer) return false;
12032
+ const type2 = splitDepID(node.id)[0];
12033
+ return type2 !== "file" && type2 !== "workspace";
12034
+ };
12035
+ var hasPeerDependencies = (node, graph) => {
12036
+ const mani = node.manifest ?? graph.manifests.get(node.id);
12037
+ return !!(mani?.peerDependencies && Object.keys(mani.peerDependencies).length > 0);
12038
+ };
12039
+ var hasPeerOutEdge = (node) => {
12040
+ for (const edge of node.edgesOut.values()) {
12041
+ if (edge.type === "peer" || edge.type === "peerOptional") {
12042
+ return true;
12043
+ }
12044
+ }
12045
+ return false;
12046
+ };
12047
+ var isPeerScoped = (node, graph) => {
12048
+ if (!canCarryPeerSuffix(node)) return false;
12049
+ return !!node.peerSetHash || hasPeerDependencies(node, graph) || hasPeerOutEdge(node);
12050
+ };
12051
+ var serializeEdges = (node, ref) => {
12052
+ const entries = [];
12053
+ for (const edge of node.edgesOut.values()) {
12054
+ entries.push(
12055
+ `${edge.name}${NUL}${edge.type}${NUL}${edge.to ? ref(edge.to) : MISSING}`
12056
+ );
12057
+ }
12058
+ entries.sort(byteCompare);
12059
+ return entries.join(NL);
12060
+ };
12061
+ var serializeNodeEnv = (node, opts = {}) => {
12062
+ const { resolvedIds, intraSccIndex } = opts;
12063
+ return serializeEdges(node, (to) => {
12064
+ const idx = intraSccIndex?.get(to);
12065
+ return idx === void 0 ? resolvedIds?.get(to) ?? to.id : `#${idx}`;
12066
+ });
12067
+ };
12068
+ var withPeerSuffix = (node, peerSetHash) => {
12069
+ const extra = joinExtra({
12070
+ modifier: node.modifier,
12071
+ peerSetHash
12072
+ });
12073
+ const parsed = splitDepID(node.id);
12074
+ switch (parsed[0]) {
12075
+ case "registry":
12076
+ return joinDepIDTuple(["registry", parsed[1], parsed[2], extra]);
12077
+ case "git":
12078
+ return joinDepIDTuple(["git", parsed[1], parsed[2], extra]);
12079
+ case "remote":
12080
+ return joinDepIDTuple(["remote", parsed[1], extra]);
12081
+ /* c8 ignore next 2 */
12082
+ default:
12083
+ return node.id;
12084
+ }
12085
+ };
12086
+ var peerlessDepID = (node) => withPeerSuffix(node, "");
12087
+ var unitEntry = (node, opts) => `${peerlessDepID(node)}${NL}${serializeNodeEnv(node, opts)}`;
12088
+ var tarjan = (nodes, adj) => {
12089
+ let index = 0;
12090
+ const indices = /* @__PURE__ */ new Map();
12091
+ const lowlink = /* @__PURE__ */ new Map();
12092
+ const onStack = /* @__PURE__ */ new Set();
12093
+ const stack = [];
12094
+ const sccs = [];
12095
+ const strongconnect = (v) => {
12096
+ indices.set(v, index);
12097
+ lowlink.set(v, index);
12098
+ index++;
12099
+ stack.push(v);
12100
+ onStack.add(v);
12101
+ const neighbors = adj.get(v) ?? [];
12102
+ for (const w of neighbors) {
12103
+ if (!indices.has(w)) {
12104
+ strongconnect(w);
12105
+ const lv = lowlink.get(v);
12106
+ const lw = lowlink.get(w);
12107
+ if (lv !== void 0 && lw !== void 0 && lw < lv) {
12108
+ lowlink.set(v, lw);
12109
+ }
12110
+ } else if (onStack.has(w)) {
12111
+ const lv = lowlink.get(v);
12112
+ const iw = indices.get(w);
12113
+ if (lv !== void 0 && iw !== void 0 && iw < lv) {
12114
+ lowlink.set(v, iw);
12115
+ }
12116
+ }
12117
+ }
12118
+ if (lowlink.get(v) === indices.get(v)) {
12119
+ const scc = [];
12120
+ for (; ; ) {
12121
+ const w = stack.pop();
12122
+ if (!w) break;
12123
+ onStack.delete(w);
12124
+ scc.push(w);
12125
+ if (w === v) break;
12126
+ }
12127
+ sccs.push(scc);
12128
+ }
12129
+ };
12130
+ for (const v of nodes) {
12131
+ if (!indices.has(v)) strongconnect(v);
12132
+ }
12133
+ return sccs;
12134
+ };
12135
+ var hasSelfEdge = (node) => {
12136
+ for (const edge of node.edgesOut.values()) {
12137
+ if (edge.to === node) return true;
12138
+ }
12139
+ return false;
12140
+ };
12141
+ var serializeWithColors = (node, sccSet, color, resolvedIds) => serializeEdges(
12142
+ node,
12143
+ (to) => sccSet.has(to) ? (
12144
+ /* c8 ignore next */
12145
+ color.get(to) ?? MISSING
12146
+ ) : resolvedIds.get(to) ?? to.id
12147
+ );
12148
+ var mapGet = (m, key) => {
12149
+ const v = m.get(key);
12150
+ return v ?? "";
12151
+ };
12152
+ var refineSccOrder = (scc, resolvedIds) => {
12153
+ const sccSet = new Set(scc);
12154
+ const bases = [...new Set(scc.map(peerlessDepID))].sort(byteCompare);
12155
+ const initRank = new Map(bases.map((b, i) => [b, String(i)]));
12156
+ let color = new Map(
12157
+ scc.map((n) => [n, mapGet(initRank, peerlessDepID(n))])
12158
+ );
12159
+ let classCount = new Set(color.values()).size;
12160
+ for (; ; ) {
12161
+ const rows = scc.map((n) => ({
12162
+ n,
12163
+ key: `${peerlessDepID(n)}${NUL}${mapGet(color, n)}${NUL}${serializeWithColors(n, sccSet, color, resolvedIds)}`
12164
+ }));
12165
+ const uniq = [...new Set(rows.map((r) => r.key))].sort(byteCompare);
12166
+ const rank = new Map(uniq.map((k, i) => [k, String(i)]));
12167
+ color = new Map(
12168
+ rows.map((r) => [r.n, mapGet(rank, r.key)])
12169
+ );
12170
+ if (uniq.length === classCount) break;
12171
+ classCount = uniq.length;
12172
+ }
12173
+ const order = [...scc].sort(
12174
+ (a, b) => byteCompare(peerlessDepID(a), peerlessDepID(b)) || byteCompare(mapGet(color, a), mapGet(color, b)) || byteCompare(a.id, b.id)
12175
+ );
12176
+ return { order, colors: color };
12177
+ };
12178
+ var colorClassKey = (node, colors) => `${peerlessDepID(node)}${NUL}${mapGet(colors, node)}`;
12179
+ var mergeNode = (graph, winner, loser) => {
12180
+ winner.dev &&= loser.dev;
12181
+ winner.optional &&= loser.optional;
12182
+ copyPackageMetadata(winner, loser);
12183
+ if (!winner.manifest && loser.manifest) {
12184
+ winner.manifest = loser.manifest;
12185
+ graph.manifests.set(winner.id, loser.manifest);
12186
+ }
12187
+ for (const edge of loser.edgesIn) {
12188
+ edge.to = winner;
12189
+ winner.edgesIn.add(edge);
12190
+ }
12191
+ loser.edgesIn.clear();
12192
+ for (const edge of loser.edgesOut.values()) {
12193
+ graph.edges.delete(edge);
12194
+ edge.to?.edgesIn.delete(edge);
12195
+ }
12196
+ loser.edgesOut.clear();
12197
+ const nbn = graph.nodesByName.get(loser.name);
12198
+ if (nbn) {
12199
+ nbn.delete(loser);
12200
+ if (nbn.size === 0) graph.nodesByName.delete(loser.name);
12201
+ }
12202
+ };
12203
+ var applyAssignments = (graph, keep, losers) => {
12204
+ const newKeys = /* @__PURE__ */ new Map();
12205
+ for (const a of keep) newKeys.set(a.newId, a.node);
12206
+ for (const a of keep) {
12207
+ if (a.node.id !== a.newId && !newKeys.has(a.node.id)) {
12208
+ graph.nodes.delete(a.node.id);
12209
+ graph.manifests.delete(a.node.id);
12210
+ }
12211
+ }
12212
+ for (const loser of losers.keys()) {
12213
+ graph.nodes.delete(loser.id);
12214
+ graph.manifests.delete(loser.id);
12215
+ }
12216
+ const moves = [];
12217
+ for (const a of keep) {
12218
+ if (a.node.id !== a.newId) {
12219
+ const from = a.node.id;
12220
+ a.node.setPeerIdentity(a.newId, a.suffix);
12221
+ if (a.node.extracted) {
12222
+ moves.push({ node: a.node, from, to: a.newId });
12223
+ }
12224
+ } else {
12225
+ a.node.peerSetHash = a.suffix;
12226
+ }
12227
+ }
12228
+ for (const a of keep) {
12229
+ graph.nodes.set(a.newId, a.node);
12230
+ if (a.node.manifest) {
12231
+ graph.manifests.set(a.newId, a.node.manifest);
12232
+ }
12233
+ }
12234
+ for (const [loser, winner] of losers) {
12235
+ if (loser.extracted) {
12236
+ moves.push({ node: loser, from: loser.id });
12237
+ }
12238
+ mergeNode(graph, winner, loser);
12239
+ }
12240
+ const names = /* @__PURE__ */ new Set();
12241
+ for (const a of keep) names.add(a.node.name);
12242
+ for (const loser of losers.keys()) names.add(loser.name);
12243
+ for (const name of names) {
12244
+ const nbn = graph.nodesByName.get(name);
12245
+ if (!nbn) continue;
12246
+ graph.nodesByName.set(
12247
+ name,
12248
+ new Set(
12249
+ [...nbn].sort((a, b) => a.id.localeCompare(b.id, "en"))
12250
+ )
12251
+ );
12252
+ }
12253
+ graph.resolutions.clear();
12254
+ graph.resolutionsReverse.clear();
12255
+ if (losers.size > 0) graph.gc();
12256
+ graph.sortNodes();
12257
+ return moves;
12258
+ };
12259
+ var canonicalizePeerIds = (graph, options = {}) => {
12260
+ const digest = options.digest ?? peerEnvDigest;
12261
+ const scoped = [...graph.nodes.values()].filter((n) => isPeerScoped(n, graph)).sort((a, b) => byteCompare(a.id, b.id));
12262
+ if (!scoped.length) return [];
12263
+ const scopedSet = new Set(scoped);
12264
+ const adj = /* @__PURE__ */ new Map();
12265
+ for (const node of scoped) {
12266
+ const next = [];
12267
+ const seen = /* @__PURE__ */ new Set();
12268
+ for (const edge of node.edgesOut.values()) {
12269
+ const to = edge.to;
12270
+ if (!to || !scopedSet.has(to) || seen.has(to)) continue;
12271
+ seen.add(to);
12272
+ next.push(to);
12273
+ }
12274
+ next.sort((a, b) => byteCompare(a.id, b.id));
12275
+ adj.set(node, next);
12276
+ }
12277
+ const resolvedIds = /* @__PURE__ */ new Map();
12278
+ const keep = [];
12279
+ const byNewId = /* @__PURE__ */ new Map();
12280
+ const losers = /* @__PURE__ */ new Map();
12281
+ const takeSuffix = (node, ser, hex, roleIdx, roleCount) => {
12282
+ const makeSuffix = (h) => roleCount > 1 ? `${PEER_PREFIX}${h}.${roleIdx}` : `${PEER_PREFIX}${h}`;
12283
+ let suffix = makeSuffix(hex);
12284
+ let newId = withPeerSuffix(node, suffix);
12285
+ let occupant = byNewId.get(newId);
12286
+ if (occupant && occupant.ser !== ser) {
12287
+ suffix = makeSuffix(digest(ser, 32));
12288
+ newId = withPeerSuffix(node, suffix);
12289
+ occupant = byNewId.get(newId);
12290
+ if (occupant && occupant.ser !== ser) {
12291
+ suffix = `${suffix}.x`;
12292
+ newId = withPeerSuffix(node, suffix);
12293
+ occupant = void 0;
12294
+ }
12295
+ }
12296
+ if (occupant?.ser === ser) {
12297
+ losers.set(node, occupant.node);
12298
+ resolvedIds.set(node, occupant.newId);
12299
+ return;
12300
+ }
12301
+ const assigned = { node, ser, suffix, newId };
12302
+ byNewId.set(newId, assigned);
12303
+ keep.push(assigned);
12304
+ resolvedIds.set(node, newId);
12305
+ };
12306
+ for (const scc of tarjan(scoped, adj)) {
12307
+ if (scc.length === 1 && scc[0]) {
12308
+ const node = scc[0];
12309
+ const intraSccIndex2 = hasSelfEdge(node) ? /* @__PURE__ */ new Map([[node, 0]]) : void 0;
12310
+ const ser = unitEntry(node, { resolvedIds, intraSccIndex: intraSccIndex2 });
12311
+ takeSuffix(node, ser, digest(ser, 16), 0, 1);
12312
+ continue;
12313
+ }
12314
+ const { order, colors } = refineSccOrder(scc, resolvedIds);
12315
+ const seenClass = /* @__PURE__ */ new Set();
12316
+ const reps = [];
12317
+ for (const n of order) {
12318
+ const key = colorClassKey(n, colors);
12319
+ if (seenClass.has(key)) continue;
12320
+ seenClass.add(key);
12321
+ reps.push(n);
12322
+ }
12323
+ const classIdx = /* @__PURE__ */ new Map();
12324
+ for (const [i, n] of reps.entries()) {
12325
+ classIdx.set(colorClassKey(n, colors), i);
12326
+ }
12327
+ const intraSccIndex = /* @__PURE__ */ new Map();
12328
+ for (const n of order) {
12329
+ intraSccIndex.set(
12330
+ n,
12331
+ /* c8 ignore next */
12332
+ classIdx.get(colorClassKey(n, colors)) ?? 0
12333
+ );
12334
+ }
12335
+ const unit = reps.map((n) => unitEntry(n, { resolvedIds, intraSccIndex })).join(NL);
12336
+ const hex = digest(unit, 16);
12337
+ const roleIdx = /* @__PURE__ */ new Map();
12338
+ const rolesPerBase = /* @__PURE__ */ new Map();
12339
+ for (const n of reps) {
12340
+ const base = peerlessDepID(n);
12341
+ const key = colorClassKey(n, colors);
12342
+ if (!roleIdx.has(key)) {
12343
+ const next = rolesPerBase.get(base) ?? 0;
12344
+ roleIdx.set(key, next);
12345
+ rolesPerBase.set(base, next + 1);
12346
+ }
12347
+ }
12348
+ for (const n of order) {
12349
+ const key = colorClassKey(n, colors);
12350
+ const idx = roleIdx.get(key) ?? 0;
12351
+ const count = rolesPerBase.get(peerlessDepID(n)) ?? 1;
12352
+ takeSuffix(n, unit, hex, idx, count);
12353
+ }
12354
+ }
12355
+ return applyAssignments(graph, keep, losers);
12356
+ };
12357
+
12358
+ // ../../src/graph/src/ideal/move-peer-store-dirs.ts
12359
+ import { existsSync as existsSync4 } from "node:fs";
12360
+ import { rename as rename2 } from "node:fs/promises";
12361
+ var movePeerStoreDirs = async (moves, {
12362
+ scurry,
12363
+ remover
12364
+ }) => {
12365
+ if (!moves.length) return;
12366
+ const store = scurry.resolve("node_modules/.vlt");
12367
+ const key = String(Math.random()).substring(2);
12368
+ const staged = await Promise.all(
12369
+ moves.map(async ({ node, from, to }) => {
12370
+ const src = scurry.resolve(store, from);
12371
+ if (!to) {
12372
+ try {
12373
+ await remover.rm(src);
12374
+ } catch {
12375
+ }
12376
+ return;
12377
+ }
12378
+ const tmp = scurry.resolve(store, `.VLT.MOVE.${key}.${to}`);
12379
+ try {
12380
+ await rename2(src, tmp);
12381
+ } catch {
12382
+ node.extracted = false;
12383
+ return;
12384
+ }
12385
+ return { node, tmp, dest: scurry.resolve(store, to) };
12386
+ })
12387
+ );
12388
+ await Promise.all(
12389
+ staged.map(async (s) => {
12390
+ if (!s) return;
12391
+ const { node, tmp, dest } = s;
12392
+ try {
12393
+ await remover.rm(dest);
12394
+ await rename2(tmp, dest);
12395
+ } catch {
12396
+ node.extracted = false;
12397
+ try {
12398
+ await remover.rm(tmp);
12399
+ } catch {
12400
+ }
12401
+ return;
12402
+ }
12403
+ if (!existsSync4(node.resolvedLocation(scurry))) {
12404
+ node.extracted = false;
12405
+ }
12406
+ })
12407
+ );
12408
+ };
12409
+
11741
12410
  // ../../src/graph/src/reify/calculate-save-value.ts
11742
12411
  var DEFAULT_SAVE_PREFIX = "^";
11743
12412
  var calculateSaveValue = (nodeType, spec2, existing, nodeVersion, saveExact, savePrefix) => {
@@ -11783,9 +12452,10 @@ var calculateSaveValue = (nodeType, spec2, existing, nodeVersion, saveExact, sav
11783
12452
  // ../../src/graph/src/fixup-added-names.ts
11784
12453
  var fixupAddedNames = (add, manifest, options, spec2) => {
11785
12454
  if (add && manifest?.name && spec2.name === "(unknown)") {
11786
- const s = add.get(String(spec2));
12455
+ const key = addKey(spec2);
12456
+ const s = add.get(key);
11787
12457
  if (s) {
11788
- add.delete(String(spec2));
12458
+ add.delete(key);
11789
12459
  spec2 = Spec2.parse(manifest.name, spec2.bareSpec, options);
11790
12460
  const n = {
11791
12461
  type: s.type,
@@ -11948,17 +12618,28 @@ var parseSpec = (name, bareSpec, fromNode, graph) => Spec2.parse(name, bareSpec,
11948
12618
  ...graph.mainImporter.options,
11949
12619
  registry: fromNode.registry ?? graph.mainImporter.options.registry
11950
12620
  });
12621
+ var ownSpecs = (entry) => {
12622
+ if (entry.sharedSpecs) {
12623
+ entry.specs = new Map(entry.specs);
12624
+ entry.sharedSpecs = false;
12625
+ }
12626
+ return entry.specs;
12627
+ };
12628
+ var bumpRev = (peerContext) => {
12629
+ peerContext.rev = (peerContext.rev ?? 0) + 1;
12630
+ };
11951
12631
  var getForkKey = (peerContext, entries) => {
11952
12632
  const base = peerContext.index ?? 0;
11953
12633
  const sig = entries.map(
11954
12634
  (e) => `${e.spec.final.name}|${e.type}|${e.target?.id ?? "\u2205"}|${e.spec}`
11955
12635
  ).sort().join(";");
11956
- return `${base}::${sig}`;
12636
+ return `${base}::${sig}::r${peerContext.rev ?? 0}`;
11957
12637
  };
11958
12638
  var shouldIgnoreContextMismatch = (peerName, contextTarget, fromNode, graph) => {
11959
12639
  const parentManifest = fromNode.manifest;
11960
12640
  if (!parentManifest) return false;
11961
12641
  for (const depType of longDependencyTypes) {
12642
+ if (!shouldInstallDepType(fromNode, depType)) continue;
11962
12643
  const declared = parentManifest[depType]?.[peerName];
11963
12644
  if (!declared) continue;
11964
12645
  const parentSpec = parseSpec(peerName, declared, fromNode, graph);
@@ -11980,7 +12661,42 @@ var buildIncompatibleResult = (target, peerSpec, type2, fromNode, graph) => {
11980
12661
  }
11981
12662
  return void 0;
11982
12663
  };
11983
- var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph) => {
12664
+ var findProvidedPeer = (peerName, peerSpec, fromNode, peerContext, parseOpts, satisfiesNodeSpec, pending) => {
12665
+ const sibling = fromNode.edgesOut.get(peerName);
12666
+ if (sibling?.to && satisfiesNodeSpec(sibling.to, peerSpec)) {
12667
+ return { target: sibling.to, spec: peerSpec, type: sibling.type };
12668
+ }
12669
+ let declared;
12670
+ const pendingDep = pending?.get(peerName);
12671
+ if (pendingDep) {
12672
+ declared = { spec: pendingDep.spec, type: pendingDep.type };
12673
+ } else if (!pending && fromNode.manifest) {
12674
+ const manifest = fromNode.manifest;
12675
+ for (const depType of longDependencyTypes) {
12676
+ if (!shouldInstallDepType(fromNode, depType)) continue;
12677
+ const bare = manifest[depType]?.[peerName];
12678
+ if (!bare) continue;
12679
+ const type2 = shorten(depType, peerName, manifest);
12680
+ if (type2 === "peerOptional") continue;
12681
+ declared = { spec: Spec2.parse(peerName, bare, parseOpts), type: type2 };
12682
+ break;
12683
+ }
12684
+ }
12685
+ if (declared) {
12686
+ const pf = declared.spec.final;
12687
+ const sf = peerSpec.final;
12688
+ if (pf.type === "registry" && sf.type === "registry" && pf.range && sf.range && !intersects(pf.range, sf.range)) {
12689
+ return void 0;
12690
+ }
12691
+ return { spec: declared.spec, type: declared.type };
12692
+ }
12693
+ const entry = peerContext.get(peerName);
12694
+ if (entry?.target && satisfiesNodeSpec(entry.target, peerSpec)) {
12695
+ return { target: entry.target, spec: peerSpec, type: entry.type };
12696
+ }
12697
+ return void 0;
12698
+ };
12699
+ var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph, pending) => {
11984
12700
  const peerDeps = existingNode.manifest?.peerDependencies;
11985
12701
  if (!peerDeps || Object.keys(peerDeps).length === 0) {
11986
12702
  return { compatible: true };
@@ -12021,7 +12737,31 @@ var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph) => {
12021
12737
  if (existingEdge === void 0) {
12022
12738
  return { compatible: false };
12023
12739
  }
12024
- if (!existingEdge.to) continue;
12740
+ if (!existingEdge.to) {
12741
+ if (existingEdge.type === "peerOptional") {
12742
+ const provided = findProvidedPeer(
12743
+ peerName,
12744
+ Spec2.parse(peerName, peerBareSpec, parseOpts),
12745
+ fromNode,
12746
+ peerContext,
12747
+ parseOpts,
12748
+ satisfiesNodeSpec,
12749
+ pending
12750
+ );
12751
+ if (provided) {
12752
+ return {
12753
+ compatible: false,
12754
+ forkEntry: {
12755
+ spec: provided.spec,
12756
+ target: provided.target,
12757
+ type: provided.type
12758
+ },
12759
+ keepVersion: true
12760
+ };
12761
+ }
12762
+ }
12763
+ continue;
12764
+ }
12025
12765
  const peerSpec = Spec2.parse(peerName, peerBareSpec, parseOpts);
12026
12766
  const contextEntry = peerContext.get(peerName);
12027
12767
  if (contextEntry?.target && contextEntry.target.id !== existingEdge.to.id && !shouldIgnoreContextMismatch(
@@ -12071,6 +12811,7 @@ var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph) => {
12071
12811
  let declaredType;
12072
12812
  if (manifest) {
12073
12813
  for (const depType of longDependencyTypes) {
12814
+ if (!shouldInstallDepType(fromNode, depType)) continue;
12074
12815
  const deps = manifest[depType];
12075
12816
  if (deps && typeof deps === "object" && !Array.isArray(deps) && peerName in deps) {
12076
12817
  declared = deps[peerName];
@@ -12087,6 +12828,7 @@ var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph) => {
12087
12828
  const candidates = graph.nodesByName.get(peerName);
12088
12829
  if (candidates) {
12089
12830
  for (const candidateNode of candidates) {
12831
+ if (candidateNode.detached) continue;
12090
12832
  if (candidateNode.id !== existingEdge.to.id && satisfiesNodeSpec(candidateNode, parentSpec) && satisfiesNodeSpec(candidateNode, peerSpec)) {
12091
12833
  return {
12092
12834
  compatible: false,
@@ -12104,8 +12846,8 @@ var checkPeerEdgesCompatible = (existingNode, fromNode, peerContext, graph) => {
12104
12846
  return { compatible: true };
12105
12847
  };
12106
12848
  var retrievePeerContextHash = (peerContext) => {
12107
- if (!peerContext?.index) return void 0;
12108
- return `peer.${peerContext.index}`;
12849
+ if (!peerContext) return void 0;
12850
+ return `peer.${peerContext.index ?? 0}`;
12109
12851
  };
12110
12852
  var peerSpecKey = (spec2) => {
12111
12853
  const f = spec2.final;
@@ -12140,6 +12882,15 @@ var checkEntriesToPeerContext = (peerContext, entries) => {
12140
12882
  };
12141
12883
  var addEntriesToPeerContext = (peerContext, entries, fromNode, monorepo) => {
12142
12884
  if (checkEntriesToPeerContext(peerContext, entries)) return true;
12885
+ const satisfiesEntrySpecs = (node, entry, from) => [...entry.specs.values()].every(
12886
+ (s) => satisfies2(
12887
+ node.id,
12888
+ s,
12889
+ from.location,
12890
+ from.projectRoot,
12891
+ monorepo
12892
+ )
12893
+ );
12143
12894
  for (const { dependent, spec: spec2, target, type: type2 } of entries) {
12144
12895
  const name = target?.name ?? spec2.final.name;
12145
12896
  let entry = peerContext.get(name);
@@ -12152,34 +12903,31 @@ var addEntriesToPeerContext = (peerContext, entries, fromNode, monorepo) => {
12152
12903
  contextDependents: /* @__PURE__ */ new Set()
12153
12904
  };
12154
12905
  peerContext.set(name, entry);
12906
+ bumpRev(peerContext);
12155
12907
  if (dependent) entry.contextDependents.add(dependent);
12156
12908
  continue;
12157
12909
  }
12158
12910
  if (incompatibleSpecs(spec2.final, entry)) return true;
12159
- if (target && [...entry.specs.values()].every(
12160
- (s) => satisfies2(
12161
- target.id,
12162
- s,
12163
- fromNode.location,
12164
- fromNode.projectRoot,
12165
- monorepo
12166
- )
12167
- )) {
12911
+ const specKey = peerSpecKey(spec2);
12912
+ if (!entry.specs.has(specKey)) ownSpecs(entry).set(specKey, spec2);
12913
+ if (target && satisfiesEntrySpecs(target, entry, fromNode)) {
12168
12914
  if (target.id !== entry.target?.id && target.version !== entry.target?.version) {
12169
12915
  for (const dep of entry.contextDependents) {
12170
12916
  const edge = dep.edgesOut.get(name);
12171
- if (edge?.to && edge.to !== target) {
12172
- edge.to.edgesIn.delete(edge);
12173
- edge.to = target;
12174
- target.edgesIn.add(edge);
12175
- }
12917
+ if (!edge?.to || edge.to === target) continue;
12918
+ if (satisfiesEntrySpecs(edge.to, entry, dep)) continue;
12919
+ dep.graph.mutations++;
12920
+ edge.to.edgesIn.delete(edge);
12921
+ edge.to = target;
12922
+ target.edgesIn.add(edge);
12176
12923
  }
12177
12924
  entry.target = target;
12925
+ bumpRev(peerContext);
12926
+ } else if (!entry.target) {
12927
+ entry.target = target;
12928
+ bumpRev(peerContext);
12178
12929
  }
12179
- entry.target ??= target;
12180
12930
  }
12181
- const specKey = peerSpecKey(spec2);
12182
- if (!entry.specs.has(specKey)) entry.specs.set(specKey, spec2);
12183
12931
  if (dependent) entry.contextDependents.add(dependent);
12184
12932
  }
12185
12933
  return false;
@@ -12195,20 +12943,25 @@ var forkPeerContext = (graph, peerContext, entries) => {
12195
12943
  graph.peerContexts[nextPeerContext.index] = nextPeerContext;
12196
12944
  graph.peerContextForkCache.set(forkKey, nextPeerContext);
12197
12945
  for (const [name, entry] of peerContext.entries()) {
12946
+ entry.sharedSpecs = true;
12198
12947
  nextPeerContext.set(name, {
12199
12948
  active: false,
12200
- specs: new Map(entry.specs),
12201
- target: void 0,
12949
+ sharedSpecs: true,
12950
+ specs: entry.specs,
12951
+ target: entry.target,
12202
12952
  type: entry.type,
12203
- contextDependents: new Set(entry.contextDependents)
12953
+ contextDependents: /* @__PURE__ */ new Set()
12204
12954
  });
12205
12955
  }
12206
12956
  for (const entry of entries) {
12207
12957
  const { dependent, spec: spec2, target, type: type2 } = entry;
12208
12958
  const name = target?.name ?? spec2.final.name;
12959
+ const inherited = peerContext.get(name);
12960
+ const specs = inherited?.target && inherited.target === target ? new Map(inherited.specs) : /* @__PURE__ */ new Map();
12961
+ specs.set(peerSpecKey(spec2), spec2);
12209
12962
  const newEntry = {
12210
12963
  active: true,
12211
- specs: /* @__PURE__ */ new Map([[peerSpecKey(spec2), spec2]]),
12964
+ specs,
12212
12965
  target,
12213
12966
  type: type2,
12214
12967
  contextDependents: dependent ? /* @__PURE__ */ new Set([dependent]) : /* @__PURE__ */ new Set()
@@ -12340,10 +13093,11 @@ var endPeerPlacement = (peerContext, nextDeps, nextPeerDeps, graph, spec2, fromN
12340
13093
  const siblingEntry = queuedEntries.find(
12341
13094
  (e) => (e.target?.name ?? e.spec.final.name) === name
12342
13095
  );
12343
- const siblingTarget = siblingEntry?.target ?? fromNode.edgesOut.get(name)?.to;
13096
+ const siblingTarget = fromNode.edgesOut.get(name)?.to ?? siblingEntry?.target;
12344
13097
  if (siblingTarget && nodeSatisfiesSpec(siblingTarget, spec3, fromNode, graph)) {
12345
13098
  const existingEdge = node.edgesOut.get(name);
12346
13099
  if (existingEdge?.to && existingEdge.to !== siblingTarget) {
13100
+ graph.mutations++;
12347
13101
  existingEdge.to.edgesIn.delete(existingEdge);
12348
13102
  existingEdge.to = siblingTarget;
12349
13103
  siblingTarget.edgesIn.add(existingEdge);
@@ -12368,7 +13122,7 @@ var endPeerPlacement = (peerContext, nextDeps, nextPeerDeps, graph, spec2, fromN
12368
13122
  graph.addEdge(type3, spec3, node, entry.target);
12369
13123
  const finalKey = peerSpecKey(spec3.final);
12370
13124
  if (!entry.specs.has(finalKey)) {
12371
- entry.specs.set(finalKey, spec3.final);
13125
+ ownSpecs(entry).set(finalKey, spec3.final);
12372
13126
  }
12373
13127
  continue;
12374
13128
  }
@@ -12424,7 +13178,6 @@ var postPlacementPeerCheck = (graph, sortedLevelResults) => {
12424
13178
  };
12425
13179
 
12426
13180
  // ../../src/graph/src/ideal/append-nodes.ts
12427
- var shouldInstallDepType = (node, depType) => depType !== "devDependencies" || node.importer || node.id.startsWith("git");
12428
13181
  var getFileTypeInfo = (spec2, fromNode, scurry) => {
12429
13182
  const f = spec2.final;
12430
13183
  if (f.type !== "file") return;
@@ -12441,56 +13194,86 @@ var getFileTypeInfo = (spec2, fromNode, scurry) => {
12441
13194
  };
12442
13195
  };
12443
13196
  var isStringArray2 = (a) => Array.isArray(a) && !a.some((b) => typeof b !== "string");
12444
- var findCompatibleResolution = (spec2, fromNode, graph, peerContext, queryModifier, _peer) => {
12445
- const fromLoc = fromNode.location;
12446
- const projectRoot = graph.projectRoot;
12447
- const monorepo = graph.monorepo;
13197
+ var actualBases = /* @__PURE__ */ new WeakMap();
13198
+ var hasActualBase = (actual2, id2) => {
13199
+ let bases = actualBases.get(actual2);
13200
+ if (!bases) {
13201
+ bases = new Set([...actual2.nodes.keys()].map(baseDepID));
13202
+ actualBases.set(actual2, bases);
13203
+ }
13204
+ return bases.has(baseDepID(id2));
13205
+ };
13206
+ var lockedFits = (n, spec2, fromNode, graph) => {
12448
13207
  const final = spec2.final;
12449
- const satisfiesCache = /* @__PURE__ */ new Map();
12450
- const satisfiesFinal = (n) => {
12451
- const key = n.id;
12452
- const cached = satisfiesCache.get(key);
12453
- if (cached !== void 0) {
12454
- return cached;
12455
- }
12456
- const result = satisfies2(
12457
- key,
12458
- final,
12459
- fromLoc,
12460
- projectRoot,
12461
- monorepo
12462
- );
12463
- satisfiesCache.set(key, result);
12464
- return result;
12465
- };
12466
- const existingEdge = fromNode.edgesOut.get(spec2.name);
12467
- let existingNode;
12468
- if (existingEdge?.to && !existingEdge.to.detached && satisfiesFinal(existingEdge.to)) {
12469
- existingNode = existingEdge.to;
12470
- } else {
12471
- existingNode = graph.findResolution(spec2, fromNode, queryModifier);
13208
+ if (satisfies2(
13209
+ n.id,
13210
+ final,
13211
+ fromNode.location,
13212
+ graph.projectRoot,
13213
+ graph.monorepo
13214
+ )) {
13215
+ return true;
12472
13216
  }
13217
+ if (final.type !== "registry") return false;
13218
+ const [idType, idRegistry] = splitDepID(n.id);
13219
+ if (idType !== "registry") return false;
13220
+ const expected = getTuple(spec2, {
13221
+ name: n.name,
13222
+ version: n.version
13223
+ });
13224
+ if (idRegistry !== expected[1]) return false;
13225
+ if (n.name !== final.name) return false;
13226
+ if (!final.range) return true;
13227
+ if (!n.version) return false;
13228
+ const version = parse(n.version);
13229
+ return !!version && final.range.test(version);
13230
+ };
13231
+ var findLockedNode = (graph, fromNode, name) => {
13232
+ const locked = graph.lockedResolutions;
13233
+ if (!locked) return;
13234
+ let id2 = locked.get(`${fromNode.id}\0${name}`);
13235
+ if (id2 === void 0) {
13236
+ const baseFrom = baseDepID(fromNode.id);
13237
+ if (baseFrom !== fromNode.id) {
13238
+ id2 = locked.get(`${baseFrom}\0${name}`);
13239
+ }
13240
+ }
13241
+ return id2 ? graph.nodes.get(id2) : void 0;
13242
+ };
13243
+ var findCompatibleResolution = (spec2, fromNode, graph, peerContext, candidate, pending) => {
13244
+ const final = spec2.final;
13245
+ let existingNode = candidate;
12473
13246
  let peerCompatResult = existingNode ? checkPeerEdgesCompatible(
12474
13247
  existingNode,
12475
13248
  fromNode,
12476
13249
  peerContext,
12477
- graph
13250
+ graph,
13251
+ pending
12478
13252
  ) : { compatible: true };
12479
13253
  if (existingNode && !peerCompatResult.compatible) {
12480
13254
  const candidates = graph.nodesByName.get(final.name);
12481
13255
  if (candidates && candidates.size > 1) {
12482
- for (const candidate of candidates) {
12483
- if (candidate === existingNode) continue;
12484
- if (candidate.detached) continue;
12485
- if (!satisfiesFinal(candidate)) continue;
13256
+ for (const alt of candidates) {
13257
+ if (alt === existingNode) continue;
13258
+ if (alt.detached) continue;
13259
+ if (!satisfies2(
13260
+ alt.id,
13261
+ final,
13262
+ fromNode.location,
13263
+ graph.projectRoot,
13264
+ graph.monorepo
13265
+ )) {
13266
+ continue;
13267
+ }
12486
13268
  const compat = checkPeerEdgesCompatible(
12487
- candidate,
13269
+ alt,
12488
13270
  fromNode,
12489
13271
  peerContext,
12490
- graph
13272
+ graph,
13273
+ pending
12491
13274
  );
12492
13275
  if (compat.compatible) {
12493
- existingNode = candidate;
13276
+ existingNode = alt;
12494
13277
  peerCompatResult = compat;
12495
13278
  break;
12496
13279
  }
@@ -12499,15 +13282,40 @@ var findCompatibleResolution = (spec2, fromNode, graph, peerContext, queryModifi
12499
13282
  }
12500
13283
  return { existingNode, peerCompatResult };
12501
13284
  };
12502
- var fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry, peerContext, modifierRefs, depth = 0) => {
13285
+ var lockedVersionFetch = (node, spec2, options) => {
13286
+ if (spec2.final.type !== "registry" || !node.version) return;
13287
+ const bare = hydrate2(node.id, spec2.name, options).bareSpec;
13288
+ const exact = Spec2.parse(spec2.name, bare, options);
13289
+ if (exact.final.registry !== spec2.final.registry) return;
13290
+ return { spec: exact, version: node.version };
13291
+ };
13292
+ var hydrateLockedNode = (node, spec2, graph, packageInfo, options, from, hydrations) => {
13293
+ const seen = hydrations.get(node.id);
13294
+ if (seen) return seen;
13295
+ const locked = lockedVersionFetch(node, spec2, options);
13296
+ if (!locked) return;
13297
+ const hydration = packageInfo.manifest(locked.spec, { from }).then(
13298
+ (manifest) => manifest,
13299
+ () => void 0
13300
+ ).then((manifest) => {
13301
+ if (manifest?.version !== locked.version) return;
13302
+ node.manifest = normalizeManifest(manifest, String(locked.spec));
13303
+ graph.manifests.set(node.id, node.manifest);
13304
+ });
13305
+ hydrations.set(node.id, hydration);
13306
+ return hydration;
13307
+ };
13308
+ var fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry, options, hydrations, peerContext, modifierRefs, depth = 0, explicit) => {
12503
13309
  const fetchTasks = [];
12504
13310
  const placementTasks = [];
12505
13311
  const reuseTasks = [];
12506
13312
  const forkRequests = [];
13313
+ const entries = [];
12507
13314
  for (const { spec: originalSpec, type: type2 } of deps) {
12508
13315
  let spec2 = originalSpec;
12509
13316
  const fileTypeInfo = getFileTypeInfo(spec2, fromNode, scurry);
12510
13317
  const activeModifier = modifierRefs?.get(spec2.name);
13318
+ const isExplicit = !!explicit?.get(fromNode.id)?.has(addKey(originalSpec));
12511
13319
  const queryModifier = activeModifier?.modifier.query;
12512
13320
  const completeModifier = activeModifier && activeModifier.interactiveBreadcrumb.current === activeModifier.modifier.breadcrumb.last;
12513
13321
  if (queryModifier && completeModifier && "spec" in activeModifier.modifier) {
@@ -12516,15 +13324,73 @@ var fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry, p
12516
13324
  continue;
12517
13325
  }
12518
13326
  }
13327
+ entries.push({
13328
+ spec: spec2,
13329
+ type: type2,
13330
+ fileTypeInfo,
13331
+ activeModifier,
13332
+ queryModifier,
13333
+ isExplicit,
13334
+ // read after the modifier swap, so an --override that replaces the
13335
+ // tag with a range keeps today's reuse
13336
+ explicitTag: isExplicit && !!spec2.final.distTag
13337
+ });
13338
+ }
13339
+ const pendingDeps = /* @__PURE__ */ new Map();
13340
+ for (const e of entries) pendingDeps.set(e.spec.name, e);
13341
+ const from = scurry.resolve(fromNode.location);
13342
+ const pending = [];
13343
+ for (const e of entries) {
13344
+ const existing = fromNode.edgesOut.get(e.spec.name)?.to;
13345
+ if (existing && !existing.detached && satisfies2(
13346
+ existing.id,
13347
+ e.spec.final,
13348
+ fromNode.location,
13349
+ graph.projectRoot,
13350
+ graph.monorepo
13351
+ )) {
13352
+ e.candidate = existing;
13353
+ continue;
13354
+ }
13355
+ const locked = findLockedNode(graph, fromNode, e.spec.name);
13356
+ e.candidate = locked && lockedFits(locked, e.spec, fromNode, graph) ? locked : graph.findResolution(e.spec, fromNode, e.queryModifier);
13357
+ if (e.explicitTag) continue;
13358
+ if (e.candidate?.detached && !e.candidate.manifest) {
13359
+ const hydration = hydrateLockedNode(
13360
+ e.candidate,
13361
+ e.spec,
13362
+ graph,
13363
+ packageInfo,
13364
+ options,
13365
+ from,
13366
+ hydrations
13367
+ );
13368
+ if (hydration) pending.push(hydration);
13369
+ }
13370
+ }
13371
+ await Promise.all(pending);
13372
+ for (const {
13373
+ spec: spec2,
13374
+ type: type2,
13375
+ fileTypeInfo,
13376
+ activeModifier,
13377
+ queryModifier,
13378
+ isExplicit,
13379
+ explicitTag,
13380
+ candidate
13381
+ } of entries) {
12519
13382
  const peer2 = type2 === "peer" || type2 === "peerOptional";
12520
- const { existingNode, peerCompatResult } = findCompatibleResolution(
13383
+ const resolved = findCompatibleResolution(
12521
13384
  spec2,
12522
13385
  fromNode,
12523
13386
  graph,
12524
13387
  peerContext,
12525
- queryModifier,
12526
- peer2
13388
+ candidate,
13389
+ pendingDeps
12527
13390
  );
13391
+ const dropForTag = explicitTag && !!resolved.existingNode && !resolved.existingNode.importer;
13392
+ const existingNode = dropForTag ? void 0 : resolved.existingNode;
13393
+ const peerCompatResult = dropForTag ? { compatible: true } : resolved.peerCompatResult;
12528
13394
  const effectivePeerContext = peerContext;
12529
13395
  if (!peerCompatResult.compatible && peerCompatResult.forkEntry) {
12530
13396
  forkRequests.push(peerCompatResult.forkEntry);
@@ -12550,11 +13416,15 @@ var fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry, p
12550
13416
  const edgeOptional = type2 === "optional" || type2 === "peerOptional";
12551
13417
  const manifestPromise = (
12552
13418
  // the "detached" node state means that it has already been load as
12553
- // part of a graph (either lockfile or actual) and it has valid manifest
12554
- // data so we shortcut the package info manifest fetch here
12555
- existingNode?.detached && existingNode.manifest ? Promise.resolve(existingNode.manifest) : packageInfo.manifest(spec2, { from: scurry.resolve(fromNode.location) }).then((manifest) => manifest).catch((er) => {
13419
+ // part of a graph (either lockfile or actual), or was hydrated with
13420
+ // its locked version above, and it has valid manifest data so we
13421
+ // shortcut the package info manifest fetch here
13422
+ // `keepVersion` marks a fork that only differs by an optional peer
13423
+ // link, so the copy keeps the version it forked from instead of
13424
+ // re-resolving the range to the newest satisfying release
13425
+ existingNode?.manifest && (existingNode.detached || peerCompatResult.keepVersion) ? Promise.resolve(existingNode.manifest) : packageInfo.manifest(spec2, { from }).then((manifest) => manifest).catch((er) => {
12556
13426
  if (isPathSecurityError(er)) throw er;
12557
- if (edgeOptional || fromNode.optional) {
13427
+ if (!isExplicit && (edgeOptional || fromNode.optional)) {
12558
13428
  return void 0;
12559
13429
  }
12560
13430
  throw er;
@@ -12641,7 +13511,11 @@ var processPlacementTasks = async (graph, options, placementTasks, add, modifier
12641
13511
  }
12642
13512
  const eligibleForExtraction = type2 !== "peer" && type2 !== "peerOptional" && remover && extractPromises && actual2 && scurry && packageInfo && node.inVltStore() && !node.isOptional() && // this fixes an issue with installing `file:pathname` specs
12643
13513
  /* c8 ignore next */
12644
- !fileTypeInfo?.isDirectory && !node.importer;
13514
+ !fileTypeInfo?.isDirectory && !node.importer && // provisional peer suffixes are rewritten by canonicalizePeerIds,
13515
+ // which moves the store dir into place. Skip when a dir for this
13516
+ // package version already exists: the rewrite most likely lands
13517
+ // back on it, and re-extracting would be pure waste.
13518
+ (!node.peerSetHash || !!fileTypeInfo || !hasActualBase(actual2, node.id));
12645
13519
  if (eligibleForExtraction) {
12646
13520
  if (seenExtracted?.has(node.id)) {
12647
13521
  continue;
@@ -12728,6 +13602,7 @@ var processPlacementTasks = async (graph, options, placementTasks, add, modifier
12728
13602
  }
12729
13603
  }
12730
13604
  }
13605
+ for (const dep of nextDeps) nextPeerDeps.delete(dep.spec.name);
12731
13606
  const updateContext = endPeerPlacement(
12732
13607
  peerContext,
12733
13608
  nextDeps,
@@ -12749,9 +13624,10 @@ var processPlacementTasks = async (graph, options, placementTasks, add, modifier
12749
13624
  }
12750
13625
  return childDepsToProcess;
12751
13626
  };
12752
- var appendNodes = async (packageInfo, graph, fromNode, deps, scurry, options, seen, add, modifiers, modifierRefs, extractPromises, actual2, seenExtracted, remover, transientAdd, transientRemove) => {
13627
+ var appendNodes = async (packageInfo, graph, fromNode, deps, scurry, options, seen, add, modifiers, modifierRefs, extractPromises, actual2, seenExtracted, remover, transientAdd, transientRemove, explicit) => {
12753
13628
  if (seen.has(fromNode.id)) return;
12754
13629
  seen.add(fromNode.id);
13630
+ const hydrations = /* @__PURE__ */ new Map();
12755
13631
  let initialPeerContext = graph.peerContexts[0];
12756
13632
  if (!initialPeerContext)
12757
13633
  throw error("no initial peer context found in graph");
@@ -12798,9 +13674,12 @@ var appendNodes = async (packageInfo, graph, fromNode, deps, scurry, options, se
12798
13674
  (a, b) => a.spec.name.localeCompare(b.spec.name, "en")
12799
13675
  ),
12800
13676
  scurry,
13677
+ options,
13678
+ hydrations,
12801
13679
  peerContext,
12802
13680
  nodeModifierRefs,
12803
- depth
13681
+ depth,
13682
+ explicit
12804
13683
  );
12805
13684
  return {
12806
13685
  entry: {
@@ -12896,23 +13775,53 @@ var refreshIdealGraph = async ({
12896
13775
  packageInfo,
12897
13776
  scurry,
12898
13777
  actual: actual2,
13778
+ explicit,
12899
13779
  remove,
12900
13780
  remover,
12901
13781
  transientAdd,
12902
13782
  transientRemove,
13783
+ lockfileOnly,
12903
13784
  ...specOptions
12904
13785
  }) => {
12905
13786
  const seen = /* @__PURE__ */ new Set();
12906
- const extractPromises = [];
13787
+ const extractPromises = lockfileOnly ? void 0 : [];
12907
13788
  const seenExtracted = /* @__PURE__ */ new Set();
12908
13789
  const orderedImporters = getOrderedImporters(graph);
13790
+ const specTexts = /* @__PURE__ */ new Map();
13791
+ for (const importer of graph.importers) {
13792
+ for (const [name, edge] of importer.edgesOut) {
13793
+ specTexts.set(`${importer.id}\0${name}`, edge.spec.bareSpec);
13794
+ }
13795
+ }
12909
13796
  const depsPerImporter = /* @__PURE__ */ new Map();
12910
13797
  for (const importer of orderedImporters) {
12911
13798
  const deps = getNodeOrderedDependencies(importer, { add, remove });
12912
13799
  depsPerImporter.set(importer, deps);
12913
13800
  }
12914
13801
  if (add.modifiedDependencies || remove.modifiedDependencies || graph.optionsChanged) {
13802
+ const locked = /* @__PURE__ */ new Map();
13803
+ const ambiguous = /* @__PURE__ */ new Set();
13804
+ const record = (key, id2) => {
13805
+ if (ambiguous.has(key)) return;
13806
+ const prev = locked.get(key);
13807
+ if (prev === void 0) {
13808
+ locked.set(key, id2);
13809
+ } else if (prev !== id2) {
13810
+ locked.delete(key);
13811
+ ambiguous.add(key);
13812
+ }
13813
+ };
13814
+ for (const edge of graph.edges) {
13815
+ if (!edge.to) continue;
13816
+ record(`${edge.from.id}\0${edge.spec.name}`, edge.to.id);
13817
+ const base = baseDepID(edge.from.id);
13818
+ if (base !== edge.from.id) {
13819
+ record(`${base}\0${edge.spec.name}`, edge.to.id);
13820
+ }
13821
+ }
13822
+ graph.lockedResolutions = locked;
12915
13823
  graph.resetEdges();
13824
+ if (graph.optionsChanged) graph.lockfileStale = true;
12916
13825
  }
12917
13826
  for (const importer of orderedImporters) {
12918
13827
  modifiers?.tryImporter(importer);
@@ -12936,20 +13845,34 @@ var refreshIdealGraph = async ({
12936
13845
  seenExtracted,
12937
13846
  remover,
12938
13847
  transientAdd,
12939
- transientRemove
13848
+ transientRemove,
13849
+ explicit
12940
13850
  );
12941
13851
  }
13852
+ graph.lockedResolutions = void 0;
13853
+ for (const importer of graph.importers) {
13854
+ for (const [name, edge] of importer.edgesOut) {
13855
+ if (specTexts.get(`${importer.id}\0${name}`) !== edge.spec.bareSpec) {
13856
+ graph.lockfileStale = true;
13857
+ }
13858
+ }
13859
+ }
12942
13860
  for (const node of graph.nodes.values()) {
12943
13861
  node.setDefaultLocation();
12944
13862
  }
12945
- if (extractPromises.length > 0) {
13863
+ if (extractPromises?.length) {
12946
13864
  await Promise.all(extractPromises);
12947
13865
  }
12948
13866
  };
12949
13867
 
12950
13868
  // ../../src/graph/src/ideal/build-ideal-from-starting-graph.ts
12951
13869
  var buildIdealFromStartingGraph = async (options) => {
13870
+ const mutationsBefore = options.graph.mutations;
12952
13871
  const importerSpecs = getImporterSpecs(options);
13872
+ const explicit = /* @__PURE__ */ new Map();
13873
+ for (const [id2, deps] of options.add) {
13874
+ explicit.set(id2, new Set(deps.keys()));
13875
+ }
12953
13876
  options.add.modifiedDependencies = options.add.modifiedDependencies || importerSpecs.add.modifiedDependencies;
12954
13877
  options.remove.modifiedDependencies = options.remove.modifiedDependencies || importerSpecs.remove.modifiedDependencies;
12955
13878
  for (const [nodeId, deps] of importerSpecs.add) {
@@ -12975,12 +13898,26 @@ var buildIdealFromStartingGraph = async (options) => {
12975
13898
  options.remove.get(nodeId)?.add(depName);
12976
13899
  }
12977
13900
  }
13901
+ for (const [edge, dep] of importerSpecs.staleSpecs) {
13902
+ edge.spec = dep.spec;
13903
+ edge.type = resolveSaveType(edge.from, edge.name, dep.type);
13904
+ }
13905
+ if (importerSpecs.staleSpecs.size) {
13906
+ options.graph.lockfileStale = true;
13907
+ }
12978
13908
  await refreshIdealGraph({
12979
13909
  ...options,
13910
+ explicit,
12980
13911
  transientAdd: importerSpecs.transientAdd,
12981
13912
  transientRemove: importerSpecs.transientRemove
12982
13913
  });
12983
13914
  options.graph.gc();
13915
+ if (options.graph.mutations !== mutationsBefore || hasProvisionalPeerIds(options.graph)) {
13916
+ await movePeerStoreDirs(
13917
+ canonicalizePeerIds(options.graph),
13918
+ options
13919
+ );
13920
+ }
12984
13921
  return options.graph;
12985
13922
  };
12986
13923
 
@@ -13031,8 +13968,8 @@ var build3 = async (options) => {
13031
13968
  });
13032
13969
  done();
13033
13970
  if (options.add) {
13034
- for (const [addKey, value] of options.add.entries()) {
13035
- if (value.size) validateFileDepNode(res, addKey);
13971
+ for (const [addKey2, value] of options.add.entries()) {
13972
+ if (value.size) validateFileDepNode(res, addKey2);
13036
13973
  }
13037
13974
  }
13038
13975
  if (options.remove) {
@@ -13726,10 +14663,11 @@ var addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
13726
14663
  found: dep.type
13727
14664
  });
13728
14665
  }
13729
- const n = node.edgesOut.get(name)?.to;
13730
- if (!n) {
14666
+ const edge = node.edgesOut.get(name);
14667
+ if (!edge?.to) {
13731
14668
  throw error("Dependency node could not be found");
13732
14669
  }
14670
+ const n = edge.to;
13733
14671
  const [nodeType] = splitDepID(n.id);
13734
14672
  for (const dtype of longDependencyTypes) {
13735
14673
  if (dtype === depType || !manifest[dtype]) continue;
@@ -13753,6 +14691,10 @@ var addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
13753
14691
  );
13754
14692
  dependencies[name] = saveValue;
13755
14693
  manifestChanged = manifestChanged || saveValue !== existing;
14694
+ if (saveValue !== edge.spec.bareSpec) {
14695
+ edge.spec = Spec2.parse(name, saveValue, dep.spec.options);
14696
+ graph.lockfileStale = true;
14697
+ }
13756
14698
  }
13757
14699
  }
13758
14700
  return manifestChanged ? manifest : void 0;
@@ -13829,17 +14771,29 @@ var reify = async (options) => {
13829
14771
  loadManifests: true
13830
14772
  });
13831
14773
  const diff2 = new Diff(actual2, graph);
14774
+ const saveImportersPackageJson = (
14775
+ /* c8 ignore next */
14776
+ options.add?.modifiedDependencies || options.remove?.modifiedDependencies ? updatePackageJson({
14777
+ add: options.add,
14778
+ remove: options.remove,
14779
+ graph,
14780
+ packageJson: options.packageJson,
14781
+ saveExact: options.saveExact,
14782
+ savePrefix: options.savePrefix
14783
+ }) : void 0
14784
+ );
13832
14785
  const noModifiedDependencies = !options.add?.modifiedDependencies && !options.remove?.modifiedDependencies;
13833
14786
  const hasLockfiles = !!scurry.lstatSync("vlt-lock.json") && !!scurry.lstatSync("node_modules/.vlt-lock.json");
13834
14787
  const skipOptionalOnly = noModifiedDependencies && diff2.optionalOnly;
13835
14788
  const skippable = skipOptionalOnly && !options.update && hasLockfiles;
13836
14789
  const res = { diff: diff2 };
13837
14790
  if (!diff2.hasChanges() || skippable) {
13838
- if (!hasLockfiles) {
13839
- saveHidden(options);
14791
+ if (!hasLockfiles || graph.lockfileStale) {
14792
+ if (!hasLockfiles || !diff2.hasChanges()) saveHidden(options);
13840
14793
  const lfData = lockfileData(options);
13841
14794
  saveData(lfData, scurry.resolve("vlt-lock.json"), false);
13842
14795
  }
14796
+ saveImportersPackageJson?.();
13843
14797
  const pending = [...actual2.nodes.values()].filter((n) => n.buildState === "needed").map((n) => n.id);
13844
14798
  if (pending.length) {
13845
14799
  res.buildQueue = pending;
@@ -13849,7 +14803,12 @@ var reify = async (options) => {
13849
14803
  }
13850
14804
  let success = false;
13851
14805
  try {
13852
- const { buildQueue } = await reify_(options, diff2, remover);
14806
+ const { buildQueue } = await reify_(
14807
+ options,
14808
+ diff2,
14809
+ remover,
14810
+ saveImportersPackageJson
14811
+ );
13853
14812
  remover.confirm();
13854
14813
  success = true;
13855
14814
  res.buildQueue = buildQueue;
@@ -13862,29 +14821,9 @@ var reify = async (options) => {
13862
14821
  done();
13863
14822
  return res;
13864
14823
  };
13865
- var reify_ = async (options, diff2, remover) => {
14824
+ var reify_ = async (options, diff2, remover, saveImportersPackageJson) => {
13866
14825
  const res = {};
13867
- const {
13868
- add,
13869
- remove,
13870
- packageInfo,
13871
- packageJson,
13872
- scurry,
13873
- allowScripts,
13874
- saveExact,
13875
- savePrefix
13876
- } = options;
13877
- const saveImportersPackageJson = (
13878
- /* c8 ignore next */
13879
- add?.modifiedDependencies || remove?.modifiedDependencies ? updatePackageJson({
13880
- add,
13881
- remove,
13882
- graph: options.graph,
13883
- packageJson,
13884
- saveExact,
13885
- savePrefix
13886
- }) : void 0
13887
- );
14826
+ const { packageInfo, packageJson, scurry, allowScripts } = options;
13888
14827
  const lfData = lockfileData(options);
13889
14828
  const actions = addNodes(
13890
14829
  diff2,
@@ -13930,11 +14869,27 @@ var reify_ = async (options, diff2, remover) => {
13930
14869
  };
13931
14870
 
13932
14871
  // ../../src/graph/src/install.ts
13933
- import { existsSync as existsSync6, rmSync as rmSync3 } from "node:fs";
14872
+ import { existsSync as existsSync7, rmSync as rmSync3 } from "node:fs";
13934
14873
  import { resolve as resolve12 } from "node:path";
13935
14874
 
14875
+ // ../../src/graph/src/lockfile/format-options-change.ts
14876
+ var redactUserinfo = (value) => {
14877
+ if (!URL.canParse(value)) return value;
14878
+ const url = new URL(value);
14879
+ if (!url.username && !url.password) return value;
14880
+ url.username = "";
14881
+ url.password = "";
14882
+ return url.href.replace("://", "://***@");
14883
+ };
14884
+ var formatOptionsChange = ({
14885
+ section,
14886
+ key,
14887
+ from,
14888
+ to
14889
+ }) => `${section}:${key === void 0 ? "" : ` ${key}`} ` + (from === void 0 ? "(missing)" : `"${redactUserinfo(from)}"`) + ` -> ${to === void 0 ? "(removed)" : `"${redactUserinfo(to)}"`}`;
14890
+
13936
14891
  // ../../src/graph/src/uninstall.ts
13937
- import { existsSync as existsSync4, rmSync } from "node:fs";
14892
+ import { existsSync as existsSync5, rmSync } from "node:fs";
13938
14893
  import { resolve as resolve10 } from "node:path";
13939
14894
  var uninstall = async (options, remove) => {
13940
14895
  const mainManifest = options.packageJson.read(options.projectRoot);
@@ -13966,6 +14921,7 @@ var uninstall = async (options, remove) => {
13966
14921
  }) : void 0
13967
14922
  );
13968
14923
  saveImportersPackageJson?.();
14924
+ remover.confirm();
13969
14925
  return { graph, diff: void 0 };
13970
14926
  }
13971
14927
  const diff2 = await reify({
@@ -13985,7 +14941,7 @@ var uninstall = async (options, remove) => {
13985
14941
  options.projectRoot,
13986
14942
  "node_modules/.vlt-lock.json"
13987
14943
  );
13988
- if (existsSync4(hiddenLockfile)) {
14944
+ if (existsSync5(hiddenLockfile)) {
13989
14945
  rmSync(hiddenLockfile, { force: true });
13990
14946
  }
13991
14947
  } catch {
@@ -13995,7 +14951,7 @@ var uninstall = async (options, remove) => {
13995
14951
  };
13996
14952
 
13997
14953
  // ../../src/graph/src/update.ts
13998
- import { existsSync as existsSync5, rmSync as rmSync2 } from "node:fs";
14954
+ import { existsSync as existsSync6, rmSync as rmSync2 } from "node:fs";
13999
14955
  import { resolve as resolve11 } from "node:path";
14000
14956
  var update = async (options) => {
14001
14957
  let mainManifest = void 0;
@@ -14049,7 +15005,7 @@ var update = async (options) => {
14049
15005
  options.projectRoot,
14050
15006
  "node_modules/.vlt-lock.json"
14051
15007
  );
14052
- if (existsSync5(hiddenLockfile)) {
15008
+ if (existsSync6(hiddenLockfile)) {
14053
15009
  rmSync2(hiddenLockfile, { force: true });
14054
15010
  }
14055
15011
  } catch {
@@ -14145,7 +15101,7 @@ var install = async (options, add) => {
14145
15101
  }
14146
15102
  if (options.expectLockfile || options.frozenLockfile) {
14147
15103
  const lockfilePath = resolve12(options.projectRoot, "vlt-lock.json");
14148
- if (!existsSync6(lockfilePath)) {
15104
+ if (!existsSync7(lockfilePath)) {
14149
15105
  throw error(
14150
15106
  "vlt-lock.json file is required when using --expect-lockfile, --frozen-lockfile, or ci command",
14151
15107
  {
@@ -14173,6 +15129,7 @@ var install = async (options, add) => {
14173
15129
  });
14174
15130
  const specCache = /* @__PURE__ */ new Map();
14175
15131
  const lockfileData2 = options.frozenLockfile ? loadData(options.projectRoot) : void 0;
15132
+ const modifiers = GraphModifier.maybeLoad(options);
14176
15133
  if (options.frozenLockfile) {
14177
15134
  if (add?.modifiedDependencies) {
14178
15135
  const dependencies = [];
@@ -14189,6 +15146,7 @@ var install = async (options, add) => {
14189
15146
  const lockfileGraph = load3({
14190
15147
  ...options,
14191
15148
  mainManifest,
15149
+ modifiers,
14192
15150
  monorepo: fullMonorepo,
14193
15151
  specCache,
14194
15152
  lockfileData: lockfileData2
@@ -14204,12 +15162,15 @@ var install = async (options, add) => {
14204
15162
  graph: lockfileGraph,
14205
15163
  add: emptyAdd,
14206
15164
  remove: emptyRemove,
14207
- ...options
15165
+ ...options,
15166
+ modifiers
14208
15167
  });
14209
15168
  const specChanges = [];
14210
15169
  for (const importer of lockfileGraph.importers) {
14211
15170
  const deps = getDependencies(importer, options);
14212
15171
  for (const [depName, dep] of deps) {
15172
+ if (modifiers?.targetsImporterEdge(importer, dep.spec))
15173
+ continue;
14213
15174
  const edge = importer.edgesOut.get(depName);
14214
15175
  if (edge?.spec) {
14215
15176
  if (edge.spec.toString() !== dep.spec.toString()) {
@@ -14225,9 +15186,10 @@ var install = async (options, add) => {
14225
15186
  if (importerSpecs.add.modifiedDependencies || importerSpecs.remove.modifiedDependencies || specChanges.length > 0 || lockfileGraph.optionsChanged) {
14226
15187
  const details = [];
14227
15188
  if (lockfileGraph.optionsChanged) {
14228
- details.push(
14229
- " Configuration options have changed (e.g. modifiers, registries, catalogs)"
14230
- );
15189
+ details.push(" Configuration options have changed:");
15190
+ for (const change of lockfileGraph.optionsChanges) {
15191
+ details.push(` ${formatOptionsChange(change)}`);
15192
+ }
14231
15193
  }
14232
15194
  if (specChanges.length > 0) {
14233
15195
  details.push(...specChanges);
@@ -14275,7 +15237,7 @@ var install = async (options, add) => {
14275
15237
  options.projectRoot,
14276
15238
  "node_modules"
14277
15239
  );
14278
- if (existsSync6(nodeModulesPath)) {
15240
+ if (existsSync7(nodeModulesPath)) {
14279
15241
  await remover.rm(nodeModulesPath);
14280
15242
  remover.confirm();
14281
15243
  }
@@ -14284,7 +15246,6 @@ var install = async (options, add) => {
14284
15246
  const remove = Object.assign(/* @__PURE__ */ new Map(), {
14285
15247
  modifiedDependencies: false
14286
15248
  });
14287
- const modifiers = GraphModifier.maybeLoad(options);
14288
15249
  let act = load4({
14289
15250
  ...options,
14290
15251
  mainManifest,
@@ -14311,7 +15272,6 @@ var install = async (options, add) => {
14311
15272
  lockfileData: lockfileData2
14312
15273
  });
14313
15274
  if (options.lockfileOnly) {
14314
- lockfile.save({ ...options, graph, modifiers });
14315
15275
  const saveImportersPackageJson = (
14316
15276
  /* c8 ignore next */
14317
15277
  add?.modifiedDependencies || remove.modifiedDependencies ? updatePackageJson({
@@ -14321,7 +15281,9 @@ var install = async (options, add) => {
14321
15281
  remove
14322
15282
  }) : void 0
14323
15283
  );
15284
+ lockfile.save({ ...options, graph, modifiers });
14324
15285
  saveImportersPackageJson?.();
15286
+ remover.confirm();
14325
15287
  return { graph, diff: void 0 };
14326
15288
  }
14327
15289
  const { diff: diff2, buildQueue } = await reify({
@@ -14343,7 +15305,7 @@ var install = async (options, add) => {
14343
15305
  options.projectRoot,
14344
15306
  "node_modules/.vlt-lock.json"
14345
15307
  );
14346
- if (existsSync6(hiddenLockfile)) {
15308
+ if (existsSync7(hiddenLockfile)) {
14347
15309
  rmSync3(hiddenLockfile, { force: true });
14348
15310
  }
14349
15311
  } catch {
@@ -14354,6 +15316,7 @@ var install = async (options, add) => {
14354
15316
 
14355
15317
  export {
14356
15318
  asDependency,
15319
+ addKey,
14357
15320
  asNode,
14358
15321
  run,
14359
15322
  runFG,