@vltpkg/graph 1.5.0 → 1.6.1
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 +5 -1
- package/dist/lockfile/load-nodes.js +4 -9
- package/dist/modifiers.d.ts +11 -0
- package/dist/modifiers.js +105 -21
- package/dist/node.js +6 -0
- 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
|
@@ -47,7 +47,10 @@ export const install = async (options, add) => {
|
|
|
47
47
|
}
|
|
48
48
|
catch (err) {
|
|
49
49
|
if (asError(err).message === 'Could not read package.json file') {
|
|
50
|
-
await init({
|
|
50
|
+
await init({
|
|
51
|
+
cwd: options.projectRoot,
|
|
52
|
+
packageJson: options.packageJson,
|
|
53
|
+
});
|
|
51
54
|
mainManifest = options.packageJson.read(options.projectRoot, {
|
|
52
55
|
reload: true,
|
|
53
56
|
});
|
|
@@ -239,6 +242,7 @@ export const install = async (options, add) => {
|
|
|
239
242
|
...options,
|
|
240
243
|
add,
|
|
241
244
|
graph,
|
|
245
|
+
modifiers,
|
|
242
246
|
remove,
|
|
243
247
|
})
|
|
244
248
|
: undefined;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { error } from '@vltpkg/error-cause';
|
|
2
2
|
import { asDepID, splitDepID, splitExtra, } from '@vltpkg/dep-id/browser';
|
|
3
3
|
import { getBooleanFlagsFromNum, getBuildStateFromNum, } from "./types.js";
|
|
4
|
-
import { tarballFormat } from '@vltpkg/types';
|
|
5
4
|
export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest) => {
|
|
6
5
|
const entries = Object.entries(nodes);
|
|
7
6
|
const nodeCount = entries.length;
|
|
@@ -76,16 +75,12 @@ export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest)
|
|
|
76
75
|
// before setResolved(), which needs it to pick the extension for a
|
|
77
76
|
// node whose `resolved` the lockfile left out
|
|
78
77
|
node.brotli = brotli;
|
|
79
|
-
node.integrity = integrity ?? referenceNode?.integrity;
|
|
80
78
|
// `integrity` is the hash of the artifact `resolved` points at, so a
|
|
81
|
-
// reference
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const refResolvedUsable = !integrity ||
|
|
85
|
-
!refResolved ||
|
|
86
|
-
(tarballFormat(refResolved) === 'brotli') === brotli;
|
|
79
|
+
// reference node only fills in for the kind the flag bit names
|
|
80
|
+
const ref = !!referenceNode?.brotli === brotli ? referenceNode : undefined;
|
|
81
|
+
node.integrity = integrity ?? ref?.integrity;
|
|
87
82
|
node.resolved =
|
|
88
|
-
type === 'remote' ? filepath : (
|
|
83
|
+
type === 'remote' ? filepath : (resolved ?? ref?.resolved);
|
|
89
84
|
node.resolvedFromLockfile = !!(node.integrity && node.resolved);
|
|
90
85
|
node.projectRoot = graph.projectRoot;
|
|
91
86
|
if (!node.resolved)
|
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/node.js
CHANGED
|
@@ -371,6 +371,12 @@ export class Node {
|
|
|
371
371
|
case 'registry':
|
|
372
372
|
this.#registryNodeResolved(tuple);
|
|
373
373
|
break;
|
|
374
|
+
case 'git':
|
|
375
|
+
// a hosted git committish is fetched as the host's tarball
|
|
376
|
+
this.resolved =
|
|
377
|
+
hydrateTuple(tuple, this.#name, this.#options).final
|
|
378
|
+
.remoteURL ?? resolved;
|
|
379
|
+
break;
|
|
374
380
|
default:
|
|
375
381
|
this.resolved = resolved;
|
|
376
382
|
break;
|
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>)[];
|