@vltpkg/graph 1.5.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 +25 -22
- package/dist/graph.js +3 -0
- package/dist/ideal/append-nodes.js +18 -5
- package/dist/ideal/remove-satisfied-specs.js +18 -10
- package/dist/install.d.ts +2 -0
- package/dist/install.js +1 -0
- 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/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 +7 -1
- 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 +11 -6
- package/dist/skills/discover.d.ts +8 -8
- package/dist/skills/discover.js +17 -13
- package/dist/skills/gitignore.d.ts +5 -0
- package/dist/skills/gitignore.js +31 -2
- package/dist/skills/index.d.ts +7 -6
- package/dist/skills/index.js +41 -12
- package/dist/skills/mounts.d.ts +20 -6
- package/dist/skills/mounts.js +51 -25
- package/dist/uninstall.d.ts +3 -0
- package/dist/uninstall.js +1 -0
- package/package.json +21 -21
package/README.md
CHANGED
|
@@ -93,13 +93,14 @@ code is not loaded. `update` and `uninstall` take `allowSkills` too.
|
|
|
93
93
|
|
|
94
94
|
Lists the agent skills (dirs with a `SKILL.md`, see
|
|
95
95
|
https://agentskills.io/specification) shipped by installed packages in
|
|
96
|
-
the vlt store (a root `SKILL.md`, then
|
|
97
|
-
`dist/skills/` and `.agents/skills/`;
|
|
98
|
-
from one version per package (a
|
|
99
|
-
|
|
100
|
-
name, and the `graph`.
|
|
101
|
-
packages (default `*`); a
|
|
102
|
-
keeps only those skills of
|
|
96
|
+
the vlt store and the project's workspaces (a root `SKILL.md`, then
|
|
97
|
+
`<dir>/SKILL.md` in `skills/`, `dist/skills/` and `.agents/skills/`;
|
|
98
|
+
the first skill of a name wins), from one version per package (a
|
|
99
|
+
direct dep or workspace, else the highest), with the agent skill dirs
|
|
100
|
+
each is linked in, `project`: the main importer name, and the `graph`.
|
|
101
|
+
`options.target` is a DSS query selecting packages (default `*`); a
|
|
102
|
+
selector ending with `:skill(<name>, …)` keeps only those skills of
|
|
103
|
+
its packages. Creates nothing.
|
|
103
104
|
|
|
104
105
|
### `linkSkills(options): Promise<LinkSkillsResult>`
|
|
105
106
|
|
|
@@ -112,21 +113,23 @@ must resolve inside the project, not in `node_modules` (else
|
|
|
112
113
|
`skipped`, as are fs errors) and are created only when a skill is
|
|
113
114
|
picked. One package per name, among picked skills, in every dir: the
|
|
114
115
|
one the first existing link points to while its skill is installed,
|
|
115
|
-
else a direct dep, then DepID; the others are reported in
|
|
116
|
-
as `<mount> (<package>)`. Installs keep a dir's link to
|
|
117
|
-
installed skill. A link follows its package to the picked
|
|
118
|
-
Then removes links whose skill is no longer installed:
|
|
119
|
-
package not in the graph, or no longer shipped. With
|
|
120
|
-
also removes every other vlt link in the scanned
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
with
|
|
116
|
+
else a direct dep or workspace, then DepID; the others are reported in
|
|
117
|
+
`conflicts` as `<mount> (<package>)`. Installs keep a dir's link to
|
|
118
|
+
another installed skill. A link follows its package to the picked
|
|
119
|
+
version. Then removes links whose skill is no longer installed:
|
|
120
|
+
dangling, package not in the graph, or no longer shipped. With
|
|
121
|
+
`options.apply`, also removes every other vlt link in the scanned
|
|
122
|
+
dirs. Only vlt links (into a `node_modules/.vlt` store, or listed in
|
|
123
|
+
the dir's `.gitignore` block and pointing to a workspace or dangling)
|
|
124
|
+
are ever replaced or removed; anything else in the way is reported in
|
|
125
|
+
`conflicts`. Keeps a marked block of link names in each dir's
|
|
126
|
+
`.gitignore` (`gitignore` lists the ones it could not edit). Linked
|
|
127
|
+
packages get private copies of files hardlinked from the global store.
|
|
128
|
+
`:skill()` patterns that match no skill of their packages are returned
|
|
129
|
+
in `unmatched`. Security queries fail closed: skills of npm registry
|
|
130
|
+
packages with no security data are left out (in `listSkills` too) and
|
|
131
|
+
returned in `unscanned` with `unscannedReason` (by `linkSkills`:
|
|
132
|
+
unless still linked).
|
|
130
133
|
|
|
131
134
|
### `assertQuery(selector, flag, partSelectors?): Promise<void>`
|
|
132
135
|
|
package/dist/graph.js
CHANGED
|
@@ -419,7 +419,7 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
|
|
|
419
419
|
* vlt store are extracted immediately (in parallel) instead of waiting for
|
|
420
420
|
* the full ideal graph to be built. This significantly improves install time.
|
|
421
421
|
*/
|
|
422
|
-
const processPlacementTasks = async (graph, options, placementTasks, add, modifiers, scurry, packageInfo, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove) => {
|
|
422
|
+
const processPlacementTasks = async (graph, options, placementTasks, add, modifiers, scurry, packageInfo, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit) => {
|
|
423
423
|
const childDepsToProcess = [];
|
|
424
424
|
// Note: placementTasks are already sorted by fetchManifestsForDeps
|
|
425
425
|
// using compareByHasPeerDeps to ensure non-peer deps are processed first.
|
|
@@ -431,18 +431,31 @@ const processPlacementTasks = async (graph, options, placementTasks, add, modifi
|
|
|
431
431
|
// reify step to properly update the package.json file dependencies
|
|
432
432
|
// using the correct names retrieved from the manifest data
|
|
433
433
|
const additiveMap = fromNode.importer ? add : transientAdd?.get(fromNode.id);
|
|
434
|
+
// a nameless explicit add is known by its fixed up name from here on
|
|
435
|
+
const explicitKeys = explicit?.get(fromNode.id);
|
|
436
|
+
const wasExplicit = explicitKeys?.has(addKey(spec));
|
|
434
437
|
spec = fixupAddedNames(additiveMap, manifest, options, spec);
|
|
438
|
+
if (wasExplicit)
|
|
439
|
+
explicitKeys?.add(spec.name);
|
|
435
440
|
// handles missing manifest resolution
|
|
436
441
|
if (!manifest) {
|
|
437
442
|
if (!edgeOptional && fromNode.isOptional()) {
|
|
438
443
|
// failed resolution of a non-optional dep of an optional node
|
|
439
444
|
// have to clean up the dependents
|
|
440
|
-
removeOptionalSubgraph(graph, fromNode)
|
|
445
|
+
for (const n of removeOptionalSubgraph(graph, fromNode)) {
|
|
446
|
+
// pruned importer deps: nothing to save, unless requested
|
|
447
|
+
for (const { from, name } of n.edgesIn) {
|
|
448
|
+
if (from.importer && !explicit?.get(from.id)?.has(name)) {
|
|
449
|
+
add?.delete(name);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
441
453
|
continue;
|
|
442
454
|
}
|
|
443
455
|
else if (edgeOptional) {
|
|
444
|
-
// failed resolution of an optional dep
|
|
445
|
-
//
|
|
456
|
+
// failed resolution of an optional dep: skip it and drop it
|
|
457
|
+
// from the add map, nothing to prune or save
|
|
458
|
+
additiveMap?.delete(addKey(spec));
|
|
446
459
|
continue;
|
|
447
460
|
}
|
|
448
461
|
else {
|
|
@@ -738,7 +751,7 @@ export const appendNodes = async (packageInfo, graph, fromNode, deps, scurry, op
|
|
|
738
751
|
graph.addEdge(type, spec, fromNode, toNode);
|
|
739
752
|
}
|
|
740
753
|
// Place nodes and collect child deps
|
|
741
|
-
const placed = await processPlacementTasks(graph, options, result.placementTasks, add, modifiers, scurry, packageInfo, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove);
|
|
754
|
+
const placed = await processPlacementTasks(graph, options, result.placementTasks, add, modifiers, scurry, packageInfo, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit);
|
|
742
755
|
levelResults.push(placed);
|
|
743
756
|
}
|
|
744
757
|
// ============================================================
|
|
@@ -31,18 +31,26 @@ export const removeSatisfiedSpecs = ({ add, graph, modifiers, }) => {
|
|
|
31
31
|
const depIsCatalog = dependency.spec.type === 'catalog';
|
|
32
32
|
if (edgeIsCatalog !== depIsCatalog)
|
|
33
33
|
continue;
|
|
34
|
+
// a governed edge is checked against the modifier value (e.g.
|
|
35
|
+
// `workspace:*` to a versionless workspace), not its own spec: one
|
|
36
|
+
// built before the modifier applied must be rebuilt
|
|
37
|
+
const mod = modifiers?.importerEdgeModifier(importer, dependency.spec);
|
|
34
38
|
// If the current graph edge is already valid, then we remove that
|
|
35
39
|
// dependency item from the list of items to be added to the graph
|
|
36
|
-
if (satisfies(edge.to?.id, dependency.spec, edge.from.location, graph.projectRoot, graph.monorepo)) {
|
|
37
|
-
// a
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
if (satisfies(edge.to?.id, mod?.type === 'edge' ? mod.spec : dependency.spec, edge.from.location, graph.projectRoot, graph.monorepo)) {
|
|
41
|
+
// `implicit` is not a real edge type, it means "keep what the
|
|
42
|
+
// edge already is", so it never makes an edge stale
|
|
43
|
+
const typeChanged = dependency.type !== 'implicit' &&
|
|
44
|
+
edge.type !== dependency.type;
|
|
45
|
+
if (mod) {
|
|
46
|
+
// keeps the modifier value, the rebuild would re-apply it
|
|
47
|
+
// anyway; only the type follows the manifest
|
|
48
|
+
if (typeChanged) {
|
|
49
|
+
staleSpecs.set(edge, { ...dependency, spec: edge.spec });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (edge.spec.bareSpec !== dependency.spec.bareSpec ||
|
|
53
|
+
typeChanged) {
|
|
46
54
|
staleSpecs.set(edge, dependency);
|
|
47
55
|
}
|
|
48
56
|
dependencies.delete(name);
|
package/dist/install.d.ts
CHANGED
package/dist/install.js
CHANGED
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
|
+
};
|
|
@@ -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,6 +8,7 @@ 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 { relinkBins } from "./relink-bins.js";
|
|
11
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";
|
|
@@ -47,6 +48,7 @@ export const reify = async (options) => {
|
|
|
47
48
|
packageJson: options.packageJson,
|
|
48
49
|
saveExact: options.saveExact,
|
|
49
50
|
savePrefix: options.savePrefix,
|
|
51
|
+
modifiers: options.modifiers,
|
|
50
52
|
})
|
|
51
53
|
: undefined;
|
|
52
54
|
const noModifiedDependencies = !options.add?.modifiedDependencies &&
|
|
@@ -127,6 +129,10 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
127
129
|
const edgeActions = addEdges(diff, scurry, remover);
|
|
128
130
|
if (edgeActions.length)
|
|
129
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 });
|
|
130
136
|
await internalHoist(diff.to, options, remover);
|
|
131
137
|
// looks up all nodes setting buildState = 'needed'
|
|
132
138
|
// on nodes that require building
|
|
@@ -155,7 +161,7 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
|
|
|
155
161
|
/* c8 ignore stop */
|
|
156
162
|
saveHidden(options);
|
|
157
163
|
// delete garbage from the store.
|
|
158
|
-
const rmActions = deleteNodes(diff, remover, scurry);
|
|
164
|
+
const rmActions = deleteNodes(diff, remover, scurry, options.keepNodes);
|
|
159
165
|
if (rmActions.length)
|
|
160
166
|
await Promise.all(rmActions);
|
|
161
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
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PackageJson } from '@vltpkg/package-json';
|
|
2
2
|
import type { AddImportersDependenciesMap, RemoveImportersDependenciesMap } from '../dependencies.ts';
|
|
3
3
|
import type { Graph } from '../graph.ts';
|
|
4
|
+
import type { GraphModifier } from '../modifiers.ts';
|
|
4
5
|
export type UpdatePackageJsonOptions = {
|
|
5
6
|
/**
|
|
6
7
|
* A `Map` in which keys are {@link DepID} linking to another `Map` in which
|
|
@@ -37,9 +38,13 @@ export type UpdatePackageJsonOptions = {
|
|
|
37
38
|
* Defaults to `^` if not specified. Ignored when `saveExact` is true.
|
|
38
39
|
*/
|
|
39
40
|
savePrefix?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Graph modifiers, importer edges they govern keep the modifier value.
|
|
43
|
+
*/
|
|
44
|
+
modifiers?: GraphModifier;
|
|
40
45
|
};
|
|
41
46
|
/**
|
|
42
47
|
* Updates nodes of a provided {@link Graph} accordingly to the
|
|
43
48
|
* provided add or remove arguments.
|
|
44
49
|
*/
|
|
45
|
-
export declare const updatePackageJson: ({ add, graph, packageJson, remove, saveExact, savePrefix, }: UpdatePackageJsonOptions) => () => void;
|
|
50
|
+
export declare const updatePackageJson: ({ add, graph, packageJson, remove, saveExact, savePrefix, modifiers, }: UpdatePackageJsonOptions) => () => void;
|
|
@@ -5,7 +5,7 @@ import { longDependencyTypes } from '@vltpkg/types';
|
|
|
5
5
|
import { longDependencyTypeNames } from "../dependencies.js";
|
|
6
6
|
import { resolveSaveType } from "../resolve-save-type.js";
|
|
7
7
|
import { calculateSaveValue } from "./calculate-save-value.js";
|
|
8
|
-
const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
8
|
+
const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix, modifiers) => {
|
|
9
9
|
const node = graph.nodes.get(nodeId);
|
|
10
10
|
if (!node) {
|
|
11
11
|
throw error('Failed to retrieve node', {
|
|
@@ -56,7 +56,10 @@ const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
|
56
56
|
}
|
|
57
57
|
const edge = node.edgesOut.get(name);
|
|
58
58
|
if (!edge?.to) {
|
|
59
|
-
throw error(
|
|
59
|
+
throw error(`Dependency node could not be found: ${name}`, {
|
|
60
|
+
spec: dep.spec,
|
|
61
|
+
from: node.location,
|
|
62
|
+
});
|
|
60
63
|
}
|
|
61
64
|
const n = edge.to;
|
|
62
65
|
const [nodeType] = splitDepID(n.id);
|
|
@@ -90,8 +93,10 @@ const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
|
90
93
|
// the lockfile has to carry the value package.json carries, or a
|
|
91
94
|
// later install reads back `latest` where the manifest says
|
|
92
95
|
// `^1.2.3`. anything keyed by the old spec from here on must be
|
|
93
|
-
// invalidated at this point.
|
|
94
|
-
|
|
96
|
+
// invalidated at this point. governed edges keep the modifier
|
|
97
|
+
// value instead.
|
|
98
|
+
if (saveValue !== edge.spec.bareSpec &&
|
|
99
|
+
!modifiers?.targetsImporterEdge(node, dep.spec)) {
|
|
95
100
|
edge.spec = Spec.parse(name, saveValue, dep.spec.options);
|
|
96
101
|
graph.lockfileStale = true;
|
|
97
102
|
}
|
|
@@ -103,7 +108,7 @@ const addOrRemoveDeps = (nodeId, graph, addOrRemove, saveExact, savePrefix) => {
|
|
|
103
108
|
* Updates nodes of a provided {@link Graph} accordingly to the
|
|
104
109
|
* provided add or remove arguments.
|
|
105
110
|
*/
|
|
106
|
-
export const updatePackageJson = ({ add, graph, packageJson, remove, saveExact, savePrefix, }) => {
|
|
111
|
+
export const updatePackageJson = ({ add, graph, packageJson, remove, saveExact, savePrefix, modifiers, }) => {
|
|
107
112
|
const manifestsToUpdate = new Set();
|
|
108
113
|
const operations = new Set([add, remove]);
|
|
109
114
|
for (const operation of operations) {
|
|
@@ -112,7 +117,7 @@ export const updatePackageJson = ({ add, graph, packageJson, remove, saveExact,
|
|
|
112
117
|
// that are nested folders from which the user can also add new
|
|
113
118
|
// dependencies to
|
|
114
119
|
for (const nodeId of operation.keys()) {
|
|
115
|
-
const manifest = addOrRemoveDeps(nodeId, graph, operation, saveExact, savePrefix);
|
|
120
|
+
const manifest = addOrRemoveDeps(nodeId, graph, operation, saveExact, savePrefix, modifiers);
|
|
116
121
|
if (manifest) {
|
|
117
122
|
manifestsToUpdate.add(manifest);
|
|
118
123
|
}
|
|
@@ -61,13 +61,13 @@ export declare const shipsSkills: (node: Node) => boolean;
|
|
|
61
61
|
/**
|
|
62
62
|
* Find the agent skills shipped by the given installed packages.
|
|
63
63
|
*
|
|
64
|
-
* Only packages in the vlt store
|
|
65
|
-
* then `<dir>/SKILL.md` in
|
|
66
|
-
* `.agents/skills/`: the first skill of a
|
|
67
|
-
* package is picked, even if it ships none:
|
|
68
|
-
*
|
|
69
|
-
* DepID. Same-name skills of different
|
|
70
|
-
* sorted by rank then DepID (`linkSkills`
|
|
71
|
-
* set each skill's `linked` dirs.
|
|
64
|
+
* Only packages in the vlt store and the project's workspaces (not its
|
|
65
|
+
* root) are scanned, for a root `SKILL.md`, then `<dir>/SKILL.md` in
|
|
66
|
+
* `skills/`, `dist/skills/` and `.agents/skills/`: the first skill of a
|
|
67
|
+
* name wins. One version per package is picked, even if it ships none:
|
|
68
|
+
* a workspace or dep of the main importer, else a dep of any importer,
|
|
69
|
+
* else the highest version, then DepID. Same-name skills of different
|
|
70
|
+
* packages are all returned, sorted by rank then DepID (`linkSkills`
|
|
71
|
+
* resolves them). `mounts` set each skill's `linked` dirs.
|
|
72
72
|
*/
|
|
73
73
|
export declare const discoverSkills: (nodes: Iterable<Node>, scurry: PathScurry, mounts?: SkillMount[]) => DiscoveredSkills;
|
package/dist/skills/discover.js
CHANGED
|
@@ -134,8 +134,11 @@ const scan = (dir, pkgName) => {
|
|
|
134
134
|
}
|
|
135
135
|
return found;
|
|
136
136
|
};
|
|
137
|
-
// 0: dep of the main importer, 1: of any
|
|
137
|
+
// 0: a workspace or dep of the main importer, 1: dep of any
|
|
138
|
+
// importer, 2: transitive
|
|
138
139
|
const rank = (node) => {
|
|
140
|
+
if (node.importer)
|
|
141
|
+
return 0;
|
|
139
142
|
let r = 2;
|
|
140
143
|
for (const { from } of node.edgesIn) {
|
|
141
144
|
if (from.mainImporter)
|
|
@@ -162,10 +165,11 @@ const aliasOf = (node) => {
|
|
|
162
165
|
edges.find(e => e.from.importer))?.name;
|
|
163
166
|
return n === node.name ? undefined : n;
|
|
164
167
|
};
|
|
165
|
-
// store pkgs
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
// store pkgs and workspaces, not the project root; lockfile keeps a
|
|
169
|
+
// tarball's path as its location
|
|
170
|
+
const scannable = (node) => node.importer ?
|
|
171
|
+
!node.mainImporter
|
|
172
|
+
: node.inVltStore() && splitDepID(node.id)[0] !== 'file';
|
|
169
173
|
/** Ships agent skills, as `discoverSkills` finds them. */
|
|
170
174
|
export const shipsSkills = (node) => scannable(node) &&
|
|
171
175
|
scan(realOr(resolve(node.projectRoot, node.location)), node.name)
|
|
@@ -173,14 +177,14 @@ export const shipsSkills = (node) => scannable(node) &&
|
|
|
173
177
|
/**
|
|
174
178
|
* Find the agent skills shipped by the given installed packages.
|
|
175
179
|
*
|
|
176
|
-
* Only packages in the vlt store
|
|
177
|
-
* then `<dir>/SKILL.md` in
|
|
178
|
-
* `.agents/skills/`: the first skill of a
|
|
179
|
-
* package is picked, even if it ships none:
|
|
180
|
-
*
|
|
181
|
-
* DepID. Same-name skills of different
|
|
182
|
-
* sorted by rank then DepID (`linkSkills`
|
|
183
|
-
* set each skill's `linked` dirs.
|
|
180
|
+
* Only packages in the vlt store and the project's workspaces (not its
|
|
181
|
+
* root) are scanned, for a root `SKILL.md`, then `<dir>/SKILL.md` in
|
|
182
|
+
* `skills/`, `dist/skills/` and `.agents/skills/`: the first skill of a
|
|
183
|
+
* name wins. One version per package is picked, even if it ships none:
|
|
184
|
+
* a workspace or dep of the main importer, else a dep of any importer,
|
|
185
|
+
* else the highest version, then DepID. Same-name skills of different
|
|
186
|
+
* packages are all returned, sorted by rank then DepID (`linkSkills`
|
|
187
|
+
* resolves them). `mounts` set each skill's `linked` dirs.
|
|
184
188
|
*/
|
|
185
189
|
export const discoverSkills = (nodes, scurry, mounts = []) => {
|
|
186
190
|
const key = (name, path) => `${lc(name)}\0${norm(path)}`;
|
|
@@ -11,3 +11,8 @@ export declare const editGitignore: (dir: string, edit: {
|
|
|
11
11
|
add: string[];
|
|
12
12
|
remove: string[];
|
|
13
13
|
}) => "ok" | "refused";
|
|
14
|
+
/**
|
|
15
|
+
* Names in vlt's block of `<dir>/.gitignore`: vlt's record of its links
|
|
16
|
+
* in `dir`. Empty if none or unreadable.
|
|
17
|
+
*/
|
|
18
|
+
export declare const gitignoreNames: (dir: string) => Set<string>;
|
package/dist/skills/gitignore.js
CHANGED
|
@@ -5,6 +5,12 @@ export const GITIGNORE_END = '# <<< vlt agent skills';
|
|
|
5
5
|
const sorted = (names) => [...names]
|
|
6
6
|
.sort((a, b) => a.localeCompare(b, 'en'))
|
|
7
7
|
.map(n => `/${n}`);
|
|
8
|
+
// vlt's block: `end` is -1 if none
|
|
9
|
+
const findBlock = (lines) => {
|
|
10
|
+
const begin = lines.findIndex(l => l.trim() === GITIGNORE_BEGIN);
|
|
11
|
+
const end = lines.findIndex((l, i) => begin !== -1 && i > begin && l.trim() === GITIGNORE_END);
|
|
12
|
+
return { begin, end };
|
|
13
|
+
};
|
|
8
14
|
const update = (file, { add, remove }) => {
|
|
9
15
|
const st = lstatSync(file, { throwIfNoEntry: false });
|
|
10
16
|
if (st && !st.isFile())
|
|
@@ -12,8 +18,7 @@ const update = (file, { add, remove }) => {
|
|
|
12
18
|
const text = st ? readFileSync(file, 'utf8') : '';
|
|
13
19
|
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
14
20
|
const lines = text.split(eol);
|
|
15
|
-
const begin
|
|
16
|
-
const end = lines.findIndex((l, i) => begin !== -1 && i > begin && l.trim() === GITIGNORE_END);
|
|
21
|
+
const { begin, end } = findBlock(lines);
|
|
17
22
|
let out;
|
|
18
23
|
if (end === -1) {
|
|
19
24
|
if (!add.length)
|
|
@@ -51,3 +56,27 @@ export const editGitignore = (dir, edit) => {
|
|
|
51
56
|
return 'refused';
|
|
52
57
|
}
|
|
53
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Names in vlt's block of `<dir>/.gitignore`: vlt's record of its links
|
|
61
|
+
* in `dir`. Empty if none or unreadable.
|
|
62
|
+
*/
|
|
63
|
+
export const gitignoreNames = (dir) => {
|
|
64
|
+
try {
|
|
65
|
+
const file = join(dir, '.gitignore');
|
|
66
|
+
if (!lstatSync(file, { throwIfNoEntry: false })?.isFile()) {
|
|
67
|
+
return new Set();
|
|
68
|
+
}
|
|
69
|
+
const lines = readFileSync(file, 'utf8').split(/\r?\n/);
|
|
70
|
+
const { begin, end } = findBlock(lines);
|
|
71
|
+
if (end === -1)
|
|
72
|
+
return new Set();
|
|
73
|
+
return new Set(lines
|
|
74
|
+
.slice(begin + 1, end)
|
|
75
|
+
.map(l => l.trim())
|
|
76
|
+
.filter(l => l.startsWith('/'))
|
|
77
|
+
.map(l => l.slice(1)));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return new Set();
|
|
81
|
+
}
|
|
82
|
+
};
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -109,15 +109,16 @@ export declare const listSkills: (o: SkillsOptions) => Promise<ListSkillsResult>
|
|
|
109
109
|
* project's agent skill dirs (see `skillsDirs`), created only when a
|
|
110
110
|
* skill is picked. `target` selectors may end with `:skill()`. One
|
|
111
111
|
* package per name, among picked skills, in every dir: the package of
|
|
112
|
-
* the first existing link (in dir order), else rank (main
|
|
113
|
-
* dep, importer dep, transitive), then DepID; the others are
|
|
112
|
+
* the first existing link (in dir order), else rank (workspace or main
|
|
113
|
+
* importer dep, importer dep, transitive), then DepID; the others are
|
|
114
114
|
* reported. Installs keep a dir's link to another installed skill.
|
|
115
115
|
* A link follows its package to the picked version, in every scanned
|
|
116
116
|
* dir. Then remove vlt links whose skill is no longer installed (with
|
|
117
117
|
* `apply`: every vlt link not made here), and keep each dir's
|
|
118
|
-
* `.gitignore` block in sync
|
|
119
|
-
*
|
|
120
|
-
* closed: skills of packages with
|
|
121
|
-
* and reported in `unscanned` unless
|
|
118
|
+
* `.gitignore` block in sync (also vlt's record of workspace links).
|
|
119
|
+
* Linked packages get private copies of files hardlinked from the
|
|
120
|
+
* global store. Security queries fail closed: skills of packages with
|
|
121
|
+
* no security data are not linked, and reported in `unscanned` unless
|
|
122
|
+
* still linked.
|
|
122
123
|
*/
|
|
123
124
|
export declare const linkSkills: (o: LinkSkillsOptions) => Promise<LinkSkillsResult>;
|
package/dist/skills/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { Monorepo } from '@vltpkg/workspaces';
|
|
2
3
|
import { load as loadActual } from "../actual/load.js";
|
|
3
4
|
import { filterNodesByQueryParts } from "../filter-nodes-by-query.js";
|
|
4
5
|
import { unshare } from "../reify/unshare.js";
|
|
@@ -6,7 +7,7 @@ import { skillsDirs } from "./dirs.js";
|
|
|
6
7
|
import { discoverSkills } from "./discover.js";
|
|
7
8
|
import { editGitignore } from "./gitignore.js";
|
|
8
9
|
import { maskSkills } from "./mask.js";
|
|
9
|
-
import { mountSkill, packageKey, readMounts, samePath, unmountSkill, } from "./mounts.js";
|
|
10
|
+
import { mountSkill, packageKey, readMounts, realOr, samePath, unmountSkill, } from "./mounts.js";
|
|
10
11
|
export * from "./discover.js";
|
|
11
12
|
const lc = (s) => s.toLowerCase();
|
|
12
13
|
const selectNodes = async (o) => {
|
|
@@ -21,6 +22,23 @@ const dirsOf = (o, create = false) => skillsDirs({
|
|
|
21
22
|
env: o.env,
|
|
22
23
|
create,
|
|
23
24
|
});
|
|
25
|
+
// every workspace, selected or not: owners of vlt links
|
|
26
|
+
const workspaceDirs = (graph, scurry) => [...graph.importers]
|
|
27
|
+
.filter(n => !n.mainImporter)
|
|
28
|
+
.map(n => ({
|
|
29
|
+
id: n.id,
|
|
30
|
+
path: realOr(n.resolvedLocation(scurry)),
|
|
31
|
+
}));
|
|
32
|
+
// every workspace, loaded or not (`-w`)
|
|
33
|
+
const allWorkspaces = (m) => m ?
|
|
34
|
+
[
|
|
35
|
+
...Monorepo.load(m.projectRoot, {
|
|
36
|
+
config: m.config,
|
|
37
|
+
scurry: m.scurry,
|
|
38
|
+
packageJson: m.packageJson,
|
|
39
|
+
}).values(),
|
|
40
|
+
].map(ws => ({ id: ws.id, path: realOr(ws.fullpath) }))
|
|
41
|
+
: [];
|
|
24
42
|
// packages behind live vlt links
|
|
25
43
|
const liveOwners = (mounts) => mounts.flatMap(m => m.owner && existsSync(m.target) ? [m.owner] : []);
|
|
26
44
|
// private copies of store-hardlinked files: edits via skill links
|
|
@@ -61,7 +79,7 @@ const report = (dropped, unscanned) => unscanned && dropped.length ?
|
|
|
61
79
|
export const listSkills = async (o) => {
|
|
62
80
|
const { graph, nodes, parts, unscanned } = await selectNodes(o);
|
|
63
81
|
const { targets, scan, skipped } = dirsOf(o);
|
|
64
|
-
const { skills } = discoverSkills(nodes, o.scurry, readMounts(scan));
|
|
82
|
+
const { skills } = discoverSkills(nodes, o.scurry, readMounts(scan, { workspaces: workspaceDirs(graph, o.scurry) }));
|
|
65
83
|
const { picked, dropped } = pick(skills, parts, unscanned);
|
|
66
84
|
return {
|
|
67
85
|
project: graph.mainImporter.name,
|
|
@@ -77,16 +95,17 @@ export const listSkills = async (o) => {
|
|
|
77
95
|
* project's agent skill dirs (see `skillsDirs`), created only when a
|
|
78
96
|
* skill is picked. `target` selectors may end with `:skill()`. One
|
|
79
97
|
* package per name, among picked skills, in every dir: the package of
|
|
80
|
-
* the first existing link (in dir order), else rank (main
|
|
81
|
-
* dep, importer dep, transitive), then DepID; the others are
|
|
98
|
+
* the first existing link (in dir order), else rank (workspace or main
|
|
99
|
+
* importer dep, importer dep, transitive), then DepID; the others are
|
|
82
100
|
* reported. Installs keep a dir's link to another installed skill.
|
|
83
101
|
* A link follows its package to the picked version, in every scanned
|
|
84
102
|
* dir. Then remove vlt links whose skill is no longer installed (with
|
|
85
103
|
* `apply`: every vlt link not made here), and keep each dir's
|
|
86
|
-
* `.gitignore` block in sync
|
|
87
|
-
*
|
|
88
|
-
* closed: skills of packages with
|
|
89
|
-
* and reported in `unscanned` unless
|
|
104
|
+
* `.gitignore` block in sync (also vlt's record of workspace links).
|
|
105
|
+
* Linked packages get private copies of files hardlinked from the
|
|
106
|
+
* global store. Security queries fail closed: skills of packages with
|
|
107
|
+
* no security data are not linked, and reported in `unscanned` unless
|
|
108
|
+
* still linked.
|
|
90
109
|
*/
|
|
91
110
|
export const linkSkills = async (o) => {
|
|
92
111
|
const { graph, nodes, parts, unscanned } = await selectNodes(o);
|
|
@@ -101,7 +120,8 @@ export const linkSkills = async (o) => {
|
|
|
101
120
|
conflicts: [],
|
|
102
121
|
dirs: targets.map(d => d.dir),
|
|
103
122
|
};
|
|
104
|
-
const
|
|
123
|
+
const workspaces = workspaceDirs(graph, o.scurry);
|
|
124
|
+
const mounts = readMounts(scan, { workspaces });
|
|
105
125
|
const byName = new Map(mounts.map(m => [`${m.dir.dir}\0${lc(m.name)}`, m]));
|
|
106
126
|
// the link's skill is no longer installed: dangling, orphan owner,
|
|
107
127
|
// or a scanned owner that no longer ships it
|
|
@@ -163,7 +183,10 @@ export const linkSkills = async (o) => {
|
|
|
163
183
|
// names taken over by entries not vlt's
|
|
164
184
|
const foreign = [];
|
|
165
185
|
const kept = new Set();
|
|
186
|
+
// links made here, recorded even if not yet in `.gitignore`
|
|
187
|
+
const made = new Set();
|
|
166
188
|
const where = new Map();
|
|
189
|
+
let every;
|
|
167
190
|
for (const { dir, winner, losers, m } of picks) {
|
|
168
191
|
if (winner) {
|
|
169
192
|
// replaced link named in another case (case-sensitive fs)
|
|
@@ -172,10 +195,16 @@ export const linkSkills = async (o) => {
|
|
|
172
195
|
gone.push(m);
|
|
173
196
|
}
|
|
174
197
|
const mount = `${dir.dir}/${winner.skill}`;
|
|
175
|
-
const r = mountSkill(winner, dir);
|
|
198
|
+
const r = mountSkill(winner, dir, m?.name === winner.skill);
|
|
199
|
+
if (r === 'linked')
|
|
200
|
+
made.add(mount);
|
|
176
201
|
if (r === 'conflict') {
|
|
177
202
|
res.conflicts.push(mount);
|
|
178
|
-
|
|
203
|
+
// vlt's link into a workspace not loaded (`-w`) keeps its line
|
|
204
|
+
every ??= allWorkspaces(o.monorepo);
|
|
205
|
+
if (!readMounts([dir], { workspaces: every }).some(l => l.name === winner.skill)) {
|
|
206
|
+
foreign.push({ dir, name: winner.skill });
|
|
207
|
+
}
|
|
179
208
|
}
|
|
180
209
|
else {
|
|
181
210
|
const k = `${winner.id}\0${winner.skill}`;
|
|
@@ -189,7 +218,7 @@ export const linkSkills = async (o) => {
|
|
|
189
218
|
for (const s of losers)
|
|
190
219
|
res.conflicts.push(`${dir.dir}/${s.skill} (${label(s)})`);
|
|
191
220
|
}
|
|
192
|
-
const after = readMounts(scan);
|
|
221
|
+
const after = readMounts(scan, { workspaces, made });
|
|
193
222
|
const present = [];
|
|
194
223
|
for (const m of after) {
|
|
195
224
|
if (o.apply ? kept.has(m.mount) : !stale(m)) {
|
package/dist/skills/mounts.d.ts
CHANGED
|
@@ -16,9 +16,17 @@ export type SkillMount = {
|
|
|
16
16
|
link: string;
|
|
17
17
|
/** absolute resolved link target */
|
|
18
18
|
target: string;
|
|
19
|
-
/** DepID of the store dir the link points into */
|
|
19
|
+
/** DepID of the store dir or workspace the link points into */
|
|
20
20
|
owner?: DepID;
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* A workspace of the project.
|
|
24
|
+
*/
|
|
25
|
+
export type WorkspaceDir = {
|
|
26
|
+
id: DepID;
|
|
27
|
+
/** realpath */
|
|
28
|
+
path: string;
|
|
29
|
+
};
|
|
22
30
|
/**
|
|
23
31
|
* Outcome of mounting a single skill.
|
|
24
32
|
*/
|
|
@@ -50,14 +58,20 @@ export declare const ownerOf: (target: string) => DepID | undefined;
|
|
|
50
58
|
*/
|
|
51
59
|
export declare const packageKey: (id: DepID) => string;
|
|
52
60
|
/**
|
|
53
|
-
* All vlt links in the given dirs
|
|
61
|
+
* All vlt links in the given dirs: links into a `node_modules/.vlt`
|
|
62
|
+
* store, and links listed in the dir's `.gitignore` block (or `made`)
|
|
63
|
+
* into a workspace or dangling. Anything else is ignored.
|
|
54
64
|
*/
|
|
55
|
-
export declare const readMounts: (scan: SkillsDir[]
|
|
65
|
+
export declare const readMounts: (scan: SkillsDir[], { workspaces, made, }?: {
|
|
66
|
+
workspaces?: WorkspaceDir[];
|
|
67
|
+
made?: Set<string>;
|
|
68
|
+
}) => SkillMount[];
|
|
56
69
|
/**
|
|
57
|
-
* Link a skill at `<dir>/<skill>`.
|
|
58
|
-
* vlt
|
|
70
|
+
* Link a skill at `<dir>/<skill>`. `'unchanged'` if the entry already
|
|
71
|
+
* is the skill. Never touches anything not managed by vlt (a store
|
|
72
|
+
* link, or `owned`): returns `'conflict'` instead.
|
|
59
73
|
*/
|
|
60
|
-
export declare const mountSkill: (skill: Pick<Skill, "skill" | "path">, dir: SkillsDir) => MountResult;
|
|
74
|
+
export declare const mountSkill: (skill: Pick<Skill, "skill" | "path">, dir: SkillsDir, owned?: boolean) => MountResult;
|
|
61
75
|
/**
|
|
62
76
|
* Remove a vlt link. Its dir is kept.
|
|
63
77
|
*/
|
package/dist/skills/mounts.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { lstatSync, readdirSync, readlinkSync, realpathSync, unlinkSync, } from 'node:fs';
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, readlinkSync, realpathSync, unlinkSync, } from 'node:fs';
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { baseDepID, isDepID, joinDepIDTuple, splitDepID, } from '@vltpkg/dep-id';
|
|
4
4
|
import { symlinkSyncMkdirp } from "../reify/symlink-sync.js";
|
|
5
|
+
import { gitignoreNames } from "./gitignore.js";
|
|
5
6
|
/** `lstat`, undefined when missing or unreadable (eg, ENOTDIR) */
|
|
6
7
|
export const lstat = (path) => {
|
|
7
8
|
try {
|
|
@@ -30,6 +31,15 @@ export const realOr = (p) => {
|
|
|
30
31
|
*/
|
|
31
32
|
export const samePath = (a, b) => norm(realOr(a)) === norm(realOr(b));
|
|
32
33
|
const storeSeg = `${sep}node_modules${sep}.vlt${sep}`;
|
|
34
|
+
// resolved target of a symlink (or junction)
|
|
35
|
+
const linkTarget = (link) => {
|
|
36
|
+
if (!lstat(link)?.isSymbolicLink())
|
|
37
|
+
return undefined;
|
|
38
|
+
let raw = readlinkSync(link);
|
|
39
|
+
if (raw.startsWith('\\\\?\\'))
|
|
40
|
+
raw = raw.slice(4);
|
|
41
|
+
return resolve(dirname(link), raw);
|
|
42
|
+
};
|
|
33
43
|
/**
|
|
34
44
|
* The target of `link` if it is a symlink (or junction) pointing into
|
|
35
45
|
* a `node_modules/.vlt` store, otherwise undefined. Any store, not just
|
|
@@ -37,13 +47,20 @@ const storeSeg = `${sep}node_modules${sep}.vlt${sep}`;
|
|
|
37
47
|
* store after the project is moved or copied.
|
|
38
48
|
*/
|
|
39
49
|
export const mountTarget = (link) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
const target = linkTarget(link);
|
|
51
|
+
return target && norm(target).includes(storeSeg) ?
|
|
52
|
+
target
|
|
53
|
+
: undefined;
|
|
54
|
+
};
|
|
55
|
+
// the deepest workspace holding `target`
|
|
56
|
+
const workspaceOf = (target, workspaces) => {
|
|
57
|
+
const t = norm(realOr(target));
|
|
58
|
+
return workspaces
|
|
59
|
+
.filter(({ path }) => {
|
|
60
|
+
const p = norm(path);
|
|
61
|
+
return t === p || t.startsWith(p + sep);
|
|
62
|
+
})
|
|
63
|
+
.sort((a, b) => b.path.length - a.path.length)[0];
|
|
47
64
|
};
|
|
48
65
|
/**
|
|
49
66
|
* The DepID of the last `node_modules/.vlt/<DepID>` segment of a raw
|
|
@@ -76,9 +93,11 @@ export const packageKey = (id) => {
|
|
|
76
93
|
]);
|
|
77
94
|
};
|
|
78
95
|
/**
|
|
79
|
-
* All vlt links in the given dirs
|
|
96
|
+
* All vlt links in the given dirs: links into a `node_modules/.vlt`
|
|
97
|
+
* store, and links listed in the dir's `.gitignore` block (or `made`)
|
|
98
|
+
* into a workspace or dangling. Anything else is ignored.
|
|
80
99
|
*/
|
|
81
|
-
export const readMounts = (scan) => {
|
|
100
|
+
export const readMounts = (scan, { workspaces = [], made, } = {}) => {
|
|
82
101
|
const mounts = [];
|
|
83
102
|
for (const dir of scan) {
|
|
84
103
|
let entries;
|
|
@@ -88,35 +107,42 @@ export const readMounts = (scan) => {
|
|
|
88
107
|
catch {
|
|
89
108
|
continue;
|
|
90
109
|
}
|
|
110
|
+
const listed = gitignoreNames(dir.real);
|
|
91
111
|
for (const name of entries) {
|
|
92
112
|
const link = join(dir.real, name);
|
|
113
|
+
const mount = `${dir.dir}/${name}`;
|
|
114
|
+
const m = { dir, name, mount, link };
|
|
93
115
|
const target = mountTarget(link);
|
|
94
116
|
if (target) {
|
|
95
|
-
mounts.push({
|
|
96
|
-
|
|
97
|
-
name,
|
|
98
|
-
mount: `${dir.dir}/${name}`,
|
|
99
|
-
link,
|
|
100
|
-
target,
|
|
101
|
-
owner: ownerOf(target),
|
|
102
|
-
});
|
|
117
|
+
mounts.push({ ...m, target, owner: ownerOf(target) });
|
|
118
|
+
continue;
|
|
103
119
|
}
|
|
120
|
+
const wsTarget = (listed.has(name) || made?.has(mount)) && linkTarget(link);
|
|
121
|
+
if (!wsTarget)
|
|
122
|
+
continue;
|
|
123
|
+
if (!existsSync(wsTarget)) {
|
|
124
|
+
mounts.push({ ...m, target: wsTarget });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const ws = workspaceOf(wsTarget, workspaces);
|
|
128
|
+
if (ws)
|
|
129
|
+
mounts.push({ ...m, target: wsTarget, owner: ws.id });
|
|
104
130
|
}
|
|
105
131
|
}
|
|
106
132
|
return mounts.sort((a, b) => a.mount.localeCompare(b.mount, 'en'));
|
|
107
133
|
};
|
|
108
134
|
/**
|
|
109
|
-
* Link a skill at `<dir>/<skill>`.
|
|
110
|
-
* vlt
|
|
135
|
+
* Link a skill at `<dir>/<skill>`. `'unchanged'` if the entry already
|
|
136
|
+
* is the skill. Never touches anything not managed by vlt (a store
|
|
137
|
+
* link, or `owned`): returns `'conflict'` instead.
|
|
111
138
|
*/
|
|
112
|
-
export const mountSkill = (skill, dir) => {
|
|
139
|
+
export const mountSkill = (skill, dir, owned = false) => {
|
|
113
140
|
const link = join(dir.real, skill.skill);
|
|
114
141
|
if (lstat(link)) {
|
|
115
|
-
|
|
116
|
-
if (!target)
|
|
117
|
-
return 'conflict';
|
|
118
|
-
if (samePath(target, skill.path))
|
|
142
|
+
if (samePath(link, skill.path))
|
|
119
143
|
return 'unchanged';
|
|
144
|
+
if (!owned && !mountTarget(link))
|
|
145
|
+
return 'conflict';
|
|
120
146
|
unlinkSync(link);
|
|
121
147
|
}
|
|
122
148
|
// junctions need no admin rights, but want an absolute target
|
package/dist/uninstall.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DepID } from '@vltpkg/dep-id';
|
|
1
2
|
import type { PackageInfoClient } from '@vltpkg/package-info';
|
|
2
3
|
import type { LoadOptions } from './actual/load.ts';
|
|
3
4
|
import type { RemoveImportersDependenciesMap } from './dependencies.ts';
|
|
@@ -18,6 +19,8 @@ export type UninstallOptions = LoadOptions & {
|
|
|
18
19
|
* none
|
|
19
20
|
*/
|
|
20
21
|
skillsDirs?: string[];
|
|
22
|
+
/** store dirs of these nodes are never removed, eg the running vlt */
|
|
23
|
+
keepNodes?: ReadonlySet<DepID>;
|
|
21
24
|
};
|
|
22
25
|
/**
|
|
23
26
|
* Result of {@link uninstall}.
|
package/dist/uninstall.js
CHANGED
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.6.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.6.0",
|
|
17
|
+
"@vltpkg/dep-id": "1.6.0",
|
|
18
|
+
"@vltpkg/dss-breadcrumb": "1.6.0",
|
|
19
|
+
"@vltpkg/error-cause": "1.6.0",
|
|
20
|
+
"@vltpkg/fast-split": "1.6.0",
|
|
21
|
+
"@vltpkg/graph-run": "1.6.0",
|
|
22
|
+
"@vltpkg/init": "1.6.0",
|
|
23
|
+
"@vltpkg/output": "1.6.0",
|
|
24
|
+
"@vltpkg/package-info": "1.6.0",
|
|
25
|
+
"@vltpkg/package-json": "1.6.0",
|
|
26
|
+
"@vltpkg/pick-manifest": "1.6.0",
|
|
27
|
+
"@vltpkg/query": "1.6.0",
|
|
28
|
+
"@vltpkg/rollback-remove": "1.6.0",
|
|
29
|
+
"@vltpkg/run": "1.6.0",
|
|
30
|
+
"@vltpkg/satisfies": "1.6.0",
|
|
31
|
+
"@vltpkg/security-archive": "1.6.0",
|
|
32
|
+
"@vltpkg/spec": "1.6.0",
|
|
33
|
+
"@vltpkg/types": "1.6.0",
|
|
34
|
+
"@vltpkg/vlt-json": "1.6.0",
|
|
35
|
+
"@vltpkg/workspaces": "1.6.0",
|
|
36
36
|
"path-scurry": "^2.0.1",
|
|
37
37
|
"promise-call-limit": "^3.0.2"
|
|
38
38
|
},
|