@vltpkg/graph 1.0.0-rc.23 → 1.0.0-rc.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actual/load.d.ts +107 -0
- package/dist/actual/load.js +336 -0
- package/dist/browser.d.ts +14 -0
- package/dist/browser.js +16 -0
- package/dist/build.d.ts +28 -0
- package/dist/build.js +78 -0
- package/dist/dependencies.d.ts +65 -0
- package/dist/dependencies.js +111 -0
- package/dist/diff.d.ts +119 -0
- package/dist/diff.js +151 -0
- package/dist/edge.d.ts +46 -0
- package/dist/edge.js +77 -0
- package/dist/fixup-added-names.d.ts +18 -0
- package/dist/fixup-added-names.js +46 -0
- package/dist/graph.d.ts +153 -0
- package/dist/graph.js +444 -0
- package/dist/ideal/append-nodes.d.ts +31 -0
- package/dist/ideal/append-nodes.js +560 -0
- package/dist/ideal/build-ideal-from-starting-graph.d.ts +14 -0
- package/dist/ideal/build-ideal-from-starting-graph.js +69 -0
- package/dist/ideal/build.d.ts +40 -0
- package/dist/ideal/build.js +84 -0
- package/dist/ideal/get-importer-specs.d.ts +20 -0
- package/dist/ideal/get-importer-specs.js +180 -0
- package/dist/ideal/peers.d.ts +160 -0
- package/dist/ideal/peers.js +696 -0
- package/dist/ideal/refresh-ideal-graph.d.ts +43 -0
- package/dist/ideal/refresh-ideal-graph.js +62 -0
- package/dist/ideal/remove-satisfied-specs.d.ts +7 -0
- package/dist/ideal/remove-satisfied-specs.js +34 -0
- package/dist/ideal/sorting.d.ts +45 -0
- package/dist/ideal/sorting.js +70 -0
- package/dist/ideal/types.d.ts +107 -0
- package/dist/ideal/types.js +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +32 -0
- package/dist/install.d.ts +19 -0
- package/dist/install.js +208 -0
- package/dist/lockfile/load-edges.d.ts +11 -0
- package/dist/lockfile/load-edges.js +105 -0
- package/dist/lockfile/load-nodes.d.ts +4 -0
- package/dist/lockfile/load-nodes.js +101 -0
- package/dist/lockfile/load.d.ts +45 -0
- package/dist/lockfile/load.js +84 -0
- package/dist/lockfile/save.d.ts +30 -0
- package/dist/lockfile/save.js +174 -0
- package/dist/lockfile/types.d.ts +95 -0
- package/dist/lockfile/types.js +49 -0
- package/dist/modifiers.d.ts +188 -0
- package/dist/modifiers.js +329 -0
- package/dist/node.d.ts +234 -0
- package/dist/node.js +388 -0
- package/dist/non-empty-list.d.ts +2 -0
- package/dist/non-empty-list.js +2 -0
- package/dist/reify/add-edge.d.ts +9 -0
- package/dist/reify/add-edge.js +71 -0
- package/dist/reify/add-edges.d.ts +4 -0
- package/dist/reify/add-edges.js +12 -0
- package/dist/reify/add-nodes.d.ts +6 -0
- package/dist/reify/add-nodes.js +16 -0
- package/dist/reify/bin-chmod.d.ts +10 -0
- package/dist/reify/bin-chmod.js +38 -0
- package/dist/reify/build.d.ts +13 -0
- package/dist/reify/build.js +111 -0
- package/dist/reify/calculate-save-value.d.ts +2 -0
- package/dist/reify/calculate-save-value.js +50 -0
- package/dist/reify/check-needed-build.d.ts +34 -0
- package/dist/reify/check-needed-build.js +71 -0
- package/dist/reify/delete-edge.d.ts +4 -0
- package/dist/reify/delete-edge.js +27 -0
- package/dist/reify/delete-edges.d.ts +4 -0
- package/dist/reify/delete-edges.js +13 -0
- package/dist/reify/delete-nodes.d.ts +4 -0
- package/dist/reify/delete-nodes.js +15 -0
- package/dist/reify/extract-node.d.ts +23 -0
- package/dist/reify/extract-node.js +83 -0
- package/dist/reify/index.d.ts +34 -0
- package/dist/reify/index.js +161 -0
- package/dist/reify/internal-hoist.d.ts +8 -0
- package/dist/reify/internal-hoist.js +133 -0
- package/dist/reify/optional-fail.d.ts +15 -0
- package/dist/reify/optional-fail.js +15 -0
- package/dist/reify/rollback.d.ts +4 -0
- package/dist/reify/rollback.js +23 -0
- package/dist/reify/update-importers-package-json.d.ts +35 -0
- package/dist/reify/update-importers-package-json.js +122 -0
- package/dist/remove-optional-subgraph.d.ts +33 -0
- package/dist/remove-optional-subgraph.js +47 -0
- package/dist/resolve-save-type.d.ts +5 -0
- package/dist/resolve-save-type.js +4 -0
- package/dist/stringify-node.d.ts +2 -0
- package/dist/stringify-node.js +32 -0
- package/dist/transfer-data/load.d.ts +43 -0
- package/dist/transfer-data/load.js +175 -0
- package/dist/uninstall.d.ts +14 -0
- package/dist/uninstall.js +75 -0
- package/dist/update.d.ts +12 -0
- package/dist/update.js +73 -0
- package/dist/virtual-root.d.ts +15 -0
- package/dist/virtual-root.js +78 -0
- package/dist/visualization/human-readable-output.d.ts +26 -0
- package/dist/visualization/human-readable-output.js +163 -0
- package/dist/visualization/json-output.d.ts +41 -0
- package/dist/visualization/json-output.js +50 -0
- package/dist/visualization/mermaid-output.d.ts +17 -0
- package/dist/visualization/mermaid-output.js +170 -0
- package/dist/visualization/object-like-output.d.ts +2 -0
- package/dist/visualization/object-like-output.js +47 -0
- package/package.json +22 -22
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { cmdShimIfExists } from '@vltpkg/cmd-shim';
|
|
2
|
+
import { mkdir, symlink } from 'node:fs/promises';
|
|
3
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
4
|
+
const clobberSymlink = async (target, link, remover, type = 'file') => {
|
|
5
|
+
// On Windows, directory symlinks require elevated privileges.
|
|
6
|
+
// Use junctions instead, which work without special permissions.
|
|
7
|
+
// Junctions require absolute target paths, so resolve relative targets.
|
|
8
|
+
const symlinkType = type === 'dir' && process.platform === 'win32' ? 'junction' : type;
|
|
9
|
+
const symlinkTarget = symlinkType === 'junction' ?
|
|
10
|
+
resolve(dirname(link), target)
|
|
11
|
+
: target;
|
|
12
|
+
await mkdir(dirname(link), { recursive: true });
|
|
13
|
+
try {
|
|
14
|
+
await symlink(symlinkTarget, link, symlinkType);
|
|
15
|
+
}
|
|
16
|
+
catch (er) {
|
|
17
|
+
/* c8 ignore start */
|
|
18
|
+
if (er.code !== 'EEXIST') {
|
|
19
|
+
throw er;
|
|
20
|
+
}
|
|
21
|
+
/* c8 ignore stop */
|
|
22
|
+
// if the symlink exists, remove it
|
|
23
|
+
await remover.rm(link);
|
|
24
|
+
try {
|
|
25
|
+
// then try to create it again
|
|
26
|
+
await symlink(symlinkTarget, link, symlinkType);
|
|
27
|
+
/* c8 ignore start */
|
|
28
|
+
}
|
|
29
|
+
catch (er) {
|
|
30
|
+
// if the symlink still exists, then multiple paths could be writing to it
|
|
31
|
+
// so now just ignore that error. See https://github.com/vltpkg/vltpkg/issues/797
|
|
32
|
+
if (er.code !== 'EEXIST') {
|
|
33
|
+
throw er;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/* c8 ignore stop */
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* reify an edge into a node_modules folder, with bins linked
|
|
41
|
+
* this does NOT chmod the bins to 0o777, because they might not exist
|
|
42
|
+
* until scripts are run, in the case of non-store deps like workspaces
|
|
43
|
+
*/
|
|
44
|
+
export const addEdge = async (edge, scurry, remover, bins) => {
|
|
45
|
+
if (!edge.to)
|
|
46
|
+
return;
|
|
47
|
+
const binRoot = scurry.resolve(edge.from.nodeModules(scurry), '.bin');
|
|
48
|
+
const path = scurry.resolve(edge.from.nodeModules(scurry), edge.spec.name);
|
|
49
|
+
const promises = [];
|
|
50
|
+
const target = relative(dirname(path), edge.to.resolvedLocation(scurry));
|
|
51
|
+
// can only parallelize this on posix, because the win32 shims
|
|
52
|
+
// need to know that they will exist before being created.
|
|
53
|
+
const p = clobberSymlink(target, path, remover, 'dir');
|
|
54
|
+
if (process.platform === 'win32')
|
|
55
|
+
await p;
|
|
56
|
+
else
|
|
57
|
+
promises.push(p);
|
|
58
|
+
if (bins) {
|
|
59
|
+
for (const [key, val] of Object.entries(bins)) {
|
|
60
|
+
const link = scurry.resolve(binRoot, key);
|
|
61
|
+
const absTarget = scurry.resolve(path, val);
|
|
62
|
+
const target = relative(binRoot, absTarget);
|
|
63
|
+
// TODO: bash/cmd/ps1 shims on Windows
|
|
64
|
+
promises.push(process.platform === 'win32' ?
|
|
65
|
+
cmdShimIfExists(absTarget, link, remover)
|
|
66
|
+
: clobberSymlink(target, link, remover));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (promises.length)
|
|
70
|
+
await Promise.all(promises);
|
|
71
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
2
|
+
import type { PathScurry } from 'path-scurry';
|
|
3
|
+
import type { Diff } from '../diff.ts';
|
|
4
|
+
export declare const addEdges: (diff: Diff, scurry: PathScurry, remover: RollbackRemove) => (() => Promise<unknown>)[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { addEdge } from "./add-edge.js";
|
|
2
|
+
export const addEdges = (diff, scurry, remover) => {
|
|
3
|
+
const actions = [];
|
|
4
|
+
for (const edge of diff.edges.add) {
|
|
5
|
+
const { to } = edge;
|
|
6
|
+
if (!to)
|
|
7
|
+
continue;
|
|
8
|
+
const bins = to.bins;
|
|
9
|
+
actions.push(() => addEdge(edge, scurry, remover, bins));
|
|
10
|
+
}
|
|
11
|
+
return actions;
|
|
12
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PackageInfoClient } from '@vltpkg/package-info';
|
|
2
|
+
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
3
|
+
import type { SpecOptions } from '@vltpkg/spec';
|
|
4
|
+
import type { PathScurry } from 'path-scurry';
|
|
5
|
+
import type { Diff } from '../diff.ts';
|
|
6
|
+
export declare const addNodes: (diff: Diff, scurry: PathScurry, remover: RollbackRemove, options: SpecOptions, packageInfo: PackageInfoClient) => (() => Promise<unknown>)[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { extractNode } from "./extract-node.js";
|
|
2
|
+
export const addNodes = (diff, scurry, remover, options, packageInfo) => {
|
|
3
|
+
const actions = [];
|
|
4
|
+
// fetch and extract all the nodes, removing any in the way
|
|
5
|
+
for (const node of diff.nodes.add) {
|
|
6
|
+
// if it's not in the store, we don't have to extract it, because
|
|
7
|
+
// we're just linking to a location that already exists.
|
|
8
|
+
if (!node.inVltStore())
|
|
9
|
+
continue;
|
|
10
|
+
// skip nodes that have already been extracted
|
|
11
|
+
if (node.extracted)
|
|
12
|
+
continue;
|
|
13
|
+
actions.push(() => extractNode(node, scurry, remover, options, packageInfo, diff));
|
|
14
|
+
}
|
|
15
|
+
return actions;
|
|
16
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PathScurry } from 'path-scurry';
|
|
2
|
+
import type { Node } from '../node.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Make all the packages' bins executable.
|
|
5
|
+
*/
|
|
6
|
+
export declare const binChmodAll: (nodes: Iterable<Node>, scurry: PathScurry) => Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Make all the package's bins executable.
|
|
9
|
+
*/
|
|
10
|
+
export declare const binChmod: (node: Node, scurry: PathScurry) => Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { statSync, existsSync } from 'node:fs';
|
|
2
|
+
import { chmod } from 'node:fs/promises';
|
|
3
|
+
/**
|
|
4
|
+
* Make all the packages' bins executable.
|
|
5
|
+
*/
|
|
6
|
+
export const binChmodAll = async (nodes, scurry) => {
|
|
7
|
+
const chmods = [];
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
chmods.push(binChmod(node, scurry));
|
|
10
|
+
}
|
|
11
|
+
await Promise.all(chmods);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Make all the package's bins executable.
|
|
15
|
+
*/
|
|
16
|
+
export const binChmod = async (node, scurry) => {
|
|
17
|
+
const chmods = [];
|
|
18
|
+
if (!node.bins)
|
|
19
|
+
return;
|
|
20
|
+
for (const bin of Object.values(node.bins)) {
|
|
21
|
+
const path = scurry.resolve(`${node.resolvedLocation(scurry)}/${bin}`);
|
|
22
|
+
// only try to make executable if the file exists
|
|
23
|
+
if (existsSync(path)) {
|
|
24
|
+
chmods.push(makeExecutable(path));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
await Promise.all(chmods);
|
|
28
|
+
};
|
|
29
|
+
// 0 is "not yet set"
|
|
30
|
+
// This is defined by doing `0o111 | <mode>` so that systems
|
|
31
|
+
// that create files group-writable result in 0o775 instead of 0o755
|
|
32
|
+
let execMode = 0;
|
|
33
|
+
const makeExecutable = async (path) => {
|
|
34
|
+
if (!execMode) {
|
|
35
|
+
execMode = (statSync(path).mode & 0o777) | 0o111;
|
|
36
|
+
}
|
|
37
|
+
await chmod(path, execMode);
|
|
38
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PackageJson } from '@vltpkg/package-json';
|
|
2
|
+
import type { PathScurry } from 'path-scurry';
|
|
3
|
+
import type { DepID } from '@vltpkg/dep-id';
|
|
4
|
+
import type { Diff } from '../diff.ts';
|
|
5
|
+
import type { Node } from '../node.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Returns an object mapping registries to the names of the packages built.
|
|
8
|
+
*/
|
|
9
|
+
export type BuildResult = {
|
|
10
|
+
success: Node[];
|
|
11
|
+
failure: Node[];
|
|
12
|
+
};
|
|
13
|
+
export declare const build: (diff: Diff, packageJson: PackageJson, scurry: PathScurry, allowScriptsNodes: Set<DepID>) => Promise<BuildResult>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// walk the graph of added nodes, building and chmoding their bins
|
|
2
|
+
// at the end, we get back to the importers, and run their prepare
|
|
3
|
+
// script as well as install script.
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { run } from '@vltpkg/run';
|
|
6
|
+
import { graphRun } from '@vltpkg/graph-run';
|
|
7
|
+
import { nonEmptyList } from "../non-empty-list.js";
|
|
8
|
+
import { optionalFail } from "./optional-fail.js";
|
|
9
|
+
import { binChmod } from "./bin-chmod.js";
|
|
10
|
+
export const build = async (diff, packageJson, scurry, allowScriptsNodes) => {
|
|
11
|
+
const graph = diff.to;
|
|
12
|
+
const nodes = nonEmptyList([...graph.importers]);
|
|
13
|
+
const res = { success: [], failure: [] };
|
|
14
|
+
// determine if scripts should run - check if node is in allowed set
|
|
15
|
+
const shouldRunScripts = (node) => allowScriptsNodes.has(node.id);
|
|
16
|
+
/* c8 ignore next - all graphs have at least one importer */
|
|
17
|
+
if (!nodes)
|
|
18
|
+
return res;
|
|
19
|
+
await graphRun({
|
|
20
|
+
graph: nodes,
|
|
21
|
+
visit: async (node, signal, path) => {
|
|
22
|
+
// if it's not an importer or an added node, nothing to do.
|
|
23
|
+
// TODO: only build importers if it has changed deps, there's never
|
|
24
|
+
// been a previous build, or it contains something newer than the
|
|
25
|
+
// most recent build.
|
|
26
|
+
// For now, just always build all importers, because we don't
|
|
27
|
+
// track all that other stuff.
|
|
28
|
+
if (!node.importer &&
|
|
29
|
+
(!diff.nodes.add.has(node) || !shouldRunScripts(node)))
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
await visit(packageJson, scurry, node, signal, path);
|
|
33
|
+
if (!node.importer) {
|
|
34
|
+
node.buildState = 'built';
|
|
35
|
+
res.success.push(node);
|
|
36
|
+
}
|
|
37
|
+
/* c8 ignore start - windows on CI is missing those tests */
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
// Check if this is an optional failure that was handled
|
|
41
|
+
if (node.optional) {
|
|
42
|
+
node.buildState = 'failed';
|
|
43
|
+
res.failure.push(node);
|
|
44
|
+
// Let optionalFail handle the error
|
|
45
|
+
await Promise.reject(err).catch(optionalFail(diff, node));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Re-throw non-optional failures
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/* c8 ignore stop */
|
|
53
|
+
},
|
|
54
|
+
getDeps: node => {
|
|
55
|
+
const deps = [];
|
|
56
|
+
for (const { to } of node.edgesOut.values()) {
|
|
57
|
+
/* c8 ignore next - vanishingly unlikely in practice */
|
|
58
|
+
if (to)
|
|
59
|
+
deps.push(to);
|
|
60
|
+
}
|
|
61
|
+
return deps;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return res;
|
|
65
|
+
};
|
|
66
|
+
const visit = async (packageJson, scurry, node, signal, _path) => {
|
|
67
|
+
// at this point we might have to read the manifest from disk if it's
|
|
68
|
+
// currently nullish, that could happen in a scenario where the ideal
|
|
69
|
+
// graph is from a lockfile and there's no actual graph available
|
|
70
|
+
// to hydrate the manifest data from.
|
|
71
|
+
node.manifest ??= packageJson.read(node.resolvedLocation(scurry));
|
|
72
|
+
const { manifest } = node;
|
|
73
|
+
const { scripts = {} } = manifest;
|
|
74
|
+
const { install, preinstall, postinstall, prepare, preprepare, postprepare, } = scripts;
|
|
75
|
+
// Check for binding.gyp file (npm's implicit install detection)
|
|
76
|
+
// "If there is a binding.gyp file in the root of your package and you
|
|
77
|
+
// haven't defined your own install or preinstall scripts, npm will default
|
|
78
|
+
// the install command to compile using node-gyp via node-gyp rebuild"
|
|
79
|
+
const hasBindingGyp = scurry
|
|
80
|
+
.lstatSync(join(node.resolvedLocation(scurry), 'binding.gyp'))
|
|
81
|
+
?.isFile() ?? false;
|
|
82
|
+
const hasImplicitInstall = hasBindingGyp && !install && !preinstall;
|
|
83
|
+
// if it has install script or binding.gyp (implicit install), run it
|
|
84
|
+
const runInstall = !!(install || preinstall || postinstall) || hasImplicitInstall;
|
|
85
|
+
if (runInstall) {
|
|
86
|
+
await run({
|
|
87
|
+
signal,
|
|
88
|
+
arg0: 'install',
|
|
89
|
+
ignoreMissing: true,
|
|
90
|
+
packageJson,
|
|
91
|
+
cwd: node.resolvedLocation(scurry),
|
|
92
|
+
projectRoot: node.projectRoot,
|
|
93
|
+
manifest,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// if it's an importer or git, run prepare
|
|
97
|
+
const prepable = node.id.startsWith('git') || node.importer || !node.inVltStore();
|
|
98
|
+
const runPrepare = !!(prepare || preprepare || postprepare) && prepable;
|
|
99
|
+
if (runPrepare) {
|
|
100
|
+
await run({
|
|
101
|
+
signal,
|
|
102
|
+
arg0: 'prepare',
|
|
103
|
+
ignoreMissing: true,
|
|
104
|
+
packageJson,
|
|
105
|
+
cwd: node.resolvedLocation(scurry),
|
|
106
|
+
projectRoot: node.projectRoot,
|
|
107
|
+
manifest,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
await binChmod(node, scurry);
|
|
111
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const SAVE_PREFIX = '^';
|
|
2
|
+
export const calculateSaveValue = (nodeType, spec, existing, nodeVersion) => {
|
|
3
|
+
// Catalog specs should always be preserved as-is in package.json.
|
|
4
|
+
// The catalog reference (e.g. "catalog:dev") is the user's intent;
|
|
5
|
+
// the resolved version is tracked in the lockfile.
|
|
6
|
+
if (spec.type === 'catalog') {
|
|
7
|
+
return spec.bareSpec;
|
|
8
|
+
}
|
|
9
|
+
if (
|
|
10
|
+
// if not from the registry, save whatever we requested
|
|
11
|
+
nodeType === 'registry' &&
|
|
12
|
+
// if we installed exactly what we already wanted, leave it untouched
|
|
13
|
+
spec.bareSpec !== existing &&
|
|
14
|
+
// if we installed a specific version, keep that specific version.
|
|
15
|
+
!spec.final.range?.isSingle) {
|
|
16
|
+
// if we had a single version, and got that version, then
|
|
17
|
+
// leave it as-is.
|
|
18
|
+
if (existing && existing === nodeVersion) {
|
|
19
|
+
// depend on 1.2.3, got 1.2.3, keep unchanged
|
|
20
|
+
return existing;
|
|
21
|
+
}
|
|
22
|
+
else if (existing && !spec.bareSpec) {
|
|
23
|
+
// if we had `"express": "5.1"` and did `vlt i express`,
|
|
24
|
+
// then leave it as-is, because we just installed our pj dep
|
|
25
|
+
return existing;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const finalRange = (spec.final.semver && spec.final.bareSpec) ||
|
|
29
|
+
`${SAVE_PREFIX}${nodeVersion}`;
|
|
30
|
+
// didn't have dep previously, or depended on a different thing
|
|
31
|
+
// than what was requested. Update with the ^ range based on
|
|
32
|
+
// the node that landed in the graph, but preserve alias prefix
|
|
33
|
+
if (spec.subspec && spec.final.namedRegistry) {
|
|
34
|
+
return `${spec.final.namedRegistry}:${spec.final.name}@${finalRange}`;
|
|
35
|
+
}
|
|
36
|
+
if (spec.final.namedJsrRegistry) {
|
|
37
|
+
// if we were given an alternative name, preserve that
|
|
38
|
+
// do this with a regexp because the Spec objects get a little
|
|
39
|
+
// weird here, and the string is relatively straightforward.
|
|
40
|
+
return spec.bareSpec
|
|
41
|
+
.replace(new RegExp(`^(?:.*?@)?${spec.final.namedJsrRegistry}:(@[^/]+/[^@]+)(@.*?)?$`), `${spec.final.namedJsrRegistry}:$1@${finalRange}`)
|
|
42
|
+
.replace(
|
|
43
|
+
// otherwise, swap out the final version for the save range
|
|
44
|
+
new RegExp(`^(?:.*?@)?${spec.final.namedJsrRegistry}:([^@].*?)?$`), `${spec.final.namedJsrRegistry}:${finalRange}`);
|
|
45
|
+
}
|
|
46
|
+
return finalRange;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return spec.bareSpec;
|
|
50
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PathScurry } from 'path-scurry';
|
|
2
|
+
import type { PackageJson } from '@vltpkg/package-json';
|
|
3
|
+
import type { Diff } from '../diff.ts';
|
|
4
|
+
import type { Node } from '../node.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Build data containing the queue of DepIDs that need building
|
|
7
|
+
*/
|
|
8
|
+
export type BuildData = {
|
|
9
|
+
needsBuildNodes: Node[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Options for checking needed build data
|
|
13
|
+
*/
|
|
14
|
+
export type CheckNeededBuildOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* The diff object containing nodes to process
|
|
17
|
+
*/
|
|
18
|
+
diff: Diff;
|
|
19
|
+
/**
|
|
20
|
+
* PathScurry instance for filesystem access
|
|
21
|
+
*/
|
|
22
|
+
scurry: PathScurry;
|
|
23
|
+
/**
|
|
24
|
+
* PackageJson instance for reading manifests from disk
|
|
25
|
+
*/
|
|
26
|
+
packageJson: PackageJson;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Check which nodes need to be built and set buildState accordingly
|
|
30
|
+
* Marks nodes with buildState = 'needed' for those that require
|
|
31
|
+
* install lifecycle scripts as part of `vlt build`
|
|
32
|
+
* @returns {BuildData} The BuildData object containing Node objects that need building
|
|
33
|
+
*/
|
|
34
|
+
export declare const checkNeededBuild: (options: CheckNeededBuildOptions) => BuildData;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a node needs to be built based on the conditions from build.ts:
|
|
4
|
+
* 1. Has install lifecycle scripts (install, preinstall, postinstall)
|
|
5
|
+
* 2. Has binding.gyp file with no install/preinstall scripts (implicit install)
|
|
6
|
+
* 3. Is an importer or git dependency with prepare scripts (prepare, preprepare, postprepare)
|
|
7
|
+
* 4. Has binary files that need to be linked
|
|
8
|
+
*/
|
|
9
|
+
const nodeNeedsBuild = (node, scurry, packageJson) => {
|
|
10
|
+
// If the node has already been built during reify, no need to build again
|
|
11
|
+
if (node.built)
|
|
12
|
+
return false;
|
|
13
|
+
// If the manifest is not available on the node, read it from disk.
|
|
14
|
+
// This can happen when the ideal graph is loaded from a lockfile
|
|
15
|
+
// and there's no actual graph available to hydrate the manifest data from.
|
|
16
|
+
let manifest = node.manifest;
|
|
17
|
+
if (!manifest) {
|
|
18
|
+
try {
|
|
19
|
+
manifest = packageJson.read(node.resolvedLocation(scurry));
|
|
20
|
+
node.manifest = manifest;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// If the manifest cannot be read (missing/corrupted), treat as
|
|
24
|
+
// "no build needed" to avoid failing the entire reification.
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const { scripts = {} } = manifest;
|
|
29
|
+
// Check for install lifecycle scripts
|
|
30
|
+
const runInstall = !!(scripts.install ||
|
|
31
|
+
scripts.preinstall ||
|
|
32
|
+
scripts.postinstall);
|
|
33
|
+
if (runInstall)
|
|
34
|
+
return true;
|
|
35
|
+
// Check for binding.gyp file (npm's implicit install detection)
|
|
36
|
+
// "If there is a binding.gyp file in the root of your package and you
|
|
37
|
+
// haven't defined your own install or preinstall scripts, npm will default
|
|
38
|
+
// the install command to compile using node-gyp via node-gyp rebuild"
|
|
39
|
+
const hasBindingGyp = scurry
|
|
40
|
+
.lstatSync(join(node.resolvedLocation(scurry), 'binding.gyp'))
|
|
41
|
+
?.isFile() ?? false;
|
|
42
|
+
if (hasBindingGyp && !scripts.install && !scripts.preinstall)
|
|
43
|
+
return true;
|
|
44
|
+
// Check for prepare scripts on importers or git dependencies
|
|
45
|
+
const prepable = node.id.startsWith('git') || node.importer || !node.inVltStore();
|
|
46
|
+
const runPrepare = !!((scripts.prepare || scripts.preprepare || scripts.postprepare)
|
|
47
|
+
/* c8 ignore next 2 */
|
|
48
|
+
) && prepable;
|
|
49
|
+
if (runPrepare)
|
|
50
|
+
return true;
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Check which nodes need to be built and set buildState accordingly
|
|
55
|
+
* Marks nodes with buildState = 'needed' for those that require
|
|
56
|
+
* install lifecycle scripts as part of `vlt build`
|
|
57
|
+
* @returns {BuildData} The BuildData object containing Node objects that need building
|
|
58
|
+
*/
|
|
59
|
+
export const checkNeededBuild = (options) => {
|
|
60
|
+
const { diff, scurry, packageJson } = options;
|
|
61
|
+
// Filter nodes to only include those that actually need to be built
|
|
62
|
+
const nodesToBuild = [...diff.nodes.add].filter(node => nodeNeedsBuild(node, scurry, packageJson));
|
|
63
|
+
// Set buildState = 'needed' on all nodes that require building
|
|
64
|
+
for (const node of nodesToBuild) {
|
|
65
|
+
node.buildState = 'needed';
|
|
66
|
+
}
|
|
67
|
+
const buildData = {
|
|
68
|
+
needsBuildNodes: nodesToBuild,
|
|
69
|
+
};
|
|
70
|
+
return buildData;
|
|
71
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const rmBinPosix = (remover, bin) => {
|
|
2
|
+
return [remover.rm(bin)];
|
|
3
|
+
};
|
|
4
|
+
const rmBinWin32 = (remover, bin) => {
|
|
5
|
+
return [
|
|
6
|
+
remover.rm(bin),
|
|
7
|
+
remover.rm(bin + '.cmd'),
|
|
8
|
+
remover.rm(bin + '.ps1'),
|
|
9
|
+
];
|
|
10
|
+
};
|
|
11
|
+
const rmBin = process.platform === 'win32' ? rmBinWin32 : rmBinPosix;
|
|
12
|
+
export const deleteEdge = async (edge, scurry, remover) => {
|
|
13
|
+
const { spec: { name }, to, } = edge;
|
|
14
|
+
const nm = edge.from.nodeModules(scurry);
|
|
15
|
+
const path = scurry.resolve(nm, name);
|
|
16
|
+
const binRoot = scurry.cwd.resolve(`${nm}/.bin`);
|
|
17
|
+
const promises = [];
|
|
18
|
+
promises.push(remover.rm(path));
|
|
19
|
+
const bins = to?.bins;
|
|
20
|
+
if (bins) {
|
|
21
|
+
for (const key of Object.keys(bins)) {
|
|
22
|
+
const bin = binRoot.resolve(key).fullpath();
|
|
23
|
+
promises.push(...rmBin(remover, bin));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
await Promise.all(promises);
|
|
27
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
2
|
+
import type { PathScurry } from 'path-scurry';
|
|
3
|
+
import type { Diff } from '../diff.ts';
|
|
4
|
+
export declare const deleteEdges: (diff: Diff, scurry: PathScurry, remover: RollbackRemove) => (() => Promise<unknown>)[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { deleteEdge } from "./delete-edge.js";
|
|
2
|
+
export const deleteEdges = (diff, scurry, remover) => {
|
|
3
|
+
const promises = [];
|
|
4
|
+
for (const edge of diff.edges.delete) {
|
|
5
|
+
// if the edge.from is a deleted node in the store, no need
|
|
6
|
+
// the entire dir will be removed in a later step.
|
|
7
|
+
if (diff.nodes.delete.has(edge.from) && edge.from.inVltStore()) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
promises.push(() => deleteEdge(edge, scurry, remover));
|
|
11
|
+
}
|
|
12
|
+
return promises;
|
|
13
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { deleteEdge } from "./delete-edge.js";
|
|
2
|
+
export const deleteNodes = (diff, remover, scurry) => {
|
|
3
|
+
const store = scurry.resolve('node_modules/.vlt');
|
|
4
|
+
const rmActions = [];
|
|
5
|
+
for (const node of diff.nodes.delete) {
|
|
6
|
+
// do not delete workspaces or link targets
|
|
7
|
+
if (!node.inVltStore())
|
|
8
|
+
continue;
|
|
9
|
+
rmActions.push(remover.rm(scurry.resolve(store, node.id)));
|
|
10
|
+
for (const edge of node.edgesIn) {
|
|
11
|
+
rmActions.push(deleteEdge(edge, scurry, remover));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return rmActions;
|
|
15
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PackageInfoClient } from '@vltpkg/package-info';
|
|
2
|
+
import type { RollbackRemove } from '@vltpkg/rollback-remove';
|
|
3
|
+
import type { SpecOptions } from '@vltpkg/spec';
|
|
4
|
+
import type { PathScurry } from 'path-scurry';
|
|
5
|
+
import type { Diff } from '../diff.ts';
|
|
6
|
+
import type { Node } from '../node.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Result of the extraction operation.
|
|
9
|
+
* Either the extracted package data or an error if extraction failed.
|
|
10
|
+
*/
|
|
11
|
+
export type ExtractResult = {
|
|
12
|
+
success: true;
|
|
13
|
+
node: Node;
|
|
14
|
+
} | {
|
|
15
|
+
success: false;
|
|
16
|
+
node: Node;
|
|
17
|
+
error: unknown;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Extract a single node to the file system.
|
|
21
|
+
* Returns a promise that resolves when the extraction is complete.
|
|
22
|
+
*/
|
|
23
|
+
export declare const extractNode: (node: Node, scurry: PathScurry, remover: RollbackRemove, options: SpecOptions, packageInfo: PackageInfoClient, diff?: Diff) => Promise<ExtractResult>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { hydrate } from '@vltpkg/dep-id';
|
|
2
|
+
import { platformCheck } from '@vltpkg/pick-manifest';
|
|
3
|
+
import { optionalFail } from "./optional-fail.js";
|
|
4
|
+
import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
|
|
5
|
+
/**
|
|
6
|
+
* Returns a function that handles removing
|
|
7
|
+
* a failed optional node from its graph.
|
|
8
|
+
* Returns undefined for non-optional nodes when no diff is provided.
|
|
9
|
+
*/
|
|
10
|
+
const getOptionalFailedNodeRemover = (node, diff) => {
|
|
11
|
+
return (diff ? optionalFail(diff, node)
|
|
12
|
+
: node.isOptional() ?
|
|
13
|
+
() => removeOptionalSubgraph(node.graph, node)
|
|
14
|
+
: undefined);
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Extract a single node to the file system.
|
|
18
|
+
* Returns a promise that resolves when the extraction is complete.
|
|
19
|
+
*/
|
|
20
|
+
export const extractNode = async (node, scurry, remover, options, packageInfo, diff) => {
|
|
21
|
+
node.extracted = true;
|
|
22
|
+
const { manifest = {} } = node;
|
|
23
|
+
const target = node.resolvedLocation(scurry);
|
|
24
|
+
const from = scurry.resolve('');
|
|
25
|
+
const spec = hydrate(node.id, node.name, options);
|
|
26
|
+
const removeOptionalFailedNode = getOptionalFailedNodeRemover(node, diff);
|
|
27
|
+
const { integrity, resolved } = node;
|
|
28
|
+
// Use platform data from node if available (from lockfile), otherwise fall back to manifest
|
|
29
|
+
const platformData = node.platform ?? manifest;
|
|
30
|
+
// Check if we should skip this node due to platform incompatibility or deprecation
|
|
31
|
+
if (removeOptionalFailedNode &&
|
|
32
|
+
(manifest.deprecated ||
|
|
33
|
+
!platformCheck(platformData, process.version, process.platform, process.arch))) {
|
|
34
|
+
removeOptionalFailedNode();
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
node,
|
|
38
|
+
error: new Error('Platform check failed or package deprecated'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await remover.rm(target);
|
|
43
|
+
if (removeOptionalFailedNode) {
|
|
44
|
+
try {
|
|
45
|
+
const result = await packageInfo.extract(spec, target, {
|
|
46
|
+
from,
|
|
47
|
+
integrity,
|
|
48
|
+
resolved,
|
|
49
|
+
});
|
|
50
|
+
// Store computed integrity for git/remote deps
|
|
51
|
+
if (result.integrity && !node.integrity) {
|
|
52
|
+
node.integrity = result.integrity;
|
|
53
|
+
}
|
|
54
|
+
return { success: true, node };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
removeOptionalFailedNode();
|
|
58
|
+
return { success: false, node, error };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const result = await packageInfo.extract(spec, target, {
|
|
63
|
+
from,
|
|
64
|
+
integrity,
|
|
65
|
+
resolved,
|
|
66
|
+
});
|
|
67
|
+
// Store computed integrity for git/remote deps
|
|
68
|
+
if (result.integrity && !node.integrity) {
|
|
69
|
+
node.integrity = result.integrity;
|
|
70
|
+
}
|
|
71
|
+
return { success: true, node };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
/* c8 ignore start */
|
|
76
|
+
if (removeOptionalFailedNode) {
|
|
77
|
+
removeOptionalFailedNode();
|
|
78
|
+
return { success: false, node, error };
|
|
79
|
+
}
|
|
80
|
+
/* c8 ignore stop */
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
};
|