@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
|
@@ -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.1",
|
|
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.1",
|
|
17
|
+
"@vltpkg/dep-id": "1.6.1",
|
|
18
|
+
"@vltpkg/dss-breadcrumb": "1.6.1",
|
|
19
|
+
"@vltpkg/error-cause": "1.6.1",
|
|
20
|
+
"@vltpkg/fast-split": "1.6.1",
|
|
21
|
+
"@vltpkg/graph-run": "1.6.1",
|
|
22
|
+
"@vltpkg/init": "1.6.1",
|
|
23
|
+
"@vltpkg/output": "1.6.1",
|
|
24
|
+
"@vltpkg/package-info": "1.6.1",
|
|
25
|
+
"@vltpkg/package-json": "1.6.1",
|
|
26
|
+
"@vltpkg/pick-manifest": "1.6.1",
|
|
27
|
+
"@vltpkg/query": "1.6.1",
|
|
28
|
+
"@vltpkg/rollback-remove": "1.6.1",
|
|
29
|
+
"@vltpkg/run": "1.6.1",
|
|
30
|
+
"@vltpkg/satisfies": "1.6.1",
|
|
31
|
+
"@vltpkg/security-archive": "1.6.1",
|
|
32
|
+
"@vltpkg/spec": "1.6.1",
|
|
33
|
+
"@vltpkg/types": "1.6.1",
|
|
34
|
+
"@vltpkg/vlt-json": "1.6.1",
|
|
35
|
+
"@vltpkg/workspaces": "1.6.1",
|
|
36
36
|
"path-scurry": "^2.0.1",
|
|
37
37
|
"promise-call-limit": "^3.0.2"
|
|
38
38
|
},
|