@vltpkg/graph 1.4.0 → 1.6.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/README.md +62 -3
- package/dist/actual/load.js +24 -12
- package/dist/build.js +30 -5
- package/dist/dependencies.d.ts +11 -0
- package/dist/dependencies.js +47 -7
- package/dist/filter-nodes-by-query.d.ts +39 -0
- package/dist/filter-nodes-by-query.js +100 -0
- package/dist/graph.js +3 -0
- package/dist/ideal/append-nodes.js +29 -11
- package/dist/ideal/get-importer-specs.js +17 -7
- package/dist/ideal/remove-satisfied-specs.js +18 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/install.d.ts +25 -8
- package/dist/install.js +25 -1
- package/dist/lockfile/load-edges.js +10 -2
- package/dist/modifiers.d.ts +11 -0
- package/dist/modifiers.js +105 -21
- package/dist/reify/add-edge.d.ts +4 -0
- package/dist/reify/add-edge.js +13 -7
- package/dist/reify/build.d.ts +7 -0
- package/dist/reify/check-needed-build.d.ts +5 -0
- package/dist/reify/check-needed-build.js +13 -0
- package/dist/reify/delete-edge.js +16 -14
- package/dist/reify/delete-nodes.d.ts +2 -1
- package/dist/reify/delete-nodes.js +4 -2
- package/dist/reify/index.d.ts +2 -0
- package/dist/reify/index.js +11 -39
- package/dist/reify/relink-bins.d.ts +9 -0
- package/dist/reify/relink-bins.js +39 -0
- package/dist/reify/update-importers-package-json.d.ts +6 -1
- package/dist/reify/update-importers-package-json.js +16 -15
- package/dist/skills/dirs.d.ts +58 -0
- package/dist/skills/dirs.js +211 -0
- package/dist/skills/discover.d.ts +73 -0
- package/dist/skills/discover.js +238 -0
- package/dist/skills/gitignore.d.ts +18 -0
- package/dist/skills/gitignore.js +82 -0
- package/dist/skills/index.d.ts +124 -0
- package/dist/skills/index.js +254 -0
- package/dist/skills/lazy.d.ts +8 -0
- package/dist/skills/lazy.js +8 -0
- package/dist/skills/mask.d.ts +26 -0
- package/dist/skills/mask.js +43 -0
- package/dist/skills/mounts.d.ts +78 -0
- package/dist/skills/mounts.js +162 -0
- package/dist/uninstall.d.ts +27 -7
- package/dist/uninstall.js +25 -1
- package/dist/update.d.ts +24 -4
- package/dist/update.js +24 -1
- package/dist/visualization/mermaid-output.d.ts +8 -0
- package/dist/visualization/mermaid-output.js +25 -8
- package/package.json +21 -21
package/dist/install.d.ts
CHANGED
|
@@ -3,19 +3,36 @@ import type { LoadOptions } from './actual/load.ts';
|
|
|
3
3
|
import type { AddImportersDependenciesMap } from './dependencies.ts';
|
|
4
4
|
import type { DepID } from '@vltpkg/dep-id';
|
|
5
5
|
import type { Graph } from './index.ts';
|
|
6
|
+
import type { Diff } from './diff.ts';
|
|
7
|
+
import type { LinkSkillsResult } from './skills/index.ts';
|
|
6
8
|
export type InstallOptions = LoadOptions & {
|
|
7
9
|
packageInfo: PackageInfoClient;
|
|
8
10
|
cleanInstall?: boolean;
|
|
9
11
|
allowScripts: string;
|
|
10
12
|
saveExact?: boolean;
|
|
11
13
|
savePrefix?: string;
|
|
14
|
+
/**
|
|
15
|
+
* DSS query: packages whose agent skills get linked into the
|
|
16
|
+
* project's agent skill dirs after install. Selectors may end with
|
|
17
|
+
* `:skill()`. Unset or `:not(*)`: none.
|
|
18
|
+
*/
|
|
19
|
+
allowSkills?: string;
|
|
20
|
+
/**
|
|
21
|
+
* agent skill dirs, relative to projectRoot. Unset: detect; `[]`:
|
|
22
|
+
* none
|
|
23
|
+
*/
|
|
24
|
+
skillsDirs?: string[];
|
|
25
|
+
/** store dirs of these nodes are never removed, eg the running vlt */
|
|
26
|
+
keepNodes?: ReadonlySet<DepID>;
|
|
12
27
|
};
|
|
13
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Result of {@link install}.
|
|
30
|
+
*/
|
|
31
|
+
export type InstallResult = {
|
|
32
|
+
buildQueue?: DepID[];
|
|
14
33
|
graph: Graph;
|
|
15
|
-
diff
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
diff: import("./diff.ts").Diff;
|
|
21
|
-
}>;
|
|
34
|
+
diff?: Diff;
|
|
35
|
+
/** skill links added or removed, if any */
|
|
36
|
+
skills?: LinkSkillsResult;
|
|
37
|
+
};
|
|
38
|
+
export declare const install: (options: InstallOptions, add?: AddImportersDependenciesMap) => Promise<InstallResult>;
|
package/dist/install.js
CHANGED
|
@@ -16,11 +16,23 @@ import { getImporterSpecs } from "./ideal/get-importer-specs.js";
|
|
|
16
16
|
import { lockfile } from "./index.js";
|
|
17
17
|
import { updatePackageJson } from "./reify/update-importers-package-json.js";
|
|
18
18
|
import { Monorepo } from '@vltpkg/workspaces';
|
|
19
|
+
import { assertSkillsDirs } from "./skills/dirs.js";
|
|
20
|
+
import { assertQuery } from "./filter-nodes-by-query.js";
|
|
19
21
|
export const install = async (options, add) => {
|
|
20
22
|
// Validate incompatible options
|
|
21
23
|
if (options.lockfileOnly && options.cleanInstall) {
|
|
22
24
|
throw error('Cannot use --lockfile-only with --clean-install (ci command). Clean install requires filesystem operations.');
|
|
23
25
|
}
|
|
26
|
+
// unset or `:not(*)`: no skills work at all, the skills code never
|
|
27
|
+
// loads
|
|
28
|
+
const allowSkills = options.allowSkills === ':not(*)' ?
|
|
29
|
+
undefined
|
|
30
|
+
: options.allowSkills;
|
|
31
|
+
// fail before installing, not after
|
|
32
|
+
if (allowSkills) {
|
|
33
|
+
await assertQuery(allowSkills, 'allow-skills', ['skill']);
|
|
34
|
+
assertSkillsDirs(options.projectRoot, options.skillsDirs);
|
|
35
|
+
}
|
|
24
36
|
if (options.expectLockfile || options.frozenLockfile) {
|
|
25
37
|
const lockfilePath = resolve(options.projectRoot, 'vlt-lock.json');
|
|
26
38
|
if (!existsSync(lockfilePath)) {
|
|
@@ -178,6 +190,7 @@ export const install = async (options, add) => {
|
|
|
178
190
|
remover.confirm();
|
|
179
191
|
}
|
|
180
192
|
}
|
|
193
|
+
let result;
|
|
181
194
|
try {
|
|
182
195
|
const remove = Object.assign(new Map(), {
|
|
183
196
|
modifiedDependencies: false,
|
|
@@ -226,6 +239,7 @@ export const install = async (options, add) => {
|
|
|
226
239
|
...options,
|
|
227
240
|
add,
|
|
228
241
|
graph,
|
|
242
|
+
modifiers,
|
|
229
243
|
remove,
|
|
230
244
|
})
|
|
231
245
|
: undefined;
|
|
@@ -247,7 +261,7 @@ export const install = async (options, add) => {
|
|
|
247
261
|
remove,
|
|
248
262
|
remover,
|
|
249
263
|
});
|
|
250
|
-
|
|
264
|
+
result = { buildQueue, graph, diff };
|
|
251
265
|
}
|
|
252
266
|
catch (err) {
|
|
253
267
|
/* c8 ignore next */
|
|
@@ -262,4 +276,14 @@ export const install = async (options, add) => {
|
|
|
262
276
|
catch { }
|
|
263
277
|
throw err;
|
|
264
278
|
}
|
|
279
|
+
// after the try: a link failure must not roll back the install
|
|
280
|
+
if (!allowSkills)
|
|
281
|
+
return result;
|
|
282
|
+
const { linkSkills } = await import("./skills/index.js");
|
|
283
|
+
result.skills = await linkSkills({
|
|
284
|
+
...options,
|
|
285
|
+
graph: result.graph,
|
|
286
|
+
target: allowSkills,
|
|
287
|
+
});
|
|
288
|
+
return result;
|
|
265
289
|
};
|
|
@@ -3,7 +3,7 @@ import { error } from '@vltpkg/error-cause';
|
|
|
3
3
|
import { fastSplit } from '@vltpkg/fast-split';
|
|
4
4
|
import { Spec } from '@vltpkg/spec/browser';
|
|
5
5
|
import { longDependencyTypes } from '@vltpkg/types';
|
|
6
|
-
import { isDependencyTypeShort } from "../dependencies.js";
|
|
6
|
+
import { asDependencySpecError, isDependencyTypeShort, } from "../dependencies.js";
|
|
7
7
|
const retrieveNodeFromGraph = (key, value, graph, fromId, seenNodes) => {
|
|
8
8
|
const foundNode = graph.nodes.get(asDepID(fromId));
|
|
9
9
|
if (!foundNode) {
|
|
@@ -104,7 +104,15 @@ export const loadEdges = (graph, edges, options, cache) => {
|
|
|
104
104
|
// loaded), so overriding here would always inject `undefined`
|
|
105
105
|
// and silently revert to the default npm registry.
|
|
106
106
|
// See vltpkg/vltpkg#1580.
|
|
107
|
-
|
|
107
|
+
let spec;
|
|
108
|
+
try {
|
|
109
|
+
spec = parseEdgeSpec(specName, valRest.substring(0, vrSplit), options, cache);
|
|
110
|
+
}
|
|
111
|
+
catch (er) {
|
|
112
|
+
// spec text is the lockfile's; hidden lockfile errors are
|
|
113
|
+
// swallowed, so only vlt-lock.json surfaces here
|
|
114
|
+
throw asDependencySpecError(er, fromNode, depType, 'vlt-lock.json');
|
|
115
|
+
}
|
|
108
116
|
if (useOptimizations) {
|
|
109
117
|
edgeProcessingQueue.push({
|
|
110
118
|
fromNode,
|
package/dist/modifiers.d.ts
CHANGED
|
@@ -101,6 +101,11 @@ export type ModifierActiveEntry = {
|
|
|
101
101
|
* for (const entry of entries)
|
|
102
102
|
* modifier.updateActiveEntry(fromNode, entry)
|
|
103
103
|
* ```
|
|
104
|
+
*
|
|
105
|
+
* A trailing `:workspace` right after an importer selector, e.g.
|
|
106
|
+
* `:project > :workspace`, is expanded into one `#<name>` entry per
|
|
107
|
+
* named workspace of the graph, on its first `tryImporter` or
|
|
108
|
+
* `importerEdgeModifier` call.
|
|
104
109
|
*/
|
|
105
110
|
export declare class GraphModifier {
|
|
106
111
|
#private;
|
|
@@ -126,6 +131,12 @@ export declare class GraphModifier {
|
|
|
126
131
|
* root's own b edge is still the manifest's to validate and heal.
|
|
127
132
|
*/
|
|
128
133
|
targetsImporterEdge(importer: Node, spec: Spec): boolean;
|
|
134
|
+
/**
|
|
135
|
+
* The modifier governing the direct `importer -> spec.name` edge, see
|
|
136
|
+
* {@link GraphModifier.targetsImporterEdge}. The most specific one
|
|
137
|
+
* wins, same as during the graph build.
|
|
138
|
+
*/
|
|
139
|
+
importerEdgeModifier(importer: Node, spec: Spec): ModifierEntry | undefined;
|
|
129
140
|
/**
|
|
130
141
|
* Selector of the modifier that wins the direct `importer -> spec.name`
|
|
131
142
|
* edge, if it removes it (value `-`). Same pick as tryNewDependency:
|
package/dist/modifiers.js
CHANGED
|
@@ -7,9 +7,20 @@ import { load } from '@vltpkg/vlt-json';
|
|
|
7
7
|
* Does this breadcrumb item select the given importer?
|
|
8
8
|
*/
|
|
9
9
|
const matchesImporter = (item, importer) => item.importer &&
|
|
10
|
+
importer.importer &&
|
|
10
11
|
(item.value === ':project' ||
|
|
11
|
-
|
|
12
|
-
(item.value === ':
|
|
12
|
+
item.value === ':workspace' ||
|
|
13
|
+
(item.value === ':root' && importer.mainImporter));
|
|
14
|
+
/**
|
|
15
|
+
* Is this a trailing `:workspace` item right after a plain importer
|
|
16
|
+
* selector, e.g. `:project > :workspace`? It selects any direct
|
|
17
|
+
* dependency named after a workspace.
|
|
18
|
+
*/
|
|
19
|
+
const isWorkspaceTarget = (item) => item.importer &&
|
|
20
|
+
item.value === ':workspace' &&
|
|
21
|
+
!!item.prev?.importer &&
|
|
22
|
+
!item.prev.name &&
|
|
23
|
+
!item.prev.prev;
|
|
13
24
|
/**
|
|
14
25
|
* Does this item's qualifier (e.g. `:semver(^1)`, `:v(^1)`) accept the
|
|
15
26
|
* given spec? Shared by traversal matching and by the frozen / healing
|
|
@@ -42,6 +53,11 @@ const matchesQualifier = (item, spec) => !!item?.comparator({ semver: spec.semve
|
|
|
42
53
|
* for (const entry of entries)
|
|
43
54
|
* modifier.updateActiveEntry(fromNode, entry)
|
|
44
55
|
* ```
|
|
56
|
+
*
|
|
57
|
+
* A trailing `:workspace` right after an importer selector, e.g.
|
|
58
|
+
* `:project > :workspace`, is expanded into one `#<name>` entry per
|
|
59
|
+
* named workspace of the graph, on its first `tryImporter` or
|
|
60
|
+
* `importerEdgeModifier` call.
|
|
45
61
|
*/
|
|
46
62
|
export class GraphModifier {
|
|
47
63
|
/** The loaded modifiers configuration */
|
|
@@ -73,6 +89,14 @@ export class GraphModifier {
|
|
|
73
89
|
activeModifiers = new Set();
|
|
74
90
|
/** A set of all modifier string values loaded from vlt.json */
|
|
75
91
|
modifierNames = new Set();
|
|
92
|
+
/** Spec options used when expanding `:workspace` targets */
|
|
93
|
+
#options = {};
|
|
94
|
+
/** `<importer> > :workspace` modifiers, expanded once a graph is known */
|
|
95
|
+
#workspaceTargets = [];
|
|
96
|
+
/** Graphs whose workspaces were already expanded */
|
|
97
|
+
#expandedGraphs = new WeakSet();
|
|
98
|
+
/** Workspace names already expanded */
|
|
99
|
+
#expandedNames = new Set();
|
|
76
100
|
constructor(options) {
|
|
77
101
|
this.load(options);
|
|
78
102
|
}
|
|
@@ -96,8 +120,18 @@ export class GraphModifier {
|
|
|
96
120
|
* root's own b edge is still the manifest's to validate and heal.
|
|
97
121
|
*/
|
|
98
122
|
targetsImporterEdge(importer, spec) {
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
return !!this.importerEdgeModifier(importer, spec);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The modifier governing the direct `importer -> spec.name` edge, see
|
|
127
|
+
* {@link GraphModifier.targetsImporterEdge}. The most specific one
|
|
128
|
+
* wins, same as during the graph build.
|
|
129
|
+
*/
|
|
130
|
+
importerEdgeModifier(importer, spec) {
|
|
131
|
+
this.#expandWorkspaceTargets(importer);
|
|
132
|
+
const found = [];
|
|
133
|
+
for (const mod of this.#modifiers) {
|
|
134
|
+
const { last } = mod.breadcrumb;
|
|
101
135
|
if (last.name !== spec.name)
|
|
102
136
|
continue;
|
|
103
137
|
// a qualifier that rejects the spec means the modifier never
|
|
@@ -106,14 +140,12 @@ export class GraphModifier {
|
|
|
106
140
|
continue;
|
|
107
141
|
const { prev } = last;
|
|
108
142
|
// a lone `#b` matches an edge to b anywhere, importers included
|
|
109
|
-
if (!prev)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
continue;
|
|
113
|
-
if (matchesImporter(prev, importer))
|
|
114
|
-
return true;
|
|
143
|
+
if (!prev || (!prev.prev && matchesImporter(prev, importer))) {
|
|
144
|
+
found.push(mod);
|
|
145
|
+
}
|
|
115
146
|
}
|
|
116
|
-
|
|
147
|
+
const [best] = specificitySort(found.map(m => m.breadcrumb));
|
|
148
|
+
return found.find(m => m.breadcrumb === best);
|
|
117
149
|
}
|
|
118
150
|
/**
|
|
119
151
|
* Selector of the modifier that wins the direct `importer -> spec.name`
|
|
@@ -143,16 +175,22 @@ export class GraphModifier {
|
|
|
143
175
|
* Loads the modifiers defined in `vlt.json` into memory.
|
|
144
176
|
*/
|
|
145
177
|
load(options) {
|
|
178
|
+
this.#options = options;
|
|
146
179
|
for (const [key, value] of Object.entries(this.config)) {
|
|
147
180
|
this.modifierNames.add(key);
|
|
148
181
|
const breadcrumb = parseBreadcrumb(key);
|
|
149
|
-
/* c8 ignore start - should not be possible */
|
|
150
182
|
if (!breadcrumb.last.name) {
|
|
151
|
-
|
|
183
|
+
// `<importer> > :workspace` names no package up front, it is
|
|
184
|
+
// expanded into one entry per workspace once a graph is known
|
|
185
|
+
if (isWorkspaceTarget(breadcrumb.last)) {
|
|
186
|
+
this.#workspaceTargets.push({ query: key, value });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
throw error('Invalid modifier selector', {
|
|
152
190
|
found: key,
|
|
191
|
+
wanted: 'a selector ending in #<name>, or :root, :project or :workspace followed by > :workspace',
|
|
153
192
|
});
|
|
154
193
|
}
|
|
155
|
-
/* c8 ignore stop */
|
|
156
194
|
let mod;
|
|
157
195
|
if (typeof value === 'string') {
|
|
158
196
|
mod = {
|
|
@@ -179,13 +217,58 @@ export class GraphModifier {
|
|
|
179
217
|
this.#nodeModifiers.add(mod);
|
|
180
218
|
}
|
|
181
219
|
/* c8 ignore end */
|
|
182
|
-
this.#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
this.#register(mod);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Adds a modifier entry to the set of known modifiers.
|
|
225
|
+
*/
|
|
226
|
+
#register(mod) {
|
|
227
|
+
this.#modifiers.add(mod);
|
|
228
|
+
// if the breadcrumb starts with an id, then add it to the
|
|
229
|
+
// map of initial entries, so that we can use it to match
|
|
230
|
+
const { name } = mod.breadcrumb.first;
|
|
231
|
+
if (name) {
|
|
232
|
+
const initialSet = this.#initialEntries.get(name) ?? new Set();
|
|
233
|
+
initialSet.add(mod);
|
|
234
|
+
this.#initialEntries.set(name, initialSet);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Expands `<importer> > :workspace` modifiers into one
|
|
239
|
+
* `<importer> > #<name>` entry per named workspace of the node's graph.
|
|
240
|
+
*/
|
|
241
|
+
#expandWorkspaceTargets(node) {
|
|
242
|
+
if (!this.#workspaceTargets.length)
|
|
243
|
+
return;
|
|
244
|
+
const { graph } = node;
|
|
245
|
+
if (this.#expandedGraphs.has(graph))
|
|
246
|
+
return;
|
|
247
|
+
this.#expandedGraphs.add(graph);
|
|
248
|
+
for (const importer of graph.importers) {
|
|
249
|
+
// nameless workspaces are named after their folder, never link those
|
|
250
|
+
const name = importer.manifest?.name;
|
|
251
|
+
if (importer.mainImporter ||
|
|
252
|
+
!name ||
|
|
253
|
+
this.#expandedNames.has(name)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
this.#expandedNames.add(name);
|
|
257
|
+
for (const { query, value } of this.#workspaceTargets) {
|
|
258
|
+
// fresh breadcrumb per name: items are a linked list and the
|
|
259
|
+
// active entry maps key on the last item's name
|
|
260
|
+
const breadcrumb = parseBreadcrumb(query);
|
|
261
|
+
breadcrumb.last.name = name;
|
|
262
|
+
const mod = {
|
|
263
|
+
breadcrumb,
|
|
264
|
+
query,
|
|
265
|
+
refs: new Set(),
|
|
266
|
+
spec: Spec.parse(name, value, this.#options),
|
|
267
|
+
type: 'edge',
|
|
268
|
+
value,
|
|
269
|
+
};
|
|
270
|
+
this.#edgeModifiers.add(mod);
|
|
271
|
+
this.#register(mod);
|
|
189
272
|
}
|
|
190
273
|
}
|
|
191
274
|
}
|
|
@@ -195,6 +278,7 @@ export class GraphModifier {
|
|
|
195
278
|
* update the active entry to the current importer node.
|
|
196
279
|
*/
|
|
197
280
|
tryImporter(importer) {
|
|
281
|
+
this.#expandWorkspaceTargets(importer);
|
|
198
282
|
for (const modifier of this.#modifiers) {
|
|
199
283
|
// if the first item in the breadcrumb is an importer and it matches
|
|
200
284
|
// any of the valid top-level selectors, then register the modifier
|
package/dist/reify/add-edge.d.ts
CHANGED
|
@@ -7,3 +7,7 @@ import type { Edge } from '../edge.ts';
|
|
|
7
7
|
* until scripts are run, in the case of non-store deps like workspaces
|
|
8
8
|
*/
|
|
9
9
|
export declare const addEdge: (edge: Edge, scurry: PathScurry, remover: RollbackRemove, bins?: Record<string, string>) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* link the `key` bin of the package at `path` into `binRoot`
|
|
12
|
+
*/
|
|
13
|
+
export declare const linkBin: (path: string, binRoot: string, key: string, val: string, remover: RollbackRemove) => Promise<void>;
|
package/dist/reify/add-edge.js
CHANGED
|
@@ -59,15 +59,21 @@ export const addEdge = async (edge, scurry, remover, bins) => {
|
|
|
59
59
|
promises.push(p);
|
|
60
60
|
if (bins) {
|
|
61
61
|
for (const [key, val] of Object.entries(bins)) {
|
|
62
|
-
|
|
63
|
-
const absTarget = resolve(path, val);
|
|
64
|
-
const target = relative(binRoot, absTarget);
|
|
65
|
-
// TODO: bash/cmd/ps1 shims on Windows
|
|
66
|
-
promises.push(process.platform === 'win32' ?
|
|
67
|
-
cmdShimIfExists(absTarget, link, remover)
|
|
68
|
-
: clobberSymlink(target, link, remover));
|
|
62
|
+
promises.push(linkBin(path, binRoot, key, val, remover));
|
|
69
63
|
}
|
|
70
64
|
}
|
|
71
65
|
if (promises.length)
|
|
72
66
|
await Promise.all(promises);
|
|
73
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* link the `key` bin of the package at `path` into `binRoot`
|
|
70
|
+
*/
|
|
71
|
+
export const linkBin = (path, binRoot, key, val, remover) => {
|
|
72
|
+
const link = resolve(binRoot, key);
|
|
73
|
+
const absTarget = resolve(path, val);
|
|
74
|
+
const target = relative(binRoot, absTarget);
|
|
75
|
+
// TODO: bash/cmd/ps1 shims on Windows
|
|
76
|
+
return process.platform === 'win32' ?
|
|
77
|
+
cmdShimIfExists(absTarget, link, remover)
|
|
78
|
+
: clobberSymlink(target, link, remover);
|
|
79
|
+
};
|
package/dist/reify/build.d.ts
CHANGED
|
@@ -9,5 +9,12 @@ import type { Node } from '../node.ts';
|
|
|
9
9
|
export type BuildResult = {
|
|
10
10
|
success: Node[];
|
|
11
11
|
failure: Node[];
|
|
12
|
+
/** needed builds skipped as they have no security data */
|
|
13
|
+
skipped?: Node[];
|
|
14
|
+
/**
|
|
15
|
+
* why `skipped` builds have no security data: the request timed
|
|
16
|
+
* out, or socket.dev has not scanned them yet
|
|
17
|
+
*/
|
|
18
|
+
skippedReason?: 'timeout' | 'unscanned';
|
|
12
19
|
};
|
|
13
20
|
export declare const build: (diff: Diff, packageJson: PackageJson, scurry: PathScurry, allowScriptsNodes: Set<DepID>) => Promise<BuildResult>;
|
|
@@ -32,3 +32,8 @@ export type CheckNeededBuildOptions = {
|
|
|
32
32
|
* @returns {BuildData} The BuildData object containing Node objects that need building
|
|
33
33
|
*/
|
|
34
34
|
export declare const checkNeededBuild: (options: CheckNeededBuildOptions) => BuildData;
|
|
35
|
+
/**
|
|
36
|
+
* Copy build state from the actual graph onto ideal nodes that are
|
|
37
|
+
* not re-extracted, since `vlt-lock.json` does not store it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const carryBuildState: (diff: Diff) => void;
|
|
@@ -70,3 +70,16 @@ export const checkNeededBuild = (options) => {
|
|
|
70
70
|
};
|
|
71
71
|
return buildData;
|
|
72
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Copy build state from the actual graph onto ideal nodes that are
|
|
75
|
+
* not re-extracted, since `vlt-lock.json` does not store it.
|
|
76
|
+
*/
|
|
77
|
+
export const carryBuildState = (diff) => {
|
|
78
|
+
for (const [id, node] of diff.to.nodes) {
|
|
79
|
+
if (diff.nodes.add.has(node))
|
|
80
|
+
continue;
|
|
81
|
+
const prev = diff.from.nodes.get(id);
|
|
82
|
+
if (prev)
|
|
83
|
+
node.buildState = prev.buildState;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
import { readCmdShimIfExists } from '@vltpkg/cmd-shim';
|
|
1
2
|
import { resolve } from 'node:path';
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
remover.rm(bin + '.ps1'),
|
|
10
|
-
];
|
|
11
|
-
};
|
|
12
|
-
const rmBin = process.platform === 'win32' ? rmBinWin32 : rmBinPosix;
|
|
3
|
+
const binPathsPosix = (bin) => [bin];
|
|
4
|
+
const binPathsWin32 = (bin) => [
|
|
5
|
+
bin,
|
|
6
|
+
bin + '.cmd',
|
|
7
|
+
bin + '.ps1',
|
|
8
|
+
];
|
|
9
|
+
const binPaths = process.platform === 'win32' ? binPathsWin32 : binPathsPosix;
|
|
13
10
|
export const deleteEdge = async (edge, scurry, remover) => {
|
|
14
11
|
const { spec: { name }, to, } = edge;
|
|
15
12
|
const { sep } = scurry.cwd;
|
|
@@ -20,9 +17,14 @@ export const deleteEdge = async (edge, scurry, remover) => {
|
|
|
20
17
|
promises.push(remover.rm(path));
|
|
21
18
|
const bins = to?.bins;
|
|
22
19
|
if (bins) {
|
|
23
|
-
for (const key of Object.
|
|
24
|
-
const bin
|
|
25
|
-
|
|
20
|
+
for (const [key, value] of Object.entries(bins)) {
|
|
21
|
+
for (const bin of binPaths(resolve(binRoot, key))) {
|
|
22
|
+
const target = await readCmdShimIfExists(bin);
|
|
23
|
+
// another dependency may provide the same command.
|
|
24
|
+
if (target && target !== resolve(path, value))
|
|
25
|
+
continue;
|
|
26
|
+
promises.push(remover.rm(bin));
|
|
27
|
+
}
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
await Promise.all(promises);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { DepID } from '@vltpkg/dep-id';
|
|
1
2
|
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
2
3
|
import type { PathScurry } from 'path-scurry';
|
|
3
4
|
import type { Diff } from '../diff.ts';
|
|
4
|
-
export declare const deleteNodes: (diff: Diff, remover: RollbackRemove, scurry: PathScurry) => Promise<unknown>[];
|
|
5
|
+
export declare const deleteNodes: (diff: Diff, remover: RollbackRemove, scurry: PathScurry, keep?: ReadonlySet<DepID>) => Promise<unknown>[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deleteEdge } from "./delete-edge.js";
|
|
2
|
-
export const deleteNodes = (diff, remover, scurry) => {
|
|
2
|
+
export const deleteNodes = (diff, remover, scurry, keep) => {
|
|
3
3
|
const store = scurry.resolve('node_modules/.vlt');
|
|
4
4
|
const { sep } = scurry.cwd;
|
|
5
5
|
const rmActions = [];
|
|
@@ -7,7 +7,9 @@ export const deleteNodes = (diff, remover, scurry) => {
|
|
|
7
7
|
// do not delete workspaces or link targets
|
|
8
8
|
if (!node.inVltStore())
|
|
9
9
|
continue;
|
|
10
|
-
|
|
10
|
+
if (!keep?.has(node.id)) {
|
|
11
|
+
rmActions.push(remover.rm(store + sep + node.id));
|
|
12
|
+
}
|
|
11
13
|
for (const edge of node.edgesIn) {
|
|
12
14
|
rmActions.push(deleteEdge(edge, scurry, remover));
|
|
13
15
|
}
|
package/dist/reify/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type ReifyOptions = LoadOptions & {
|
|
|
18
18
|
update?: boolean;
|
|
19
19
|
saveExact?: boolean;
|
|
20
20
|
savePrefix?: string;
|
|
21
|
+
/** store dirs of these nodes are never removed, eg the running vlt */
|
|
22
|
+
keepNodes?: ReadonlySet<DepID>;
|
|
21
23
|
};
|
|
22
24
|
export type ReifyResult = {
|
|
23
25
|
/**
|
package/dist/reify/index.js
CHANGED
|
@@ -8,51 +8,17 @@ import { addEdges } from "./add-edges.js";
|
|
|
8
8
|
import { addNodes } from "./add-nodes.js";
|
|
9
9
|
import { build } from "./build.js";
|
|
10
10
|
import { deleteEdges } from "./delete-edges.js";
|
|
11
|
-
import {
|
|
11
|
+
import { relinkBins } from "./relink-bins.js";
|
|
12
|
+
import { carryBuildState, checkNeededBuild, } from "./check-needed-build.js";
|
|
12
13
|
import { deleteNodes } from "./delete-nodes.js";
|
|
13
14
|
import { internalHoist } from "./internal-hoist.js";
|
|
14
15
|
import { pruneUnsupportedOptional } from "./optional-fail.js";
|
|
15
16
|
import { rollback } from "./rollback.js";
|
|
16
17
|
import { updatePackageJson } from "./update-importers-package-json.js";
|
|
17
18
|
import { copyFileSync } from 'node:fs';
|
|
18
|
-
import { Query } from '@vltpkg/query';
|
|
19
|
-
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
20
19
|
import { binChmodAll } from "./bin-chmod.js";
|
|
20
|
+
import { filterNodesByQuery } from "../filter-nodes-by-query.js";
|
|
21
21
|
const limit = Math.max(availableParallelism() - 1, 1) * 8;
|
|
22
|
-
/**
|
|
23
|
-
* Filter nodes using a DSS query string
|
|
24
|
-
*/
|
|
25
|
-
const filterNodesByQuery = async (graph, allowScriptsQuery) => {
|
|
26
|
-
// shortcut no packages included
|
|
27
|
-
if (allowScriptsQuery === ':not(*)' /* c8 ignore next */ ||
|
|
28
|
-
!allowScriptsQuery) {
|
|
29
|
-
return new Set();
|
|
30
|
-
}
|
|
31
|
-
// shortcut all packages included
|
|
32
|
-
if (allowScriptsQuery === '*') {
|
|
33
|
-
return new Set(graph.nodes.keys());
|
|
34
|
-
}
|
|
35
|
-
/* c8 ignore start */
|
|
36
|
-
const securityArchive = Query.hasSecuritySelectors(allowScriptsQuery) ?
|
|
37
|
-
await SecurityArchive.start({
|
|
38
|
-
nodes: [...graph.nodes.values()],
|
|
39
|
-
})
|
|
40
|
-
: undefined;
|
|
41
|
-
/* c8 ignore stop */
|
|
42
|
-
const edges = graph.edges;
|
|
43
|
-
const nodes = new Set(graph.nodes.values());
|
|
44
|
-
const importers = graph.importers;
|
|
45
|
-
const query = new Query({
|
|
46
|
-
edges,
|
|
47
|
-
nodes,
|
|
48
|
-
importers,
|
|
49
|
-
securityArchive,
|
|
50
|
-
});
|
|
51
|
-
const { nodes: resultNodes } = await query.search(allowScriptsQuery, {
|
|
52
|
-
signal: new AbortController().signal,
|
|
53
|
-
});
|
|
54
|
-
return new Set(resultNodes.map(node => node.id));
|
|
55
|
-
};
|
|
56
22
|
/**
|
|
57
23
|
* Make the current project match the supplied graph.
|
|
58
24
|
*/
|
|
@@ -65,6 +31,7 @@ export const reify = async (options) => {
|
|
|
65
31
|
loadManifests: true,
|
|
66
32
|
});
|
|
67
33
|
const diff = new Diff(actual, graph);
|
|
34
|
+
carryBuildState(diff);
|
|
68
35
|
// normalise importer edge specs and compute the package.json changes
|
|
69
36
|
// before deciding whether anything needs reifying: an add whose target
|
|
70
37
|
// is already in place has no node diff but still has a saved value to
|
|
@@ -81,6 +48,7 @@ export const reify = async (options) => {
|
|
|
81
48
|
packageJson: options.packageJson,
|
|
82
49
|
saveExact: options.saveExact,
|
|
83
50
|
savePrefix: options.savePrefix,
|
|
51
|
+
modifiers: options.modifiers,
|
|
84
52
|
})
|
|
85
53
|
: undefined;
|
|
86
54
|
const noModifiedDependencies = !options.add?.modifiedDependencies &&
|
|
@@ -161,6 +129,10 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
161
129
|
const edgeActions = addEdges(diff, scurry, remover);
|
|
162
130
|
if (edgeActions.length)
|
|
163
131
|
await callLimit(edgeActions, { limit });
|
|
132
|
+
// link bins that deleted edges removed but other deps still declare
|
|
133
|
+
const binActions = relinkBins(diff, scurry, remover);
|
|
134
|
+
if (binActions.length)
|
|
135
|
+
await callLimit(binActions, { limit });
|
|
164
136
|
await internalHoist(diff.to, options, remover);
|
|
165
137
|
// looks up all nodes setting buildState = 'needed'
|
|
166
138
|
// on nodes that require building
|
|
@@ -173,7 +145,7 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
173
145
|
await build(diff, packageJson, scurry, allowScriptsNodes);
|
|
174
146
|
// set the buildQueue on the result object containing
|
|
175
147
|
// an array with all the ids of nodes that need building
|
|
176
|
-
res.buildQueue = [...diff.nodes.
|
|
148
|
+
res.buildQueue = [...diff.to.nodes.values()]
|
|
177
149
|
.filter(node => node.buildState === 'needed')
|
|
178
150
|
.map(node => node.id);
|
|
179
151
|
// if we had to change the actual graph along the way,
|
|
@@ -189,7 +161,7 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
189
161
|
/* c8 ignore stop */
|
|
190
162
|
saveHidden(options);
|
|
191
163
|
// delete garbage from the store.
|
|
192
|
-
const rmActions = deleteNodes(diff, remover, scurry);
|
|
164
|
+
const rmActions = deleteNodes(diff, remover, scurry, options.keepNodes);
|
|
193
165
|
if (rmActions.length)
|
|
194
166
|
await Promise.all(rmActions);
|
|
195
167
|
// updates package.json files if anything was added / removed
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
2
|
+
import type { PathScurry } from 'path-scurry';
|
|
3
|
+
import type { Diff } from '../diff.ts';
|
|
4
|
+
/**
|
|
5
|
+
* A deleted edge removes the bins it owns, but another dependency
|
|
6
|
+
* that did not change may declare the same bin name. Link the first
|
|
7
|
+
* one found if the bin is still missing once added edges are linked.
|
|
8
|
+
*/
|
|
9
|
+
export declare const relinkBins: (diff: Diff, scurry: PathScurry, remover: RollbackRemove) => (() => Promise<unknown>)[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { linkBin } from "./add-edge.js";
|
|
3
|
+
/**
|
|
4
|
+
* A deleted edge removes the bins it owns, but another dependency
|
|
5
|
+
* that did not change may declare the same bin name. Link the first
|
|
6
|
+
* one found if the bin is still missing once added edges are linked.
|
|
7
|
+
*/
|
|
8
|
+
export const relinkBins = (diff, scurry, remover) => {
|
|
9
|
+
const actions = [];
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const { sep } = scurry.cwd;
|
|
12
|
+
for (const deleted of diff.edges.delete) {
|
|
13
|
+
const node = diff.to.nodes.get(deleted.from.id);
|
|
14
|
+
const bins = deleted.to?.bins;
|
|
15
|
+
if (!node || !bins)
|
|
16
|
+
continue;
|
|
17
|
+
const nm = node.nodeModules(scurry);
|
|
18
|
+
const binRoot = nm + sep + '.bin';
|
|
19
|
+
for (const key of Object.keys(bins)) {
|
|
20
|
+
const link = resolve(binRoot, key);
|
|
21
|
+
if (seen.has(link))
|
|
22
|
+
continue;
|
|
23
|
+
seen.add(link);
|
|
24
|
+
for (const edge of node.edgesOut.values()) {
|
|
25
|
+
const val = edge.to?.bins?.[key];
|
|
26
|
+
if (!val)
|
|
27
|
+
continue;
|
|
28
|
+
const path = nm + sep + edge.spec.name.replace('/', sep);
|
|
29
|
+
actions.push(async () => {
|
|
30
|
+
if (await scurry.lstat(link))
|
|
31
|
+
return;
|
|
32
|
+
await linkBin(path, binRoot, key, val, remover);
|
|
33
|
+
});
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return actions;
|
|
39
|
+
};
|