@vltpkg/cli-sdk 1.1.1 → 1.3.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.
- package/dist/commands/cache.d.ts +27 -3
- package/dist/commands/cache.js +127 -2
- package/dist/commands/init.js +17 -5
- package/dist/commands/install/reporter.js +3 -1
- package/dist/commands/publish.js +26 -2
- package/dist/commands/view.js +1 -1
- package/dist/config/definition.d.ts +14 -0
- package/dist/config/definition.js +46 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +24 -1
- package/dist/custom-help.js +1 -1
- package/dist/index.js +1 -1
- package/dist/pack-tarball.d.ts +2 -0
- package/dist/pack-tarball.js +1 -0
- package/dist/print-err.js +7 -3
- package/dist/verbose-log.js +1 -0
- package/package.json +28 -27
package/dist/commands/cache.d.ts
CHANGED
|
@@ -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<
|
|
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<
|
|
88
|
+
export declare const command: CommandFn<CacheResult>;
|
|
65
89
|
export {};
|
package/dist/commands/cache.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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', {
|
package/dist/commands/init.js
CHANGED
|
@@ -4,7 +4,7 @@ import { minimatch } from 'minimatch';
|
|
|
4
4
|
import { init } from '@vltpkg/init';
|
|
5
5
|
import { install } from '@vltpkg/graph';
|
|
6
6
|
import { load, save } from '@vltpkg/vlt-json';
|
|
7
|
-
import { assertWSConfig,
|
|
7
|
+
import { assertWSConfig, resolveWSConfig, splitNegatedPatterns, } from '@vltpkg/workspaces';
|
|
8
8
|
import { commandUsage } from "../config/usage.js";
|
|
9
9
|
export const usage = () => commandUsage({
|
|
10
10
|
command: 'init',
|
|
@@ -55,8 +55,18 @@ export const command = async (conf) => {
|
|
|
55
55
|
: ':not(*)';
|
|
56
56
|
/* c8 ignore stop */
|
|
57
57
|
if (conf.values.workspace?.length) {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// the *effective* config, which may come from the root package.json
|
|
59
|
+
// rather than vlt.json
|
|
60
|
+
const { config: parsedWSConfig, source } = resolveWSConfig(conf.options.projectRoot, conf.options.packageJson);
|
|
61
|
+
// what gets written back to vlt.json. The raw vlt.json value is
|
|
62
|
+
// reused as-is so its existing shape is preserved. When the
|
|
63
|
+
// definitions live in package.json instead, seed vlt.json with them
|
|
64
|
+
// first -- otherwise adding a `workspaces` field here would shadow
|
|
65
|
+
// every workspace package.json already declares.
|
|
66
|
+
const workspacesConfig = load('workspaces', assertWSConfig) ??
|
|
67
|
+
(source === 'package.json' ?
|
|
68
|
+
Object.values(parsedWSConfig).flat()
|
|
69
|
+
: undefined);
|
|
60
70
|
const results = [];
|
|
61
71
|
const addToConfig = [];
|
|
62
72
|
// create a new package.json file for every workspace
|
|
@@ -69,8 +79,10 @@ export const command = async (conf) => {
|
|
|
69
79
|
// run the initialization script and collect results
|
|
70
80
|
results.push(await init({ cwd }));
|
|
71
81
|
// Check if this workspace path is covered by existing workspace patterns
|
|
72
|
-
const isMatched = Object.values(parsedWSConfig).some((
|
|
73
|
-
|
|
82
|
+
const isMatched = Object.values(parsedWSConfig).some((group) => {
|
|
83
|
+
const { patterns, ignore } = splitNegatedPatterns(group);
|
|
84
|
+
return (patterns.some(pattern => minimatch(workspace, pattern)) &&
|
|
85
|
+
!ignore.some(pattern => minimatch(workspace, pattern)));
|
|
74
86
|
});
|
|
75
87
|
// When a workspace is not matched we track it for insertion later
|
|
76
88
|
if (!isMatched) {
|
|
@@ -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' ||
|
|
41
|
+
else if (state === 'cache' ||
|
|
42
|
+
state === 'stale' ||
|
|
43
|
+
state === 'store') {
|
|
42
44
|
setCacheHit(p => p + 1);
|
|
43
45
|
}
|
|
44
46
|
};
|
package/dist/commands/publish.js
CHANGED
|
@@ -4,7 +4,8 @@ import { run } from '@vltpkg/run';
|
|
|
4
4
|
import { commandUsage } from "../config/usage.js";
|
|
5
5
|
import { packTarball } from "../pack-tarball.js";
|
|
6
6
|
import assert from 'node:assert';
|
|
7
|
-
import {
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
9
|
import prettyBytes from 'pretty-bytes';
|
|
9
10
|
import { actual } from '@vltpkg/graph';
|
|
10
11
|
import { Query } from '@vltpkg/query';
|
|
@@ -156,6 +157,28 @@ const getPublishConfig = (manifest) => {
|
|
|
156
157
|
}
|
|
157
158
|
return undefined;
|
|
158
159
|
};
|
|
160
|
+
/** Of several readmes at the package root, npm prefers the markdown one. */
|
|
161
|
+
const README_MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown|mdwn|mkd|mkdn)$/i;
|
|
162
|
+
/**
|
|
163
|
+
* The readme fields npm's CLI puts in a publish payload, read out of the
|
|
164
|
+
* files that were just packed. Registries serve this as the package's front
|
|
165
|
+
* page, so a payload without it leaves the page blank even though the readme
|
|
166
|
+
* is sitting inside the tarball.
|
|
167
|
+
*
|
|
168
|
+
* npm takes the readme at the package root and prefers the markdown one; a
|
|
169
|
+
* package without a readme gets no fields at all.
|
|
170
|
+
*/
|
|
171
|
+
const readPublishReadme = (packDir, files) => {
|
|
172
|
+
const readmes = files.filter(file => /^readme(\.[^.]*)?$/i.test(file));
|
|
173
|
+
const readmeFilename = readmes.find(file => README_MARKDOWN_EXTENSION.test(file)) ??
|
|
174
|
+
readmes[0];
|
|
175
|
+
if (!readmeFilename)
|
|
176
|
+
return undefined;
|
|
177
|
+
return {
|
|
178
|
+
readme: readFileSync(join(packDir, readmeFilename), 'utf8'),
|
|
179
|
+
readmeFilename,
|
|
180
|
+
};
|
|
181
|
+
};
|
|
159
182
|
const commandSingle = async (location, conf) => {
|
|
160
183
|
const manifestPath = conf.options.packageJson.find(location);
|
|
161
184
|
assert(manifestPath, 'No package.json found');
|
|
@@ -196,7 +219,7 @@ const commandSingle = async (location, conf) => {
|
|
|
196
219
|
const updatedManifest = conf.options.packageJson.read(manifestDir, {
|
|
197
220
|
reload: true,
|
|
198
221
|
});
|
|
199
|
-
const { name, version, tarballName, tarballData, unpackedSize, files, integrity, shasum, resolvedManifest, } = await packTarball(updatedManifest, manifestDir, conf);
|
|
222
|
+
const { name, version, tarballName, tarballData, unpackedSize, files, integrity, shasum, resolvedManifest, packDir, } = await packTarball(updatedManifest, manifestDir, conf);
|
|
200
223
|
await run({
|
|
201
224
|
...runOptions,
|
|
202
225
|
arg0: 'postpack',
|
|
@@ -215,6 +238,7 @@ const commandSingle = async (location, conf) => {
|
|
|
215
238
|
versions: {
|
|
216
239
|
[version]: {
|
|
217
240
|
...resolvedManifest,
|
|
241
|
+
...readPublishReadme(packDir, files),
|
|
218
242
|
_id: `${name}@${version}`,
|
|
219
243
|
_nodeVersion: process.versions.node,
|
|
220
244
|
dist: {
|
package/dist/commands/view.js
CHANGED
|
@@ -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>;
|
|
@@ -397,12 +406,17 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
397
406
|
'lockfile-only': {
|
|
398
407
|
description: string;
|
|
399
408
|
};
|
|
409
|
+
'brotli-tarballs': {
|
|
410
|
+
description: string;
|
|
411
|
+
default: true;
|
|
412
|
+
};
|
|
400
413
|
} & {
|
|
401
414
|
'dry-run': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
402
415
|
force: import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
403
416
|
'expect-lockfile': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
404
417
|
'frozen-lockfile': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
405
418
|
'lockfile-only': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
419
|
+
'brotli-tarballs': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
406
420
|
} & {
|
|
407
421
|
'allow-scripts': {
|
|
408
422
|
hint: string;
|
|
@@ -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',
|
|
@@ -718,6 +751,19 @@ export const definition = j
|
|
|
718
751
|
'lockfile-only': {
|
|
719
752
|
description: 'Only update the lockfile (vlt-lock.json) and package.json files, skip all node_modules operations including package extraction and filesystem changes.',
|
|
720
753
|
},
|
|
754
|
+
'brotli-tarballs': {
|
|
755
|
+
description: `Resolve to the Brotli (\`.tar.br\`) tarball when the
|
|
756
|
+
registry advertises one for a version, transferring far
|
|
757
|
+
less than the gzip \`.tgz\`.
|
|
758
|
+
|
|
759
|
+
The \`.tar.br\` is its own artifact and hashes
|
|
760
|
+
differently, so a version installed this way records that
|
|
761
|
+
hash in \`vlt-lock.json\`. An existing lockfile keeps
|
|
762
|
+
whichever artifact it already pinned; set
|
|
763
|
+
\`--no-brotli-tarballs\` before resolving to pin the
|
|
764
|
+
\`.tgz\` instead.`,
|
|
765
|
+
default: true,
|
|
766
|
+
},
|
|
721
767
|
})
|
|
722
768
|
.opt({
|
|
723
769
|
'allow-scripts': {
|
package/dist/config/index.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/config/index.js
CHANGED
|
@@ -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');
|
package/dist/custom-help.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ const run = async () => {
|
|
|
57
57
|
if (vlt.command !== 'init' &&
|
|
58
58
|
(vlt.get('workspace') || vlt.get('workspace-group')) &&
|
|
59
59
|
![...(monorepo?.values() ?? [])].length) {
|
|
60
|
-
stderr(`Error: No matching workspaces found. Make sure the vlt.json config contains the correct workspaces.`);
|
|
60
|
+
stderr(`Error: No matching workspaces found. Make sure the vlt.json config, or the "workspaces" field of the root package.json, contains the correct workspaces.`);
|
|
61
61
|
if (vlt.get('workspace')) {
|
|
62
62
|
stderr(indent(`Workspace: ${format(vlt.get('workspace'))}`));
|
|
63
63
|
}
|
package/dist/pack-tarball.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type PackTarballResult = {
|
|
|
12
12
|
shasum?: string;
|
|
13
13
|
/** The manifest used for packing (may differ from input when publishConfig.directory is set). */
|
|
14
14
|
resolvedManifest: NormalizedManifest;
|
|
15
|
+
/** The directory that was packed (differs from input when publishConfig.directory is set). */
|
|
16
|
+
packDir: string;
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
19
|
* Build the tar filter function for packing.
|
package/dist/pack-tarball.js
CHANGED
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) =>
|
|
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
|
-
|
|
165
|
-
|
|
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
|
}
|
package/dist/verbose-log.js
CHANGED
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.
|
|
4
|
+
"version": "1.3.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.
|
|
18
|
-
"@vltpkg/dep-id": "1.
|
|
19
|
-
"@vltpkg/dot-prop": "1.
|
|
20
|
-
"@vltpkg/error-cause": "1.
|
|
21
|
-
"@vltpkg/git": "1.
|
|
22
|
-
"@vltpkg/graph": "1.
|
|
23
|
-
"@vltpkg/graph-run": "1.
|
|
24
|
-
"@vltpkg/init": "1.
|
|
25
|
-
"@vltpkg/output": "1.
|
|
26
|
-
"@vltpkg/package-info": "1.
|
|
27
|
-
"@vltpkg/package-json": "1.
|
|
28
|
-
"@vltpkg/promise-spawn": "1.
|
|
29
|
-
"@vltpkg/query": "1.
|
|
30
|
-
"@vltpkg/registry-client": "1.
|
|
31
|
-
"@vltpkg/rollback-remove": "1.
|
|
32
|
-
"@vltpkg/run": "1.
|
|
33
|
-
"@vltpkg/security-archive": "1.
|
|
34
|
-
"@vltpkg/spec": "1.
|
|
35
|
-
"@vltpkg/
|
|
36
|
-
"@vltpkg/
|
|
37
|
-
"@vltpkg/
|
|
38
|
-
"@vltpkg/
|
|
39
|
-
"@vltpkg/
|
|
40
|
-
"@vltpkg/
|
|
17
|
+
"@vltpkg/config": "1.3.0",
|
|
18
|
+
"@vltpkg/dep-id": "1.3.0",
|
|
19
|
+
"@vltpkg/dot-prop": "1.3.0",
|
|
20
|
+
"@vltpkg/error-cause": "1.3.0",
|
|
21
|
+
"@vltpkg/git": "1.3.0",
|
|
22
|
+
"@vltpkg/graph": "1.3.0",
|
|
23
|
+
"@vltpkg/graph-run": "1.3.0",
|
|
24
|
+
"@vltpkg/init": "1.3.0",
|
|
25
|
+
"@vltpkg/output": "1.3.0",
|
|
26
|
+
"@vltpkg/package-info": "1.3.0",
|
|
27
|
+
"@vltpkg/package-json": "1.3.0",
|
|
28
|
+
"@vltpkg/promise-spawn": "1.3.0",
|
|
29
|
+
"@vltpkg/query": "1.3.0",
|
|
30
|
+
"@vltpkg/registry-client": "1.3.0",
|
|
31
|
+
"@vltpkg/rollback-remove": "1.3.0",
|
|
32
|
+
"@vltpkg/run": "1.3.0",
|
|
33
|
+
"@vltpkg/security-archive": "1.3.0",
|
|
34
|
+
"@vltpkg/spec": "1.3.0",
|
|
35
|
+
"@vltpkg/tar": "1.3.0",
|
|
36
|
+
"@vltpkg/types": "1.3.0",
|
|
37
|
+
"@vltpkg/url-open": "1.3.0",
|
|
38
|
+
"@vltpkg/vlt-json": "1.3.0",
|
|
39
|
+
"@vltpkg/vlx": "1.3.0",
|
|
40
|
+
"@vltpkg/workspaces": "1.3.0",
|
|
41
|
+
"@vltpkg/xdg": "1.3.0",
|
|
41
42
|
"ansi-to-pre": "^1.0.6",
|
|
42
43
|
"beautiful-mermaid": "^1.1.3",
|
|
43
44
|
"chalk": "^5.6.2",
|
|
@@ -62,11 +63,11 @@
|
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@eslint/js": "^9.39.1",
|
|
64
65
|
"@types/node": "^22.19.2",
|
|
65
|
-
"@types/react": "^
|
|
66
|
+
"@types/react": "^19",
|
|
66
67
|
"@types/ssri": "^7.1.5",
|
|
67
68
|
"eslint": "^9.39.1",
|
|
68
69
|
"prettier": "^3.7.4",
|
|
69
|
-
"tap": "^21.
|
|
70
|
+
"tap": "^21.8.0",
|
|
70
71
|
"typedoc": "~0.27.9",
|
|
71
72
|
"typescript": "5.7.3",
|
|
72
73
|
"typescript-eslint": "^8.49.0"
|