@vltpkg/graph 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/actual/load.js +1 -0
  2. package/dist/dependencies.d.ts +16 -0
  3. package/dist/dependencies.js +24 -0
  4. package/dist/fixup-added-names.js +4 -2
  5. package/dist/graph.d.ts +41 -4
  6. package/dist/graph.js +145 -25
  7. package/dist/ideal/append-nodes.d.ts +2 -2
  8. package/dist/ideal/append-nodes.js +226 -60
  9. package/dist/ideal/build-ideal-from-starting-graph.js +28 -0
  10. package/dist/ideal/canonicalize-peer-ids.d.ts +44 -0
  11. package/dist/ideal/canonicalize-peer-ids.js +421 -0
  12. package/dist/ideal/get-importer-specs.d.ts +5 -1
  13. package/dist/ideal/get-importer-specs.js +51 -24
  14. package/dist/ideal/move-peer-store-dirs.d.ts +7 -0
  15. package/dist/ideal/move-peer-store-dirs.js +59 -0
  16. package/dist/ideal/peers.d.ts +16 -5
  17. package/dist/ideal/peers.js +187 -31
  18. package/dist/ideal/refresh-ideal-graph.d.ts +11 -2
  19. package/dist/ideal/refresh-ideal-graph.js +62 -4
  20. package/dist/ideal/remove-satisfied-specs.d.ts +11 -2
  21. package/dist/ideal/remove-satisfied-specs.js +18 -1
  22. package/dist/ideal/types.d.ts +20 -0
  23. package/dist/install.js +22 -3
  24. package/dist/lockfile/format-options-change.d.ts +5 -0
  25. package/dist/lockfile/format-options-change.js +20 -0
  26. package/dist/lockfile/load-nodes.js +3 -1
  27. package/dist/lockfile/load.d.ts +7 -1
  28. package/dist/lockfile/load.js +63 -7
  29. package/dist/lockfile/types.d.ts +17 -0
  30. package/dist/modifiers.d.ts +10 -0
  31. package/dist/modifiers.js +45 -9
  32. package/dist/node.d.ts +12 -0
  33. package/dist/node.js +42 -0
  34. package/dist/reify/index.js +30 -18
  35. package/dist/reify/update-importers-package-json.js +12 -2
  36. package/dist/transfer-data/load.js +3 -3
  37. package/dist/uninstall.js +2 -0
  38. package/package.json +21 -21
@@ -352,6 +352,7 @@ export const load = (options) => {
352
352
  });
353
353
  }
354
354
  }
355
+ graph.sortNodes();
355
356
  done();
356
357
  return graph;
357
358
  };
@@ -53,10 +53,26 @@ export type RemoveImportersDependenciesMap = Map<DepID, Set<string>> & {
53
53
  };
54
54
  export declare const isDependency: (o: unknown) => o is Dependency;
55
55
  export declare const asDependency: (obj: unknown) => Dependency;
56
+ /**
57
+ * The key an added dependency is filed under. A nameless spec
58
+ * (`github:u/r`, `file:../x`, a tarball url) has no name to key by, so
59
+ * the CLI uses its stringified form; every reader of an `add` map has to
60
+ * derive the key the same way.
61
+ */
62
+ export declare const addKey: (spec: Spec) => string;
56
63
  /**
57
64
  * Get the {@link DependencyTypeShort} from a {@link DependencyTypeLong}.
58
65
  */
59
66
  export declare const shorten: (typeLong: DependencyTypeLong, name?: string, manifest?: Pick<Manifest, "peerDependenciesMeta"> | null) => DependencyTypeShort;
67
+ /**
68
+ * Only install devDeps for git dependencies and importers
69
+ * Everything else always gets installed
70
+ *
71
+ * Deliberately stricter than {@link getRawDependencies}, which also keeps
72
+ * devDeps of `file:` nodes: this is the rule the ideal build places with, so
73
+ * peer checks use it to match what actually lands in the graph.
74
+ */
75
+ export declare const shouldInstallDepType: (node: NodeLike, depType: DependencyTypeLong) => boolean;
60
76
  export declare const getRawDependencies: (node: NodeLike) => Map<string, RawDependency>;
61
77
  /**
62
78
  * Retrieves a map of all dependencies, of all types, that can be inferred
@@ -27,6 +27,13 @@ export const asDependency = (obj) => {
27
27
  }
28
28
  return obj;
29
29
  };
30
+ /**
31
+ * The key an added dependency is filed under. A nameless spec
32
+ * (`github:u/r`, `file:../x`, a tarball url) has no name to key by, so
33
+ * the CLI uses its stringified form; every reader of an `add` map has to
34
+ * derive the key the same way.
35
+ */
36
+ export const addKey = (spec) => spec.name === '(unknown)' ? spec.spec : spec.name;
30
37
  /**
31
38
  * Get the {@link DependencyTypeShort} from a {@link DependencyTypeLong}.
32
39
  */
@@ -48,6 +55,17 @@ export const shorten = (typeLong, name, manifest) => {
48
55
  return 'peer';
49
56
  };
50
57
  const isStringArray = (a) => Array.isArray(a) && !a.some(b => typeof b !== 'string');
58
+ /**
59
+ * Only install devDeps for git dependencies and importers
60
+ * Everything else always gets installed
61
+ *
62
+ * Deliberately stricter than {@link getRawDependencies}, which also keeps
63
+ * devDeps of `file:` nodes: this is the rule the ideal build places with, so
64
+ * peer checks use it to match what actually lands in the graph.
65
+ */
66
+ export const shouldInstallDepType = (node, depType) => depType !== 'devDependencies' ||
67
+ node.importer ||
68
+ node.id.startsWith('git');
51
69
  /*
52
70
  * Retrieves a map of all dependencies, of all types, that can be iterated
53
71
  * on and consulted when parsing the directory contents of a given node.
@@ -78,6 +96,12 @@ export const getRawDependencies = (node) => {
78
96
  // if it's a bundled dependency, we just ignore it entirely.
79
97
  if (bundled.has(name))
80
98
  continue;
99
+ // a name already collected from a regular dependency type is the
100
+ // package's own dependency; the peer entry only constrains it
101
+ if (depType === 'peerDependencies' &&
102
+ dependencies.has(name)) {
103
+ continue;
104
+ }
81
105
  dependencies.set(name, {
82
106
  name,
83
107
  type: depType,
@@ -1,4 +1,5 @@
1
1
  import { Spec } from '@vltpkg/spec';
2
+ import { addKey } from "./dependencies.js";
2
3
  import { calculateSaveValue } from "./reify/calculate-save-value.js";
3
4
  /**
4
5
  * When adding new dependencies, it's very common to not have a dependency
@@ -16,10 +17,11 @@ import { calculateSaveValue } from "./reify/calculate-save-value.js";
16
17
  export const fixupAddedNames = (add, manifest, options, spec) => {
17
18
  // Handle nameless dependencies
18
19
  if (add && manifest?.name && spec.name === '(unknown)') {
19
- const s = add.get(String(spec));
20
+ const key = addKey(spec);
21
+ const s = add.get(key);
20
22
  if (s) {
21
23
  // removes the previous, placeholder entry key
22
- add.delete(String(spec));
24
+ add.delete(key);
23
25
  // replaces spec with a version with the correct name
24
26
  spec = Spec.parse(manifest.name, spec.bareSpec, options);
25
27
  // updates the add map with the fixed up spec
package/dist/graph.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { SpecOptions } from '@vltpkg/spec';
4
4
  import type { GraphLike, NodeLike, NormalizedManifest, DependencySaveType } from '@vltpkg/types';
5
5
  import type { Monorepo } from '@vltpkg/workspaces';
6
6
  import type { InspectOptions } from 'node:util';
7
+ import type { OptionsChange } from './lockfile/types.ts';
7
8
  import { Edge } from './edge.ts';
8
9
  import { Node } from './node.ts';
9
10
  import type { PeerContext } from './ideal/types.ts';
@@ -91,10 +92,35 @@ export declare class Graph implements GraphLike {
91
92
  * the ideal builder must reset edges and rebuild the graph.
92
93
  */
93
94
  optionsChanged: boolean;
95
+ /**
96
+ * The individual differences behind `optionsChanged`, in the order
97
+ * they are reported to the user. Empty when nothing changed.
98
+ */
99
+ optionsChanges: OptionsChange[];
100
+ /**
101
+ * Whether the lockfile on disk no longer matches this graph even
102
+ * though no node changed, e.g. an importer edge spec was rewritten to
103
+ * the value saved to `package.json`, or the config options the graph
104
+ * was built with differ from the ones stored in the lockfile. Reify
105
+ * saves the lockfiles from its no-diff early return when set.
106
+ */
107
+ lockfileStale: boolean;
108
+ /**
109
+ * Lockfile edge targets captured immediately before `resetEdges()`.
110
+ * Used by the ideal builder to reuse locked resolutions across a rebuild.
111
+ */
112
+ lockedResolutions?: Map<string, DepID>;
94
113
  /**
95
114
  * Tracks the current peer context index.
96
115
  */
97
116
  currentPeerContextIndex: number;
117
+ /**
118
+ * Count of structural writes: nodes placed or removed, edges reset,
119
+ * created or re-pointed. Never reset; callers snapshot it and compare,
120
+ * so the ideal builder can tell whether a rebuild touched anything the
121
+ * peer identities depend on.
122
+ */
123
+ mutations: number;
98
124
  constructor(options: GraphOptions);
99
125
  /**
100
126
  * Get the next peer context index.
@@ -104,12 +130,23 @@ export declare class Graph implements GraphLike {
104
130
  * Delete all nodes and edges that are unreachable from the importers.
105
131
  * The collection of deleted nodes is returned.
106
132
  *
107
- * NOTE: This can be extremely slow for large graphs, and is almost always
108
- * unnecessary! Only call when it is known that some unreachable nodes may
109
- * have been created, for example when deleting the unneeded subgraph when an
133
+ * Marking is O(N+E) and always runs, but when nothing is unreachable
134
+ * and every reachable node is registered under its own id, `nodes` is
135
+ * left alone — no rebuild, no sort, and an empty map is returned.
136
+ * Only call when it is known that some unreachable nodes may have been
137
+ * created, for example when deleting the unneeded subgraph when an
110
138
  * optional node fails to resolve/install.
111
139
  */
112
140
  gc(): Map<DepID, Node>;
141
+ /**
142
+ * Rebuild `nodes` so importers come first, then remaining nodes in
143
+ * DepID order. Matches lockfile save so a built graph and a
144
+ * save→load round-trip iterate the same way.
145
+ *
146
+ * A graph coming from the lockfile loader is already in that order, so
147
+ * the pass below detects it and keeps the map as-is.
148
+ */
149
+ sortNodes(): void;
113
150
  /**
114
151
  * Create a new edge between two nodes of the graph in case both exist,
115
152
  * in case the destination node does not exists, then a dangling edge,
@@ -153,7 +190,7 @@ export declare class Graph implements GraphLike {
153
190
  * This allows the graph to be reconstructed efficiently using the existing nodes.
154
191
  */
155
192
  resetEdges(): void;
156
- toJSON(): import("./index.ts").LockfileData;
193
+ toJSON(): import("./lockfile/types.ts").LockfileData;
157
194
  [kCustomInspect](_: number, options: InspectOptions): string;
158
195
  }
159
196
  export {};
package/dist/graph.js CHANGED
@@ -1,11 +1,11 @@
1
- import { getId, joinDepIDTuple, splitExtra } from '@vltpkg/dep-id';
1
+ import { baseDepID, getId, joinDepIDTuple, splitExtra, } from '@vltpkg/dep-id';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { satisfies } from '@vltpkg/satisfies';
4
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";
8
- import { Node } from "./node.js";
8
+ import { copyPackageMetadata, Node } from "./node.js";
9
9
  import { resolveSaveType } from "./resolve-save-type.js";
10
10
  const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
11
11
  const cacheKeySeparator = '│';
@@ -102,10 +102,35 @@ export class Graph {
102
102
  * the ideal builder must reset edges and rebuild the graph.
103
103
  */
104
104
  optionsChanged = false;
105
+ /**
106
+ * The individual differences behind `optionsChanged`, in the order
107
+ * they are reported to the user. Empty when nothing changed.
108
+ */
109
+ optionsChanges = [];
110
+ /**
111
+ * Whether the lockfile on disk no longer matches this graph even
112
+ * though no node changed, e.g. an importer edge spec was rewritten to
113
+ * the value saved to `package.json`, or the config options the graph
114
+ * was built with differ from the ones stored in the lockfile. Reify
115
+ * saves the lockfiles from its no-diff early return when set.
116
+ */
117
+ lockfileStale = false;
118
+ /**
119
+ * Lockfile edge targets captured immediately before `resetEdges()`.
120
+ * Used by the ideal builder to reuse locked resolutions across a rebuild.
121
+ */
122
+ lockedResolutions;
105
123
  /**
106
124
  * Tracks the current peer context index.
107
125
  */
108
126
  currentPeerContextIndex = 0;
127
+ /**
128
+ * Count of structural writes: nodes placed or removed, edges reset,
129
+ * created or re-pointed. Never reset; callers snapshot it and compare,
130
+ * so the ideal builder can tell whether a rebuild touched anything the
131
+ * peer identities depend on.
132
+ */
133
+ mutations = 0;
109
134
  constructor(options) {
110
135
  const { mainManifest, monorepo } = options;
111
136
  this.#options = options;
@@ -174,41 +199,97 @@ export class Graph {
174
199
  * Delete all nodes and edges that are unreachable from the importers.
175
200
  * The collection of deleted nodes is returned.
176
201
  *
177
- * NOTE: This can be extremely slow for large graphs, and is almost always
178
- * unnecessary! Only call when it is known that some unreachable nodes may
179
- * have been created, for example when deleting the unneeded subgraph when an
202
+ * Marking is O(N+E) and always runs, but when nothing is unreachable
203
+ * and every reachable node is registered under its own id, `nodes` is
204
+ * left alone — no rebuild, no sort, and an empty map is returned.
205
+ * Only call when it is known that some unreachable nodes may have been
206
+ * created, for example when deleting the unneeded subgraph when an
180
207
  * optional node fails to resolve/install.
181
208
  */
182
209
  gc() {
183
210
  const { nodes } = this;
184
211
  this.edges.clear();
185
- this.nodes = new Map();
186
212
  const marked = new Set(this.importers);
187
- for (const imp of marked) {
188
- // don't delete the importer!
189
- nodes.delete(imp.id);
190
- this.nodes.set(imp.id, imp);
191
- for (const edge of imp.edgesOut.values()) {
213
+ for (const node of marked) {
214
+ for (const edge of node.edgesOut.values()) {
192
215
  this.edges.add(edge);
193
216
  const { to } = edge;
194
- if (!to || marked.has(to))
195
- continue;
196
- marked.add(to);
197
- nodes.delete(to.id);
198
- this.nodes.set(to.id, to);
217
+ if (to)
218
+ marked.add(to);
199
219
  }
200
220
  }
221
+ let intact = marked.size === nodes.size;
222
+ if (intact) {
223
+ for (const node of marked) {
224
+ if (nodes.get(node.id) !== node) {
225
+ intact = false;
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ if (intact)
231
+ return new Map();
232
+ this.nodes = new Map();
233
+ for (const node of marked) {
234
+ // don't delete the reachable ones!
235
+ nodes.delete(node.id);
236
+ this.nodes.set(node.id, node);
237
+ }
201
238
  for (const node of nodes.values()) {
202
239
  this.removeNode(node);
203
240
  }
241
+ this.sortNodes();
204
242
  return nodes;
205
243
  }
244
+ /**
245
+ * Rebuild `nodes` so importers come first, then remaining nodes in
246
+ * DepID order. Matches lockfile save so a built graph and a
247
+ * save→load round-trip iterate the same way.
248
+ *
249
+ * A graph coming from the lockfile loader is already in that order, so
250
+ * the pass below detects it and keeps the map as-is.
251
+ */
252
+ sortNodes() {
253
+ const cmp = (a, b) => a.id.localeCompare(b.id, 'en');
254
+ const rest = [];
255
+ const importers = this.importers.values();
256
+ let seenImporters = 0;
257
+ let sorted = true;
258
+ let prev;
259
+ for (const node of this.nodes.values()) {
260
+ if (this.importers.has(node)) {
261
+ // importers must come first, in importer-set order
262
+ if (rest.length || importers.next().value !== node) {
263
+ sorted = false;
264
+ }
265
+ seenImporters++;
266
+ continue;
267
+ }
268
+ if (sorted && prev && cmp(prev, node) > 0)
269
+ sorted = false;
270
+ prev = node;
271
+ rest.push(node);
272
+ }
273
+ if (sorted && seenImporters === this.importers.size)
274
+ return;
275
+ rest.sort(cmp);
276
+ const nodes = new Map();
277
+ for (const node of this.importers)
278
+ nodes.set(node.id, node);
279
+ for (const node of rest)
280
+ nodes.set(node.id, node);
281
+ this.nodes = nodes;
282
+ }
206
283
  /**
207
284
  * Create a new edge between two nodes of the graph in case both exist,
208
285
  * in case the destination node does not exists, then a dangling edge,
209
286
  * pointing to nothing will be created to represent that missing dependency.
210
287
  */
211
288
  addEdge(type, spec, from, to) {
289
+ if (to) {
290
+ const toNode = to;
291
+ toNode.detached = false;
292
+ }
212
293
  // fix any nameless spec
213
294
  if (spec.name === '(unknown)') {
214
295
  if (to) {
@@ -227,6 +308,7 @@ export class Graph {
227
308
  if (edge.type === type &&
228
309
  edge.spec.bareSpec === spec.bareSpec) {
229
310
  if (to && to !== edge.to) {
311
+ this.mutations++;
230
312
  // removes this edge from its destination edgesIn ref
231
313
  edge.to?.edgesIn.delete(edge);
232
314
  // now swap the destination to the new one
@@ -237,6 +319,7 @@ export class Graph {
237
319
  }
238
320
  this.edges.delete(edge);
239
321
  }
322
+ this.mutations++;
240
323
  const f = from;
241
324
  const edgeOut = f.addEdgesTo(resolveSaveType(from, spec.name, type), spec, to);
242
325
  this.edges.add(edgeOut);
@@ -249,19 +332,31 @@ export class Graph {
249
332
  const f = spec.final;
250
333
  const sf = getResolutionCacheKey(f, fromNode.location, extra);
251
334
  const cached = this.resolutions.get(sf);
252
- if (cached)
335
+ if (cached && !cached.detached)
253
336
  return cached;
254
337
  const nbn = this.nodesByName.get(f.name);
255
338
  if (!nbn)
256
339
  return undefined;
340
+ const sat = (n) => satisfies(n.id, f, fromNode.location, this.projectRoot, this.monorepo);
341
+ // a live node always wins; a detached one is only a fallback, and is
342
+ // never cached, placePackage caches it again when it reattaches. a
343
+ // detached cached entry is what this location resolved to last time,
344
+ // so it wins over nodesByName (sorted id) order - but the cache is
345
+ // written at placement, not by satisfies(), so re-check it here.
346
+ let detached = cached?.detached && sat(cached) ? cached : undefined;
257
347
  for (const node of nbn) {
258
- if (satisfies(node.id, f, fromNode.location, this.projectRoot, this.monorepo)) {
259
- this.resolutions.set(sf, node);
260
- // always set by now, because the node was added at some point
261
- this.resolutionsReverse.get(node)?.add(sf);
262
- return node;
348
+ if (!sat(node))
349
+ continue;
350
+ if (node.detached) {
351
+ detached ??= node;
352
+ continue;
263
353
  }
354
+ this.resolutions.set(sf, node);
355
+ // always set by now, because the node was added at some point
356
+ this.resolutionsReverse.get(node)?.add(sf);
357
+ return node;
264
358
  }
359
+ return detached;
265
360
  }
266
361
  /**
267
362
  * Create a new node in the graph.
@@ -273,7 +368,7 @@ export class Graph {
273
368
  nbn.add(node);
274
369
  // ensure the nodes by name set is always sorted, this will help
275
370
  // keeping a deterministic graph resolution when reusing nodes
276
- const newByNameSet = new Set([...nbn].sort((a, b) => a.id.localeCompare(b.id)));
371
+ const newByNameSet = new Set([...nbn].sort((a, b) => a.id.localeCompare(b.id, 'en')));
277
372
  this.nodesByName.set(node.name, newByNameSet);
278
373
  if (manifest) {
279
374
  this.manifests.set(node.id, manifest);
@@ -289,6 +384,7 @@ export class Graph {
289
384
  * consider using `addNode()` and `addEdge()` instead.
290
385
  */
291
386
  placePackage(fromNode, depType, spec, manifest, id, extra) {
387
+ this.mutations++;
292
388
  // if no manifest is available, then create an edge that has no
293
389
  // reference to any other node, representing a missing dependency
294
390
  if (!manifest && !id) {
@@ -336,10 +432,30 @@ export class Graph {
336
432
  return toFoundNode;
337
433
  }
338
434
  // creates a new node and edges to its parent
435
+ // Peer-fork rebuilds mint a provisional `peer.N` DepID that does not
436
+ // match the lockfile's content-hash ID, so exact-ID reuse misses.
437
+ // Copy integrity/resolved from the same package version (same tarball).
438
+ let samePackage;
439
+ const base = baseDepID(depId);
440
+ const nbn = this.nodesByName.get(manifest?.name ?? spec.name);
441
+ if (nbn) {
442
+ for (const n of nbn) {
443
+ if (baseDepID(n.id) !== base)
444
+ continue;
445
+ if (!n.integrity && !n.resolved)
446
+ continue;
447
+ samePackage = n;
448
+ if (n.integrity)
449
+ break;
450
+ }
451
+ }
339
452
  const toNode = this.addNode(depId, manifest, spec);
340
453
  toNode.registry = spec.registry;
341
454
  toNode.dev = flags.dev;
342
455
  toNode.optional = flags.optional;
456
+ if (samePackage)
457
+ copyPackageMetadata(toNode, samePackage);
458
+ toNode.integrity ??= manifest?.dist?.integrity;
343
459
  // split extra into modifier and peerSetHash
344
460
  if (extra) {
345
461
  const { modifier, peerSetHash } = splitExtra(extra);
@@ -388,6 +504,7 @@ export class Graph {
388
504
  * if it is valid to do so.
389
505
  */
390
506
  removeNode(node, replacement, keepEdges) {
507
+ this.mutations++;
391
508
  this.nodes.delete(node.id);
392
509
  const nbn = this.nodesByName.get(node.name);
393
510
  // if it's the last one, just remove the set
@@ -421,6 +538,7 @@ export class Graph {
421
538
  * Removes the resolved node of a given edge.
422
539
  */
423
540
  removeEdgeResolution(edge, extra = '') {
541
+ this.mutations++;
424
542
  const node = edge.to;
425
543
  const resolutionKey = getResolutionCacheKey(edge.spec, edge.from.location, extra);
426
544
  if (node) {
@@ -439,6 +557,7 @@ export class Graph {
439
557
  * This allows the graph to be reconstructed efficiently using the existing nodes.
440
558
  */
441
559
  resetEdges() {
560
+ this.mutations++;
442
561
  // Clear the global edges set
443
562
  this.edges.clear();
444
563
  // Clear all node edge relationships
@@ -446,8 +565,9 @@ export class Graph {
446
565
  // Mark nodes as detached so ideal rebuild treats them as candidates
447
566
  // that must be (re)placed during traversal. Detached nodes with a
448
567
  // manifest can skip refetch; detached nodes without a manifest must
449
- // fetch from package-info during ideal rebuild.
450
- node.detached = true;
568
+ // fetch from package-info during ideal rebuild. Importers are the
569
+ // traversal roots and are never re-placed, so they stay attached.
570
+ node.detached = !node.importer;
451
571
  // detaches all edges from this node
452
572
  node.edgesOut.clear();
453
573
  node.edgesIn.clear();
@@ -8,7 +8,7 @@ import type { Node } from '../node.ts';
8
8
  import type { GraphModifier, ModifierActiveEntry } from '../modifiers.ts';
9
9
  import type { ExtractResult } from '../reify/extract-node.ts';
10
10
  import type { RollbackRemove } from '@vltpkg/rollback-remove';
11
- import type { TransientAddMap, TransientRemoveMap } from './types.ts';
11
+ import type { ExplicitAddMap, TransientAddMap, TransientRemoveMap } from './types.ts';
12
12
  /**
13
13
  * Append new nodes in the given `graph` for dependencies specified at `add`
14
14
  * and missing dependencies from the `deps` parameter.
@@ -28,4 +28,4 @@ import type { TransientAddMap, TransientRemoveMap } from './types.ts';
28
28
  * **Early Extraction**: When `actual` graph is provided, nodes are extracted
29
29
  * to the vlt store during graph construction (not after), improving performance.
30
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>;
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, explicit?: ExplicitAddMap) => Promise<void>;