@vltpkg/graph 1.3.8 → 1.4.0
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 +4 -0
- package/dist/ideal/append-nodes.js +8 -0
- package/dist/ideal/build-ideal-from-starting-graph.js +8 -0
- package/dist/ideal/get-importer-specs.d.ts +1 -0
- package/dist/ideal/get-importer-specs.js +10 -0
- package/dist/ideal/refresh-ideal-graph.js +6 -1
- package/dist/install.js +22 -0
- package/dist/lockfile/load-edges.js +0 -2
- package/dist/modifiers.d.ts +7 -0
- package/dist/modifiers.js +24 -0
- package/dist/reify/index.js +5 -1
- package/dist/uninstall.js +2 -0
- package/package.json +21 -21
package/dist/actual/load.js
CHANGED
|
@@ -246,6 +246,10 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
|
|
|
246
246
|
});
|
|
247
247
|
// Check for active modifiers and replace spec for missing dependencies
|
|
248
248
|
const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, name, modifierRefs);
|
|
249
|
+
// a `-` modifier removes the dependency, so it is not missing
|
|
250
|
+
if (modifiedSpec !== spec && modifiedSpec.bareSpec === '-') {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
249
253
|
spec = modifiedSpec;
|
|
250
254
|
graph.placePackage(fromNode, depType, spec, undefined, undefined, joinExtra({ modifier: queryModifier }));
|
|
251
255
|
}
|
|
@@ -228,6 +228,10 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
|
|
|
228
228
|
spec = activeModifier.modifier.spec;
|
|
229
229
|
// bareSpec of '-' means "remove this dependency"
|
|
230
230
|
if (spec.bareSpec === '-') {
|
|
231
|
+
// nothing to install or save for an explicit request
|
|
232
|
+
if (isExplicit) {
|
|
233
|
+
throw error(`Cannot add ${originalSpec.name}: it is removed by a graph modifier`, { code: 'ECONFIG', found: queryModifier });
|
|
234
|
+
}
|
|
231
235
|
continue;
|
|
232
236
|
}
|
|
233
237
|
}
|
|
@@ -366,6 +370,8 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
|
|
|
366
370
|
}
|
|
367
371
|
throw er;
|
|
368
372
|
});
|
|
373
|
+
// awaited below; a throw before that must not leave it unhandled
|
|
374
|
+
manifestPromise.catch(() => { });
|
|
369
375
|
const fetchTask = {
|
|
370
376
|
spec,
|
|
371
377
|
type,
|
|
@@ -380,6 +386,8 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
|
|
|
380
386
|
};
|
|
381
387
|
fetchTasks.push(fetchTask);
|
|
382
388
|
}
|
|
389
|
+
// fail on the first rejection, not once the ones before it settle
|
|
390
|
+
await Promise.all(fetchTasks.map(t => t.manifestPromise));
|
|
383
391
|
// Create placement tasks from fetch tasks
|
|
384
392
|
for (const fetchTask of fetchTasks) {
|
|
385
393
|
const manifest = await fetchTask.manifestPromise;
|
|
@@ -79,6 +79,14 @@ export const buildIdealFromStartingGraph = async (options) => {
|
|
|
79
79
|
if (importerSpecs.staleSpecs.size) {
|
|
80
80
|
options.graph.lockfileStale = true;
|
|
81
81
|
}
|
|
82
|
+
// importer edges a `-` modifier now removes: drop them, gc() takes
|
|
83
|
+
// whatever only they reached
|
|
84
|
+
for (const edge of importerSpecs.removedEdges) {
|
|
85
|
+
edge.from.edgesOut.delete(edge.name);
|
|
86
|
+
edge.to?.edgesIn.delete(edge);
|
|
87
|
+
options.graph.edges.delete(edge);
|
|
88
|
+
options.graph.lockfileStale = true;
|
|
89
|
+
}
|
|
82
90
|
// refreshs the current graph adding the nodes marked for addition
|
|
83
91
|
// and removing the ones marked for removal, while also recalculating
|
|
84
92
|
// peer dependencies and default locations
|
|
@@ -18,6 +18,7 @@ export type GetImporterSpecsOptions = BuildIdealAddOptions & BuildIdealFromGraph
|
|
|
18
18
|
export declare const getImporterSpecs: (options: GetImporterSpecsOptions) => {
|
|
19
19
|
add: AddImportersDependenciesMap;
|
|
20
20
|
remove: RemoveImportersDependenciesMap;
|
|
21
|
+
removedEdges: Set<Edge>;
|
|
21
22
|
staleSpecs: Map<Edge, Dependency>;
|
|
22
23
|
transientAdd: TransientAddMap;
|
|
23
24
|
transientRemove: TransientRemoveMap;
|
|
@@ -25,6 +25,8 @@ export const getImporterSpecs = (options) => {
|
|
|
25
25
|
const { add, graph, remove } = options;
|
|
26
26
|
const addResult = new AddImportersDependenciesMapImpl();
|
|
27
27
|
const removeResult = new RemoveImportersDependenciesMapImpl();
|
|
28
|
+
// importer edges a `-` modifier now removes, package.json keeps them
|
|
29
|
+
const removedEdges = new Set();
|
|
28
30
|
// traverse the list of importers in the starting graph
|
|
29
31
|
for (const importer of graph.importers) {
|
|
30
32
|
// uses a Map keying to the spec.name in order to easily make sure there's
|
|
@@ -71,6 +73,13 @@ export const getImporterSpecs = (options) => {
|
|
|
71
73
|
continue;
|
|
72
74
|
}
|
|
73
75
|
const spec = Spec.parse(depName, depSpec, options);
|
|
76
|
+
// a `-` modifier removes this dependency, nothing to place; an
|
|
77
|
+
// edge from before (e.g. a qualifier now matching) has to go
|
|
78
|
+
if (options.modifiers?.removesImporterEdge(importer, spec)) {
|
|
79
|
+
if (edge)
|
|
80
|
+
removedEdges.add(edge);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
74
83
|
// if a workspace dep references a workspace that no longer exists
|
|
75
84
|
// (folder was removed), mark it for removal instead of trying to
|
|
76
85
|
// resolve it — the workspace can't be satisfied
|
|
@@ -212,6 +221,7 @@ export const getImporterSpecs = (options) => {
|
|
|
212
221
|
return {
|
|
213
222
|
add: addResult,
|
|
214
223
|
remove: removeResult,
|
|
224
|
+
removedEdges,
|
|
215
225
|
staleSpecs,
|
|
216
226
|
transientAdd,
|
|
217
227
|
transientRemove,
|
|
@@ -99,7 +99,12 @@ export const refreshIdealGraph = async ({ add, graph, modifiers, packageInfo, sc
|
|
|
99
99
|
const modifierRefs = modifiers?.tryDependencies(importer, deps);
|
|
100
100
|
// Add new nodes for packages defined in the dependencies list fetching
|
|
101
101
|
// metadata from the registry manifests and updating the graph
|
|
102
|
-
await appendNodes(packageInfo, graph, importer, deps, scurry, specOptions, seen, addedDeps, modifiers, modifierRefs, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit)
|
|
102
|
+
await appendNodes(packageInfo, graph, importer, deps, scurry, specOptions, seen, addedDeps, modifiers, modifierRefs, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit).catch((er) => {
|
|
103
|
+
// extractions still in flight are moot now
|
|
104
|
+
for (const p of extractPromises ?? [])
|
|
105
|
+
p.catch(() => { });
|
|
106
|
+
throw er;
|
|
107
|
+
});
|
|
103
108
|
}
|
|
104
109
|
// locked resolutions only apply to the rebuild that captured them
|
|
105
110
|
graph.lockedResolutions = undefined;
|
package/dist/install.js
CHANGED
|
@@ -7,6 +7,7 @@ import { error } from '@vltpkg/error-cause';
|
|
|
7
7
|
import { asError } from '@vltpkg/types';
|
|
8
8
|
import { getDependencies } from "./dependencies.js";
|
|
9
9
|
import { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
10
|
+
import { joinDepIDTuple } from '@vltpkg/dep-id';
|
|
10
11
|
import { existsSync, rmSync } from 'node:fs';
|
|
11
12
|
import { resolve } from 'node:path';
|
|
12
13
|
import { load as loadVirtual, loadData } from "./lockfile/load.js";
|
|
@@ -109,6 +110,7 @@ export const install = async (options, add) => {
|
|
|
109
110
|
}
|
|
110
111
|
if (importerSpecs.add.modifiedDependencies ||
|
|
111
112
|
importerSpecs.remove.modifiedDependencies ||
|
|
113
|
+
importerSpecs.removedEdges.size > 0 ||
|
|
112
114
|
specChanges.length > 0 ||
|
|
113
115
|
lockfileGraph.optionsChanged) {
|
|
114
116
|
const details = [];
|
|
@@ -141,6 +143,9 @@ export const install = async (options, add) => {
|
|
|
141
143
|
details.push(` ${location}: ${deps.size} ${depLabelRemove} to remove (${depNames.join(', ')})`);
|
|
142
144
|
}
|
|
143
145
|
}
|
|
146
|
+
for (const edge of importerSpecs.removedEdges) {
|
|
147
|
+
details.push(` ${edge.from.location}: ${edge.name} is removed by a graph modifier`);
|
|
148
|
+
}
|
|
144
149
|
const lockfilePath = resolve(options.projectRoot, 'vlt-lock.json');
|
|
145
150
|
throw error('Lockfile is out of sync with package.json. Run "vlt install" to update.\n' +
|
|
146
151
|
details.join('\n'), {
|
|
@@ -148,6 +153,23 @@ export const install = async (options, add) => {
|
|
|
148
153
|
});
|
|
149
154
|
}
|
|
150
155
|
}
|
|
156
|
+
// an explicit add of a dep a `-` modifier removes fails before
|
|
157
|
+
// anything on disk is touched
|
|
158
|
+
if (modifiers && add?.modifiedDependencies) {
|
|
159
|
+
const wsIds = new Set([...(fullMonorepo?.values() ?? [])].map(ws => ws.id));
|
|
160
|
+
for (const [id, deps] of add) {
|
|
161
|
+
const mainImporter = id === joinDepIDTuple(['file', '.']);
|
|
162
|
+
// nested folders are no importers, appendNodes checks those
|
|
163
|
+
if (!mainImporter && !wsIds.has(id))
|
|
164
|
+
continue;
|
|
165
|
+
for (const { spec } of deps.values()) {
|
|
166
|
+
const found = modifiers.removesImporterEdge({ importer: true, mainImporter }, spec);
|
|
167
|
+
if (found) {
|
|
168
|
+
throw error(`Cannot add ${spec.name}: it is removed by a graph modifier`, { code: 'ECONFIG', found });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
151
173
|
const remover = new RollbackRemove();
|
|
152
174
|
if (options.cleanInstall) {
|
|
153
175
|
const nodeModulesPath = resolve(options.projectRoot, 'node_modules');
|
|
@@ -52,11 +52,9 @@ export const loadEdges = (graph, edges, options, cache) => {
|
|
|
52
52
|
const [depType, valRest] = fastSplit(value, ' ', 2);
|
|
53
53
|
const vrSplit = valRest?.lastIndexOf(' ') ?? -1;
|
|
54
54
|
// not a valid edge record
|
|
55
|
-
/* c8 ignore start */
|
|
56
55
|
if (!valRest || !depType || !fromId || !specName || vrSplit < 1) {
|
|
57
56
|
continue;
|
|
58
57
|
}
|
|
59
|
-
/* c8 ignore stop */
|
|
60
58
|
// Validate dependency type early
|
|
61
59
|
if (!isDependencyTypeShort(depType)) {
|
|
62
60
|
throw error('Found unsupported dependency type in lockfile', {
|
package/dist/modifiers.d.ts
CHANGED
|
@@ -126,6 +126,13 @@ export declare class GraphModifier {
|
|
|
126
126
|
* root's own b edge is still the manifest's to validate and heal.
|
|
127
127
|
*/
|
|
128
128
|
targetsImporterEdge(importer: Node, spec: Spec): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Selector of the modifier that wins the direct `importer -> spec.name`
|
|
131
|
+
* edge, if it removes it (value `-`). Same pick as tryNewDependency:
|
|
132
|
+
* complete entries whose qualifier accepts the spec, highest
|
|
133
|
+
* specificity, importer-anchored before lone on ties.
|
|
134
|
+
*/
|
|
135
|
+
removesImporterEdge(importer: Pick<Node, 'importer' | 'mainImporter'>, spec: Spec): string | undefined;
|
|
129
136
|
/**
|
|
130
137
|
* Loads the modifiers defined in `vlt.json` into memory.
|
|
131
138
|
*/
|
package/dist/modifiers.js
CHANGED
|
@@ -115,6 +115,30 @@ export class GraphModifier {
|
|
|
115
115
|
}
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Selector of the modifier that wins the direct `importer -> spec.name`
|
|
120
|
+
* edge, if it removes it (value `-`). Same pick as tryNewDependency:
|
|
121
|
+
* complete entries whose qualifier accepts the spec, highest
|
|
122
|
+
* specificity, importer-anchored before lone on ties.
|
|
123
|
+
*/
|
|
124
|
+
removesImporterEdge(importer, spec) {
|
|
125
|
+
const anchored = [];
|
|
126
|
+
const lone = [];
|
|
127
|
+
for (const mod of this.#modifiers) {
|
|
128
|
+
const { last } = mod.breadcrumb;
|
|
129
|
+
if (last.name !== spec.name || !matchesQualifier(last, spec))
|
|
130
|
+
continue;
|
|
131
|
+
const { prev } = last;
|
|
132
|
+
if (!prev)
|
|
133
|
+
lone.push(mod);
|
|
134
|
+
else if (!prev.prev && matchesImporter(prev, importer))
|
|
135
|
+
anchored.push(mod);
|
|
136
|
+
}
|
|
137
|
+
const all = [...anchored, ...lone];
|
|
138
|
+
const [winner] = specificitySort(all.map(m => m.breadcrumb));
|
|
139
|
+
const mod = all.find(m => m.breadcrumb === winner);
|
|
140
|
+
return mod?.value === '-' ? mod.query : undefined;
|
|
141
|
+
}
|
|
118
142
|
/**
|
|
119
143
|
* Loads the modifiers defined in `vlt.json` into memory.
|
|
120
144
|
*/
|
package/dist/reify/index.js
CHANGED
|
@@ -89,7 +89,11 @@ export const reify = async (options) => {
|
|
|
89
89
|
// Otherwise a first install of an optional-only tree would never extract.
|
|
90
90
|
const hasLockfiles = !!scurry.lstatSync('vlt-lock.json') &&
|
|
91
91
|
!!scurry.lstatSync('node_modules/.vlt-lock.json');
|
|
92
|
-
|
|
92
|
+
// a removal is never optional-only: deleting is what reify is for
|
|
93
|
+
const skipOptionalOnly = noModifiedDependencies &&
|
|
94
|
+
diff.optionalOnly &&
|
|
95
|
+
!diff.nodes.delete.size &&
|
|
96
|
+
!diff.edges.delete.size;
|
|
93
97
|
const skippable = skipOptionalOnly && !options.update && hasLockfiles;
|
|
94
98
|
const res = { diff };
|
|
95
99
|
if (!diff.hasChanges() || skippable) {
|
package/dist/uninstall.js
CHANGED
|
@@ -38,6 +38,7 @@ export const uninstall = async (options, remove) => {
|
|
|
38
38
|
remove,
|
|
39
39
|
mainManifest,
|
|
40
40
|
loadManifests: true,
|
|
41
|
+
modifiers,
|
|
41
42
|
remover,
|
|
42
43
|
});
|
|
43
44
|
// If lockfileOnly is enabled, skip reify and only save the lockfile
|
|
@@ -67,6 +68,7 @@ export const uninstall = async (options, remove) => {
|
|
|
67
68
|
actual: act,
|
|
68
69
|
graph,
|
|
69
70
|
loadManifests: true,
|
|
71
|
+
modifiers,
|
|
70
72
|
remover,
|
|
71
73
|
});
|
|
72
74
|
return { graph, diff };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vltpkg/graph",
|
|
3
3
|
"description": "A library that helps understanding & expressing what happens on an install",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -13,26 +13,26 @@
|
|
|
13
13
|
"url": "http://vlt.sh"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@vltpkg/cmd-shim": "1.
|
|
17
|
-
"@vltpkg/dep-id": "1.
|
|
18
|
-
"@vltpkg/dss-breadcrumb": "1.
|
|
19
|
-
"@vltpkg/error-cause": "1.
|
|
20
|
-
"@vltpkg/fast-split": "1.
|
|
21
|
-
"@vltpkg/graph-run": "1.
|
|
22
|
-
"@vltpkg/init": "1.
|
|
23
|
-
"@vltpkg/output": "1.
|
|
24
|
-
"@vltpkg/package-info": "1.
|
|
25
|
-
"@vltpkg/package-json": "1.
|
|
26
|
-
"@vltpkg/pick-manifest": "1.
|
|
27
|
-
"@vltpkg/query": "1.
|
|
28
|
-
"@vltpkg/rollback-remove": "1.
|
|
29
|
-
"@vltpkg/run": "1.
|
|
30
|
-
"@vltpkg/satisfies": "1.
|
|
31
|
-
"@vltpkg/security-archive": "1.
|
|
32
|
-
"@vltpkg/spec": "1.
|
|
33
|
-
"@vltpkg/types": "1.
|
|
34
|
-
"@vltpkg/vlt-json": "1.
|
|
35
|
-
"@vltpkg/workspaces": "1.
|
|
16
|
+
"@vltpkg/cmd-shim": "1.4.0",
|
|
17
|
+
"@vltpkg/dep-id": "1.4.0",
|
|
18
|
+
"@vltpkg/dss-breadcrumb": "1.4.0",
|
|
19
|
+
"@vltpkg/error-cause": "1.4.0",
|
|
20
|
+
"@vltpkg/fast-split": "1.4.0",
|
|
21
|
+
"@vltpkg/graph-run": "1.4.0",
|
|
22
|
+
"@vltpkg/init": "1.4.0",
|
|
23
|
+
"@vltpkg/output": "1.4.0",
|
|
24
|
+
"@vltpkg/package-info": "1.4.0",
|
|
25
|
+
"@vltpkg/package-json": "1.4.0",
|
|
26
|
+
"@vltpkg/pick-manifest": "1.4.0",
|
|
27
|
+
"@vltpkg/query": "1.4.0",
|
|
28
|
+
"@vltpkg/rollback-remove": "1.4.0",
|
|
29
|
+
"@vltpkg/run": "1.4.0",
|
|
30
|
+
"@vltpkg/satisfies": "1.4.0",
|
|
31
|
+
"@vltpkg/security-archive": "1.4.0",
|
|
32
|
+
"@vltpkg/spec": "1.4.0",
|
|
33
|
+
"@vltpkg/types": "1.4.0",
|
|
34
|
+
"@vltpkg/vlt-json": "1.4.0",
|
|
35
|
+
"@vltpkg/workspaces": "1.4.0",
|
|
36
36
|
"path-scurry": "^2.0.1",
|
|
37
37
|
"promise-call-limit": "^3.0.2"
|
|
38
38
|
},
|