@vltpkg/graph 1.0.0-rc.9 → 1.0.2

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 (228) hide show
  1. package/README.md +101 -22
  2. package/dist/{esm/actual → actual}/load.d.ts +8 -1
  3. package/dist/{esm/actual → actual}/load.js +29 -6
  4. package/dist/{esm/browser.d.ts → browser.d.ts} +0 -1
  5. package/dist/{esm/browser.js → browser.js} +0 -1
  6. package/dist/{esm/build.d.ts → build.d.ts} +0 -1
  7. package/dist/{esm/build.js → build.js} +0 -1
  8. package/dist/{esm/dependencies.d.ts → dependencies.d.ts} +0 -1
  9. package/dist/{esm/dependencies.js → dependencies.js} +3 -2
  10. package/dist/{esm/diff.d.ts → diff.d.ts} +2 -1
  11. package/dist/{esm/diff.js → diff.js} +5 -2
  12. package/dist/{esm/edge.d.ts → edge.d.ts} +0 -1
  13. package/dist/{esm/edge.js → edge.js} +0 -1
  14. package/dist/{esm/fixup-added-names.d.ts → fixup-added-names.d.ts} +4 -2
  15. package/dist/{esm/fixup-added-names.js → fixup-added-names.js} +16 -1
  16. package/dist/{esm/graph.d.ts → graph.d.ts} +13 -1
  17. package/dist/{esm/graph.js → graph.js} +49 -12
  18. package/dist/{esm/ideal → ideal}/append-nodes.d.ts +14 -3
  19. package/dist/{esm/ideal → ideal}/append-nodes.js +252 -64
  20. package/dist/{esm/ideal → ideal}/build-ideal-from-starting-graph.d.ts +0 -1
  21. package/dist/{esm/ideal → ideal}/build-ideal-from-starting-graph.js +0 -1
  22. package/dist/{esm/ideal → ideal}/build.d.ts +0 -1
  23. package/dist/{esm/ideal → ideal}/build.js +15 -2
  24. package/dist/{esm/ideal → ideal}/get-importer-specs.d.ts +0 -1
  25. package/dist/{esm/ideal → ideal}/get-importer-specs.js +13 -2
  26. package/dist/ideal/peers.d.ts +160 -0
  27. package/dist/ideal/peers.js +696 -0
  28. package/dist/{esm/ideal → ideal}/refresh-ideal-graph.d.ts +0 -5
  29. package/dist/{esm/ideal → ideal}/refresh-ideal-graph.js +11 -26
  30. package/dist/{esm/ideal → ideal}/remove-satisfied-specs.d.ts +0 -1
  31. package/dist/{esm/ideal → ideal}/remove-satisfied-specs.js +8 -1
  32. package/dist/ideal/sorting.d.ts +45 -0
  33. package/dist/ideal/sorting.js +70 -0
  34. package/dist/{esm/ideal → ideal}/types.d.ts +1 -6
  35. package/dist/ideal/types.js +1 -0
  36. package/dist/{esm/index.d.ts → index.d.ts} +0 -1
  37. package/dist/{esm/index.js → index.js} +0 -1
  38. package/dist/{esm/install.d.ts → install.d.ts} +2 -1
  39. package/dist/{esm/install.js → install.js} +24 -4
  40. package/dist/{esm/lockfile → lockfile}/load-edges.d.ts +0 -1
  41. package/dist/{esm/lockfile → lockfile}/load-edges.js +16 -6
  42. package/dist/{esm/lockfile → lockfile}/load-nodes.d.ts +0 -1
  43. package/dist/{esm/lockfile → lockfile}/load-nodes.js +6 -3
  44. package/dist/{esm/lockfile → lockfile}/load.d.ts +0 -1
  45. package/dist/lockfile/load.js +171 -0
  46. package/dist/{esm/lockfile → lockfile}/save.d.ts +1 -2
  47. package/dist/{esm/lockfile → lockfile}/save.js +17 -11
  48. package/dist/{esm/lockfile → lockfile}/types.d.ts +7 -1
  49. package/dist/{esm/lockfile → lockfile}/types.js +6 -1
  50. package/dist/{esm/modifiers.d.ts → modifiers.d.ts} +0 -1
  51. package/dist/{esm/modifiers.js → modifiers.js} +0 -1
  52. package/dist/{esm/node.d.ts → node.d.ts} +2 -1
  53. package/dist/{esm/node.js → node.js} +5 -1
  54. package/dist/{esm/non-empty-list.d.ts → non-empty-list.d.ts} +0 -1
  55. package/dist/{esm/non-empty-list.js → non-empty-list.js} +0 -1
  56. package/dist/path-security-error.d.ts +5 -0
  57. package/dist/path-security-error.js +13 -0
  58. package/dist/{esm/reify → reify}/add-edge.d.ts +0 -1
  59. package/dist/{esm/reify → reify}/add-edge.js +10 -4
  60. package/dist/{esm/reify → reify}/add-edges.d.ts +0 -1
  61. package/dist/{esm/reify → reify}/add-edges.js +0 -1
  62. package/dist/{esm/reify → reify}/add-nodes.d.ts +0 -1
  63. package/dist/{esm/reify → reify}/add-nodes.js +0 -1
  64. package/dist/{esm/reify → reify}/bin-chmod.d.ts +0 -1
  65. package/dist/{esm/reify → reify}/bin-chmod.js +0 -1
  66. package/dist/{esm/reify → reify}/build.d.ts +0 -1
  67. package/dist/{esm/reify → reify}/build.js +12 -4
  68. package/dist/{esm/reify → reify}/calculate-save-value.d.ts +1 -2
  69. package/dist/{esm/reify → reify}/calculate-save-value.js +11 -5
  70. package/dist/{esm/reify → reify}/check-needed-build.d.ts +10 -1
  71. package/dist/reify/check-needed-build.js +71 -0
  72. package/dist/{esm/reify → reify}/delete-edge.d.ts +0 -1
  73. package/dist/{esm/reify → reify}/delete-edge.js +0 -1
  74. package/dist/{esm/reify → reify}/delete-edges.d.ts +0 -1
  75. package/dist/{esm/reify → reify}/delete-edges.js +0 -1
  76. package/dist/{esm/reify → reify}/delete-nodes.d.ts +0 -1
  77. package/dist/{esm/reify → reify}/delete-nodes.js +0 -1
  78. package/dist/{esm/reify → reify}/extract-node.d.ts +0 -1
  79. package/dist/{esm/reify → reify}/extract-node.js +10 -3
  80. package/dist/{esm/reify → reify}/index.d.ts +3 -1
  81. package/dist/{esm/reify → reify}/index.js +25 -5
  82. package/dist/{esm/reify → reify}/internal-hoist.d.ts +0 -1
  83. package/dist/{esm/reify → reify}/internal-hoist.js +0 -1
  84. package/dist/{esm/reify → reify}/optional-fail.d.ts +0 -1
  85. package/dist/{esm/reify → reify}/optional-fail.js +0 -1
  86. package/dist/{esm/reify → reify}/rollback.d.ts +0 -1
  87. package/dist/{esm/reify → reify}/rollback.js +0 -1
  88. package/dist/{esm/reify → reify}/update-importers-package-json.d.ts +11 -2
  89. package/dist/{esm/reify → reify}/update-importers-package-json.js +4 -5
  90. package/dist/{esm/remove-optional-subgraph.d.ts → remove-optional-subgraph.d.ts} +0 -1
  91. package/dist/{esm/remove-optional-subgraph.js → remove-optional-subgraph.js} +0 -1
  92. package/dist/{esm/resolve-save-type.d.ts → resolve-save-type.d.ts} +0 -1
  93. package/dist/{esm/resolve-save-type.js → resolve-save-type.js} +0 -1
  94. package/dist/{esm/stringify-node.d.ts → stringify-node.d.ts} +0 -1
  95. package/dist/{esm/stringify-node.js → stringify-node.js} +0 -1
  96. package/dist/{esm/transfer-data → transfer-data}/load.d.ts +0 -1
  97. package/dist/{esm/transfer-data → transfer-data}/load.js +8 -5
  98. package/dist/{esm/uninstall.d.ts → uninstall.d.ts} +0 -1
  99. package/dist/{esm/uninstall.js → uninstall.js} +17 -7
  100. package/dist/{esm/update.d.ts → update.d.ts} +0 -1
  101. package/dist/{esm/update.js → update.js} +1 -1
  102. package/dist/{esm/virtual-root.d.ts → virtual-root.d.ts} +0 -1
  103. package/dist/{esm/virtual-root.js → virtual-root.js} +0 -1
  104. package/dist/{esm/visualization → visualization}/human-readable-output.d.ts +0 -1
  105. package/dist/{esm/visualization → visualization}/human-readable-output.js +0 -1
  106. package/dist/{esm/visualization → visualization}/json-output.d.ts +0 -1
  107. package/dist/{esm/visualization → visualization}/json-output.js +0 -1
  108. package/dist/{esm/visualization → visualization}/mermaid-output.d.ts +2 -2
  109. package/dist/{esm/visualization → visualization}/mermaid-output.js +27 -21
  110. package/dist/{esm/visualization → visualization}/object-like-output.d.ts +0 -1
  111. package/dist/{esm/visualization → visualization}/object-like-output.js +0 -1
  112. package/package.json +57 -62
  113. package/dist/esm/actual/load.d.ts.map +0 -1
  114. package/dist/esm/actual/load.js.map +0 -1
  115. package/dist/esm/browser.d.ts.map +0 -1
  116. package/dist/esm/browser.js.map +0 -1
  117. package/dist/esm/build.d.ts.map +0 -1
  118. package/dist/esm/build.js.map +0 -1
  119. package/dist/esm/dependencies.d.ts.map +0 -1
  120. package/dist/esm/dependencies.js.map +0 -1
  121. package/dist/esm/diff.d.ts.map +0 -1
  122. package/dist/esm/diff.js.map +0 -1
  123. package/dist/esm/edge.d.ts.map +0 -1
  124. package/dist/esm/edge.js.map +0 -1
  125. package/dist/esm/fixup-added-names.d.ts.map +0 -1
  126. package/dist/esm/fixup-added-names.js.map +0 -1
  127. package/dist/esm/graph.d.ts.map +0 -1
  128. package/dist/esm/graph.js.map +0 -1
  129. package/dist/esm/ideal/append-nodes.d.ts.map +0 -1
  130. package/dist/esm/ideal/append-nodes.js.map +0 -1
  131. package/dist/esm/ideal/build-ideal-from-starting-graph.d.ts.map +0 -1
  132. package/dist/esm/ideal/build-ideal-from-starting-graph.js.map +0 -1
  133. package/dist/esm/ideal/build.d.ts.map +0 -1
  134. package/dist/esm/ideal/build.js.map +0 -1
  135. package/dist/esm/ideal/get-importer-specs.d.ts.map +0 -1
  136. package/dist/esm/ideal/get-importer-specs.js.map +0 -1
  137. package/dist/esm/ideal/get-ordered-dependencies.d.ts +0 -10
  138. package/dist/esm/ideal/get-ordered-dependencies.d.ts.map +0 -1
  139. package/dist/esm/ideal/get-ordered-dependencies.js +0 -42
  140. package/dist/esm/ideal/get-ordered-dependencies.js.map +0 -1
  141. package/dist/esm/ideal/peers.d.ts +0 -71
  142. package/dist/esm/ideal/peers.d.ts.map +0 -1
  143. package/dist/esm/ideal/peers.js +0 -318
  144. package/dist/esm/ideal/peers.js.map +0 -1
  145. package/dist/esm/ideal/refresh-ideal-graph.d.ts.map +0 -1
  146. package/dist/esm/ideal/refresh-ideal-graph.js.map +0 -1
  147. package/dist/esm/ideal/remove-satisfied-specs.d.ts.map +0 -1
  148. package/dist/esm/ideal/remove-satisfied-specs.js.map +0 -1
  149. package/dist/esm/ideal/types.d.ts.map +0 -1
  150. package/dist/esm/ideal/types.js +0 -2
  151. package/dist/esm/ideal/types.js.map +0 -1
  152. package/dist/esm/index.d.ts.map +0 -1
  153. package/dist/esm/index.js.map +0 -1
  154. package/dist/esm/install.d.ts.map +0 -1
  155. package/dist/esm/install.js.map +0 -1
  156. package/dist/esm/lockfile/load-edges.d.ts.map +0 -1
  157. package/dist/esm/lockfile/load-edges.js.map +0 -1
  158. package/dist/esm/lockfile/load-nodes.d.ts.map +0 -1
  159. package/dist/esm/lockfile/load-nodes.js.map +0 -1
  160. package/dist/esm/lockfile/load.d.ts.map +0 -1
  161. package/dist/esm/lockfile/load.js +0 -64
  162. package/dist/esm/lockfile/load.js.map +0 -1
  163. package/dist/esm/lockfile/save.d.ts.map +0 -1
  164. package/dist/esm/lockfile/save.js.map +0 -1
  165. package/dist/esm/lockfile/types.d.ts.map +0 -1
  166. package/dist/esm/lockfile/types.js.map +0 -1
  167. package/dist/esm/modifiers.d.ts.map +0 -1
  168. package/dist/esm/modifiers.js.map +0 -1
  169. package/dist/esm/node.d.ts.map +0 -1
  170. package/dist/esm/node.js.map +0 -1
  171. package/dist/esm/non-empty-list.d.ts.map +0 -1
  172. package/dist/esm/non-empty-list.js.map +0 -1
  173. package/dist/esm/package.json +0 -3
  174. package/dist/esm/reify/add-edge.d.ts.map +0 -1
  175. package/dist/esm/reify/add-edge.js.map +0 -1
  176. package/dist/esm/reify/add-edges.d.ts.map +0 -1
  177. package/dist/esm/reify/add-edges.js.map +0 -1
  178. package/dist/esm/reify/add-nodes.d.ts.map +0 -1
  179. package/dist/esm/reify/add-nodes.js.map +0 -1
  180. package/dist/esm/reify/bin-chmod.d.ts.map +0 -1
  181. package/dist/esm/reify/bin-chmod.js.map +0 -1
  182. package/dist/esm/reify/build.d.ts.map +0 -1
  183. package/dist/esm/reify/build.js.map +0 -1
  184. package/dist/esm/reify/calculate-save-value.d.ts.map +0 -1
  185. package/dist/esm/reify/calculate-save-value.js.map +0 -1
  186. package/dist/esm/reify/check-needed-build.d.ts.map +0 -1
  187. package/dist/esm/reify/check-needed-build.js +0 -50
  188. package/dist/esm/reify/check-needed-build.js.map +0 -1
  189. package/dist/esm/reify/delete-edge.d.ts.map +0 -1
  190. package/dist/esm/reify/delete-edge.js.map +0 -1
  191. package/dist/esm/reify/delete-edges.d.ts.map +0 -1
  192. package/dist/esm/reify/delete-edges.js.map +0 -1
  193. package/dist/esm/reify/delete-nodes.d.ts.map +0 -1
  194. package/dist/esm/reify/delete-nodes.js.map +0 -1
  195. package/dist/esm/reify/extract-node.d.ts.map +0 -1
  196. package/dist/esm/reify/extract-node.js.map +0 -1
  197. package/dist/esm/reify/index.d.ts.map +0 -1
  198. package/dist/esm/reify/index.js.map +0 -1
  199. package/dist/esm/reify/internal-hoist.d.ts.map +0 -1
  200. package/dist/esm/reify/internal-hoist.js.map +0 -1
  201. package/dist/esm/reify/optional-fail.d.ts.map +0 -1
  202. package/dist/esm/reify/optional-fail.js.map +0 -1
  203. package/dist/esm/reify/rollback.d.ts.map +0 -1
  204. package/dist/esm/reify/rollback.js.map +0 -1
  205. package/dist/esm/reify/update-importers-package-json.d.ts.map +0 -1
  206. package/dist/esm/reify/update-importers-package-json.js.map +0 -1
  207. package/dist/esm/remove-optional-subgraph.d.ts.map +0 -1
  208. package/dist/esm/remove-optional-subgraph.js.map +0 -1
  209. package/dist/esm/resolve-save-type.d.ts.map +0 -1
  210. package/dist/esm/resolve-save-type.js.map +0 -1
  211. package/dist/esm/stringify-node.d.ts.map +0 -1
  212. package/dist/esm/stringify-node.js.map +0 -1
  213. package/dist/esm/transfer-data/load.d.ts.map +0 -1
  214. package/dist/esm/transfer-data/load.js.map +0 -1
  215. package/dist/esm/uninstall.d.ts.map +0 -1
  216. package/dist/esm/uninstall.js.map +0 -1
  217. package/dist/esm/update.d.ts.map +0 -1
  218. package/dist/esm/update.js.map +0 -1
  219. package/dist/esm/virtual-root.d.ts.map +0 -1
  220. package/dist/esm/virtual-root.js.map +0 -1
  221. package/dist/esm/visualization/human-readable-output.d.ts.map +0 -1
  222. package/dist/esm/visualization/human-readable-output.js.map +0 -1
  223. package/dist/esm/visualization/json-output.d.ts.map +0 -1
  224. package/dist/esm/visualization/json-output.js.map +0 -1
  225. package/dist/esm/visualization/mermaid-output.d.ts.map +0 -1
  226. package/dist/esm/visualization/mermaid-output.js.map +0 -1
  227. package/dist/esm/visualization/object-like-output.d.ts.map +0 -1
  228. package/dist/esm/visualization/object-like-output.js.map +0 -1
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ title: '@vltpkg/graph'
3
+ ---
4
+
1
5
  ![graph](https://github.com/user-attachments/assets/dfbed9e0-8ef0-4a43-993d-d3e5d1e5ae1d)
2
6
 
3
7
  # @vltpkg/graph
@@ -41,57 +45,87 @@ At a glance:
41
45
  `node_modules/.vlt-lock.json` mirroring the current on-disk state to
42
46
  accelerate subsequent loads of the Actual graph.
43
47
  - Modifiers: Configuration for selectively altering dependency
44
- resolution; Ideal/Actual builders support skipping node loads when
45
- modifiers change.
48
+ resolution via DSS queries in `vlt.json`.
49
+ - Peer Contexts: Isolation mechanism for peer dependencies that allows
50
+ multiple versions of the same package when peer requirements differ.
46
51
 
47
52
  ## API
48
53
 
49
- ### `actual.load({ projectRoot: string }): Graph`
54
+ ### `actual.load(options): Graph`
50
55
 
51
56
  Recursively loads the `node_modules` folder found at `projectRoot` in
52
57
  order to create a graph representation of the current installed
53
58
  packages.
54
59
 
55
- ### `async ideal.build({ projectRoot: string }): Promise<Graph>`
60
+ ### `ideal.build(options): Promise<Graph>`
56
61
 
57
- This method returns a new `Graph` object, reading from the
58
- `package.json` file located at `projectRoot` dir and building up the
59
- graph representation of nodes and edges from the files read from the
60
- local file system.
62
+ Builds the ideal dependency graph by loading from lockfile (preferred)
63
+ or actual graph, then expanding dependencies by fetching manifests.
64
+ Requires `packageInfo` and `remover` in addition to standard options.
61
65
 
62
- ### `lockfile.load({ mainManifest: Manifest, projectRoot: string }): Graph`
66
+ ### `lockfile.load(options): Graph`
63
67
 
64
68
  Loads the lockfile file found at `projectRoot` and returns the graph.
65
69
 
66
- ### `reify(options): Promise<Diff>`
70
+ ### `lockfile.save(options): void`
71
+
72
+ Saves the graph to `vlt-lock.json`.
73
+
74
+ ### `reify(options): Promise<ReifyResult>`
67
75
 
68
76
  Computes a `Diff` between the Actual and Ideal graphs and applies the
69
77
  minimal filesystem changes (creating/deleting links, writing
70
78
  lockfiles, hoisting, lifecycle scripts) to make the on-disk install
71
- match the Ideal graph.
79
+ match the Ideal graph. Returns `{ diff, buildQueue }`.
80
+
81
+ ### `install(options, add?): Promise<{ graph, diff, buildQueue }>`
82
+
83
+ High-level install orchestration that handles graph building, reify,
84
+ and lockfile management. Supports `--frozen-lockfile`,
85
+ `--clean-install`, and `--lockfile-only` modes.
86
+
87
+ ### `mermaidOutput(graph): string`
88
+
89
+ Generates Mermaid flowchart syntax from graph data.
90
+
91
+ ### `humanReadableOutput(graph, options): string`
92
+
93
+ Generates ASCII tree output with optional colors. Used in `vlt ls`.
94
+
95
+ ### `jsonOutput(graph): JSONOutputItem[]`
96
+
97
+ Returns array of `{name, fromID, spec, type, to, overridden}` items.
72
98
 
73
99
  ## Usage
74
100
 
75
- Here's a quick example of how to use the `@vltpkg/graph.ideal.build`
76
- method to build a graph representation of the install defined at the
77
- `projectRoot` directory.
101
+ ### High-Level Install
78
102
 
79
- ```
80
- import { ideal } from '@vltpkg/graph'
103
+ ```ts
104
+ import { install } from '@vltpkg/graph'
81
105
 
82
- const graph = await ideal.build({ projectRoot: process.cwd() })
106
+ const { graph, diff, buildQueue } = await install({
107
+ projectRoot: process.cwd(),
108
+ packageInfo,
109
+ packageJson,
110
+ scurry,
111
+ allowScripts: '*',
112
+ })
83
113
  ```
84
114
 
85
115
  ### Load Actual Graph and Reify
86
116
 
87
117
  ```ts
88
118
  import { actual, ideal, reify } from '@vltpkg/graph'
119
+ import { RollbackRemove } from '@vltpkg/rollback-remove'
120
+
121
+ const remover = new RollbackRemove()
89
122
 
90
123
  // Load current on-disk state
91
124
  const from = actual.load({
92
125
  projectRoot: process.cwd(),
93
126
  packageJson,
94
127
  scurry,
128
+ loadManifests: true,
95
129
  })
96
130
 
97
131
  // Build intended end state (may start from lockfile or actual)
@@ -100,15 +134,18 @@ const to = await ideal.build({
100
134
  packageInfo,
101
135
  packageJson,
102
136
  scurry,
137
+ remover,
103
138
  })
104
139
 
105
140
  // Apply minimal changes to match Ideal
106
- await reify({
141
+ const { diff, buildQueue } = await reify({
107
142
  graph: to,
108
143
  actual: from,
109
144
  packageInfo,
110
145
  packageJson,
111
146
  scurry,
147
+ remover,
148
+ allowScripts: '*',
112
149
  })
113
150
  ```
114
151
 
@@ -118,15 +155,48 @@ await reify({
118
155
  import { lockfile } from '@vltpkg/graph'
119
156
 
120
157
  // Load virtual graph from vlt-lock.json
121
- const g = lockfile.load({
158
+ const graph = lockfile.load({
122
159
  projectRoot,
123
160
  mainManifest,
124
161
  packageJson,
125
- scurry,
126
162
  })
127
163
 
128
- // Save both lockfile formats
129
- lockfile.save({ graph: g, projectRoot, packageJson, scurry })
164
+ // Save to vlt-lock.json
165
+ lockfile.save({ graph })
166
+ ```
167
+
168
+ ### Graph Visualization
169
+
170
+ ```ts
171
+ import {
172
+ mermaidOutput,
173
+ humanReadableOutput,
174
+ jsonOutput,
175
+ } from '@vltpkg/graph'
176
+
177
+ // Mermaid flowchart (for docs, dashboards)
178
+ const mermaid = mermaidOutput({
179
+ edges: [...graph.edges],
180
+ nodes: [...graph.nodes.values()],
181
+ importers: graph.importers,
182
+ })
183
+
184
+ // ASCII tree with colors (used in `vlt ls`)
185
+ const tree = humanReadableOutput(
186
+ {
187
+ edges: [...graph.edges],
188
+ nodes: [...graph.nodes.values()],
189
+ importers: graph.importers,
190
+ },
191
+ { colors: true },
192
+ )
193
+
194
+ // JSON array of dependency items
195
+ const json = jsonOutput({
196
+ edges: [...graph.edges],
197
+ nodes: [...graph.nodes.values()],
198
+ importers: graph.importers,
199
+ })
130
200
  ```
131
201
 
132
202
  ## Architecture
@@ -137,6 +207,7 @@ Graph construction modes supported by the library:
137
207
  - Load and save via `src/lockfile/load.ts` and
138
208
  `src/lockfile/save.ts`
139
209
  - Hidden lockfile: `node_modules/.vlt-lock.json` for faster loads
210
+ - 📖 [Lockfile README](./src/lockfile/README.md)
140
211
 
141
212
  - Actual Graphs (filesystem-based)
142
213
  - Loaded by traversing `node_modules` via `src/actual/load.ts`
@@ -150,20 +221,28 @@ Graph construction modes supported by the library:
150
221
  `src/ideal/get-importer-specs.ts`
151
222
  - Fetches and expands manifests using `@vltpkg/package-info`, reuses
152
223
  existing nodes that satisfy specs
224
+ - 📖 [Ideal README](./src/ideal/README.md)
153
225
 
154
226
  Finally, `src/diff.ts` computes changes and `src/reify/` applies them
155
227
  to the filesystem.
156
228
 
229
+ - 📖 [Reify README](./src/reify/README.md)
230
+ - 📖 [Architecture Guide](./ARCHITECTURE.md)
231
+
157
232
  ## Related Workspaces
158
233
 
159
234
  - `@vltpkg/dep-id`: Unique IDs for packages, ensuring `Node` identity
160
235
  - `@vltpkg/spec`: Parse/normalize dependency specifiers and registry
161
236
  semantics
162
237
  - `@vltpkg/semver`: Semantic version parsing/comparison
238
+ - `@vltpkg/satisfies`: Check if a DepID satisfies a Spec
163
239
  - `@vltpkg/package-info`: Fetch remote manifests and artifacts
164
240
  (registry, git, tarball)
165
241
  - `@vltpkg/package-json`: Read and cache local `package.json` files
166
242
  - `@vltpkg/workspaces`: Monorepo workspace discovery and grouping
243
+ - `@vltpkg/rollback-remove`: Safe file removal with rollback
244
+ capability
245
+ - `@vltpkg/vlt-json`: Load `vlt.json` configuration (modifiers, etc.)
167
246
 
168
247
  ## References
169
248
 
@@ -91,10 +91,17 @@ export declare const asStoreConfigObject: (obj: unknown) => StoreConfigObject;
91
91
  * path-based or a registry spec, otherwise returns `undefined`.
92
92
  */
93
93
  export declare const getPathBasedId: (spec: Spec, path: Path) => DepID | undefined;
94
+ /**
95
+ * Verify that all importer node_modules directories exist on disk.
96
+ * The hidden lockfile may report deps as installed even when a
97
+ * workspace's node_modules has been manually deleted. Throws if
98
+ * any importer node_modules directory is missing, causing the
99
+ * caller to fall through to filesystem-based graph loading.
100
+ */
101
+ export declare const verifyImporterNodeModules: (graph: Graph, projectRoot: string) => void;
94
102
  /**
95
103
  * Read the file system looking for `node_modules` folders and
96
104
  * returns a new {@link Graph} that represents the relationship
97
105
  * between the dependencies found.
98
106
  */
99
107
  export declare const load: (options: LoadOptions) => Graph;
100
- //# sourceMappingURL=load.d.ts.map
@@ -6,7 +6,8 @@ import { shorten, getRawDependencies, getDependencies, } from "../dependencies.j
6
6
  import { Graph } from "../graph.js";
7
7
  import { loadHidden } from "../lockfile/load.js";
8
8
  import { saveHidden } from "../lockfile/save.js";
9
- import { readFileSync } from 'node:fs';
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { resolve } from 'node:path';
10
11
  /**
11
12
  * Checks if a given object is a {@link StoreConfigObject}.
12
13
  */
@@ -179,7 +180,10 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
179
180
  const depType = shorten(type, alias, fromNode.manifest);
180
181
  let spec = Spec.parse(alias, bareSpec, {
181
182
  ...options,
182
- registry: fromNode.registry,
183
+ // fall back to options.registry (which the lockfile merges into)
184
+ // so importer-level edges don't silently revert to the default
185
+ // npm registry. see vltpkg/vltpkg#1580.
186
+ registry: fromNode.registry ?? options.registry,
183
187
  });
184
188
  // Check for active modifiers and replace spec if a modifier is complete
185
189
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, alias, modifierRefs);
@@ -238,7 +242,7 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
238
242
  const depType = shorten(type, name, fromNode.manifest);
239
243
  let spec = Spec.parse(name, bareSpec, {
240
244
  ...options,
241
- registry: fromNode.registry,
245
+ registry: fromNode.registry ?? options.registry,
242
246
  });
243
247
  // Check for active modifiers and replace spec for missing dependencies
244
248
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, name, modifierRefs);
@@ -247,6 +251,21 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
247
251
  }
248
252
  }
249
253
  };
254
+ /**
255
+ * Verify that all importer node_modules directories exist on disk.
256
+ * The hidden lockfile may report deps as installed even when a
257
+ * workspace's node_modules has been manually deleted. Throws if
258
+ * any importer node_modules directory is missing, causing the
259
+ * caller to fall through to filesystem-based graph loading.
260
+ */
261
+ export const verifyImporterNodeModules = (graph, projectRoot) => {
262
+ for (const importer of graph.importers) {
263
+ const nm = resolve(projectRoot, importer.location, 'node_modules');
264
+ if (!existsSync(nm)) {
265
+ throw new Error(`Missing node_modules for importer: ${importer.location}`);
266
+ }
267
+ }
268
+ };
250
269
  /**
251
270
  * Read the file system looking for `node_modules` folders and
252
271
  * returns a new {@link Graph} that represents the relationship
@@ -267,12 +286,17 @@ export const load = (options) => {
267
286
  monorepo,
268
287
  scurry,
269
288
  });
289
+ verifyImporterNodeModules(graph, projectRoot);
270
290
  done();
271
291
  return graph;
272
292
  }
273
293
  catch {
274
- // if validation fails or any other error occurs,
275
- // fall back to filesystem traversal
294
+ // Hidden lockfile is cache-only, safe to regenerate on any error
295
+ // as it will fall back to filesystem traversal. Deliberately also
296
+ // swallows EINVALIDNAME, unlike ideal/build.ts: this cache lives
297
+ // inside node_modules and regenerating it is the right response,
298
+ // whereas failing loud would let it brick installs.
299
+ // TODO: Warn version mismatch to @vltpkg/output for debugging
276
300
  }
277
301
  }
278
302
  const graph = new Graph({ ...options, mainManifest });
@@ -316,4 +340,3 @@ export const load = (options) => {
316
340
  done();
317
341
  return graph;
318
342
  };
319
- //# sourceMappingURL=load.js.map
@@ -12,4 +12,3 @@ declare const transfer: {
12
12
  load: (transfered: import("./transfer-data/load.ts").TransferData) => import("./transfer-data/load.ts").LoadResult;
13
13
  };
14
14
  export { asDependencyTypeShort, getBooleanFlagsFromNum, lockfile, longDependencyTypes, shorten, stringifyNode, transfer, };
15
- //# sourceMappingURL=browser.d.ts.map
@@ -14,4 +14,3 @@ const transfer = {
14
14
  load,
15
15
  };
16
16
  export { asDependencyTypeShort, getBooleanFlagsFromNum, lockfile, longDependencyTypes, shorten, stringifyNode, transfer, };
17
- //# sourceMappingURL=browser.js.map
@@ -26,4 +26,3 @@ export interface BuildOptions extends LoadOptions {
26
26
  * 6. Persists build results to lockfile
27
27
  */
28
28
  export declare const build: (options: BuildOptions) => Promise<BuildResult>;
29
- //# sourceMappingURL=build.d.ts.map
@@ -76,4 +76,3 @@ export const build = async (options) => {
76
76
  });
77
77
  return buildResult;
78
78
  };
79
- //# sourceMappingURL=build.js.map
@@ -63,4 +63,3 @@ export declare const getRawDependencies: (node: NodeLike) => Map<string, RawDepe
63
63
  * from a given node manifest, including missing dependencies.
64
64
  */
65
65
  export declare const getDependencies: (node: NodeLike, options: SpecOptions) => Map<string, Dependency>;
66
- //# sourceMappingURL=dependencies.d.ts.map
@@ -100,7 +100,9 @@ export const getDependencies = (node, options) => {
100
100
  const depType = shorten(type, name, node.manifest);
101
101
  const spec = Spec.parse(name, bareSpec, {
102
102
  ...options,
103
- registry: node.registry,
103
+ // fall back to options.registry so deps don't lose the configured
104
+ // registry when the node itself has none. see vltpkg/vltpkg#1580.
105
+ registry: node.registry ?? options.registry,
104
106
  });
105
107
  res.set(name, {
106
108
  spec,
@@ -109,4 +111,3 @@ export const getDependencies = (node, options) => {
109
111
  }
110
112
  return res;
111
113
  };
112
- //# sourceMappingURL=dependencies.js.map
@@ -71,6 +71,7 @@ export declare class Diff {
71
71
  engines?: Record<string, string>;
72
72
  os?: string[] | string;
73
73
  cpu?: string[] | string;
74
+ libc?: string[] | string;
74
75
  } | undefined;
75
76
  buildState: "none" | "needed" | "built" | "failed";
76
77
  }[];
@@ -94,6 +95,7 @@ export declare class Diff {
94
95
  engines?: Record<string, string>;
95
96
  os?: string[] | string;
96
97
  cpu?: string[] | string;
98
+ libc?: string[] | string;
97
99
  } | undefined;
98
100
  buildState: "none" | "needed" | "built" | "failed";
99
101
  }[];
@@ -115,4 +117,3 @@ export declare class Diff {
115
117
  };
116
118
  }
117
119
  export {};
118
- //# sourceMappingURL=diff.d.ts.map
@@ -80,8 +80,12 @@ export class Diff {
80
80
  continue;
81
81
  if (fromEdge?.to)
82
82
  this.edges.delete.add(fromEdge);
83
- if (edge.to)
83
+ if (edge.to) {
84
84
  this.edges.add.add(edge);
85
+ if (!edge.optional) {
86
+ this.optionalOnly = false;
87
+ }
88
+ }
85
89
  }
86
90
  for (const edge of this.from.edges) {
87
91
  // the node with this dep, in the to graph
@@ -145,4 +149,3 @@ ${lines
145
149
  };
146
150
  }
147
151
  }
148
- //# sourceMappingURL=diff.js.map
@@ -44,4 +44,3 @@ export declare class Edge implements EdgeLike {
44
44
  toString(): string;
45
45
  }
46
46
  export {};
47
- //# sourceMappingURL=edge.d.ts.map
@@ -75,4 +75,3 @@ export class Edge {
75
75
  return `Edge from: ${this.from.id} --|${this.type}|--> ${to}`;
76
76
  }
77
77
  }
78
- //# sourceMappingURL=edge.js.map
@@ -11,6 +11,8 @@ import type { SpecOptions } from '@vltpkg/spec';
11
11
  *
12
12
  * This helper function fixes unknown names in the `add` map by replacing
13
13
  * placeholder specs with the correct names from the provided manifest.
14
+ *
15
+ * It also fixes empty bareSpec values (from CLI args like `vlt install foo`)
16
+ * by calculating the proper semver range from the resolved manifest version.
14
17
  */
15
- export declare const fixupAddedNames: (add: Map<string, Dependency> | undefined, manifest: Pick<Manifest, "name"> | undefined, options: SpecOptions, spec: Spec) => Spec;
16
- //# sourceMappingURL=fixup-added-names.d.ts.map
18
+ export declare const fixupAddedNames: (add: Map<string, Dependency> | undefined, manifest: Pick<Manifest, "name" | "version"> | undefined, options: SpecOptions, spec: Spec) => Spec;
@@ -1,4 +1,5 @@
1
1
  import { Spec } from '@vltpkg/spec';
2
+ import { calculateSaveValue } from "./reify/calculate-save-value.js";
2
3
  /**
3
4
  * When adding new dependencies, it's very common to not have a dependency
4
5
  * name directly available to reference, e.g: `file:local/folder` or
@@ -8,6 +9,9 @@ import { Spec } from '@vltpkg/spec';
8
9
  *
9
10
  * This helper function fixes unknown names in the `add` map by replacing
10
11
  * placeholder specs with the correct names from the provided manifest.
12
+ *
13
+ * It also fixes empty bareSpec values (from CLI args like `vlt install foo`)
14
+ * by calculating the proper semver range from the resolved manifest version.
11
15
  */
12
16
  export const fixupAddedNames = (add, manifest, options, spec) => {
13
17
  // Handle nameless dependencies
@@ -26,6 +30,17 @@ export const fixupAddedNames = (add, manifest, options, spec) => {
26
30
  add.set(manifest.name, n);
27
31
  }
28
32
  }
33
+ // Fix empty bareSpec (usually from CLI args like `vlt install foo`)
34
+ // by calculating the proper semver range from the resolved manifest version
35
+ if (add && manifest?.version && !spec.bareSpec) {
36
+ const addEntry = add.get(spec.name);
37
+ if (addEntry) {
38
+ const saveValue = calculateSaveValue(spec.final.type, spec, undefined, manifest.version);
39
+ if (saveValue && saveValue !== spec.bareSpec) {
40
+ spec = Spec.parse(spec.name, saveValue, options);
41
+ addEntry.spec = spec;
42
+ }
43
+ }
44
+ }
29
45
  return spec;
30
46
  };
31
- //# sourceMappingURL=fixup-added-names.js.map
@@ -78,6 +78,19 @@ export declare class Graph implements GraphLike {
78
78
  * The peer context sets used to resolve peer dependencies within this graph.
79
79
  */
80
80
  peerContexts: PeerContext[];
81
+ /**
82
+ * Cache of forked peer contexts so identical fork operations can reuse
83
+ * previously created contexts instead of duplicating them.
84
+ *
85
+ * Key format is internal and constructed in `ideal/peers.ts`.
86
+ */
87
+ peerContextForkCache: Map<string, PeerContext>;
88
+ /**
89
+ * Whether the options used to create this graph differ from the
90
+ * options stored in the lockfile it was loaded from. When true,
91
+ * the ideal builder must reset edges and rebuild the graph.
92
+ */
93
+ optionsChanged: boolean;
81
94
  /**
82
95
  * Tracks the current peer context index.
83
96
  */
@@ -144,4 +157,3 @@ export declare class Graph implements GraphLike {
144
157
  [kCustomInspect](_: number, options: InspectOptions): string;
145
158
  }
146
159
  export {};
147
- //# sourceMappingURL=graph.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { getId, joinDepIDTuple, splitExtra } from '@vltpkg/dep-id';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { satisfies } from '@vltpkg/satisfies';
4
- import { getOptions, Spec } from '@vltpkg/spec';
4
+ import { assertPathSafeName, getOptions, Spec } from '@vltpkg/spec';
5
5
  import { inspect } from 'node:util';
6
6
  import { lockfileData } from "./lockfile/save.js";
7
7
  import { Edge } from "./edge.js";
@@ -23,9 +23,12 @@ const getResolutionCacheKey = (spec, location, extra) => {
23
23
  const fromPrefix = f.type === 'file' ? location + ' : ' : '';
24
24
  // the unique key should also precise what is the type of the spec,
25
25
  // and in the case of a registry, what registry it is from.
26
- const typePrecisionKey = f.registry ?
26
+ const typePrecisionKey = f.registry || f.type === 'registry' ?
27
+ // registry-type specs always get a registry segment, even when
28
+ // no registry is configured, since findResolution trusts cache
29
+ // hits without re-running satisfies()
27
30
  `${cacheKeySeparator}registry` +
28
- `${cacheKeySeparator}${f.registry}`
31
+ `${cacheKeySeparator}${f.registry ?? '<none>'}`
29
32
  : f.gitRemote ?
30
33
  `${cacheKeySeparator}git` + `${cacheKeySeparator}${f.gitRemote}`
31
34
  : `${cacheKeySeparator}${f.type}`;
@@ -86,6 +89,19 @@ export class Graph {
86
89
  * The peer context sets used to resolve peer dependencies within this graph.
87
90
  */
88
91
  peerContexts;
92
+ /**
93
+ * Cache of forked peer contexts so identical fork operations can reuse
94
+ * previously created contexts instead of duplicating them.
95
+ *
96
+ * Key format is internal and constructed in `ideal/peers.ts`.
97
+ */
98
+ peerContextForkCache = new Map();
99
+ /**
100
+ * Whether the options used to create this graph differ from the
101
+ * options stored in the lockfile it was loaded from. When true,
102
+ * the ideal builder must reset edges and rebuild the graph.
103
+ */
104
+ optionsChanged = false;
89
105
  /**
90
106
  * Tracks the current peer context index.
91
107
  */
@@ -99,7 +115,7 @@ export class Graph {
99
115
  registries: options.registries,
100
116
  'git-hosts': options['git-hosts'],
101
117
  'git-host-archives': options['git-host-archives'],
102
- 'scope-registries': options['scope-registries'],
118
+ 'scoped-registries': options['scoped-registries'],
103
119
  'jsr-registries': options['jsr-registries'],
104
120
  catalog: options.catalog,
105
121
  catalogs: options.catalogs,
@@ -130,6 +146,12 @@ export class Graph {
130
146
  wsNode.setImporterLocation(`./${ws.path}`);
131
147
  if (wsNode.manifest) {
132
148
  this.manifests.set(wsNode.id, wsNode.manifest);
149
+ // set bins for workspace nodes so they can be linked
150
+ // when another importer depends on this workspace
151
+ /* c8 ignore next 3 - tested by integration tests */
152
+ if (wsNode.manifest.bin) {
153
+ wsNode.bins = wsNode.manifest.bin;
154
+ }
133
155
  }
134
156
  this.importers.add(wsNode);
135
157
  // creates a virtual edge to connect the workspaces to the root node
@@ -192,6 +214,8 @@ export class Graph {
192
214
  if (to) {
193
215
  spec.name = to.name /* c8 ignore next */ || '(unknown)';
194
216
  spec.spec = `${to.name}@${spec.bareSpec}`;
217
+ // this alias becomes the symlink segment in add-edge
218
+ assertPathSafeName(spec.name, spec.spec);
195
219
  }
196
220
  else {
197
221
  throw error('Impossible to place a missing, nameless dependency', { spec });
@@ -297,6 +321,18 @@ export class Graph {
297
321
  toFoundNode.detached = false;
298
322
  toFoundNode.dev &&= flags.dev;
299
323
  toFoundNode.optional &&= flags.optional;
324
+ // Hydrate manifest and integrity on existing nodes (e.g. loaded
325
+ // from a lockfile without manifest data). Without this, nodes
326
+ // that already have `resolved` set from the lockfile will never
327
+ // get their integrity populated because `setResolved()` is
328
+ // skipped when `resolved` is already present.
329
+ if (manifest) {
330
+ if (!toFoundNode.manifest) {
331
+ toFoundNode.manifest = manifest;
332
+ this.manifests.set(toFoundNode.id, manifest);
333
+ }
334
+ toFoundNode.integrity ??= manifest.dist?.integrity;
335
+ }
300
336
  return toFoundNode;
301
337
  }
302
338
  // creates a new node and edges to its parent
@@ -312,9 +348,9 @@ export class Graph {
312
348
  }
313
349
  // add extra manifest info if available
314
350
  if (manifest) {
315
- const { bin, engines, os, cpu } = manifest;
351
+ const { bin, engines, os, cpu, libc } = manifest;
316
352
  // add platform info if available
317
- if (engines || os || cpu) {
353
+ if (engines || os || cpu || libc) {
318
354
  const platform = {};
319
355
  if (engines)
320
356
  platform.engines = engines;
@@ -322,6 +358,8 @@ export class Graph {
322
358
  platform.os = os;
323
359
  if (cpu)
324
360
  platform.cpu = cpu;
361
+ if (libc)
362
+ platform.libc = libc;
325
363
  toNode.platform = platform;
326
364
  }
327
365
  // add bin info if available
@@ -405,11 +443,11 @@ export class Graph {
405
443
  this.edges.clear();
406
444
  // Clear all node edge relationships
407
445
  for (const node of this.nodes.values()) {
408
- // marking nodes as detached needs to be restricted to only those
409
- // that had a manifest, otherwise we'd be skipping fetching manifest
410
- // for nodes we don't have a manifest during the ideal build phase
411
- if (node.manifest)
412
- node.detached = true;
446
+ // Mark nodes as detached so ideal rebuild treats them as candidates
447
+ // that must be (re)placed during traversal. Detached nodes with a
448
+ // manifest can skip refetch; detached nodes without a manifest must
449
+ // fetch from package-info during ideal rebuild.
450
+ node.detached = true;
413
451
  // detaches all edges from this node
414
452
  node.edgesOut.clear();
415
453
  node.edgesIn.clear();
@@ -427,4 +465,3 @@ export class Graph {
427
465
  return `${this[Symbol.toStringTag]} ${inspect(data, options)}`;
428
466
  }
429
467
  }
430
- //# sourceMappingURL=graph.js.map
@@ -13,8 +13,19 @@ import type { TransientAddMap, TransientRemoveMap } from './types.ts';
13
13
  * Append new nodes in the given `graph` for dependencies specified at `add`
14
14
  * and missing dependencies from the `deps` parameter.
15
15
  *
16
- * It also applies any modifiers that applies to a given node as it processes
17
- * and builds the graph.
16
+ * Uses **breadth-first traversal** (BFS) with **deterministic ordering** to
17
+ * ensure reproducible builds. The algorithm:
18
+ *
19
+ * 1. Process all deps at the current level in parallel
20
+ * 2. After each level, run `postPlacementPeerCheck` to handle peer contexts
21
+ * 3. Collect child deps for the next level
22
+ * 4. Repeat until no more deps to process
23
+ *
24
+ * **Peer Context Isolation**: Each workspace importer gets its own peer context
25
+ * to prevent cross-workspace leakage. Without this, `react@^18` from workspace A
26
+ * could incorrectly satisfy `react@^19` peer deps in workspace B.
27
+ *
28
+ * **Early Extraction**: When `actual` graph is provided, nodes are extracted
29
+ * to the vlt store during graph construction (not after), improving performance.
18
30
  */
19
31
  export declare const appendNodes: (packageInfo: PackageInfoClient, graph: Graph, fromNode: Node, deps: Dependency[], scurry: PathScurry, options: SpecOptions, seen: Set<DepID>, add?: Map<string, Dependency>, modifiers?: GraphModifier, modifierRefs?: Map<string, ModifierActiveEntry>, extractPromises?: Promise<ExtractResult>[], actual?: Graph, seenExtracted?: Set<DepID>, remover?: RollbackRemove, transientAdd?: TransientAddMap, transientRemove?: TransientRemoveMap) => Promise<void>;
20
- //# sourceMappingURL=append-nodes.d.ts.map