@vltpkg/graph 1.0.0-rc.22 → 1.0.0-rc.24

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 (109) hide show
  1. package/dist/actual/load.d.ts +107 -0
  2. package/dist/actual/load.js +336 -0
  3. package/dist/browser.d.ts +14 -0
  4. package/dist/browser.js +16 -0
  5. package/dist/build.d.ts +28 -0
  6. package/dist/build.js +78 -0
  7. package/dist/dependencies.d.ts +65 -0
  8. package/dist/dependencies.js +111 -0
  9. package/dist/diff.d.ts +119 -0
  10. package/dist/diff.js +151 -0
  11. package/dist/edge.d.ts +46 -0
  12. package/dist/edge.js +77 -0
  13. package/dist/fixup-added-names.d.ts +18 -0
  14. package/dist/fixup-added-names.js +46 -0
  15. package/dist/graph.d.ts +153 -0
  16. package/dist/graph.js +444 -0
  17. package/dist/ideal/append-nodes.d.ts +31 -0
  18. package/dist/ideal/append-nodes.js +560 -0
  19. package/dist/ideal/build-ideal-from-starting-graph.d.ts +14 -0
  20. package/dist/ideal/build-ideal-from-starting-graph.js +69 -0
  21. package/dist/ideal/build.d.ts +40 -0
  22. package/dist/ideal/build.js +84 -0
  23. package/dist/ideal/get-importer-specs.d.ts +20 -0
  24. package/dist/ideal/get-importer-specs.js +180 -0
  25. package/dist/ideal/peers.d.ts +160 -0
  26. package/dist/ideal/peers.js +696 -0
  27. package/dist/ideal/refresh-ideal-graph.d.ts +43 -0
  28. package/dist/ideal/refresh-ideal-graph.js +62 -0
  29. package/dist/ideal/remove-satisfied-specs.d.ts +7 -0
  30. package/dist/ideal/remove-satisfied-specs.js +34 -0
  31. package/dist/ideal/sorting.d.ts +45 -0
  32. package/dist/ideal/sorting.js +70 -0
  33. package/dist/ideal/types.d.ts +107 -0
  34. package/dist/ideal/types.js +1 -0
  35. package/dist/index.d.ts +38 -0
  36. package/dist/index.js +32 -0
  37. package/dist/install.d.ts +19 -0
  38. package/dist/install.js +208 -0
  39. package/dist/lockfile/load-edges.d.ts +11 -0
  40. package/dist/lockfile/load-edges.js +105 -0
  41. package/dist/lockfile/load-nodes.d.ts +4 -0
  42. package/dist/lockfile/load-nodes.js +101 -0
  43. package/dist/lockfile/load.d.ts +45 -0
  44. package/dist/lockfile/load.js +84 -0
  45. package/dist/lockfile/save.d.ts +30 -0
  46. package/dist/lockfile/save.js +174 -0
  47. package/dist/lockfile/types.d.ts +95 -0
  48. package/dist/lockfile/types.js +49 -0
  49. package/dist/modifiers.d.ts +188 -0
  50. package/dist/modifiers.js +329 -0
  51. package/dist/node.d.ts +234 -0
  52. package/dist/node.js +388 -0
  53. package/dist/non-empty-list.d.ts +2 -0
  54. package/dist/non-empty-list.js +2 -0
  55. package/dist/reify/add-edge.d.ts +9 -0
  56. package/dist/reify/add-edge.js +71 -0
  57. package/dist/reify/add-edges.d.ts +4 -0
  58. package/dist/reify/add-edges.js +12 -0
  59. package/dist/reify/add-nodes.d.ts +6 -0
  60. package/dist/reify/add-nodes.js +16 -0
  61. package/dist/reify/bin-chmod.d.ts +10 -0
  62. package/dist/reify/bin-chmod.js +38 -0
  63. package/dist/reify/build.d.ts +13 -0
  64. package/dist/reify/build.js +111 -0
  65. package/dist/reify/calculate-save-value.d.ts +2 -0
  66. package/dist/reify/calculate-save-value.js +50 -0
  67. package/dist/reify/check-needed-build.d.ts +34 -0
  68. package/dist/reify/check-needed-build.js +71 -0
  69. package/dist/reify/delete-edge.d.ts +4 -0
  70. package/dist/reify/delete-edge.js +27 -0
  71. package/dist/reify/delete-edges.d.ts +4 -0
  72. package/dist/reify/delete-edges.js +13 -0
  73. package/dist/reify/delete-nodes.d.ts +4 -0
  74. package/dist/reify/delete-nodes.js +15 -0
  75. package/dist/reify/extract-node.d.ts +23 -0
  76. package/dist/reify/extract-node.js +83 -0
  77. package/dist/reify/index.d.ts +34 -0
  78. package/dist/reify/index.js +161 -0
  79. package/dist/reify/internal-hoist.d.ts +8 -0
  80. package/dist/reify/internal-hoist.js +133 -0
  81. package/dist/reify/optional-fail.d.ts +15 -0
  82. package/dist/reify/optional-fail.js +15 -0
  83. package/dist/reify/rollback.d.ts +4 -0
  84. package/dist/reify/rollback.js +23 -0
  85. package/dist/reify/update-importers-package-json.d.ts +35 -0
  86. package/dist/reify/update-importers-package-json.js +122 -0
  87. package/dist/remove-optional-subgraph.d.ts +33 -0
  88. package/dist/remove-optional-subgraph.js +47 -0
  89. package/dist/resolve-save-type.d.ts +5 -0
  90. package/dist/resolve-save-type.js +4 -0
  91. package/dist/stringify-node.d.ts +2 -0
  92. package/dist/stringify-node.js +32 -0
  93. package/dist/transfer-data/load.d.ts +43 -0
  94. package/dist/transfer-data/load.js +175 -0
  95. package/dist/uninstall.d.ts +14 -0
  96. package/dist/uninstall.js +75 -0
  97. package/dist/update.d.ts +12 -0
  98. package/dist/update.js +73 -0
  99. package/dist/virtual-root.d.ts +15 -0
  100. package/dist/virtual-root.js +78 -0
  101. package/dist/visualization/human-readable-output.d.ts +26 -0
  102. package/dist/visualization/human-readable-output.js +163 -0
  103. package/dist/visualization/json-output.d.ts +41 -0
  104. package/dist/visualization/json-output.js +50 -0
  105. package/dist/visualization/mermaid-output.d.ts +17 -0
  106. package/dist/visualization/mermaid-output.js +170 -0
  107. package/dist/visualization/object-like-output.d.ts +2 -0
  108. package/dist/visualization/object-like-output.js +47 -0
  109. package/package.json +25 -25
@@ -0,0 +1,153 @@
1
+ import type { DepID } from '@vltpkg/dep-id';
2
+ import { Spec } from '@vltpkg/spec';
3
+ import type { SpecOptions } from '@vltpkg/spec';
4
+ import type { GraphLike, NodeLike, NormalizedManifest, DependencySaveType } from '@vltpkg/types';
5
+ import type { Monorepo } from '@vltpkg/workspaces';
6
+ import type { InspectOptions } from 'node:util';
7
+ import { Edge } from './edge.ts';
8
+ import { Node } from './node.ts';
9
+ import type { PeerContext } from './ideal/types.ts';
10
+ declare const kCustomInspect: unique symbol;
11
+ export type ManifestInventory = Map<DepID, NormalizedManifest>;
12
+ export type GraphOptions = SpecOptions & {
13
+ /**
14
+ * The main importer manifest info.
15
+ */
16
+ mainManifest: NormalizedManifest;
17
+ /**
18
+ * An inventory of seen manifests.
19
+ */
20
+ manifests?: ManifestInventory;
21
+ /**
22
+ * A {@link Monorepo} object, for managing workspaces
23
+ */
24
+ monorepo?: Monorepo;
25
+ /**
26
+ * Root of the project this graph represents
27
+ */
28
+ projectRoot: string;
29
+ };
30
+ export declare class Graph implements GraphLike {
31
+ #private;
32
+ get [Symbol.toStringTag](): string;
33
+ /**
34
+ * A {@link Monorepo} instance, used for managing workspaces.
35
+ */
36
+ monorepo?: Monorepo;
37
+ /**
38
+ * An inventory with all manifests related to an install.
39
+ */
40
+ manifests: ManifestInventory;
41
+ /**
42
+ * A set of all edges in this graph.
43
+ */
44
+ edges: Set<Edge>;
45
+ /**
46
+ * Map registered dep ids to the node that represent them in the graph.
47
+ */
48
+ nodes: Map<DepID, Node>;
49
+ /**
50
+ * Map of nodes by their name
51
+ */
52
+ nodesByName: Map<string, Set<Node>>;
53
+ /**
54
+ * Cached resolutions for spec lookups
55
+ */
56
+ resolutions: Map<string, Node>;
57
+ /**
58
+ * Reverse map of resolutions
59
+ */
60
+ resolutionsReverse: Map<Node, Set<string>>;
61
+ /**
62
+ * A set of importer nodes in this graph.
63
+ */
64
+ importers: Set<Node>;
65
+ /**
66
+ * The {@link Node} that represents the project root `package.json`.
67
+ */
68
+ mainImporter: Node;
69
+ /**
70
+ * A set of extraneous dependencies found when building the graph.
71
+ */
72
+ extraneousDependencies: Set<Edge>;
73
+ /**
74
+ * The root of the project this graph represents
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;
92
+ constructor(options: GraphOptions);
93
+ /**
94
+ * Get the next peer context index.
95
+ */
96
+ nextPeerContextIndex(): number;
97
+ /**
98
+ * Delete all nodes and edges that are unreachable from the importers.
99
+ * The collection of deleted nodes is returned.
100
+ *
101
+ * NOTE: This can be extremely slow for large graphs, and is almost always
102
+ * unnecessary! Only call when it is known that some unreachable nodes may
103
+ * have been created, for example when deleting the unneeded subgraph when an
104
+ * optional node fails to resolve/install.
105
+ */
106
+ gc(): Map<DepID, Node>;
107
+ /**
108
+ * Create a new edge between two nodes of the graph in case both exist,
109
+ * in case the destination node does not exists, then a dangling edge,
110
+ * pointing to nothing will be created to represent that missing dependency.
111
+ */
112
+ addEdge(type: DependencySaveType, spec: Spec, from: NodeLike, to?: NodeLike): Edge;
113
+ /**
114
+ * Find an existing node to satisfy a dependency
115
+ */
116
+ findResolution(spec: Spec, fromNode: Node, extra?: string): Node | undefined;
117
+ /**
118
+ * Create a new node in the graph.
119
+ */
120
+ addNode(id?: DepID, manifest?: NormalizedManifest, spec?: Spec, name?: string, version?: string): Node;
121
+ /**
122
+ * Place a new package into the graph representation, creating the new
123
+ * edges and possibly new nodes that are to be expected when traversing
124
+ * the graph in a top-down direction, e.g: from importers to leafs.
125
+ *
126
+ * For different uses that are not a direct top-down traversal of the graph
127
+ * consider using `addNode()` and `addEdge()` instead.
128
+ */
129
+ placePackage(fromNode: Node, depType: DependencySaveType, spec: Spec, manifest?: NormalizedManifest, id?: DepID, extra?: string): Node | undefined;
130
+ /**
131
+ * Removes a node and its relevant edges from the graph.
132
+ *
133
+ * Use the `keepEdges` option to keep the edges that were pointing to
134
+ * this node and only removes their `to` property value.
135
+ *
136
+ * If a replacement is provided, then any edges that were previously
137
+ * pointing to the removed node will be directed to the replacement,
138
+ * if it is valid to do so.
139
+ */
140
+ removeNode(node: Node, replacement?: Node, keepEdges?: boolean): void;
141
+ /**
142
+ * Removes the resolved node of a given edge.
143
+ */
144
+ removeEdgeResolution(edge: Edge, extra?: string): void;
145
+ /**
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.
148
+ */
149
+ resetEdges(): void;
150
+ toJSON(): import("./index.ts").LockfileData;
151
+ [kCustomInspect](_: number, options: InspectOptions): string;
152
+ }
153
+ export {};
package/dist/graph.js ADDED
@@ -0,0 +1,444 @@
1
+ import { getId, joinDepIDTuple, splitExtra } from '@vltpkg/dep-id';
2
+ import { error } from '@vltpkg/error-cause';
3
+ import { satisfies } from '@vltpkg/satisfies';
4
+ import { getOptions, Spec } from '@vltpkg/spec';
5
+ import { inspect } from 'node:util';
6
+ import { lockfileData } from "./lockfile/save.js";
7
+ import { Edge } from "./edge.js";
8
+ import { Node } from "./node.js";
9
+ import { resolveSaveType } from "./resolve-save-type.js";
10
+ const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
11
+ const cacheKeySeparator = '│';
12
+ // this is always the same, but we don't hard code it as a string,
13
+ // in case the DepID module needs to change its delimiter again ever.
14
+ const mainDepID = joinDepIDTuple(['file', '.']);
15
+ const getMap = (m) => m ?? new Map();
16
+ /**
17
+ * Get a cache key for a resolution based on the
18
+ * spec, location and query modifier.
19
+ */
20
+ const getResolutionCacheKey = (spec, location, extra) => {
21
+ const f = spec.final;
22
+ // if it's a file: dep, then the fromNode location matters
23
+ const fromPrefix = f.type === 'file' ? location + ' : ' : '';
24
+ // the unique key should also precise what is the type of the spec,
25
+ // and in the case of a registry, what registry it is from.
26
+ const typePrecisionKey = f.registry ?
27
+ `${cacheKeySeparator}registry` +
28
+ `${cacheKeySeparator}${f.registry}`
29
+ : f.gitRemote ?
30
+ `${cacheKeySeparator}git` + `${cacheKeySeparator}${f.gitRemote}`
31
+ : `${cacheKeySeparator}${f.type}`;
32
+ const modifierSuffix = `${cacheKeySeparator}${extra}`;
33
+ return fromPrefix + String(f) + typePrecisionKey + modifierSuffix;
34
+ };
35
+ export class Graph {
36
+ get [Symbol.toStringTag]() {
37
+ return '@vltpkg/graph.Graph';
38
+ }
39
+ #options;
40
+ #nodeOptions;
41
+ /**
42
+ * A {@link Monorepo} instance, used for managing workspaces.
43
+ */
44
+ monorepo;
45
+ /**
46
+ * An inventory with all manifests related to an install.
47
+ */
48
+ manifests;
49
+ /**
50
+ * A set of all edges in this graph.
51
+ */
52
+ edges = new Set();
53
+ /**
54
+ * Map registered dep ids to the node that represent them in the graph.
55
+ */
56
+ nodes = new Map();
57
+ /**
58
+ * Map of nodes by their name
59
+ */
60
+ nodesByName = new Map();
61
+ /**
62
+ * Cached resolutions for spec lookups
63
+ */
64
+ resolutions = new Map();
65
+ /**
66
+ * Reverse map of resolutions
67
+ */
68
+ resolutionsReverse = new Map();
69
+ /**
70
+ * A set of importer nodes in this graph.
71
+ */
72
+ importers = new Set();
73
+ /**
74
+ * The {@link Node} that represents the project root `package.json`.
75
+ */
76
+ mainImporter;
77
+ /**
78
+ * A set of extraneous dependencies found when building the graph.
79
+ */
80
+ extraneousDependencies = new Set();
81
+ /**
82
+ * The root of the project this graph represents
83
+ */
84
+ projectRoot;
85
+ /**
86
+ * The peer context sets used to resolve peer dependencies within this graph.
87
+ */
88
+ peerContexts;
89
+ /**
90
+ * Cache of forked peer contexts so identical fork operations can reuse
91
+ * previously created contexts instead of duplicating them.
92
+ *
93
+ * Key format is internal and constructed in `ideal/peers.ts`.
94
+ */
95
+ peerContextForkCache = new Map();
96
+ /**
97
+ * Tracks the current peer context index.
98
+ */
99
+ currentPeerContextIndex = 0;
100
+ constructor(options) {
101
+ const { mainManifest, monorepo } = options;
102
+ this.#options = options;
103
+ // hydrate spec options to their full contents, including defaults
104
+ const specOptions = getOptions({
105
+ registry: options.registry,
106
+ registries: options.registries,
107
+ 'git-hosts': options['git-hosts'],
108
+ 'git-host-archives': options['git-host-archives'],
109
+ 'scope-registries': options['scope-registries'],
110
+ 'jsr-registries': options['jsr-registries'],
111
+ catalog: options.catalog,
112
+ catalogs: options.catalogs,
113
+ });
114
+ this.manifests = getMap(options.manifests);
115
+ this.projectRoot = options.projectRoot;
116
+ this.#nodeOptions = {
117
+ ...this.#options,
118
+ ...specOptions,
119
+ graph: this,
120
+ };
121
+ // add the project root node
122
+ const mainImporterLocation = '.';
123
+ const mainImporterSpec = Spec.parse(mainManifest.name || '(root)', mainImporterLocation);
124
+ const mainImporter = this.addNode(mainDepID, mainManifest, mainImporterSpec);
125
+ mainImporter.setImporterLocation(mainImporterLocation);
126
+ mainImporter.mainImporter = true;
127
+ this.mainImporter = mainImporter;
128
+ this.mainImporter.workspaces = new Map();
129
+ this.importers.add(mainImporter);
130
+ this.manifests.set(mainImporter.id, mainManifest);
131
+ // uses the monorepo instance in order to retrieve info on
132
+ // workspaces and create importer nodes for each of them
133
+ this.monorepo = monorepo;
134
+ if (this.monorepo) {
135
+ for (const ws of this.monorepo) {
136
+ const wsNode = this.addNode(ws.id, ws.manifest, undefined, ws.name);
137
+ wsNode.setImporterLocation(`./${ws.path}`);
138
+ if (wsNode.manifest) {
139
+ this.manifests.set(wsNode.id, wsNode.manifest);
140
+ // set bins for workspace nodes so they can be linked
141
+ // when another importer depends on this workspace
142
+ /* c8 ignore next 3 - tested by integration tests */
143
+ if (wsNode.manifest.bin) {
144
+ wsNode.bins = wsNode.manifest.bin;
145
+ }
146
+ }
147
+ this.importers.add(wsNode);
148
+ // creates a virtual edge to connect the workspaces to the root node
149
+ const edge = new Edge('prod', Spec.parse(wsNode.name, 'workspace:*', this.#options), this.mainImporter, wsNode);
150
+ this.mainImporter.workspaces.set(wsNode.name, edge);
151
+ }
152
+ }
153
+ // initializes the peer context set collection
154
+ const initialPeerContext = new Map();
155
+ initialPeerContext.index = this.currentPeerContextIndex;
156
+ this.peerContexts = [initialPeerContext];
157
+ }
158
+ /**
159
+ * Get the next peer context index.
160
+ */
161
+ nextPeerContextIndex() {
162
+ return ++this.currentPeerContextIndex;
163
+ }
164
+ /**
165
+ * Delete all nodes and edges that are unreachable from the importers.
166
+ * The collection of deleted nodes is returned.
167
+ *
168
+ * NOTE: This can be extremely slow for large graphs, and is almost always
169
+ * unnecessary! Only call when it is known that some unreachable nodes may
170
+ * have been created, for example when deleting the unneeded subgraph when an
171
+ * optional node fails to resolve/install.
172
+ */
173
+ gc() {
174
+ const { nodes } = this;
175
+ this.edges.clear();
176
+ this.nodes = new Map();
177
+ const marked = new Set(this.importers);
178
+ for (const imp of marked) {
179
+ // don't delete the importer!
180
+ nodes.delete(imp.id);
181
+ this.nodes.set(imp.id, imp);
182
+ for (const edge of imp.edgesOut.values()) {
183
+ this.edges.add(edge);
184
+ const { to } = edge;
185
+ if (!to || marked.has(to))
186
+ continue;
187
+ marked.add(to);
188
+ nodes.delete(to.id);
189
+ this.nodes.set(to.id, to);
190
+ }
191
+ }
192
+ for (const node of nodes.values()) {
193
+ this.removeNode(node);
194
+ }
195
+ return nodes;
196
+ }
197
+ /**
198
+ * Create a new edge between two nodes of the graph in case both exist,
199
+ * in case the destination node does not exists, then a dangling edge,
200
+ * pointing to nothing will be created to represent that missing dependency.
201
+ */
202
+ addEdge(type, spec, from, to) {
203
+ // fix any nameless spec
204
+ if (spec.name === '(unknown)') {
205
+ if (to) {
206
+ spec.name = to.name /* c8 ignore next */ || '(unknown)';
207
+ spec.spec = `${to.name}@${spec.bareSpec}`;
208
+ }
209
+ else {
210
+ throw error('Impossible to place a missing, nameless dependency', { spec });
211
+ }
212
+ }
213
+ const existing = from.edgesOut.get(spec.name);
214
+ if (existing) {
215
+ const edge = existing;
216
+ if (edge.type === type &&
217
+ edge.spec.bareSpec === spec.bareSpec) {
218
+ if (to && to !== edge.to) {
219
+ // removes this edge from its destination edgesIn ref
220
+ edge.to?.edgesIn.delete(edge);
221
+ // now swap the destination to the new one
222
+ edge.to = to;
223
+ edge.to.edgesIn.add(edge);
224
+ }
225
+ return edge;
226
+ }
227
+ this.edges.delete(edge);
228
+ }
229
+ const f = from;
230
+ const edgeOut = f.addEdgesTo(resolveSaveType(from, spec.name, type), spec, to);
231
+ this.edges.add(edgeOut);
232
+ return edgeOut;
233
+ }
234
+ /**
235
+ * Find an existing node to satisfy a dependency
236
+ */
237
+ findResolution(spec, fromNode, extra = '') {
238
+ const f = spec.final;
239
+ const sf = getResolutionCacheKey(f, fromNode.location, extra);
240
+ const cached = this.resolutions.get(sf);
241
+ if (cached)
242
+ return cached;
243
+ const nbn = this.nodesByName.get(f.name);
244
+ if (!nbn)
245
+ return undefined;
246
+ for (const node of nbn) {
247
+ if (satisfies(node.id, f, fromNode.location, this.projectRoot, this.monorepo)) {
248
+ this.resolutions.set(sf, node);
249
+ // always set by now, because the node was added at some point
250
+ this.resolutionsReverse.get(node)?.add(sf);
251
+ return node;
252
+ }
253
+ }
254
+ }
255
+ /**
256
+ * Create a new node in the graph.
257
+ */
258
+ addNode(id, manifest, spec, name, version) {
259
+ const node = new Node(this.#nodeOptions, id, manifest, spec, name, version);
260
+ this.nodes.set(node.id, node);
261
+ const nbn = this.nodesByName.get(node.name) ?? new Set();
262
+ nbn.add(node);
263
+ // ensure the nodes by name set is always sorted, this will help
264
+ // keeping a deterministic graph resolution when reusing nodes
265
+ const newByNameSet = new Set([...nbn].sort((a, b) => a.id.localeCompare(b.id)));
266
+ this.nodesByName.set(node.name, newByNameSet);
267
+ if (manifest) {
268
+ this.manifests.set(node.id, manifest);
269
+ }
270
+ return node;
271
+ }
272
+ /**
273
+ * Place a new package into the graph representation, creating the new
274
+ * edges and possibly new nodes that are to be expected when traversing
275
+ * the graph in a top-down direction, e.g: from importers to leafs.
276
+ *
277
+ * For different uses that are not a direct top-down traversal of the graph
278
+ * consider using `addNode()` and `addEdge()` instead.
279
+ */
280
+ placePackage(fromNode, depType, spec, manifest, id, extra) {
281
+ // if no manifest is available, then create an edge that has no
282
+ // reference to any other node, representing a missing dependency
283
+ if (!manifest && !id) {
284
+ this.addEdge(depType, spec, fromNode);
285
+ return;
286
+ }
287
+ // flags set on the node we're about to create or find.
288
+ const flags = {
289
+ dev: fromNode.dev || depType === 'dev',
290
+ optional: fromNode.optional ||
291
+ depType === 'optional' ||
292
+ depType === 'peerOptional',
293
+ };
294
+ const depId = id || (manifest && getId(spec, manifest, extra));
295
+ /* c8 ignore start - should not be possible */
296
+ if (!depId) {
297
+ throw error('Could not find dep id when placing package', {
298
+ spec,
299
+ manifest,
300
+ });
301
+ }
302
+ /* c8 ignore stop */
303
+ // if a node for this package is already represented by a node
304
+ // in the graph, then just creates a new edge to that node
305
+ const toFoundNode = this.nodes.get(depId);
306
+ if (toFoundNode) {
307
+ this.addEdge(depType, spec, fromNode, toFoundNode);
308
+ // the current only stays dev/optional if this dep lets it remain so
309
+ // if it's not already, we don't make it dev or optional.
310
+ toFoundNode.detached = false;
311
+ toFoundNode.dev &&= flags.dev;
312
+ toFoundNode.optional &&= flags.optional;
313
+ return toFoundNode;
314
+ }
315
+ // creates a new node and edges to its parent
316
+ const toNode = this.addNode(depId, manifest, spec);
317
+ toNode.registry = spec.registry;
318
+ toNode.dev = flags.dev;
319
+ toNode.optional = flags.optional;
320
+ // split extra into modifier and peerSetHash
321
+ if (extra) {
322
+ const { modifier, peerSetHash } = splitExtra(extra);
323
+ toNode.modifier = modifier;
324
+ toNode.peerSetHash = peerSetHash;
325
+ }
326
+ // add extra manifest info if available
327
+ if (manifest) {
328
+ const { bin, engines, os, cpu, libc } = manifest;
329
+ // add platform info if available
330
+ if (engines || os || cpu || libc) {
331
+ const platform = {};
332
+ if (engines)
333
+ platform.engines = engines;
334
+ if (os)
335
+ platform.os = os;
336
+ if (cpu)
337
+ platform.cpu = cpu;
338
+ if (libc)
339
+ platform.libc = libc;
340
+ toNode.platform = platform;
341
+ }
342
+ // add bin info if available
343
+ if (bin) {
344
+ toNode.bins = bin;
345
+ }
346
+ }
347
+ toNode.maybeSetConfusedManifest(spec, manifest);
348
+ this.addEdge(depType, spec, fromNode, toNode);
349
+ // populate resolution cache if applicable
350
+ const f = getResolutionCacheKey(spec.final, fromNode.location, extra || '');
351
+ this.resolutions.set(f, toNode);
352
+ const rrev = this.resolutionsReverse.get(toNode) ?? new Set();
353
+ rrev.add(f);
354
+ this.resolutionsReverse.set(toNode, rrev);
355
+ return toNode;
356
+ }
357
+ /**
358
+ * Removes a node and its relevant edges from the graph.
359
+ *
360
+ * Use the `keepEdges` option to keep the edges that were pointing to
361
+ * this node and only removes their `to` property value.
362
+ *
363
+ * If a replacement is provided, then any edges that were previously
364
+ * pointing to the removed node will be directed to the replacement,
365
+ * if it is valid to do so.
366
+ */
367
+ removeNode(node, replacement, keepEdges) {
368
+ this.nodes.delete(node.id);
369
+ const nbn = this.nodesByName.get(node.name);
370
+ // if it's the last one, just remove the set
371
+ if (nbn?.size === 1)
372
+ this.nodesByName.delete(node.name);
373
+ else
374
+ nbn?.delete(node);
375
+ for (const r of this.resolutionsReverse.get(node) ?? new Set()) {
376
+ this.resolutions.delete(r);
377
+ }
378
+ this.resolutionsReverse.delete(node);
379
+ this.manifests.delete(node.id);
380
+ for (const edge of node.edgesOut.values()) {
381
+ this.edges.delete(edge);
382
+ }
383
+ for (const edge of node.edgesIn) {
384
+ if (replacement &&
385
+ satisfies(replacement.id, edge.spec, edge.from.location, this.projectRoot, this.monorepo)) {
386
+ edge.to = replacement;
387
+ }
388
+ else if (keepEdges) {
389
+ edge.to = undefined;
390
+ }
391
+ else {
392
+ edge.from.edgesOut.delete(edge.spec.name);
393
+ this.edges.delete(edge);
394
+ }
395
+ }
396
+ }
397
+ /**
398
+ * Removes the resolved node of a given edge.
399
+ */
400
+ removeEdgeResolution(edge, extra = '') {
401
+ const node = edge.to;
402
+ const resolutionKey = getResolutionCacheKey(edge.spec, edge.from.location, extra);
403
+ if (node) {
404
+ edge.to = undefined;
405
+ this.resolutions.delete(resolutionKey);
406
+ this.resolutionsReverse.get(node)?.delete(resolutionKey);
407
+ this.nodesByName.delete(node.name);
408
+ node.edgesIn.delete(edge);
409
+ if (node.edgesIn.size === 0) {
410
+ this.nodes.delete(node.id);
411
+ }
412
+ }
413
+ }
414
+ /**
415
+ * Remove all edges from the graph while preserving nodes and resolution caches.
416
+ * This allows the graph to be reconstructed efficiently using the existing nodes.
417
+ */
418
+ resetEdges() {
419
+ // Clear the global edges set
420
+ this.edges.clear();
421
+ // Clear all node edge relationships
422
+ for (const node of this.nodes.values()) {
423
+ // Mark nodes as detached so ideal rebuild treats them as candidates
424
+ // that must be (re)placed during traversal. Detached nodes with a
425
+ // manifest can skip refetch; detached nodes without a manifest must
426
+ // fetch from package-info during ideal rebuild.
427
+ node.detached = true;
428
+ // detaches all edges from this node
429
+ node.edgesOut.clear();
430
+ node.edgesIn.clear();
431
+ }
432
+ }
433
+ toJSON() {
434
+ return lockfileData({
435
+ ...this.#options,
436
+ graph: this,
437
+ saveManifests: true,
438
+ });
439
+ }
440
+ [kCustomInspect](_, options) {
441
+ const data = this.toJSON();
442
+ return `${this[Symbol.toStringTag]} ${inspect(data, options)}`;
443
+ }
444
+ }
@@ -0,0 +1,31 @@
1
+ import type { DepID } from '@vltpkg/dep-id';
2
+ import type { PackageInfoClient } from '@vltpkg/package-info';
3
+ import type { SpecOptions } from '@vltpkg/spec';
4
+ import type { PathScurry } from 'path-scurry';
5
+ import type { Dependency } from '../dependencies.ts';
6
+ import type { Graph } from '../graph.ts';
7
+ import type { Node } from '../node.ts';
8
+ import type { GraphModifier, ModifierActiveEntry } from '../modifiers.ts';
9
+ import type { ExtractResult } from '../reify/extract-node.ts';
10
+ import type { RollbackRemove } from '@vltpkg/rollback-remove';
11
+ import type { TransientAddMap, TransientRemoveMap } from './types.ts';
12
+ /**
13
+ * Append new nodes in the given `graph` for dependencies specified at `add`
14
+ * and missing dependencies from the `deps` parameter.
15
+ *
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.
30
+ */
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>;