@vltpkg/cli-sdk 1.1.1 → 1.2.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.
@@ -4,12 +4,20 @@ import type { CommandFn, CommandUsage } from '../index.ts';
4
4
  import type { ViewOptions, Views } from '../view.ts';
5
5
  import { ViewClass } from '../view.ts';
6
6
  export type CacheMap = Record<string, ReturnType<CacheEntry['toJSON']>>;
7
+ /** `verify`, `prune-store`: entries checked, and why each was removed */
8
+ export type StoreResult = {
9
+ checked: number;
10
+ removed: Record<string, string>;
11
+ /** `verify <spec>`: specs not in the global store */
12
+ missing?: string[];
13
+ };
14
+ export type CacheResult = void | CacheMap | StoreResult;
7
15
  export type CacheSubcommands = keyof (typeof usageDef)['subcommands'];
8
16
  export declare class CacheView extends ViewClass {
9
17
  constructor(options: ViewOptions, conf: LoadedConfig);
10
18
  stdout(...args: unknown[]): void;
11
19
  }
12
- export declare const views: Views<void | CacheMap>;
20
+ export declare const views: Views<CacheResult>;
13
21
  declare const usageDef: {
14
22
  readonly command: "cache";
15
23
  readonly usage: "<command> [flags]";
@@ -37,12 +45,20 @@ declare const usageDef: {
37
45
  };
38
46
  readonly 'delete-before': {
39
47
  readonly usage: "<date>";
40
- readonly description: "Purge all cache items from before a given date. Date can be\n provided in any format that JavaScript can parse.";
48
+ readonly description: "Purge all cache items from before a given date, and\n global store entries written before it. Date can be\n provided in any format that JavaScript can parse.";
41
49
  };
42
50
  readonly 'delete-all': {
43
51
  readonly usage: "";
44
52
  readonly description: "Delete the entire cache folder to make vlt slower.";
45
53
  };
54
+ readonly verify: {
55
+ readonly usage: ["<package-spec> [<package-spec>...]", "--all"];
56
+ readonly description: "Check global store entries against their cached\n tarballs (file list, contents, exec bits) and remove\n any that differ or have no cached tarball, e.g. after\n a file in `node_modules` was edited in place.";
57
+ };
58
+ readonly 'prune-store': {
59
+ readonly usage: "";
60
+ readonly description: "Remove global store entries that no `node_modules`\n folder links to. Entries ever copied from (install\n scripts, `store-linker=copy`, a cache on another\n drive) stay: copies do not show use.";
61
+ };
46
62
  };
47
63
  readonly examples: {
48
64
  readonly 'vlt cache ls https://registry.npmjs.org/typescript': {
@@ -57,9 +73,17 @@ declare const usageDef: {
57
73
  readonly 'vlt cache delete-before 2025-01-01': {
58
74
  readonly description: "Delete all entries created before Jan 1, 2025";
59
75
  };
76
+ readonly 'verify --all': {
77
+ readonly description: "Check every global store entry";
78
+ };
79
+ };
80
+ readonly options: {
81
+ readonly all: {
82
+ readonly description: "With `verify`, check every global store entry.";
83
+ };
60
84
  };
61
85
  };
62
86
  export declare const needsRegistry = true;
63
87
  export declare const usage: CommandUsage;
64
- export declare const command: CommandFn<void | CacheMap>;
88
+ export declare const command: CommandFn<CacheResult>;
65
89
  export {};
@@ -1,7 +1,12 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
2
  import { CacheEntry, assertOk } from '@vltpkg/registry-client';
3
3
  import { Spec } from '@vltpkg/spec';
4
+ import { removeStoreEntry, storeEntryCopied, storeEntryLinked, storeEntryNames, storeEntryTime, verifyStoreEntry, } from '@vltpkg/tar/store-entry';
5
+ import { readStoreIndex } from '@vltpkg/tar/store-index';
6
+ import { integrityHex } from '@vltpkg/types';
7
+ import { readFileSync } from 'node:fs';
4
8
  import { mkdir, rm } from 'node:fs/promises';
9
+ import { resolve } from 'node:path';
5
10
  import prettyBytes from 'pretty-bytes';
6
11
  import { commandUsage } from "../config/usage.js";
7
12
  import { stderr, stdout } from "../output.js";
@@ -55,13 +60,28 @@ const usageDef = {
55
60
  },
56
61
  'delete-before': {
57
62
  usage: '<date>',
58
- description: `Purge all cache items from before a given date. Date can be
63
+ description: `Purge all cache items from before a given date, and
64
+ global store entries written before it. Date can be
59
65
  provided in any format that JavaScript can parse.`,
60
66
  },
61
67
  'delete-all': {
62
68
  usage: '',
63
69
  description: `Delete the entire cache folder to make vlt slower.`,
64
70
  },
71
+ verify: {
72
+ usage: ['<package-spec> [<package-spec>...]', '--all'],
73
+ description: `Check global store entries against their cached
74
+ tarballs (file list, contents, exec bits) and remove
75
+ any that differ or have no cached tarball, e.g. after
76
+ a file in \`node_modules\` was edited in place.`,
77
+ },
78
+ 'prune-store': {
79
+ usage: '',
80
+ description: `Remove global store entries that no \`node_modules\`
81
+ folder links to. Entries ever copied from (install
82
+ scripts, \`store-linker=copy\`, a cache on another
83
+ drive) stay: copies do not show use.`,
84
+ },
65
85
  },
66
86
  examples: {
67
87
  'vlt cache ls https://registry.npmjs.org/typescript': {
@@ -78,6 +98,14 @@ const usageDef = {
78
98
  'vlt cache delete-before 2025-01-01': {
79
99
  description: 'Delete all entries created before Jan 1, 2025',
80
100
  },
101
+ 'verify --all': {
102
+ description: 'Check every global store entry',
103
+ },
104
+ },
105
+ options: {
106
+ all: {
107
+ description: 'With `verify`, check every global store entry.',
108
+ },
81
109
  },
82
110
  };
83
111
  export const needsRegistry = true;
@@ -99,6 +127,10 @@ export const command = async (conf) => {
99
127
  return deleteBefore(conf, args, view);
100
128
  case 'delete-all':
101
129
  return deleteAll(conf, args, view);
130
+ case 'verify':
131
+ return verify(conf, args, view);
132
+ case 'prune-store':
133
+ return pruneStore(conf, args, view);
102
134
  default: {
103
135
  throw error('Unrecognized cache command', {
104
136
  code: 'EUSAGE',
@@ -212,7 +244,18 @@ const deleteBefore = async (conf, args, view) => {
212
244
  found: before,
213
245
  });
214
246
  }
215
- return deleteEntries(conf, [], entry => !!entry.date && entry.date < before, view);
247
+ const map = await deleteEntries(conf, [], entry => !!entry.date && entry.date < before, view);
248
+ const { storeRoot } = conf.options;
249
+ let count = 0;
250
+ for (const hex of storeEntryNames(storeRoot)) {
251
+ const entry = resolve(storeRoot, hex);
252
+ if (storeEntryTime(entry) < before.getTime()) {
253
+ removeStoreEntry(entry);
254
+ count++;
255
+ }
256
+ }
257
+ view?.stdout(`Removed ${entries(count)}`);
258
+ return map;
216
259
  };
217
260
  const deleteKeys = async (conf, keys, view) => {
218
261
  if (!keys.length) {
@@ -225,9 +268,91 @@ const deleteKeys = async (conf, keys, view) => {
225
268
  const deleteAll = async (conf, _, view) => {
226
269
  const { cache } = await conf.options.packageInfo.getRegistryClient();
227
270
  await rm(cache.path(), { recursive: true, force: true });
271
+ // every global store layout version, and a store root set elsewhere
272
+ const rf = { recursive: true, force: true };
273
+ await rm(resolve(conf.options.cache, 'store'), rf);
274
+ if (cache.store)
275
+ await rm(cache.store, rf);
228
276
  await mkdir(cache.path(), { recursive: true });
229
277
  view?.stdout('Deleted all cache entries.');
230
278
  };
279
+ const entries = (n) => `${n} global store entr${n === 1 ? 'y' : 'ies'}`;
280
+ /** `name@version` from the entry's sidecar, else `hex` */
281
+ const entryLabel = (entry, hex) => {
282
+ const i = readStoreIndex(entry);
283
+ return i?.name && i.version ? `${i.name}@${i.version}` : hex;
284
+ };
285
+ const verify = async (conf, specs, view) => {
286
+ const all = !!conf.values.all;
287
+ if (!specs.length && !all) {
288
+ throw error('Must provide specs to verify, or --all', {
289
+ code: 'EUSAGE',
290
+ });
291
+ }
292
+ const { packageInfo, storeRoot } = conf.options;
293
+ const cachePath = (await packageInfo.getRegistryClient()).cache.path();
294
+ const present = new Set(storeEntryNames(storeRoot));
295
+ const checks = [];
296
+ const missing = [];
297
+ if (all) {
298
+ for (const hex of present)
299
+ checks.push([hex, hex]);
300
+ }
301
+ else {
302
+ for (const spec of specs) {
303
+ const { integrity } = await packageInfo.resolve(Spec.parseArgs(spec, conf.options));
304
+ const hex = integrityHex(integrity);
305
+ if (hex && present.has(hex))
306
+ checks.push([spec, hex]);
307
+ else {
308
+ missing.push(spec);
309
+ view?.stdout('Not in the global store:', spec);
310
+ }
311
+ }
312
+ }
313
+ const removed = {};
314
+ for (const [key, hex] of checks) {
315
+ const entry = resolve(storeRoot, hex);
316
+ let tarball;
317
+ try {
318
+ tarball = CacheEntry.decode(readFileSync(resolve(cachePath, hex))).buffer();
319
+ }
320
+ catch { }
321
+ const reason = tarball ? verifyStoreEntry(entry, tarball) : 'no cached tarball';
322
+ if (reason) {
323
+ // read before the sidecar goes
324
+ const label = all ? entryLabel(entry, hex) : key;
325
+ removeStoreEntry(entry);
326
+ removed[key] = reason;
327
+ view?.stdout('-', label, reason);
328
+ }
329
+ }
330
+ const n = Object.keys(removed).length;
331
+ view?.stdout(`Checked ${entries(checks.length)}, removed ${n}`);
332
+ return {
333
+ checked: checks.length,
334
+ removed,
335
+ ...(!all && { missing }),
336
+ };
337
+ };
338
+ const pruneStore = async (conf, _, view) => {
339
+ const { storeRoot } = conf.options;
340
+ const names = storeEntryNames(storeRoot);
341
+ const removed = {};
342
+ for (const hex of names) {
343
+ const entry = resolve(storeRoot, hex);
344
+ const index = readStoreIndex(entry);
345
+ if (storeEntryCopied(entry, index) ||
346
+ storeEntryLinked(entry, index)) {
347
+ continue;
348
+ }
349
+ removeStoreEntry(entry);
350
+ removed[hex] = 'unused';
351
+ }
352
+ const n = Object.keys(removed).length;
353
+ view?.stdout(`Removed ${n} of ${entries(names.length)}`);
354
+ return { checked: names.length, removed };
355
+ };
231
356
  const add = async (conf, specs, view) => {
232
357
  if (!specs.length) {
233
358
  throw error('Must provide specs to add to the cache', {
@@ -38,7 +38,9 @@ const App = ({ trailer }) => {
38
38
  if (state === 'start') {
39
39
  setRequests(p => p + 1);
40
40
  }
41
- else if (state === 'cache' || state === 'stale') {
41
+ else if (state === 'cache' ||
42
+ state === 'stale' ||
43
+ state === 'store') {
42
44
  setCacheHit(p => p + 1);
43
45
  }
44
46
  };
@@ -301,7 +301,7 @@ export const command = async (conf) => {
301
301
  const pic = new PackageInfoClient(conf.options);
302
302
  // Fetch the full packument (needs time, maintainers) and resolved manifest
303
303
  const [packument, resolvedManifest] = await Promise.all([
304
- pic.packument(spec),
304
+ pic.packument(spec, { full: true }),
305
305
  pic.manifest(spec),
306
306
  ]);
307
307
  const manifest = resolvedManifest;
@@ -73,6 +73,8 @@ export declare const getCommand: (s?: string) => Commands[keyof Commands] | unde
73
73
  export declare const recordFields: readonly ["git-hosts", "registries", "git-host-archives", "scoped-registries", "jsr-registries"];
74
74
  export type RecordField = (typeof recordFields)[number];
75
75
  export declare const isRecordField: (s: string) => s is RecordField;
76
+ /** Valid `store-linker` values. */
77
+ export declare const storeLinkers: readonly ["auto", "hardlink", "copy", "unpack"];
76
78
  export declare const definition: import("jackspeak").Jack<{
77
79
  color: {
78
80
  short: string;
@@ -133,6 +135,12 @@ export declare const definition: import("jackspeak").Jack<{
133
135
  description: string;
134
136
  default: string;
135
137
  };
138
+ 'store-linker': {
139
+ hint: string;
140
+ description: string;
141
+ validOptions: readonly ["auto", "hardlink", "copy", "unpack"];
142
+ default: string;
143
+ };
136
144
  tag: {
137
145
  description: string;
138
146
  default: string;
@@ -159,6 +167,7 @@ export declare const definition: import("jackspeak").Jack<{
159
167
  };
160
168
  } & {
161
169
  cache: import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
170
+ 'store-linker': import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
162
171
  tag: import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
163
172
  before: import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
164
173
  os: import("jackspeak").ConfigOption<"string", false, readonly string[] | undefined>;
@@ -109,6 +109,13 @@ export const recordFields = [
109
109
  'jsr-registries',
110
110
  ];
111
111
  export const isRecordField = (s) => recordFields.includes(s);
112
+ /** Valid `store-linker` values. */
113
+ export const storeLinkers = [
114
+ 'auto',
115
+ 'hardlink',
116
+ 'copy',
117
+ 'unpack',
118
+ ];
112
119
  const j = jack({
113
120
  envPrefix: 'VLT',
114
121
  allowPositionals: true,
@@ -308,6 +315,32 @@ export const definition = j
308
315
  `,
309
316
  default: cacheDir,
310
317
  },
318
+ 'store-linker': {
319
+ hint: 'auto | hardlink | copy | unpack',
320
+ description: `How packages are placed in \`node_modules\`. Every
321
+ linker but \`unpack\` fills a global store under
322
+ \`cache\` in the background, then places packages
323
+ from it.
324
+
325
+ - auto: Default. On Linux, hardlink from the global
326
+ store, copying any file that cannot be linked.
327
+ Elsewhere, same as \`unpack\`: a hardlink is the
328
+ slowest way to place a file on APFS, so macOS and
329
+ Windows unpack by default.
330
+ - hardlink: Hardlink from the global store on every
331
+ platform.
332
+ - copy: Copy from the global store. Use this when
333
+ editing files in \`node_modules\` in place, since
334
+ a hardlink shares its content with every project.
335
+ - unpack: Unpack each package tarball, skipping the
336
+ global store.
337
+
338
+ Packages with install scripts are always copied.
339
+ An invalid \`VLT_STORE_LINKER\` warns and uses
340
+ \`unpack\`.`,
341
+ validOptions: storeLinkers,
342
+ default: 'auto',
343
+ },
311
344
  tag: {
312
345
  description: `Default \`dist-tag\` to install or publish`,
313
346
  default: 'latest',
@@ -81,6 +81,8 @@ export type ConfigOptions = ConfigOptionsNoExtras & Pick<SpecOptions, 'catalog'
81
81
  packageJson: PackageJson;
82
82
  scurry: PathScurry;
83
83
  projectRoot: string;
84
+ /** global store root, under `cache` */
85
+ storeRoot: string;
84
86
  monorepo?: Monorepo;
85
87
  packageInfo: PackageInfoClient;
86
88
  };
@@ -23,6 +23,7 @@
23
23
  import { error } from '@vltpkg/error-cause';
24
24
  import { PackageInfoClient } from '@vltpkg/package-info';
25
25
  import { PackageJson } from '@vltpkg/package-json';
26
+ import { storeRoot } from '@vltpkg/registry-client/store-root';
26
27
  import { resetCaches } from '@vltpkg/dep-id';
27
28
  import { getOptions } from '@vltpkg/spec';
28
29
  import { assertRecordStringString, assertRecordStringT, isObject, isRecordStringString, } from '@vltpkg/types';
@@ -31,7 +32,7 @@ import { Monorepo } from '@vltpkg/workspaces';
31
32
  import { readFile, rm, writeFile } from 'node:fs/promises';
32
33
  import { dirname } from 'node:path';
33
34
  import { PathScurry } from 'path-scurry';
34
- import { commands, definition, defaultValues, getCommand, isRecordField, recordFields, } from "./definition.js";
35
+ import { commands, definition, defaultValues, getCommand, isRecordField, recordFields, storeLinkers, } from "./definition.js";
35
36
  import { merge } from "./merge.js";
36
37
  import { cloneLayer, mergeLayers } from "./merge-layers.js";
37
38
  export { commands, definition, isRecordField, recordFields, };
@@ -92,6 +93,7 @@ export const recordsToPairs = (obj) => {
92
93
  k === 'packageJson' ||
93
94
  k === 'monorepo' ||
94
95
  k === 'projectRoot' ||
96
+ k === 'storeRoot' ||
95
97
  k === 'packageInfo'))
96
98
  .map(([k, v]) => [
97
99
  k,
@@ -170,6 +172,7 @@ export class Config {
170
172
  const asRecords = pairsToRecords(this.parse().values);
171
173
  const extras = {
172
174
  projectRoot: this.projectRoot,
175
+ storeRoot: storeRoot(asRecords.cache),
173
176
  scurry,
174
177
  packageJson,
175
178
  monorepo: Monorepo.maybeLoad(this.projectRoot, {
@@ -250,6 +253,12 @@ export class Config {
250
253
  return this;
251
254
  // Store the original args for potential reload
252
255
  this.#originalArgs = [...args];
256
+ // an invalid env linker is treated as unpack
257
+ const badLinker = process.env.VLT_STORE_LINKER;
258
+ const fixLinker = badLinker !== undefined &&
259
+ !storeLinkers.includes(badLinker);
260
+ if (fixLinker)
261
+ process.env.VLT_STORE_LINKER = 'unpack';
253
262
  const envKeys = Object.keys(defaultValues).filter(k => process.env[envKey(k)] !== undefined);
254
263
  const parent = parentEnv();
255
264
  // file layer values, before env replaces them
@@ -267,6 +276,15 @@ export class Config {
267
276
  this.jack.setConfigValues(cmdPairs);
268
277
  // ok, applied cmd-specific defaults, do rest of the parse
269
278
  this.jack.applyDefaults(p);
279
+ // `auto` only hardlinks from the global store on linux: on APFS a
280
+ // hardlink is the slowest way to place a file, so darwin and win32
281
+ // unpack by default. the env layer is rewritten too, so `explicit`
282
+ // records the resolved value.
283
+ if (p.values['store-linker'] === 'auto' &&
284
+ process.platform !== 'linux') {
285
+ p.values['store-linker'] = 'unpack';
286
+ envValues['store-linker'] = 'unpack';
287
+ }
270
288
  // what was set on the cli or env for this run. record fields hold
271
289
  // only those pairs, env then cli, so later ones win per key.
272
290
  const values = p.values;
@@ -318,6 +336,11 @@ export class Config {
318
336
  if (p.values.verbose && p.values.loglevel === 'info') {
319
337
  p.values.loglevel = 'verbose';
320
338
  }
339
+ const { loglevel } = p.values;
340
+ if (fixLinker && loglevel !== 'silent' && loglevel !== 'error') {
341
+ // eslint-disable-next-line no-console
342
+ console.error(`Warning: invalid VLT_STORE_LINKER ${JSON.stringify(badLinker)}, using ${p.values['store-linker']}`);
343
+ }
321
344
  /* c8 ignore start - unpossible */
322
345
  if (!isParsed(this))
323
346
  throw error('failed to parse config');
@@ -63,7 +63,7 @@ const commandHelp = {
63
63
  defaultOrder: 4,
64
64
  },
65
65
  cache: {
66
- args: '[add|ls|info|clean|delete|delete-before|delete-all]',
66
+ args: '[add|ls|info|clean|delete|delete-before|delete-all|verify|prune-store]',
67
67
  desc: 'Manage the package cache',
68
68
  },
69
69
  ci: {
package/dist/print-err.js CHANGED
@@ -16,7 +16,9 @@ const formatURL = (v, format) => v instanceof URL ? v.toString() : /* c8 ignore
16
16
  // True when `v` overrides the default `Object.prototype.toString`, i.e.
17
17
  // stringifying it yields something meaningful (like a `Spec` → `next@*`)
18
18
  // rather than `[object Object]`.
19
- const hasCustomToString = (v) => isObject(v) && v.toString !== Object.prototype.toString;
19
+ const hasCustomToString = (v) => typeof v === 'object' &&
20
+ v !== null &&
21
+ v.toString !== Object.prototype.toString;
20
22
  // Render a value that has a meaningful custom `toString()` (such as a
21
23
  // `Spec`, e.g. `next@*`) as a concise string rather than dumping its
22
24
  // entire internal structure via util.inspect. Falls back to `format`
@@ -161,8 +163,10 @@ const printCode = (err, usage, stderr, format) => {
161
163
  if (from) {
162
164
  stderr(indent(`From: ${format(from)}`));
163
165
  }
164
- if (response) {
165
- stderr(indent(`Response: ${format(response)}`));
166
+ // the full response is in the error log; here only the status
167
+ const status = response?.statusCode;
168
+ if (status) {
169
+ stderr(indent(`Status: ${format(status)}`));
166
170
  }
167
171
  return { file: true };
168
172
  }
@@ -15,6 +15,7 @@ const tagStyle = (state, statusCode) => {
15
15
  switch (state) {
16
16
  case 'cache':
17
17
  case 'stale':
18
+ case 'store':
18
19
  case '304':
19
20
  return 'green';
20
21
  case 'start':
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/cli-sdk",
3
3
  "description": "The source for the vlt CLI",
4
- "version": "1.1.1",
4
+ "version": "1.2.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -14,30 +14,31 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@resvg/resvg-wasm": "^2.6.2",
17
- "@vltpkg/config": "1.1.1",
18
- "@vltpkg/dep-id": "1.1.1",
19
- "@vltpkg/dot-prop": "1.1.1",
20
- "@vltpkg/error-cause": "1.1.1",
21
- "@vltpkg/git": "1.1.1",
22
- "@vltpkg/graph": "1.1.1",
23
- "@vltpkg/graph-run": "1.1.1",
24
- "@vltpkg/init": "1.1.1",
25
- "@vltpkg/output": "1.1.1",
26
- "@vltpkg/package-info": "1.1.1",
27
- "@vltpkg/package-json": "1.1.1",
28
- "@vltpkg/promise-spawn": "1.1.1",
29
- "@vltpkg/query": "1.1.1",
30
- "@vltpkg/registry-client": "1.1.1",
31
- "@vltpkg/rollback-remove": "1.1.1",
32
- "@vltpkg/run": "1.1.1",
33
- "@vltpkg/security-archive": "1.1.1",
34
- "@vltpkg/spec": "1.1.1",
35
- "@vltpkg/types": "1.1.1",
36
- "@vltpkg/url-open": "1.1.1",
37
- "@vltpkg/vlt-json": "1.1.1",
38
- "@vltpkg/vlx": "1.1.1",
39
- "@vltpkg/workspaces": "1.1.1",
40
- "@vltpkg/xdg": "1.1.1",
17
+ "@vltpkg/config": "1.2.0",
18
+ "@vltpkg/dep-id": "1.2.0",
19
+ "@vltpkg/dot-prop": "1.2.0",
20
+ "@vltpkg/error-cause": "1.2.0",
21
+ "@vltpkg/git": "1.2.0",
22
+ "@vltpkg/graph": "1.2.0",
23
+ "@vltpkg/graph-run": "1.2.0",
24
+ "@vltpkg/init": "1.2.0",
25
+ "@vltpkg/output": "1.2.0",
26
+ "@vltpkg/package-info": "1.2.0",
27
+ "@vltpkg/package-json": "1.2.0",
28
+ "@vltpkg/promise-spawn": "1.2.0",
29
+ "@vltpkg/query": "1.2.0",
30
+ "@vltpkg/registry-client": "1.2.0",
31
+ "@vltpkg/rollback-remove": "1.2.0",
32
+ "@vltpkg/run": "1.2.0",
33
+ "@vltpkg/security-archive": "1.2.0",
34
+ "@vltpkg/spec": "1.2.0",
35
+ "@vltpkg/tar": "1.2.0",
36
+ "@vltpkg/types": "1.2.0",
37
+ "@vltpkg/url-open": "1.2.0",
38
+ "@vltpkg/vlt-json": "1.2.0",
39
+ "@vltpkg/vlx": "1.2.0",
40
+ "@vltpkg/workspaces": "1.2.0",
41
+ "@vltpkg/xdg": "1.2.0",
41
42
  "ansi-to-pre": "^1.0.6",
42
43
  "beautiful-mermaid": "^1.1.3",
43
44
  "chalk": "^5.6.2",
@@ -66,7 +67,7 @@
66
67
  "@types/ssri": "^7.1.5",
67
68
  "eslint": "^9.39.1",
68
69
  "prettier": "^3.7.4",
69
- "tap": "^21.5.0",
70
+ "tap": "^21.8.0",
70
71
  "typedoc": "~0.27.9",
71
72
  "typescript": "5.7.3",
72
73
  "typescript-eslint": "^8.49.0"