@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.
Files changed (109) hide show
  1. package/dist/actual/load.d.ts +107 -0
  2. package/dist/actual/load.js +336 -0
  3. package/dist/browser.d.ts +14 -0
  4. package/dist/browser.js +16 -0
  5. package/dist/build.d.ts +28 -0
  6. package/dist/build.js +78 -0
  7. package/dist/dependencies.d.ts +65 -0
  8. package/dist/dependencies.js +111 -0
  9. package/dist/diff.d.ts +119 -0
  10. package/dist/diff.js +151 -0
  11. package/dist/edge.d.ts +46 -0
  12. package/dist/edge.js +77 -0
  13. package/dist/fixup-added-names.d.ts +18 -0
  14. package/dist/fixup-added-names.js +46 -0
  15. package/dist/graph.d.ts +153 -0
  16. package/dist/graph.js +444 -0
  17. package/dist/ideal/append-nodes.d.ts +31 -0
  18. package/dist/ideal/append-nodes.js +560 -0
  19. package/dist/ideal/build-ideal-from-starting-graph.d.ts +14 -0
  20. package/dist/ideal/build-ideal-from-starting-graph.js +69 -0
  21. package/dist/ideal/build.d.ts +40 -0
  22. package/dist/ideal/build.js +84 -0
  23. package/dist/ideal/get-importer-specs.d.ts +20 -0
  24. package/dist/ideal/get-importer-specs.js +180 -0
  25. package/dist/ideal/peers.d.ts +160 -0
  26. package/dist/ideal/peers.js +696 -0
  27. package/dist/ideal/refresh-ideal-graph.d.ts +43 -0
  28. package/dist/ideal/refresh-ideal-graph.js +62 -0
  29. package/dist/ideal/remove-satisfied-specs.d.ts +7 -0
  30. package/dist/ideal/remove-satisfied-specs.js +34 -0
  31. package/dist/ideal/sorting.d.ts +45 -0
  32. package/dist/ideal/sorting.js +70 -0
  33. package/dist/ideal/types.d.ts +107 -0
  34. package/dist/ideal/types.js +1 -0
  35. package/dist/index.d.ts +38 -0
  36. package/dist/index.js +32 -0
  37. package/dist/install.d.ts +19 -0
  38. package/dist/install.js +208 -0
  39. package/dist/lockfile/load-edges.d.ts +11 -0
  40. package/dist/lockfile/load-edges.js +105 -0
  41. package/dist/lockfile/load-nodes.d.ts +4 -0
  42. package/dist/lockfile/load-nodes.js +101 -0
  43. package/dist/lockfile/load.d.ts +45 -0
  44. package/dist/lockfile/load.js +84 -0
  45. package/dist/lockfile/save.d.ts +30 -0
  46. package/dist/lockfile/save.js +174 -0
  47. package/dist/lockfile/types.d.ts +95 -0
  48. package/dist/lockfile/types.js +49 -0
  49. package/dist/modifiers.d.ts +188 -0
  50. package/dist/modifiers.js +329 -0
  51. package/dist/node.d.ts +234 -0
  52. package/dist/node.js +388 -0
  53. package/dist/non-empty-list.d.ts +2 -0
  54. package/dist/non-empty-list.js +2 -0
  55. package/dist/reify/add-edge.d.ts +9 -0
  56. package/dist/reify/add-edge.js +71 -0
  57. package/dist/reify/add-edges.d.ts +4 -0
  58. package/dist/reify/add-edges.js +12 -0
  59. package/dist/reify/add-nodes.d.ts +6 -0
  60. package/dist/reify/add-nodes.js +16 -0
  61. package/dist/reify/bin-chmod.d.ts +10 -0
  62. package/dist/reify/bin-chmod.js +38 -0
  63. package/dist/reify/build.d.ts +13 -0
  64. package/dist/reify/build.js +111 -0
  65. package/dist/reify/calculate-save-value.d.ts +2 -0
  66. package/dist/reify/calculate-save-value.js +50 -0
  67. package/dist/reify/check-needed-build.d.ts +34 -0
  68. package/dist/reify/check-needed-build.js +71 -0
  69. package/dist/reify/delete-edge.d.ts +4 -0
  70. package/dist/reify/delete-edge.js +27 -0
  71. package/dist/reify/delete-edges.d.ts +4 -0
  72. package/dist/reify/delete-edges.js +13 -0
  73. package/dist/reify/delete-nodes.d.ts +4 -0
  74. package/dist/reify/delete-nodes.js +15 -0
  75. package/dist/reify/extract-node.d.ts +23 -0
  76. package/dist/reify/extract-node.js +83 -0
  77. package/dist/reify/index.d.ts +34 -0
  78. package/dist/reify/index.js +161 -0
  79. package/dist/reify/internal-hoist.d.ts +8 -0
  80. package/dist/reify/internal-hoist.js +133 -0
  81. package/dist/reify/optional-fail.d.ts +15 -0
  82. package/dist/reify/optional-fail.js +15 -0
  83. package/dist/reify/rollback.d.ts +4 -0
  84. package/dist/reify/rollback.js +23 -0
  85. package/dist/reify/update-importers-package-json.d.ts +35 -0
  86. package/dist/reify/update-importers-package-json.js +122 -0
  87. package/dist/remove-optional-subgraph.d.ts +33 -0
  88. package/dist/remove-optional-subgraph.js +47 -0
  89. package/dist/resolve-save-type.d.ts +5 -0
  90. package/dist/resolve-save-type.js +4 -0
  91. package/dist/stringify-node.d.ts +2 -0
  92. package/dist/stringify-node.js +32 -0
  93. package/dist/transfer-data/load.d.ts +43 -0
  94. package/dist/transfer-data/load.js +175 -0
  95. package/dist/uninstall.d.ts +14 -0
  96. package/dist/uninstall.js +75 -0
  97. package/dist/update.d.ts +12 -0
  98. package/dist/update.js +73 -0
  99. package/dist/virtual-root.d.ts +15 -0
  100. package/dist/virtual-root.js +78 -0
  101. package/dist/visualization/human-readable-output.d.ts +26 -0
  102. package/dist/visualization/human-readable-output.js +163 -0
  103. package/dist/visualization/json-output.d.ts +41 -0
  104. package/dist/visualization/json-output.js +50 -0
  105. package/dist/visualization/mermaid-output.d.ts +17 -0
  106. package/dist/visualization/mermaid-output.js +170 -0
  107. package/dist/visualization/object-like-output.d.ts +2 -0
  108. package/dist/visualization/object-like-output.js +47 -0
  109. 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,2 @@
1
+ import type { Spec } from '@vltpkg/spec';
2
+ export declare const calculateSaveValue: (nodeType: string, spec: Spec, existing: string | undefined, nodeVersion: string | undefined) => string;
@@ -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,4 @@
1
+ import type { RollbackRemove } from '@vltpkg/rollback-remove';
2
+ import type { PathScurry } from 'path-scurry';
3
+ import type { Edge } from '../edge.ts';
4
+ export declare const deleteEdge: (edge: Edge, scurry: PathScurry, remover: RollbackRemove) => Promise<void>;
@@ -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,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 deleteNodes: (diff: Diff, remover: RollbackRemove, scurry: PathScurry) => Promise<unknown>[];
@@ -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
+ };