@vltpkg/graph 1.0.0-rc.3 → 1.0.0-rc.31

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 (227) 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 +57 -22
  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} +0 -1
  10. package/dist/{esm/diff.d.ts → diff.d.ts} +4 -1
  11. package/dist/{esm/diff.js → diff.js} +5 -2
  12. package/dist/{esm/edge.d.ts → edge.d.ts} +1 -1
  13. package/dist/{esm/edge.js → edge.js} +4 -1
  14. package/dist/fixup-added-names.d.ts +18 -0
  15. package/dist/fixup-added-names.js +46 -0
  16. package/dist/{esm/graph.d.ts → graph.d.ts} +25 -5
  17. package/dist/{esm/graph.js → graph.js} +92 -43
  18. package/dist/ideal/append-nodes.d.ts +31 -0
  19. package/dist/ideal/append-nodes.js +560 -0
  20. package/dist/{esm/ideal → ideal}/build-ideal-from-starting-graph.d.ts +4 -5
  21. package/dist/ideal/build-ideal-from-starting-graph.js +69 -0
  22. package/dist/{esm/ideal → ideal}/build.d.ts +0 -1
  23. package/dist/ideal/build.js +84 -0
  24. package/dist/{esm/ideal → ideal}/get-importer-specs.d.ts +9 -3
  25. package/dist/{esm/ideal → ideal}/get-importer-specs.js +80 -6
  26. package/dist/ideal/peers.d.ts +160 -0
  27. package/dist/ideal/peers.js +696 -0
  28. package/dist/ideal/refresh-ideal-graph.d.ts +43 -0
  29. package/dist/ideal/refresh-ideal-graph.js +62 -0
  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/ideal/types.d.ts +107 -0
  35. package/dist/ideal/types.js +1 -0
  36. package/dist/{esm/index.d.ts → index.d.ts} +1 -1
  37. package/dist/{esm/index.js → index.js} +1 -1
  38. package/dist/{esm/install.d.ts → install.d.ts} +3 -3
  39. package/dist/{esm/install.js → install.js} +49 -9
  40. package/dist/{esm/lockfile → lockfile}/load-edges.d.ts +0 -1
  41. package/dist/{esm/lockfile → lockfile}/load-edges.js +7 -4
  42. package/dist/{esm/lockfile → lockfile}/load-nodes.d.ts +0 -1
  43. package/dist/{esm/lockfile → lockfile}/load-nodes.js +10 -4
  44. package/dist/{esm/lockfile → lockfile}/load.d.ts +0 -5
  45. package/dist/{esm/lockfile → lockfile}/load.js +31 -33
  46. package/dist/{esm/lockfile → lockfile}/save.d.ts +1 -2
  47. package/dist/{esm/lockfile → lockfile}/save.js +8 -7
  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} +16 -1
  53. package/dist/{esm/node.js → node.js} +21 -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/{esm/reify → reify}/add-edge.d.ts +0 -1
  57. package/dist/{esm/reify → reify}/add-edge.js +10 -4
  58. package/dist/{esm/reify → reify}/add-edges.d.ts +1 -2
  59. package/dist/{esm/reify → reify}/add-edges.js +2 -2
  60. package/dist/{esm/reify → reify}/add-nodes.d.ts +0 -1
  61. package/dist/{esm/reify → reify}/add-nodes.js +0 -1
  62. package/dist/{esm/reify → reify}/bin-chmod.d.ts +0 -1
  63. package/dist/{esm/reify → reify}/bin-chmod.js +0 -1
  64. package/dist/{esm/reify → reify}/build.d.ts +0 -1
  65. package/dist/{esm/reify → reify}/build.js +12 -4
  66. package/dist/{esm/reify → reify}/calculate-save-value.d.ts +0 -1
  67. package/dist/{esm/reify → reify}/calculate-save-value.js +6 -1
  68. package/dist/{esm/reify → reify}/check-needed-build.d.ts +10 -1
  69. package/dist/reify/check-needed-build.js +71 -0
  70. package/dist/{esm/reify → reify}/delete-edge.d.ts +0 -1
  71. package/dist/{esm/reify → reify}/delete-edge.js +0 -1
  72. package/dist/{esm/reify → reify}/delete-edges.d.ts +0 -1
  73. package/dist/{esm/reify → reify}/delete-edges.js +0 -1
  74. package/dist/{esm/reify → reify}/delete-nodes.d.ts +0 -1
  75. package/dist/{esm/reify → reify}/delete-nodes.js +0 -1
  76. package/dist/{esm/reify → reify}/extract-node.d.ts +0 -1
  77. package/dist/{esm/reify → reify}/extract-node.js +10 -3
  78. package/dist/{esm/reify → reify}/index.d.ts +1 -1
  79. package/dist/{esm/reify → reify}/index.js +4 -4
  80. package/dist/{esm/reify → reify}/internal-hoist.d.ts +0 -1
  81. package/dist/{esm/reify → reify}/internal-hoist.js +0 -1
  82. package/dist/{esm/reify → reify}/optional-fail.d.ts +0 -1
  83. package/dist/{esm/reify → reify}/optional-fail.js +0 -1
  84. package/dist/{esm/reify → reify}/rollback.d.ts +0 -1
  85. package/dist/{esm/reify → reify}/rollback.js +0 -1
  86. package/dist/{esm/reify → reify}/update-importers-package-json.d.ts +1 -2
  87. package/dist/{esm/reify → reify}/update-importers-package-json.js +19 -17
  88. package/dist/{esm/remove-optional-subgraph.d.ts → remove-optional-subgraph.d.ts} +0 -1
  89. package/dist/{esm/remove-optional-subgraph.js → remove-optional-subgraph.js} +0 -1
  90. package/dist/{esm/resolve-save-type.d.ts → resolve-save-type.d.ts} +0 -1
  91. package/dist/{esm/resolve-save-type.js → resolve-save-type.js} +0 -1
  92. package/dist/{esm/stringify-node.d.ts → stringify-node.d.ts} +0 -1
  93. package/dist/{esm/stringify-node.js → stringify-node.js} +10 -2
  94. package/dist/{esm/transfer-data → transfer-data}/load.d.ts +0 -1
  95. package/dist/{esm/transfer-data → transfer-data}/load.js +5 -3
  96. package/dist/{esm/uninstall.d.ts → uninstall.d.ts} +0 -1
  97. package/dist/{esm/uninstall.js → uninstall.js} +27 -7
  98. package/dist/{esm/update.d.ts → update.d.ts} +0 -1
  99. package/dist/{esm/update.js → update.js} +11 -1
  100. package/dist/{esm/virtual-root.d.ts → virtual-root.d.ts} +0 -1
  101. package/dist/{esm/virtual-root.js → virtual-root.js} +0 -1
  102. package/dist/{esm/visualization → visualization}/human-readable-output.d.ts +0 -1
  103. package/dist/{esm/visualization → visualization}/human-readable-output.js +7 -3
  104. package/dist/{esm/visualization → visualization}/json-output.d.ts +2 -2
  105. package/dist/{esm/visualization → visualization}/json-output.js +2 -3
  106. package/dist/{esm/visualization → visualization}/mermaid-output.d.ts +2 -2
  107. package/dist/visualization/mermaid-output.js +170 -0
  108. package/dist/{esm/visualization → visualization}/object-like-output.d.ts +0 -1
  109. package/dist/{esm/visualization → visualization}/object-like-output.js +0 -1
  110. package/package.json +51 -63
  111. package/dist/esm/actual/load.d.ts.map +0 -1
  112. package/dist/esm/actual/load.js.map +0 -1
  113. package/dist/esm/browser.d.ts.map +0 -1
  114. package/dist/esm/browser.js.map +0 -1
  115. package/dist/esm/build.d.ts.map +0 -1
  116. package/dist/esm/build.js.map +0 -1
  117. package/dist/esm/dependencies.d.ts.map +0 -1
  118. package/dist/esm/dependencies.js.map +0 -1
  119. package/dist/esm/diff.d.ts.map +0 -1
  120. package/dist/esm/diff.js.map +0 -1
  121. package/dist/esm/edge.d.ts.map +0 -1
  122. package/dist/esm/edge.js.map +0 -1
  123. package/dist/esm/graph.d.ts.map +0 -1
  124. package/dist/esm/graph.js.map +0 -1
  125. package/dist/esm/ideal/add-nodes.d.ts +0 -34
  126. package/dist/esm/ideal/add-nodes.d.ts.map +0 -1
  127. package/dist/esm/ideal/add-nodes.js +0 -39
  128. package/dist/esm/ideal/add-nodes.js.map +0 -1
  129. package/dist/esm/ideal/append-nodes.d.ts +0 -19
  130. package/dist/esm/ideal/append-nodes.d.ts.map +0 -1
  131. package/dist/esm/ideal/append-nodes.js +0 -289
  132. package/dist/esm/ideal/append-nodes.js.map +0 -1
  133. package/dist/esm/ideal/build-ideal-from-starting-graph.d.ts.map +0 -1
  134. package/dist/esm/ideal/build-ideal-from-starting-graph.js +0 -55
  135. package/dist/esm/ideal/build-ideal-from-starting-graph.js.map +0 -1
  136. package/dist/esm/ideal/build.d.ts.map +0 -1
  137. package/dist/esm/ideal/build.js +0 -48
  138. package/dist/esm/ideal/build.js.map +0 -1
  139. package/dist/esm/ideal/get-importer-specs.d.ts.map +0 -1
  140. package/dist/esm/ideal/get-importer-specs.js.map +0 -1
  141. package/dist/esm/ideal/remove-nodes.d.ts +0 -7
  142. package/dist/esm/ideal/remove-nodes.d.ts.map +0 -1
  143. package/dist/esm/ideal/remove-nodes.js +0 -19
  144. package/dist/esm/ideal/remove-nodes.js.map +0 -1
  145. package/dist/esm/ideal/remove-satisfied-specs.d.ts.map +0 -1
  146. package/dist/esm/ideal/remove-satisfied-specs.js.map +0 -1
  147. package/dist/esm/ideal/types.d.ts +0 -35
  148. package/dist/esm/ideal/types.d.ts.map +0 -1
  149. package/dist/esm/ideal/types.js +0 -2
  150. package/dist/esm/ideal/types.js.map +0 -1
  151. package/dist/esm/index.d.ts.map +0 -1
  152. package/dist/esm/index.js.map +0 -1
  153. package/dist/esm/install.d.ts.map +0 -1
  154. package/dist/esm/install.js.map +0 -1
  155. package/dist/esm/lockfile/load-edges.d.ts.map +0 -1
  156. package/dist/esm/lockfile/load-edges.js.map +0 -1
  157. package/dist/esm/lockfile/load-nodes.d.ts.map +0 -1
  158. package/dist/esm/lockfile/load-nodes.js.map +0 -1
  159. package/dist/esm/lockfile/load.d.ts.map +0 -1
  160. package/dist/esm/lockfile/load.js.map +0 -1
  161. package/dist/esm/lockfile/save.d.ts.map +0 -1
  162. package/dist/esm/lockfile/save.js.map +0 -1
  163. package/dist/esm/lockfile/types.d.ts.map +0 -1
  164. package/dist/esm/lockfile/types.js.map +0 -1
  165. package/dist/esm/modifiers.d.ts.map +0 -1
  166. package/dist/esm/modifiers.js.map +0 -1
  167. package/dist/esm/node.d.ts.map +0 -1
  168. package/dist/esm/node.js.map +0 -1
  169. package/dist/esm/non-empty-list.d.ts.map +0 -1
  170. package/dist/esm/non-empty-list.js.map +0 -1
  171. package/dist/esm/package.json +0 -3
  172. package/dist/esm/reify/add-edge.d.ts.map +0 -1
  173. package/dist/esm/reify/add-edge.js.map +0 -1
  174. package/dist/esm/reify/add-edges.d.ts.map +0 -1
  175. package/dist/esm/reify/add-edges.js.map +0 -1
  176. package/dist/esm/reify/add-nodes.d.ts.map +0 -1
  177. package/dist/esm/reify/add-nodes.js.map +0 -1
  178. package/dist/esm/reify/bin-chmod.d.ts.map +0 -1
  179. package/dist/esm/reify/bin-chmod.js.map +0 -1
  180. package/dist/esm/reify/build.d.ts.map +0 -1
  181. package/dist/esm/reify/build.js.map +0 -1
  182. package/dist/esm/reify/calculate-save-value.d.ts.map +0 -1
  183. package/dist/esm/reify/calculate-save-value.js.map +0 -1
  184. package/dist/esm/reify/check-needed-build.d.ts.map +0 -1
  185. package/dist/esm/reify/check-needed-build.js +0 -50
  186. package/dist/esm/reify/check-needed-build.js.map +0 -1
  187. package/dist/esm/reify/delete-edge.d.ts.map +0 -1
  188. package/dist/esm/reify/delete-edge.js.map +0 -1
  189. package/dist/esm/reify/delete-edges.d.ts.map +0 -1
  190. package/dist/esm/reify/delete-edges.js.map +0 -1
  191. package/dist/esm/reify/delete-nodes.d.ts.map +0 -1
  192. package/dist/esm/reify/delete-nodes.js.map +0 -1
  193. package/dist/esm/reify/extract-node.d.ts.map +0 -1
  194. package/dist/esm/reify/extract-node.js.map +0 -1
  195. package/dist/esm/reify/index.d.ts.map +0 -1
  196. package/dist/esm/reify/index.js.map +0 -1
  197. package/dist/esm/reify/internal-hoist.d.ts.map +0 -1
  198. package/dist/esm/reify/internal-hoist.js.map +0 -1
  199. package/dist/esm/reify/optional-fail.d.ts.map +0 -1
  200. package/dist/esm/reify/optional-fail.js.map +0 -1
  201. package/dist/esm/reify/rollback.d.ts.map +0 -1
  202. package/dist/esm/reify/rollback.js.map +0 -1
  203. package/dist/esm/reify/update-importers-package-json.d.ts.map +0 -1
  204. package/dist/esm/reify/update-importers-package-json.js.map +0 -1
  205. package/dist/esm/remove-optional-subgraph.d.ts.map +0 -1
  206. package/dist/esm/remove-optional-subgraph.js.map +0 -1
  207. package/dist/esm/resolve-save-type.d.ts.map +0 -1
  208. package/dist/esm/resolve-save-type.js.map +0 -1
  209. package/dist/esm/stringify-node.d.ts.map +0 -1
  210. package/dist/esm/stringify-node.js.map +0 -1
  211. package/dist/esm/transfer-data/load.d.ts.map +0 -1
  212. package/dist/esm/transfer-data/load.js.map +0 -1
  213. package/dist/esm/uninstall.d.ts.map +0 -1
  214. package/dist/esm/uninstall.js.map +0 -1
  215. package/dist/esm/update.d.ts.map +0 -1
  216. package/dist/esm/update.js.map +0 -1
  217. package/dist/esm/virtual-root.d.ts.map +0 -1
  218. package/dist/esm/virtual-root.js.map +0 -1
  219. package/dist/esm/visualization/human-readable-output.d.ts.map +0 -1
  220. package/dist/esm/visualization/human-readable-output.js.map +0 -1
  221. package/dist/esm/visualization/json-output.d.ts.map +0 -1
  222. package/dist/esm/visualization/json-output.js.map +0 -1
  223. package/dist/esm/visualization/mermaid-output.d.ts.map +0 -1
  224. package/dist/esm/visualization/mermaid-output.js +0 -123
  225. package/dist/esm/visualization/mermaid-output.js.map +0 -1
  226. package/dist/esm/visualization/object-like-output.d.ts.map +0 -1
  227. 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
@@ -1,4 +1,4 @@
1
- import { asDepID, hydrate, joinDepIDTuple } from '@vltpkg/dep-id';
1
+ import { asDepID, hydrate, joinDepIDTuple, joinExtra, splitDepID, splitExtra, } from '@vltpkg/dep-id';
2
2
  import { Spec } from '@vltpkg/spec';
3
3
  import { graphStep } from '@vltpkg/output';
4
4
  import { isObject } from '@vltpkg/types';
@@ -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
  */
@@ -136,12 +137,14 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
136
137
  if (!loadManifests) {
137
138
  const depId = findDepID(realpath);
138
139
  if (depId) {
139
- let h = hydrate(depId, alias, {
140
- ...options,
141
- });
140
+ let h = hydrate(depId, alias, options);
142
141
  // if the parsed registry value is using the default value, then
143
142
  // the node should inherit the registry value from its parent node
144
- h.inheritedRegistry = fromNode.registry;
143
+ if (h.type === 'registry' &&
144
+ h.registry === h.options.registry &&
145
+ fromNode.registry) {
146
+ h.registry = fromNode.registry;
147
+ }
145
148
  // Check for active modifiers and replace spec even when not loading manifests
146
149
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(h, alias, modifierRefs);
147
150
  h = modifiedSpec;
@@ -152,7 +155,7 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
152
155
  h, // uses spec from hydrated id
153
156
  {
154
157
  name,
155
- }, depId, queryModifier);
158
+ }, depId, joinExtra({ modifier: queryModifier }));
156
159
  // Update active entry after placing package
157
160
  const activeModifier = modifierRefs?.get(alias);
158
161
  if (activeModifier && node) {
@@ -177,15 +180,31 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
177
180
  const depType = shorten(type, alias, fromNode.manifest);
178
181
  let spec = Spec.parse(alias, bareSpec, {
179
182
  ...options,
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,
180
187
  });
181
- // if the parsed registry value is using the default value, then
182
- // the node should inherit the registry value from its parent node
183
- spec.inheritedRegistry = fromNode.registry;
184
188
  // Check for active modifiers and replace spec if a modifier is complete
185
189
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, alias, modifierRefs);
186
190
  spec = modifiedSpec;
187
191
  const maybeId = getPathBasedId(spec, realpath);
188
- node = graph.placePackage(fromNode, depType, spec, mani, maybeId, queryModifier);
192
+ let peerSetHash;
193
+ if (maybeId) {
194
+ // parses extra info from depID to retrieve peerSetHash
195
+ try {
196
+ const tuple = splitDepID(maybeId);
197
+ const type = tuple[0];
198
+ const extra = type === 'registry' || type === 'git' ?
199
+ tuple[3]
200
+ : tuple[2];
201
+ peerSetHash =
202
+ extra ? splitExtra(extra).peerSetHash : undefined;
203
+ /* c8 ignore next - impossible: getPathBasedId asserts valid dep id */
204
+ }
205
+ catch { }
206
+ }
207
+ node = graph.placePackage(fromNode, depType, spec, mani, maybeId, joinExtra({ modifier: queryModifier, peerSetHash }));
189
208
  // Update active entry after placing package
190
209
  const activeModifier = modifierRefs?.get(alias);
191
210
  if (activeModifier && node) {
@@ -223,14 +242,27 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
223
242
  const depType = shorten(type, name, fromNode.manifest);
224
243
  let spec = Spec.parse(name, bareSpec, {
225
244
  ...options,
245
+ registry: fromNode.registry ?? options.registry,
226
246
  });
227
- // if the parsed registry value is using the default value, then
228
- // the node should inherit the registry value from its parent node
229
- spec.inheritedRegistry = fromNode.registry;
230
247
  // Check for active modifiers and replace spec for missing dependencies
231
248
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, name, modifierRefs);
232
249
  spec = modifiedSpec;
233
- graph.placePackage(fromNode, depType, spec, undefined, undefined, queryModifier);
250
+ graph.placePackage(fromNode, depType, spec, undefined, undefined, joinExtra({ modifier: queryModifier }));
251
+ }
252
+ }
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}`);
234
266
  }
235
267
  }
236
268
  };
@@ -254,12 +286,14 @@ export const load = (options) => {
254
286
  monorepo,
255
287
  scurry,
256
288
  });
289
+ verifyImporterNodeModules(graph, projectRoot);
257
290
  done();
258
291
  return graph;
259
292
  }
260
293
  catch {
261
- // if validation fails or any other error occurs,
262
- // 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
296
+ // TODO: Warn version mismatch to @vltpkg/output for debugging
263
297
  }
264
298
  }
265
299
  const graph = new Graph({ ...options, mainManifest });
@@ -293,12 +327,13 @@ export const load = (options) => {
293
327
  // Clean up any pending modifier entries that were never completed
294
328
  modifiers?.rollbackActiveEntries();
295
329
  // caches the load result to the hidden lockfile when enabled
296
- saveHidden({
297
- ...options,
298
- graph,
299
- });
330
+ if (scurry.cwd.resolve('node_modules').lstatSync()?.isDirectory()) {
331
+ saveHidden({
332
+ ...options,
333
+ graph,
334
+ });
335
+ }
300
336
  }
301
337
  done();
302
338
  return graph;
303
339
  };
304
- //# 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
@@ -109,4 +109,3 @@ export const getDependencies = (node, options) => {
109
109
  }
110
110
  return res;
111
111
  };
112
- //# sourceMappingURL=dependencies.js.map
@@ -53,6 +53,7 @@ export declare class Diff {
53
53
  nodes: {
54
54
  add: {
55
55
  rawManifest?: import("@vltpkg/types").Override<import("@vltpkg/types").Manifest, import("@vltpkg/types").NormalizedFields> | undefined;
56
+ peerSetHash?: string | undefined;
56
57
  id: import("@vltpkg/dep-id").DepID;
57
58
  name: string;
58
59
  version: string | undefined;
@@ -70,11 +71,13 @@ export declare class Diff {
70
71
  engines?: Record<string, string>;
71
72
  os?: string[] | string;
72
73
  cpu?: string[] | string;
74
+ libc?: string[] | string;
73
75
  } | undefined;
74
76
  buildState: "none" | "needed" | "built" | "failed";
75
77
  }[];
76
78
  delete: {
77
79
  rawManifest?: import("@vltpkg/types").Override<import("@vltpkg/types").Manifest, import("@vltpkg/types").NormalizedFields> | undefined;
80
+ peerSetHash?: string | undefined;
78
81
  id: import("@vltpkg/dep-id").DepID;
79
82
  name: string;
80
83
  version: string | undefined;
@@ -92,6 +95,7 @@ export declare class Diff {
92
95
  engines?: Record<string, string>;
93
96
  os?: string[] | string;
94
97
  cpu?: string[] | string;
98
+ libc?: string[] | string;
95
99
  } | undefined;
96
100
  buildState: "none" | "needed" | "built" | "failed";
97
101
  }[];
@@ -113,4 +117,3 @@ export declare class Diff {
113
117
  };
114
118
  }
115
119
  export {};
116
- //# 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
@@ -41,6 +41,6 @@ export declare class Edge implements EdgeLike {
41
41
  type: DependencyTypeShort;
42
42
  spec: string;
43
43
  };
44
+ toString(): string;
44
45
  }
45
46
  export {};
46
- //# sourceMappingURL=edge.d.ts.map
@@ -70,5 +70,8 @@ export class Edge {
70
70
  spec: String(this.spec),
71
71
  };
72
72
  }
73
+ toString() {
74
+ const to = `${this.name}${this.to ? '' : ' (missing)'}`;
75
+ return `Edge from: ${this.from.id} --|${this.type}|--> ${to}`;
76
+ }
73
77
  }
74
- //# sourceMappingURL=edge.js.map
@@ -0,0 +1,18 @@
1
+ import { Spec } from '@vltpkg/spec';
2
+ import type { Dependency } from './dependencies.ts';
3
+ import type { Manifest } from '@vltpkg/types';
4
+ import type { SpecOptions } from '@vltpkg/spec';
5
+ /**
6
+ * When adding new dependencies, it's very common to not have a dependency
7
+ * name directly available to reference, e.g: `file:local/folder` or
8
+ * `remote:tarball-url.tgz`. In these cases, a placeholder `(unknown)` name
9
+ * is used in the `Spec` object and the `add` Map structure that holds
10
+ * references to added dependencies will use the stringified spec as a key.
11
+ *
12
+ * This helper function fixes unknown names in the `add` map by replacing
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.
17
+ */
18
+ export declare const fixupAddedNames: (add: Map<string, Dependency> | undefined, manifest: Pick<Manifest, "name" | "version"> | undefined, options: SpecOptions, spec: Spec) => Spec;
@@ -0,0 +1,46 @@
1
+ import { Spec } from '@vltpkg/spec';
2
+ import { calculateSaveValue } from "./reify/calculate-save-value.js";
3
+ /**
4
+ * When adding new dependencies, it's very common to not have a dependency
5
+ * name directly available to reference, e.g: `file:local/folder` or
6
+ * `remote:tarball-url.tgz`. In these cases, a placeholder `(unknown)` name
7
+ * is used in the `Spec` object and the `add` Map structure that holds
8
+ * references to added dependencies will use the stringified spec as a key.
9
+ *
10
+ * This helper function fixes unknown names in the `add` map by replacing
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.
15
+ */
16
+ export const fixupAddedNames = (add, manifest, options, spec) => {
17
+ // Handle nameless dependencies
18
+ if (add && manifest?.name && spec.name === '(unknown)') {
19
+ const s = add.get(String(spec));
20
+ if (s) {
21
+ // removes the previous, placeholder entry key
22
+ add.delete(String(spec));
23
+ // replaces spec with a version with the correct name
24
+ spec = Spec.parse(manifest.name, spec.bareSpec, options);
25
+ // updates the add map with the fixed up spec
26
+ const n = {
27
+ type: s.type,
28
+ spec,
29
+ };
30
+ add.set(manifest.name, n);
31
+ }
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
+ }
45
+ return spec;
46
+ };
@@ -6,6 +6,7 @@ import type { Monorepo } from '@vltpkg/workspaces';
6
6
  import type { InspectOptions } from 'node:util';
7
7
  import { Edge } from './edge.ts';
8
8
  import { Node } from './node.ts';
9
+ import type { PeerContext } from './ideal/types.ts';
9
10
  declare const kCustomInspect: unique symbol;
10
11
  export type ManifestInventory = Map<DepID, NormalizedManifest>;
11
12
  export type GraphOptions = SpecOptions & {
@@ -73,7 +74,26 @@ export declare class Graph implements GraphLike {
73
74
  * The root of the project this graph represents
74
75
  */
75
76
  projectRoot: string;
77
+ /**
78
+ * The peer context sets used to resolve peer dependencies within this graph.
79
+ */
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
+ * Tracks the current peer context index.
90
+ */
91
+ currentPeerContextIndex: number;
76
92
  constructor(options: GraphOptions);
93
+ /**
94
+ * Get the next peer context index.
95
+ */
96
+ nextPeerContextIndex(): number;
77
97
  /**
78
98
  * Delete all nodes and edges that are unreachable from the importers.
79
99
  * The collection of deleted nodes is returned.
@@ -93,7 +113,7 @@ export declare class Graph implements GraphLike {
93
113
  /**
94
114
  * Find an existing node to satisfy a dependency
95
115
  */
96
- findResolution(spec: Spec, fromNode: Node, queryModifier?: string): Node | undefined;
116
+ findResolution(spec: Spec, fromNode: Node, extra?: string): Node | undefined;
97
117
  /**
98
118
  * Create a new node in the graph.
99
119
  */
@@ -121,13 +141,13 @@ export declare class Graph implements GraphLike {
121
141
  /**
122
142
  * Removes the resolved node of a given edge.
123
143
  */
124
- removeEdgeResolution(edge: Edge, queryModifier?: string): void;
144
+ removeEdgeResolution(edge: Edge, extra?: string): void;
125
145
  /**
126
- * Reset resolution cache data.
146
+ * Remove all edges from the graph while preserving nodes and resolution caches.
147
+ * This allows the graph to be reconstructed efficiently using the existing nodes.
127
148
  */
128
- resetResolution(): void;
149
+ resetEdges(): void;
129
150
  toJSON(): import("./index.ts").LockfileData;
130
151
  [kCustomInspect](_: number, options: InspectOptions): string;
131
152
  }
132
153
  export {};
133
- //# sourceMappingURL=graph.d.ts.map