@vltpkg/graph 1.0.0-rc.23 → 1.0.0-rc.25

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,34 @@
1
+ import type { PackageInfoClient } from '@vltpkg/package-info';
2
+ import type { RollbackRemove } from '@vltpkg/rollback-remove';
3
+ import type { DepID } from '@vltpkg/dep-id';
4
+ import type { LoadOptions } from '../actual/load.ts';
5
+ import type { AddImportersDependenciesMap, RemoveImportersDependenciesMap } from '../dependencies.ts';
6
+ import { Diff } from '../diff.ts';
7
+ import type { Graph } from '../graph.ts';
8
+ import type { GraphModifier } from '../modifiers.ts';
9
+ export type ReifyOptions = LoadOptions & {
10
+ add?: AddImportersDependenciesMap;
11
+ allowScripts: string;
12
+ remove?: RemoveImportersDependenciesMap;
13
+ graph: Graph;
14
+ actual?: Graph;
15
+ packageInfo: PackageInfoClient;
16
+ modifiers?: GraphModifier;
17
+ remover: RollbackRemove;
18
+ update?: boolean;
19
+ };
20
+ export type ReifyResult = {
21
+ /**
22
+ * The diff object that was used to reify the project.
23
+ */
24
+ diff: Diff;
25
+ /**
26
+ * Optional queue of DepIDs that requires building (running lifecycle scripts
27
+ * and binary linking) after the reification is complete.
28
+ */
29
+ buildQueue?: DepID[];
30
+ };
31
+ /**
32
+ * Make the current project match the supplied graph.
33
+ */
34
+ export declare const reify: (options: ReifyOptions) => Promise<ReifyResult>;
@@ -0,0 +1,161 @@
1
+ import { graphStep } from '@vltpkg/output';
2
+ import { availableParallelism } from 'node:os';
3
+ import { callLimit } from 'promise-call-limit';
4
+ import { load as loadActual } from "../actual/load.js";
5
+ import { Diff } from "../diff.js";
6
+ import { lockfileData, saveData, saveHidden, } from "../lockfile/save.js";
7
+ import { addEdges } from "./add-edges.js";
8
+ import { addNodes } from "./add-nodes.js";
9
+ import { build } from "./build.js";
10
+ import { deleteEdges } from "./delete-edges.js";
11
+ import { checkNeededBuild } from "./check-needed-build.js";
12
+ import { deleteNodes } from "./delete-nodes.js";
13
+ import { internalHoist } from "./internal-hoist.js";
14
+ import { rollback } from "./rollback.js";
15
+ import { updatePackageJson } from "./update-importers-package-json.js";
16
+ import { copyFileSync } from 'node:fs';
17
+ import { Query } from '@vltpkg/query';
18
+ import { SecurityArchive } from '@vltpkg/security-archive';
19
+ import { binChmodAll } from "./bin-chmod.js";
20
+ const limit = Math.max(availableParallelism() - 1, 1) * 8;
21
+ /**
22
+ * Filter nodes using a DSS query string
23
+ */
24
+ const filterNodesByQuery = async (graph, allowScriptsQuery) => {
25
+ // shortcut no packages included
26
+ if (allowScriptsQuery === ':not(*)' /* c8 ignore next */ ||
27
+ !allowScriptsQuery) {
28
+ return new Set();
29
+ }
30
+ // shortcut all packages included
31
+ if (allowScriptsQuery === '*') {
32
+ return new Set(graph.nodes.keys());
33
+ }
34
+ /* c8 ignore start */
35
+ const securityArchive = Query.hasSecuritySelectors(allowScriptsQuery) ?
36
+ await SecurityArchive.start({
37
+ nodes: [...graph.nodes.values()],
38
+ })
39
+ : undefined;
40
+ /* c8 ignore stop */
41
+ const edges = graph.edges;
42
+ const nodes = new Set(graph.nodes.values());
43
+ const importers = graph.importers;
44
+ const query = new Query({
45
+ edges,
46
+ nodes,
47
+ importers,
48
+ securityArchive,
49
+ });
50
+ const { nodes: resultNodes } = await query.search(allowScriptsQuery, {
51
+ signal: new AbortController().signal,
52
+ });
53
+ return new Set(resultNodes.map(node => node.id));
54
+ };
55
+ /**
56
+ * Make the current project match the supplied graph.
57
+ */
58
+ export const reify = async (options) => {
59
+ const done = graphStep('reify');
60
+ const { graph, scurry, remover } = options;
61
+ const actual = options.actual ??
62
+ loadActual({
63
+ ...options,
64
+ loadManifests: true,
65
+ });
66
+ const diff = new Diff(actual, graph);
67
+ const noModifiedDependencies = !options.add?.modifiedDependencies &&
68
+ !options.remove?.modifiedDependencies;
69
+ const skipOptionalOnly = noModifiedDependencies && diff.optionalOnly;
70
+ const skippable = skipOptionalOnly && !options.update;
71
+ const res = { diff };
72
+ if (!diff.hasChanges() || skippable) {
73
+ // nothing to do, so just return the diff
74
+ done();
75
+ return res;
76
+ }
77
+ let success = false;
78
+ try {
79
+ const { buildQueue } = await reify_(options, diff, remover);
80
+ remover.confirm();
81
+ success = true;
82
+ res.buildQueue = buildQueue;
83
+ }
84
+ finally {
85
+ /* c8 ignore start */
86
+ if (!success) {
87
+ await rollback(remover, diff, scurry).catch(() => { });
88
+ }
89
+ /* c8 ignore stop */
90
+ }
91
+ done();
92
+ return res;
93
+ };
94
+ const reify_ = async (options, diff, remover) => {
95
+ const res = {};
96
+ const { add, remove, packageInfo, packageJson, scurry, allowScripts, } = options;
97
+ const saveImportersPackageJson =
98
+ /* c8 ignore next */
99
+ add?.modifiedDependencies || remove?.modifiedDependencies ?
100
+ updatePackageJson({
101
+ add,
102
+ remove,
103
+ graph: options.graph,
104
+ packageJson,
105
+ })
106
+ : undefined;
107
+ // before anything else happens, grab the ideal tree as it was resolved
108
+ // so that we can store it in the lockfile. We do this here so that
109
+ // any failed/removed optional deps are not reflected in the lockfile
110
+ // data as it is saved.
111
+ const lfData = lockfileData(options);
112
+ const actions = addNodes(diff, scurry, remover, options, packageInfo).concat(deleteEdges(diff, scurry, remover));
113
+ // need to wait, so that the nodes exist to link to
114
+ if (actions.length)
115
+ await callLimit(actions, { limit });
116
+ // create all node_modules symlinks, and link bins to nm/.bin
117
+ const edgeActions = addEdges(diff, scurry, remover);
118
+ if (edgeActions.length)
119
+ await callLimit(edgeActions, { limit });
120
+ await internalHoist(diff.to, options, remover);
121
+ // looks up all nodes setting buildState = 'needed'
122
+ // on nodes that require building
123
+ checkNeededBuild({ diff, scurry, packageJson });
124
+ // Filter nodes allowed to run scripts if allowScripts query is provided
125
+ const allowScriptsNodes = await filterNodesByQuery(diff.to, allowScripts);
126
+ // ensure that all added bins are chmod +x
127
+ await binChmodAll(diff.nodes.add, scurry);
128
+ // run install lifecycle scripts and link any binary files
129
+ await build(diff, packageJson, scurry, allowScriptsNodes);
130
+ // set the buildQueue on the result object containing
131
+ // an array with all the ids of nodes that need building
132
+ res.buildQueue = [...diff.nodes.add]
133
+ .filter(node => node.buildState === 'needed')
134
+ .map(node => node.id);
135
+ // if we had to change the actual graph along the way,
136
+ // make sure we do not leave behind any unreachable nodes
137
+ // TODO: add tests to cover this
138
+ /* c8 ignore start */
139
+ if (diff.hadOptionalFailures) {
140
+ for (const node of options.graph.gc().values()) {
141
+ diff.nodes.add.delete(node);
142
+ diff.nodes.delete.add(node);
143
+ }
144
+ }
145
+ /* c8 ignore stop */
146
+ saveHidden(options);
147
+ // delete garbage from the store.
148
+ const rmActions = deleteNodes(diff, remover, scurry);
149
+ if (rmActions.length)
150
+ await Promise.all(rmActions);
151
+ // updates package.json files if anything was added / removed
152
+ saveImportersPackageJson?.();
153
+ // write the ideal graph data to the lockfile
154
+ saveData(lfData, scurry.resolve('vlt-lock.json'), false);
155
+ // update the store config reference if a config file was used
156
+ if (scurry.lstatSync('vlt.json')) {
157
+ copyFileSync(scurry.resolve('vlt.json'), scurry.resolve('node_modules/.vlt/vlt.json'));
158
+ }
159
+ // returns the result object
160
+ return res;
161
+ };
@@ -0,0 +1,8 @@
1
+ import type { RollbackRemove } from '@vltpkg/rollback-remove';
2
+ import type { Graph } from '../graph.ts';
3
+ import type { Node } from '../node.ts';
4
+ import type { ReifyOptions } from './index.ts';
5
+ type InternalHoistOptions = Pick<ReifyOptions, 'projectRoot' | 'scurry'>;
6
+ export declare const pickNodeToHoist: (nodes: Set<Node>) => Node | undefined;
7
+ export declare const internalHoist: (graph: Graph, options: InternalHoistOptions, remover: RollbackRemove) => Promise<void>;
8
+ export {};
@@ -0,0 +1,133 @@
1
+ import { splitDepID } from '@vltpkg/dep-id';
2
+ import { Version } from '@vltpkg/semver';
3
+ import { mkdir, symlink } from 'node:fs/promises';
4
+ import { dirname, relative } from 'node:path';
5
+ export const pickNodeToHoist = (nodes) => {
6
+ let pick = undefined;
7
+ let pickNode = undefined;
8
+ for (const node of nodes) {
9
+ if (node.importer || !node.inVltStore()) {
10
+ continue;
11
+ }
12
+ const id = splitDepID(node.id);
13
+ if (!pick || !pickNode) {
14
+ pick = id;
15
+ pickNode = node;
16
+ continue;
17
+ }
18
+ // if one is a dep of an importer, privilege that one
19
+ const impDep = [...node.edgesIn].some(n => n.from.importer);
20
+ const pickImpDep = [...pickNode.edgesIn].some(n => n.from.importer);
21
+ if (impDep !== pickImpDep) {
22
+ if (!pickImpDep) {
23
+ pick = id;
24
+ pickNode = node;
25
+ }
26
+ continue;
27
+ }
28
+ if (id[0] === 'registry') {
29
+ if (pick[0] !== 'registry') {
30
+ pick = id;
31
+ pickNode = node;
32
+ continue;
33
+ }
34
+ // both registry.
35
+ // otherwise, privilege the higher-version dependency
36
+ const pickVersion = pickNode.version;
37
+ const nodeVersion = node.version;
38
+ if (pickVersion) {
39
+ if (!nodeVersion)
40
+ continue;
41
+ if (Version.parse(nodeVersion).greaterThan(Version.parse(pickVersion))) {
42
+ pick = id;
43
+ pickNode = node;
44
+ }
45
+ continue;
46
+ }
47
+ else if (nodeVersion) {
48
+ pick = id;
49
+ pickNode = node;
50
+ continue;
51
+ }
52
+ }
53
+ else {
54
+ // current node is not registry
55
+ if (pick[0] === 'registry')
56
+ continue;
57
+ // neither is pick node, select highest lexically sorted
58
+ if (pickNode.id.localeCompare(node.id, 'en') > 0) {
59
+ pick = id;
60
+ pickNode = node;
61
+ }
62
+ }
63
+ }
64
+ return pickNode;
65
+ };
66
+ export const internalHoist = async (graph, options, remover) => {
67
+ // create a list of all the nodes that we need to hoist.
68
+ // For each name, we prioritize registry deps over other types,
69
+ // and higher versions over lower ones. In the case of non-registry
70
+ // deps, we just pick the first item by sorting the DepIDs.
71
+ const links = new Map();
72
+ for (const [name, nodes] of graph.nodesByName) {
73
+ const pickNode = pickNodeToHoist(nodes);
74
+ if (pickNode) {
75
+ let picked = false;
76
+ if (pickNode.edgesIn.size > 0) {
77
+ for (const edgeIn of pickNode.edgesIn) {
78
+ const otherName = edgeIn.name;
79
+ if (otherName !== name && !links.has(otherName)) {
80
+ picked = true;
81
+ links.set(otherName, pickNode);
82
+ }
83
+ }
84
+ }
85
+ if (!picked) {
86
+ links.set(name, pickNode);
87
+ }
88
+ }
89
+ }
90
+ // now we have a list of everything to hoist
91
+ // first, remove anything that is not what we want, and skip
92
+ // anything that would be linking what we already indend to link.
93
+ const { scurry } = options;
94
+ const hoistDir = scurry.cwd.resolve('node_modules/.vlt/node_modules');
95
+ await mkdir(hoistDir.fullpath(), { recursive: true });
96
+ const removes = [];
97
+ for (const entry of await hoistDir.readdir()) {
98
+ const name = entry.name;
99
+ // scoped package namespace
100
+ if (name.startsWith('@')) {
101
+ for (const pkg of await entry.readdir()) {
102
+ await checkExisting(`${name}/${pkg.name}`, pkg, links, removes, scurry, remover);
103
+ }
104
+ }
105
+ else {
106
+ await checkExisting(name, entry, links, removes, scurry, remover);
107
+ }
108
+ }
109
+ await Promise.all(removes);
110
+ const symlinks = [];
111
+ for (const [name, { name: nodeName, id }] of links) {
112
+ const target = scurry.resolve(`node_modules/.vlt/${id}/node_modules/${nodeName}`);
113
+ const path = scurry.resolve(`node_modules/.vlt/node_modules/${name}`);
114
+ if (name.includes('/')) {
115
+ await mkdir(dirname(path), { recursive: true });
116
+ }
117
+ symlinks.push(symlink(relative(dirname(path), target), path, 'dir'));
118
+ }
119
+ await Promise.all(symlinks);
120
+ };
121
+ const checkExisting = async (name, entry, links, removes, scurry, remover) => {
122
+ const target = await entry.readlink();
123
+ const { id } = links.get(name) /* c8 ignore next */ ?? {};
124
+ if (!target ||
125
+ !id ||
126
+ target !==
127
+ scurry.cwd.resolve(`node_modules/.vlt/${id}/node_modules/${name}`)) {
128
+ removes.push(remover.rm(entry.fullpath()));
129
+ }
130
+ else {
131
+ links.delete(name);
132
+ }
133
+ };
@@ -0,0 +1,15 @@
1
+ import type { Diff } from '../diff.ts';
2
+ import type { Node } from '../node.ts';
3
+ /**
4
+ * If the node is optional, then returns an error handler that removes
5
+ * the node and its connected optional subgraph, by moving them into the
6
+ * "deleted" portion of the Diff object.
7
+ * Otherwise, it returns `undefined` so that the promise will simply reject.
8
+ */
9
+ export declare function optionalFail(diff: Diff, node: Node & {
10
+ optional: true;
11
+ }): () => void;
12
+ export declare function optionalFail(diff: Diff, node: Node & {
13
+ optional: false;
14
+ }): undefined;
15
+ export declare function optionalFail(diff: Diff, node: Node): (() => void) | undefined;
@@ -0,0 +1,15 @@
1
+ import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
2
+ export function optionalFail(diff, node) {
3
+ return node.isOptional() ? () => del(diff, node) : undefined;
4
+ }
5
+ const del = (diff, node) => {
6
+ diff.hadOptionalFailures = true;
7
+ for (const del of removeOptionalSubgraph(diff.to, node)) {
8
+ // add it to the set of nodes being deleted
9
+ diff.nodes.delete.add(del);
10
+ // delete it from the set of nodes being added
11
+ diff.nodes.add.delete(del);
12
+ }
13
+ diff.nodes.add.delete(node);
14
+ diff.nodes.delete.add(node);
15
+ };
@@ -0,0 +1,4 @@
1
+ import { RollbackRemove } from '@vltpkg/rollback-remove';
2
+ import type { PathScurry } from 'path-scurry';
3
+ import type { Diff } from '../diff.ts';
4
+ export declare const rollback: (remover: RollbackRemove, diff: Diff, scurry: PathScurry) => Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { RollbackRemove } from '@vltpkg/rollback-remove';
2
+ import { deleteEdge } from "./delete-edge.js";
3
+ export const rollback = async (remover, diff, scurry) => {
4
+ const promises = [];
5
+ const store = scurry.resolve('node_modules/.vlt');
6
+ // remove everything the diff tried to add
7
+ const backRoller = new RollbackRemove();
8
+ for (const node of diff.nodes.add) {
9
+ if (!node.inVltStore())
10
+ continue;
11
+ const path = scurry.resolve(store, node.id);
12
+ /* c8 ignore next */
13
+ promises.push(backRoller.rm(path).catch(() => { }));
14
+ }
15
+ for (const edge of diff.edges.add) {
16
+ promises.push(deleteEdge(edge, scurry, backRoller));
17
+ }
18
+ /* c8 ignore next */
19
+ await Promise.all(promises).catch(() => { });
20
+ backRoller.confirm();
21
+ /* c8 ignore next */
22
+ await remover.rollback().catch(() => { });
23
+ };
@@ -0,0 +1,35 @@
1
+ import type { PackageJson } from '@vltpkg/package-json';
2
+ import type { AddImportersDependenciesMap, RemoveImportersDependenciesMap } from '../dependencies.ts';
3
+ import type { Graph } from '../graph.ts';
4
+ export type UpdatePackageJsonOptions = {
5
+ /**
6
+ * A `Map` in which keys are {@link DepID} linking to another `Map` in which
7
+ * keys are the dependency names and values are {@link Dependency}. This
8
+ * structure represents dependencies that need to be added to the importer
9
+ * represented by {@link DepID}.
10
+ */
11
+ add?: AddImportersDependenciesMap;
12
+ /**
13
+ * A `Map` object representing nodes to be removed from the ideal graph.
14
+ * Each {@link DepID} key represents an importer node and the `Set` of
15
+ * dependency names to be removed from its dependency list.
16
+ */
17
+ remove?: RemoveImportersDependenciesMap;
18
+ /**
19
+ * The {@link Graph} instance that contain the importer nodes to which
20
+ * the manifest data (and it's corresponding `package.json` file) are
21
+ * going to be updated.
22
+ */
23
+ graph: Graph;
24
+ /**
25
+ * An instance of {@link PackageJson} to use when writing updated manifest
26
+ * data to `package.json` files. It's necessary that this is the same
27
+ * instance used to load these `package.json` files previously.
28
+ */
29
+ packageJson: PackageJson;
30
+ };
31
+ /**
32
+ * Updates nodes of a provided {@link Graph} accordingly to the
33
+ * provided add or remove arguments.
34
+ */
35
+ export declare const updatePackageJson: ({ add, graph, packageJson, remove, }: UpdatePackageJsonOptions) => () => void;
@@ -0,0 +1,122 @@
1
+ import { splitDepID } from '@vltpkg/dep-id';
2
+ import { error } from '@vltpkg/error-cause';
3
+ import { longDependencyTypes } from '@vltpkg/types';
4
+ import { resolveSaveType } from "../resolve-save-type.js";
5
+ import { calculateSaveValue } from "./calculate-save-value.js";
6
+ const depTypesMap = new Map([
7
+ ['prod', 'dependencies'],
8
+ ['dev', 'devDependencies'],
9
+ ['peer', 'peerDependencies'],
10
+ ['peerOptional', 'peerDependencies'],
11
+ ['optional', 'optionalDependencies'],
12
+ ]);
13
+ const addOrRemoveDeps = (nodeId, graph, addOrRemove) => {
14
+ const node = graph.nodes.get(nodeId);
15
+ if (!node) {
16
+ throw error('Failed to retrieve node', {
17
+ found: nodeId,
18
+ });
19
+ }
20
+ const manifest = node.manifest;
21
+ if (!manifest) {
22
+ throw error('Could not find manifest data for node', {
23
+ found: nodeId,
24
+ });
25
+ }
26
+ const deps = addOrRemove?.get(nodeId);
27
+ /* c8 ignore start -- impossible but TS doesn't know that */
28
+ if (!deps) {
29
+ throw error('Failed to retrieve added deps info', {
30
+ manifest,
31
+ });
32
+ }
33
+ let manifestChanged = false;
34
+ /* c8 ignore stop */
35
+ for (const deleteNameOrAddItem of deps) {
36
+ if (typeof deleteNameOrAddItem === 'string') {
37
+ const name = deleteNameOrAddItem;
38
+ for (const depType of longDependencyTypes) {
39
+ if (manifest[depType]?.[name]) {
40
+ delete manifest[depType][name];
41
+ manifestChanged = true;
42
+ }
43
+ }
44
+ if (manifest.peerDependenciesMeta?.[name]) {
45
+ delete manifest.peerDependenciesMeta[name];
46
+ manifestChanged = true;
47
+ }
48
+ }
49
+ else {
50
+ const [name, dep] = deleteNameOrAddItem;
51
+ // peerOptional also needs to add peerDependenciesMeta entry
52
+ const depTypeShort = resolveSaveType(node, name, dep.type);
53
+ const depType = depTypesMap.get(depTypeShort);
54
+ if (!depType) {
55
+ throw error('Failed to retrieve dependency type', {
56
+ validOptions: [...depTypesMap.keys()],
57
+ found: dep.type,
58
+ });
59
+ }
60
+ const n = node.edgesOut.get(name)?.to;
61
+ if (!n) {
62
+ throw error('Dependency node could not be found');
63
+ }
64
+ const [nodeType] = splitDepID(n.id);
65
+ for (const dtype of longDependencyTypes) {
66
+ if (dtype === depType || !manifest[dtype])
67
+ continue;
68
+ delete manifest[dtype][name];
69
+ }
70
+ if (depTypeShort === 'peerOptional') {
71
+ manifest.peerDependenciesMeta ??= {};
72
+ manifest.peerDependenciesMeta[name] = { optional: true };
73
+ }
74
+ else if (manifest.peerDependenciesMeta?.[name]) {
75
+ delete manifest.peerDependenciesMeta[name];
76
+ }
77
+ const dependencies = manifest[depType] ?? (manifest[depType] = {});
78
+ // check to see if we need to save a different version
79
+ // - If you install a single specific version, that is deliberate,
80
+ // we save that exact version, no matter what.
81
+ // - If the requested spec matches the manifest, make no change
82
+ // If the requested spec had no bareSpec, and the manifest has
83
+ // a dependency entry, make no change.
84
+ // If the requested spec has a bareSpec that did NOT match the manifest,
85
+ // then update it.
86
+ // If the manifest does not contain anything, then update it.
87
+ // Only for registry dependencies
88
+ const existing = dependencies[name];
89
+ const saveValue = calculateSaveValue(nodeType, dep.spec, existing, n.version);
90
+ dependencies[name] = saveValue;
91
+ manifestChanged = manifestChanged || saveValue !== existing;
92
+ }
93
+ }
94
+ return manifestChanged ? manifest : undefined;
95
+ };
96
+ /**
97
+ * Updates nodes of a provided {@link Graph} accordingly to the
98
+ * provided add or remove arguments.
99
+ */
100
+ export const updatePackageJson = ({ add, graph, packageJson, remove, }) => {
101
+ const manifestsToUpdate = new Set();
102
+ const operations = new Set([add, remove]);
103
+ for (const operation of operations) {
104
+ if (operation) {
105
+ // These node ids are from either importer nodes or dependencies
106
+ // that are nested folders from which the user can also add new
107
+ // dependencies to
108
+ for (const nodeId of operation.keys()) {
109
+ const manifest = addOrRemoveDeps(nodeId, graph, operation);
110
+ if (manifest) {
111
+ manifestsToUpdate.add(manifest);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ const commit = () => {
117
+ for (const manifest of manifestsToUpdate) {
118
+ packageJson.save(manifest);
119
+ }
120
+ };
121
+ return commit;
122
+ };
@@ -0,0 +1,33 @@
1
+ import type { Graph } from './graph.ts';
2
+ import type { Node } from './node.ts';
3
+ /**
4
+ * Remove the affected optional nodes starting from a given optional node
5
+ * that failed to resolve/install.
6
+ *
7
+ * Removing these dependents will frequently leave optional *dependencies*
8
+ * behind that are made unreachable from any project importer. For performance
9
+ * reasons, `graph.gc()` is not called automatically by this method, since
10
+ * multiple optional subgraphs may need to be removed in a single operation,
11
+ * and the mark-and-sweep garbage collection (a) can be excessively expensive,
12
+ * and (b) may need to communicate the set of garbage-collected nodes
13
+ * for cleanup (as in the case of optional node build failures during
14
+ * reification).
15
+ */
16
+ export declare const removeOptionalSubgraph: (graph: Graph, startingNode: Node & {
17
+ optional: true;
18
+ }) => Set<Node>;
19
+ /**
20
+ * Given a starting node, this generator will walk back from the optional node
21
+ * to all of its optional dependents, emitting each one, and skipping any
22
+ * cycles.
23
+ *
24
+ * Note that when these are removed, it may create unreachable nodes in the
25
+ * graph! Be sure to call `graph.gc()` if these are removed.
26
+ */
27
+ export declare function findOptionalSubgraph(node: Node & {
28
+ optional: true;
29
+ }, seen?: Set<Node & {
30
+ optional: true;
31
+ }>): Generator<Node & {
32
+ optional: true;
33
+ }, void>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Remove the affected optional nodes starting from a given optional node
3
+ * that failed to resolve/install.
4
+ *
5
+ * Removing these dependents will frequently leave optional *dependencies*
6
+ * behind that are made unreachable from any project importer. For performance
7
+ * reasons, `graph.gc()` is not called automatically by this method, since
8
+ * multiple optional subgraphs may need to be removed in a single operation,
9
+ * and the mark-and-sweep garbage collection (a) can be excessively expensive,
10
+ * and (b) may need to communicate the set of garbage-collected nodes
11
+ * for cleanup (as in the case of optional node build failures during
12
+ * reification).
13
+ */
14
+ export const removeOptionalSubgraph = (graph, startingNode) => {
15
+ const removed = new Set();
16
+ for (const node of findOptionalSubgraph(startingNode)) {
17
+ graph.removeNode(node, undefined, true);
18
+ removed.add(node);
19
+ }
20
+ return removed;
21
+ };
22
+ /**
23
+ * Given a starting node, this generator will walk back from the optional node
24
+ * to all of its optional dependents, emitting each one, and skipping any
25
+ * cycles.
26
+ *
27
+ * Note that when these are removed, it may create unreachable nodes in the
28
+ * graph! Be sure to call `graph.gc()` if these are removed.
29
+ */
30
+ export function* findOptionalSubgraph(node, seen = new Set()) {
31
+ // already visited, prevent looping
32
+ if (seen.has(node))
33
+ return;
34
+ // the node itself is part of the subgraph, of course
35
+ seen.add(node);
36
+ yield node;
37
+ // and any of its dependents that are also optional
38
+ for (const { from, optional } of node.edgesIn) {
39
+ // if it's an optional *edge*, we don't need to remove it, because it
40
+ // doesn't need the node anyway. But if the node is optional and the
41
+ // dep isn't, then we do need to remove it.
42
+ if (from.isOptional() && !optional) {
43
+ for (const dep of findOptionalSubgraph(from, seen))
44
+ yield dep;
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,5 @@
1
+ import type { DependencySaveType, DependencyTypeShort, NodeLike } from '@vltpkg/types';
2
+ /**
3
+ * Resolve a {@link DependencySaveType} to a {@link DependencyTypeShort}
4
+ */
5
+ export declare const resolveSaveType: (node: NodeLike, name: string, saveType: DependencySaveType) => DependencyTypeShort;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Resolve a {@link DependencySaveType} to a {@link DependencyTypeShort}
3
+ */
4
+ export const resolveSaveType = (node, name, saveType) => saveType !== 'implicit' ? saveType : ((node.edgesOut.get(name)?.type ?? 'prod'));
@@ -0,0 +1,2 @@
1
+ import type { NodeLike } from '@vltpkg/types';
2
+ export declare const stringifyNode: (node?: NodeLike) => string;