@vltpkg/graph 1.3.7 → 1.4.0

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.
@@ -246,6 +246,10 @@ const parseDir = (options, scurry, packageJson, depsFound, graph, fromNode, curr
246
246
  });
247
247
  // Check for active modifiers and replace spec for missing dependencies
248
248
  const { spec: modifiedSpec, queryModifier } = maybeApplyModifierToSpec(spec, name, modifierRefs);
249
+ // a `-` modifier removes the dependency, so it is not missing
250
+ if (modifiedSpec !== spec && modifiedSpec.bareSpec === '-') {
251
+ continue;
252
+ }
249
253
  spec = modifiedSpec;
250
254
  graph.placePackage(fromNode, depType, spec, undefined, undefined, joinExtra({ modifier: queryModifier }));
251
255
  }
package/dist/graph.d.ts CHANGED
@@ -100,9 +100,10 @@ export declare class Graph implements GraphLike {
100
100
  /**
101
101
  * Whether the lockfile on disk no longer matches this graph even
102
102
  * though no node changed, e.g. an importer edge spec was rewritten to
103
- * the value saved to `package.json`, or the config options the graph
104
- * was built with differ from the ones stored in the lockfile. Reify
105
- * saves the lockfiles from its no-diff early return when set.
103
+ * the value saved to `package.json`, a node dev/optional flag was
104
+ * re-derived, or the config options the graph was built with differ
105
+ * from the ones stored in the lockfile. Reify saves the lockfiles
106
+ * from its no-diff early return when set.
106
107
  */
107
108
  lockfileStale: boolean;
108
109
  /**
@@ -126,6 +127,13 @@ export declare class Graph implements GraphLike {
126
127
  * Get the next peer context index.
127
128
  */
128
129
  nextPeerContextIndex(): number;
130
+ /**
131
+ * Re-derive node `dev`/`optional` flags: a node is dev/optional only
132
+ * when every importer path crosses a dev/optional edge. Placement only
133
+ * ever lowers these flags, so removed or retyped edges leave them
134
+ * stale. Sets `lockfileStale` when any flag changes.
135
+ */
136
+ recomputeFlags(): void;
129
137
  /**
130
138
  * Delete all nodes and edges that are unreachable from the importers.
131
139
  * The collection of deleted nodes is returned.
package/dist/graph.js CHANGED
@@ -110,9 +110,10 @@ export class Graph {
110
110
  /**
111
111
  * Whether the lockfile on disk no longer matches this graph even
112
112
  * though no node changed, e.g. an importer edge spec was rewritten to
113
- * the value saved to `package.json`, or the config options the graph
114
- * was built with differ from the ones stored in the lockfile. Reify
115
- * saves the lockfiles from its no-diff early return when set.
113
+ * the value saved to `package.json`, a node dev/optional flag was
114
+ * re-derived, or the config options the graph was built with differ
115
+ * from the ones stored in the lockfile. Reify saves the lockfiles
116
+ * from its no-diff early return when set.
116
117
  */
117
118
  lockfileStale = false;
118
119
  /**
@@ -195,6 +196,35 @@ export class Graph {
195
196
  nextPeerContextIndex() {
196
197
  return ++this.currentPeerContextIndex;
197
198
  }
199
+ /**
200
+ * Re-derive node `dev`/`optional` flags: a node is dev/optional only
201
+ * when every importer path crosses a dev/optional edge. Placement only
202
+ * ever lowers these flags, so removed or retyped edges leave them
203
+ * stale. Sets `lockfileStale` when any flag changes.
204
+ */
205
+ recomputeFlags() {
206
+ const reach = (skip) => {
207
+ const seen = new Set(this.importers);
208
+ for (const node of seen) {
209
+ for (const edge of node.edgesOut.values()) {
210
+ if (edge.to && !skip(edge))
211
+ seen.add(edge.to);
212
+ }
213
+ }
214
+ return seen;
215
+ };
216
+ const nonDev = reach(edge => edge.dev);
217
+ const nonOptional = reach(edge => edge.optional);
218
+ for (const node of this.nodes.values()) {
219
+ const dev = !nonDev.has(node);
220
+ const optional = !nonOptional.has(node);
221
+ if (node.dev !== dev || node.optional !== optional) {
222
+ node.dev = dev;
223
+ node.optional = optional;
224
+ this.lockfileStale = true;
225
+ }
226
+ }
227
+ }
198
228
  /**
199
229
  * Delete all nodes and edges that are unreachable from the importers.
200
230
  * The collection of deleted nodes is returned.
@@ -228,6 +228,10 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
228
228
  spec = activeModifier.modifier.spec;
229
229
  // bareSpec of '-' means "remove this dependency"
230
230
  if (spec.bareSpec === '-') {
231
+ // nothing to install or save for an explicit request
232
+ if (isExplicit) {
233
+ throw error(`Cannot add ${originalSpec.name}: it is removed by a graph modifier`, { code: 'ECONFIG', found: queryModifier });
234
+ }
231
235
  continue;
232
236
  }
233
237
  }
@@ -366,6 +370,8 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
366
370
  }
367
371
  throw er;
368
372
  });
373
+ // awaited below; a throw before that must not leave it unhandled
374
+ manifestPromise.catch(() => { });
369
375
  const fetchTask = {
370
376
  spec,
371
377
  type,
@@ -380,6 +386,8 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
380
386
  };
381
387
  fetchTasks.push(fetchTask);
382
388
  }
389
+ // fail on the first rejection, not once the ones before it settle
390
+ await Promise.all(fetchTasks.map(t => t.manifestPromise));
383
391
  // Create placement tasks from fetch tasks
384
392
  for (const fetchTask of fetchTasks) {
385
393
  const manifest = await fetchTask.manifestPromise;
@@ -79,6 +79,14 @@ export const buildIdealFromStartingGraph = async (options) => {
79
79
  if (importerSpecs.staleSpecs.size) {
80
80
  options.graph.lockfileStale = true;
81
81
  }
82
+ // importer edges a `-` modifier now removes: drop them, gc() takes
83
+ // whatever only they reached
84
+ for (const edge of importerSpecs.removedEdges) {
85
+ edge.from.edgesOut.delete(edge.name);
86
+ edge.to?.edgesIn.delete(edge);
87
+ options.graph.edges.delete(edge);
88
+ options.graph.lockfileStale = true;
89
+ }
82
90
  // refreshs the current graph adding the nodes marked for addition
83
91
  // and removing the ones marked for removal, while also recalculating
84
92
  // peer dependencies and default locations
@@ -93,5 +101,6 @@ export const buildIdealFromStartingGraph = async (options) => {
93
101
  hasProvisionalPeerIds(options.graph)) {
94
102
  await movePeerStoreDirs(canonicalizePeerIds(options.graph), options);
95
103
  }
104
+ options.graph.recomputeFlags();
96
105
  return options.graph;
97
106
  };
@@ -18,6 +18,7 @@ export type GetImporterSpecsOptions = BuildIdealAddOptions & BuildIdealFromGraph
18
18
  export declare const getImporterSpecs: (options: GetImporterSpecsOptions) => {
19
19
  add: AddImportersDependenciesMap;
20
20
  remove: RemoveImportersDependenciesMap;
21
+ removedEdges: Set<Edge>;
21
22
  staleSpecs: Map<Edge, Dependency>;
22
23
  transientAdd: TransientAddMap;
23
24
  transientRemove: TransientRemoveMap;
@@ -25,6 +25,8 @@ export const getImporterSpecs = (options) => {
25
25
  const { add, graph, remove } = options;
26
26
  const addResult = new AddImportersDependenciesMapImpl();
27
27
  const removeResult = new RemoveImportersDependenciesMapImpl();
28
+ // importer edges a `-` modifier now removes, package.json keeps them
29
+ const removedEdges = new Set();
28
30
  // traverse the list of importers in the starting graph
29
31
  for (const importer of graph.importers) {
30
32
  // uses a Map keying to the spec.name in order to easily make sure there's
@@ -71,6 +73,13 @@ export const getImporterSpecs = (options) => {
71
73
  continue;
72
74
  }
73
75
  const spec = Spec.parse(depName, depSpec, options);
76
+ // a `-` modifier removes this dependency, nothing to place; an
77
+ // edge from before (e.g. a qualifier now matching) has to go
78
+ if (options.modifiers?.removesImporterEdge(importer, spec)) {
79
+ if (edge)
80
+ removedEdges.add(edge);
81
+ continue;
82
+ }
74
83
  // if a workspace dep references a workspace that no longer exists
75
84
  // (folder was removed), mark it for removal instead of trying to
76
85
  // resolve it — the workspace can't be satisfied
@@ -212,6 +221,7 @@ export const getImporterSpecs = (options) => {
212
221
  return {
213
222
  add: addResult,
214
223
  remove: removeResult,
224
+ removedEdges,
215
225
  staleSpecs,
216
226
  transientAdd,
217
227
  transientRemove,
@@ -99,7 +99,12 @@ export const refreshIdealGraph = async ({ add, graph, modifiers, packageInfo, sc
99
99
  const modifierRefs = modifiers?.tryDependencies(importer, deps);
100
100
  // Add new nodes for packages defined in the dependencies list fetching
101
101
  // metadata from the registry manifests and updating the graph
102
- await appendNodes(packageInfo, graph, importer, deps, scurry, specOptions, seen, addedDeps, modifiers, modifierRefs, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit);
102
+ await appendNodes(packageInfo, graph, importer, deps, scurry, specOptions, seen, addedDeps, modifiers, modifierRefs, extractPromises, actual, seenExtracted, remover, transientAdd, transientRemove, explicit).catch((er) => {
103
+ // extractions still in flight are moot now
104
+ for (const p of extractPromises ?? [])
105
+ p.catch(() => { });
106
+ throw er;
107
+ });
103
108
  }
104
109
  // locked resolutions only apply to the rebuild that captured them
105
110
  graph.lockedResolutions = undefined;
package/dist/install.js CHANGED
@@ -7,6 +7,7 @@ import { error } from '@vltpkg/error-cause';
7
7
  import { asError } from '@vltpkg/types';
8
8
  import { getDependencies } from "./dependencies.js";
9
9
  import { RollbackRemove } from '@vltpkg/rollback-remove';
10
+ import { joinDepIDTuple } from '@vltpkg/dep-id';
10
11
  import { existsSync, rmSync } from 'node:fs';
11
12
  import { resolve } from 'node:path';
12
13
  import { load as loadVirtual, loadData } from "./lockfile/load.js";
@@ -109,6 +110,7 @@ export const install = async (options, add) => {
109
110
  }
110
111
  if (importerSpecs.add.modifiedDependencies ||
111
112
  importerSpecs.remove.modifiedDependencies ||
113
+ importerSpecs.removedEdges.size > 0 ||
112
114
  specChanges.length > 0 ||
113
115
  lockfileGraph.optionsChanged) {
114
116
  const details = [];
@@ -141,6 +143,9 @@ export const install = async (options, add) => {
141
143
  details.push(` ${location}: ${deps.size} ${depLabelRemove} to remove (${depNames.join(', ')})`);
142
144
  }
143
145
  }
146
+ for (const edge of importerSpecs.removedEdges) {
147
+ details.push(` ${edge.from.location}: ${edge.name} is removed by a graph modifier`);
148
+ }
144
149
  const lockfilePath = resolve(options.projectRoot, 'vlt-lock.json');
145
150
  throw error('Lockfile is out of sync with package.json. Run "vlt install" to update.\n' +
146
151
  details.join('\n'), {
@@ -148,6 +153,23 @@ export const install = async (options, add) => {
148
153
  });
149
154
  }
150
155
  }
156
+ // an explicit add of a dep a `-` modifier removes fails before
157
+ // anything on disk is touched
158
+ if (modifiers && add?.modifiedDependencies) {
159
+ const wsIds = new Set([...(fullMonorepo?.values() ?? [])].map(ws => ws.id));
160
+ for (const [id, deps] of add) {
161
+ const mainImporter = id === joinDepIDTuple(['file', '.']);
162
+ // nested folders are no importers, appendNodes checks those
163
+ if (!mainImporter && !wsIds.has(id))
164
+ continue;
165
+ for (const { spec } of deps.values()) {
166
+ const found = modifiers.removesImporterEdge({ importer: true, mainImporter }, spec);
167
+ if (found) {
168
+ throw error(`Cannot add ${spec.name}: it is removed by a graph modifier`, { code: 'ECONFIG', found });
169
+ }
170
+ }
171
+ }
172
+ }
151
173
  const remover = new RollbackRemove();
152
174
  if (options.cleanInstall) {
153
175
  const nodeModulesPath = resolve(options.projectRoot, 'node_modules');
@@ -52,11 +52,9 @@ export const loadEdges = (graph, edges, options, cache) => {
52
52
  const [depType, valRest] = fastSplit(value, ' ', 2);
53
53
  const vrSplit = valRest?.lastIndexOf(' ') ?? -1;
54
54
  // not a valid edge record
55
- /* c8 ignore start */
56
55
  if (!valRest || !depType || !fromId || !specName || vrSplit < 1) {
57
56
  continue;
58
57
  }
59
- /* c8 ignore stop */
60
58
  // Validate dependency type early
61
59
  if (!isDependencyTypeShort(depType)) {
62
60
  throw error('Found unsupported dependency type in lockfile', {
@@ -126,6 +126,13 @@ export declare class GraphModifier {
126
126
  * root's own b edge is still the manifest's to validate and heal.
127
127
  */
128
128
  targetsImporterEdge(importer: Node, spec: Spec): boolean;
129
+ /**
130
+ * Selector of the modifier that wins the direct `importer -> spec.name`
131
+ * edge, if it removes it (value `-`). Same pick as tryNewDependency:
132
+ * complete entries whose qualifier accepts the spec, highest
133
+ * specificity, importer-anchored before lone on ties.
134
+ */
135
+ removesImporterEdge(importer: Pick<Node, 'importer' | 'mainImporter'>, spec: Spec): string | undefined;
129
136
  /**
130
137
  * Loads the modifiers defined in `vlt.json` into memory.
131
138
  */
package/dist/modifiers.js CHANGED
@@ -115,6 +115,30 @@ export class GraphModifier {
115
115
  }
116
116
  return false;
117
117
  }
118
+ /**
119
+ * Selector of the modifier that wins the direct `importer -> spec.name`
120
+ * edge, if it removes it (value `-`). Same pick as tryNewDependency:
121
+ * complete entries whose qualifier accepts the spec, highest
122
+ * specificity, importer-anchored before lone on ties.
123
+ */
124
+ removesImporterEdge(importer, spec) {
125
+ const anchored = [];
126
+ const lone = [];
127
+ for (const mod of this.#modifiers) {
128
+ const { last } = mod.breadcrumb;
129
+ if (last.name !== spec.name || !matchesQualifier(last, spec))
130
+ continue;
131
+ const { prev } = last;
132
+ if (!prev)
133
+ lone.push(mod);
134
+ else if (!prev.prev && matchesImporter(prev, importer))
135
+ anchored.push(mod);
136
+ }
137
+ const all = [...anchored, ...lone];
138
+ const [winner] = specificitySort(all.map(m => m.breadcrumb));
139
+ const mod = all.find(m => m.breadcrumb === winner);
140
+ return mod?.value === '-' ? mod.query : undefined;
141
+ }
118
142
  /**
119
143
  * Loads the modifiers defined in `vlt.json` into memory.
120
144
  */
@@ -89,15 +89,20 @@ export const reify = async (options) => {
89
89
  // Otherwise a first install of an optional-only tree would never extract.
90
90
  const hasLockfiles = !!scurry.lstatSync('vlt-lock.json') &&
91
91
  !!scurry.lstatSync('node_modules/.vlt-lock.json');
92
- const skipOptionalOnly = noModifiedDependencies && diff.optionalOnly;
92
+ // a removal is never optional-only: deleting is what reify is for
93
+ const skipOptionalOnly = noModifiedDependencies &&
94
+ diff.optionalOnly &&
95
+ !diff.nodes.delete.size &&
96
+ !diff.edges.delete.size;
93
97
  const skippable = skipOptionalOnly && !options.update && hasLockfiles;
94
98
  const res = { diff };
95
99
  if (!diff.hasChanges() || skippable) {
96
100
  // Even when there are no changes to reify, ensure lockfiles
97
101
  // exist on disk. This handles the case where a project has no
98
102
  // dependencies (or only workspace importers) but still needs
99
- // lockfiles written on the first install. A stale flag means an
100
- // edge spec was rewritten above, which the lockfiles have to carry.
103
+ // lockfiles written on the first install. A stale flag means
104
+ // lockfile data changed w/o a node diff (edge spec/type, options,
105
+ // dev/optional flags), which the lockfiles have to carry.
101
106
  if (!hasLockfiles || graph.lockfileStale) {
102
107
  // with no diff at all the ideal graph is the actual graph, so the
103
108
  // hidden lockfile can be written from it; an optional-only diff
package/dist/uninstall.js CHANGED
@@ -5,12 +5,18 @@ import { reify } from "./reify/index.js";
5
5
  import { lockfile } from "./index.js";
6
6
  import { updatePackageJson } from "./reify/update-importers-package-json.js";
7
7
  import { RollbackRemove } from '@vltpkg/rollback-remove';
8
+ import { Monorepo } from '@vltpkg/workspaces';
8
9
  import { existsSync, rmSync } from 'node:fs';
9
10
  import { resolve } from 'node:path';
10
11
  export const uninstall = async (options, remove) => {
11
12
  const mainManifest = options.packageJson.read(options.projectRoot);
12
13
  const modifiers = GraphModifier.maybeLoad(options);
13
14
  const remover = new RollbackRemove();
15
+ // graph spans every workspace; -w only picks the `remove` targets
16
+ const monorepo = Monorepo.maybeLoad(options.projectRoot, {
17
+ packageJson: options.packageJson,
18
+ scurry: options.scurry,
19
+ });
14
20
  try {
15
21
  // Load the actual graph before building the ideal graph so that
16
22
  // manifest data from installed packages can be used to hydrate
@@ -20,16 +26,19 @@ export const uninstall = async (options, remove) => {
20
26
  // due to throwOnMissingManifest and leaving stale entries behind.
21
27
  const act = actualLoad({
22
28
  ...options,
29
+ monorepo,
23
30
  modifiers: undefined,
24
31
  mainManifest,
25
32
  loadManifests: true,
26
33
  });
27
34
  const graph = await idealBuild({
28
35
  ...options,
36
+ monorepo,
29
37
  actual: act,
30
38
  remove,
31
39
  mainManifest,
32
40
  loadManifests: true,
41
+ modifiers,
33
42
  remover,
34
43
  });
35
44
  // If lockfileOnly is enabled, skip reify and only save the lockfile
@@ -59,6 +68,7 @@ export const uninstall = async (options, remove) => {
59
68
  actual: act,
60
69
  graph,
61
70
  loadManifests: true,
71
+ modifiers,
62
72
  remover,
63
73
  });
64
74
  return { graph, diff };
package/dist/update.js CHANGED
@@ -5,10 +5,45 @@ import { GraphModifier } from "./modifiers.js";
5
5
  import { init } from '@vltpkg/init';
6
6
  import { asError } from '@vltpkg/types';
7
7
  import { Graph } from "./graph.js";
8
+ import { load as loadVirtual } from "./lockfile/load.js";
9
+ import { isPathSecurityError } from "./path-security-error.js";
8
10
  import { graphStep } from '@vltpkg/output';
9
11
  import { RollbackRemove } from '@vltpkg/rollback-remove';
12
+ import { Monorepo } from '@vltpkg/workspaces';
10
13
  import { existsSync, rmSync } from 'node:fs';
11
14
  import { resolve } from 'node:path';
15
+ /**
16
+ * Graph to update from. `-w` filtered: the lockfile (else installed)
17
+ * graph minus the selected workspaces' deps, so only those re-resolve
18
+ * (deps shared with other workspaces stay locked). Else: empty.
19
+ */
20
+ const startingGraph = (options, monorepo) => {
21
+ const fresh = () => new Graph({ ...options, monorepo });
22
+ if (!options.monorepo || !monorepo)
23
+ return fresh();
24
+ const selected = new Set([...options.monorepo.values()].map(ws => ws.id));
25
+ if (selected.size === monorepo.size)
26
+ return fresh();
27
+ let graph;
28
+ try {
29
+ graph = loadVirtual({ ...options, monorepo });
30
+ }
31
+ catch (err) {
32
+ if (isPathSecurityError(err))
33
+ throw err;
34
+ graph = actualLoad({ ...options, monorepo, loadManifests: true });
35
+ }
36
+ for (const importer of graph.importers) {
37
+ if (!selected.has(importer.id))
38
+ continue;
39
+ for (const edge of importer.edgesOut.values()) {
40
+ edge.to?.edgesIn.delete(edge);
41
+ }
42
+ importer.edgesOut.clear();
43
+ }
44
+ graph.gc();
45
+ return graph;
46
+ };
12
47
  export const update = async (options) => {
13
48
  let mainManifest = undefined;
14
49
  try {
@@ -27,6 +62,10 @@ export const update = async (options) => {
27
62
  }
28
63
  const modifiers = GraphModifier.maybeLoad(options);
29
64
  const remover = new RollbackRemove();
65
+ const monorepo = Monorepo.maybeLoad(options.projectRoot, {
66
+ packageJson: options.packageJson,
67
+ scurry: options.scurry,
68
+ });
30
69
  try {
31
70
  const done = graphStep('build');
32
71
  const graph = await buildIdealFromStartingGraph({
@@ -35,13 +74,14 @@ export const update = async (options) => {
35
74
  remove: Object.assign(new Map(), {
36
75
  modifiedDependencies: false,
37
76
  }),
38
- graph: new Graph({ ...options, mainManifest }),
77
+ graph: startingGraph({ ...options, mainManifest, modifiers }, monorepo),
39
78
  modifiers,
40
79
  remover,
41
80
  });
42
81
  done();
43
82
  const act = actualLoad({
44
83
  ...options,
84
+ monorepo,
45
85
  mainManifest,
46
86
  loadManifests: true,
47
87
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/graph",
3
3
  "description": "A library that helps understanding & expressing what happens on an install",
4
- "version": "1.3.7",
4
+ "version": "1.4.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -13,26 +13,26 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/cmd-shim": "1.3.7",
17
- "@vltpkg/dep-id": "1.3.7",
18
- "@vltpkg/dss-breadcrumb": "1.3.7",
19
- "@vltpkg/error-cause": "1.3.7",
20
- "@vltpkg/fast-split": "1.3.7",
21
- "@vltpkg/graph-run": "1.3.7",
22
- "@vltpkg/init": "1.3.7",
23
- "@vltpkg/output": "1.3.7",
24
- "@vltpkg/package-info": "1.3.7",
25
- "@vltpkg/package-json": "1.3.7",
26
- "@vltpkg/pick-manifest": "1.3.7",
27
- "@vltpkg/query": "1.3.7",
28
- "@vltpkg/rollback-remove": "1.3.7",
29
- "@vltpkg/run": "1.3.7",
30
- "@vltpkg/satisfies": "1.3.7",
31
- "@vltpkg/security-archive": "1.3.7",
32
- "@vltpkg/spec": "1.3.7",
33
- "@vltpkg/types": "1.3.7",
34
- "@vltpkg/vlt-json": "1.3.7",
35
- "@vltpkg/workspaces": "1.3.7",
16
+ "@vltpkg/cmd-shim": "1.4.0",
17
+ "@vltpkg/dep-id": "1.4.0",
18
+ "@vltpkg/dss-breadcrumb": "1.4.0",
19
+ "@vltpkg/error-cause": "1.4.0",
20
+ "@vltpkg/fast-split": "1.4.0",
21
+ "@vltpkg/graph-run": "1.4.0",
22
+ "@vltpkg/init": "1.4.0",
23
+ "@vltpkg/output": "1.4.0",
24
+ "@vltpkg/package-info": "1.4.0",
25
+ "@vltpkg/package-json": "1.4.0",
26
+ "@vltpkg/pick-manifest": "1.4.0",
27
+ "@vltpkg/query": "1.4.0",
28
+ "@vltpkg/rollback-remove": "1.4.0",
29
+ "@vltpkg/run": "1.4.0",
30
+ "@vltpkg/satisfies": "1.4.0",
31
+ "@vltpkg/security-archive": "1.4.0",
32
+ "@vltpkg/spec": "1.4.0",
33
+ "@vltpkg/types": "1.4.0",
34
+ "@vltpkg/vlt-json": "1.4.0",
35
+ "@vltpkg/workspaces": "1.4.0",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },