@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.
- package/dist/actual/load.js +1 -0
- package/dist/dependencies.d.ts +16 -0
- package/dist/dependencies.js +24 -0
- package/dist/fixup-added-names.js +4 -2
- package/dist/graph.d.ts +41 -4
- package/dist/graph.js +145 -25
- package/dist/ideal/append-nodes.d.ts +2 -2
- package/dist/ideal/append-nodes.js +226 -60
- package/dist/ideal/build-ideal-from-starting-graph.js +28 -0
- package/dist/ideal/canonicalize-peer-ids.d.ts +44 -0
- package/dist/ideal/canonicalize-peer-ids.js +421 -0
- package/dist/ideal/get-importer-specs.d.ts +5 -1
- package/dist/ideal/get-importer-specs.js +51 -24
- package/dist/ideal/move-peer-store-dirs.d.ts +7 -0
- package/dist/ideal/move-peer-store-dirs.js +59 -0
- package/dist/ideal/peers.d.ts +16 -5
- package/dist/ideal/peers.js +187 -31
- package/dist/ideal/refresh-ideal-graph.d.ts +11 -2
- package/dist/ideal/refresh-ideal-graph.js +62 -4
- package/dist/ideal/remove-satisfied-specs.d.ts +11 -2
- package/dist/ideal/remove-satisfied-specs.js +18 -1
- package/dist/ideal/types.d.ts +20 -0
- package/dist/install.js +22 -3
- package/dist/lockfile/format-options-change.d.ts +5 -0
- package/dist/lockfile/format-options-change.js +20 -0
- package/dist/lockfile/load-nodes.js +3 -1
- package/dist/lockfile/load.d.ts +7 -1
- package/dist/lockfile/load.js +63 -7
- package/dist/lockfile/types.d.ts +17 -0
- package/dist/modifiers.d.ts +10 -0
- package/dist/modifiers.js +45 -9
- package/dist/node.d.ts +12 -0
- package/dist/node.js +42 -0
- package/dist/reify/index.js +30 -18
- package/dist/reify/update-importers-package-json.js +12 -2
- package/dist/transfer-data/load.js +3 -3
- package/dist/uninstall.js +2 -0
- package/package.json +21 -21
package/dist/actual/load.js
CHANGED
package/dist/dependencies.d.ts
CHANGED
|
@@ -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
|
package/dist/dependencies.js
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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("./
|
|
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
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
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
|
|
188
|
-
|
|
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 (
|
|
195
|
-
|
|
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 (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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>;
|