@vltpkg/cli-sdk 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.
- package/dist/commands/cache.d.ts +2 -2
- package/dist/commands/cache.js +22 -7
- package/dist/commands/exec.js +8 -0
- package/dist/commands/init.js +4 -6
- package/dist/commands/install.js +6 -0
- package/dist/commands/outdated.js +22 -15
- package/dist/commands/pack.js +2 -1
- package/dist/commands/pkg.js +4 -1
- package/dist/commands/publish.js +4 -1
- package/dist/commands/update.js +10 -1
- package/dist/commands/version.js +4 -1
- package/dist/commands/view.js +61 -13
- package/dist/config/definition.d.ts +6 -2
- package/dist/config/definition.js +26 -10
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +22 -6
- package/dist/config/spec-aliases.d.ts +22 -0
- package/dist/config/spec-aliases.js +47 -0
- package/dist/exec-command.js +12 -6
- package/dist/index.js +12 -8
- package/dist/output.js +2 -1
- package/dist/query-auth.d.ts +2 -1
- package/dist/query-auth.js +7 -3
- package/dist/save-types.d.ts +20 -0
- package/dist/save-types.js +105 -0
- package/dist/sort-scope-locations.d.ts +11 -0
- package/dist/sort-scope-locations.js +57 -0
- package/package.json +28 -28
package/dist/commands/cache.d.ts
CHANGED
|
@@ -56,8 +56,8 @@ declare const usageDef: {
|
|
|
56
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
57
|
};
|
|
58
58
|
readonly 'prune-store': {
|
|
59
|
-
readonly usage: "";
|
|
60
|
-
readonly description: "Remove global store entries that no `node_modules`\n folder links to.
|
|
59
|
+
readonly usage: "[<date>]";
|
|
60
|
+
readonly description: "Remove global store entries that no `node_modules`\n folder links to. Copies do not show use: an entry\n copied or cloned from (install scripts,\n `store-linker=copy`, macOS, a cache on another\n drive) stays if that last happened after\n `<date>`, or in the last 30 days\n without one.";
|
|
61
61
|
};
|
|
62
62
|
};
|
|
63
63
|
readonly examples: {
|
package/dist/commands/cache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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';
|
|
4
|
+
import { removeStoreEntry, storeEntryCopied, storeEntryLastCopied, storeEntryLinked, storeEntryNames, storeEntryTime, verifyStoreEntry, } from '@vltpkg/tar/store-entry';
|
|
5
5
|
import { readStoreIndex } from '@vltpkg/tar/store-index';
|
|
6
6
|
import { integrityHex } from '@vltpkg/types';
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
@@ -24,6 +24,8 @@ export class CacheView extends ViewClass {
|
|
|
24
24
|
export const views = {
|
|
25
25
|
human: CacheView,
|
|
26
26
|
};
|
|
27
|
+
/** `prune-store`: days a copied entry stays, by default */
|
|
28
|
+
const pruneDays = 30;
|
|
27
29
|
const usageDef = {
|
|
28
30
|
command: 'cache',
|
|
29
31
|
usage: '<command> [flags]',
|
|
@@ -76,11 +78,14 @@ const usageDef = {
|
|
|
76
78
|
a file in \`node_modules\` was edited in place.`,
|
|
77
79
|
},
|
|
78
80
|
'prune-store': {
|
|
79
|
-
usage: '',
|
|
81
|
+
usage: '[<date>]',
|
|
80
82
|
description: `Remove global store entries that no \`node_modules\`
|
|
81
|
-
folder links to.
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
folder links to. Copies do not show use: an entry
|
|
84
|
+
copied or cloned from (install scripts,
|
|
85
|
+
\`store-linker=copy\`, macOS, a cache on another
|
|
86
|
+
drive) stays if that last happened after
|
|
87
|
+
\`<date>\`, or in the last ${pruneDays} days
|
|
88
|
+
without one.`,
|
|
84
89
|
},
|
|
85
90
|
},
|
|
86
91
|
examples: {
|
|
@@ -335,14 +340,24 @@ const verify = async (conf, specs, view) => {
|
|
|
335
340
|
...(!all && { missing }),
|
|
336
341
|
};
|
|
337
342
|
};
|
|
338
|
-
const pruneStore = async (conf,
|
|
343
|
+
const pruneStore = async (conf, args, view) => {
|
|
344
|
+
const since = args.length ?
|
|
345
|
+
new Date(args.join(' ')).getTime()
|
|
346
|
+
: Date.now() - pruneDays * 24 * 60 * 60 * 1000;
|
|
347
|
+
if (Number.isNaN(since)) {
|
|
348
|
+
throw error('Invalid date', {
|
|
349
|
+
code: 'EUSAGE',
|
|
350
|
+
found: args.join(' '),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
339
353
|
const { storeRoot } = conf.options;
|
|
340
354
|
const names = storeEntryNames(storeRoot);
|
|
341
355
|
const removed = {};
|
|
342
356
|
for (const hex of names) {
|
|
343
357
|
const entry = resolve(storeRoot, hex);
|
|
344
358
|
const index = readStoreIndex(entry);
|
|
345
|
-
if (storeEntryCopied(entry, index)
|
|
359
|
+
if ((storeEntryCopied(entry, index) &&
|
|
360
|
+
storeEntryLastCopied(entry) >= since) ||
|
|
346
361
|
storeEntryLinked(entry, index)) {
|
|
347
362
|
continue;
|
|
348
363
|
}
|
package/dist/commands/exec.js
CHANGED
|
@@ -44,6 +44,11 @@ export const usage = () => commandUsage({
|
|
|
44
44
|
If the appropriate excutable cannot be determined, then
|
|
45
45
|
an error will be raised.
|
|
46
46
|
|
|
47
|
+
If the package is a path to a local directory (eg, \`.\`
|
|
48
|
+
or \`../pkg\`, relative to the current directory), its
|
|
49
|
+
bins are run in place. Nothing is installed, and its own
|
|
50
|
+
\`node_modules\` is used as-is.
|
|
51
|
+
|
|
47
52
|
At no point will \`vlt exec\` change the locally installed
|
|
48
53
|
dependencies. Any installs it performs is done in vlt's XDG
|
|
49
54
|
data directory.
|
|
@@ -58,6 +63,9 @@ export const usage = () => commandUsage({
|
|
|
58
63
|
'eslint@9.24 src/file.js': {
|
|
59
64
|
description: 'Run the default bin provided by eslint version 9.24',
|
|
60
65
|
},
|
|
66
|
+
'.': {
|
|
67
|
+
description: 'Run the default bin of the package in the current directory',
|
|
68
|
+
},
|
|
61
69
|
'create-react-app --call="echo $PWD"': {
|
|
62
70
|
description: 'Install create-react-app and run an arbitrary command with its bins in PATH',
|
|
63
71
|
},
|
package/dist/commands/init.js
CHANGED
|
@@ -60,13 +60,11 @@ export const command = async (conf) => {
|
|
|
60
60
|
const { config: parsedWSConfig, source } = resolveWSConfig(conf.options.projectRoot, conf.options.packageJson);
|
|
61
61
|
// what gets written back to vlt.json. The raw vlt.json value is
|
|
62
62
|
// reused as-is so its existing shape is preserved. When the
|
|
63
|
-
// definitions live in package.json
|
|
64
|
-
// first -- otherwise adding a `workspaces`
|
|
65
|
-
// every workspace
|
|
63
|
+
// definitions live in package.json or pnpm-workspace.yaml instead,
|
|
64
|
+
// seed vlt.json with them first -- otherwise adding a `workspaces`
|
|
65
|
+
// field here would shadow every workspace already declared there.
|
|
66
66
|
const workspacesConfig = load('workspaces', assertWSConfig) ??
|
|
67
|
-
(source
|
|
68
|
-
Object.values(parsedWSConfig).flat()
|
|
69
|
-
: undefined);
|
|
67
|
+
(source ? Object.values(parsedWSConfig).flat() : undefined);
|
|
70
68
|
const results = [];
|
|
71
69
|
const addToConfig = [];
|
|
72
70
|
// create a new package.json file for every workspace
|
package/dist/commands/install.js
CHANGED
|
@@ -3,6 +3,7 @@ import { install } from '@vltpkg/graph';
|
|
|
3
3
|
import { parseAddArgs } from "../parse-add-remove-args.js";
|
|
4
4
|
import { planSpecConfigPersist } from "../persist-spec-config.js";
|
|
5
5
|
import { asUnknownSpecPrefix } from "../require-registry.js";
|
|
6
|
+
import { addTypesDeps } from "../save-types.js";
|
|
6
7
|
import { trackInstall } from "../telemetry.js";
|
|
7
8
|
import { lazyView } from "../view.js";
|
|
8
9
|
export const needsRegistry = true;
|
|
@@ -32,6 +33,9 @@ export const usage = () => commandUsage({
|
|
|
32
33
|
'save-prod': {
|
|
33
34
|
description: 'Save installed packages to package.json as dependencies.',
|
|
34
35
|
},
|
|
36
|
+
'save-types': {
|
|
37
|
+
description: 'Also save matching @types/* packages to devDependencies for added packages that ship no types.',
|
|
38
|
+
},
|
|
35
39
|
'save-config': {
|
|
36
40
|
description: 'Save registry and git host options given on the command line or via env (e.g. `--registries name=url`) to the project vlt.json, or the user vlt.json with --config=user.',
|
|
37
41
|
},
|
|
@@ -130,6 +134,8 @@ export const command = async (conf) => {
|
|
|
130
134
|
const scurry = conf.options.scurry;
|
|
131
135
|
const { add } = parseAddArgs(conf, scurry, monorepo);
|
|
132
136
|
const persist = planSpecConfigPersist(conf);
|
|
137
|
+
if (conf.values['save-types'])
|
|
138
|
+
await addTypesDeps(conf, add);
|
|
133
139
|
const frozenLockfile = conf.options['frozen-lockfile'];
|
|
134
140
|
const expectLockfile = conf.options['expect-lockfile'];
|
|
135
141
|
const lockfileOnly = conf.options['lockfile-only'];
|
|
@@ -166,7 +166,9 @@ const explain = (entry) => {
|
|
|
166
166
|
};
|
|
167
167
|
/**
|
|
168
168
|
* The commands that perform the upgrades, one `vlt install` per set of
|
|
169
|
-
* flags so that the specs can be combined.
|
|
169
|
+
* flags so that the specs can be combined. Several entries can call
|
|
170
|
+
* for the same step, such as every workspace that shares a catalog
|
|
171
|
+
* entry, so each step is listed once.
|
|
170
172
|
*/
|
|
171
173
|
const suggestActions = (result) => {
|
|
172
174
|
const lines = [];
|
|
@@ -175,26 +177,28 @@ const suggestActions = (result) => {
|
|
|
175
177
|
lines.push(`Run \`vlt update\` to pick up ${plural(updates, 'in-range update')}.`);
|
|
176
178
|
}
|
|
177
179
|
const installs = new Map();
|
|
180
|
+
const edits = new Set();
|
|
178
181
|
for (const { action } of result) {
|
|
179
|
-
if (!action
|
|
182
|
+
if (!action)
|
|
180
183
|
continue;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
if (action.startsWith('vlt install ')) {
|
|
185
|
+
const [spec = '', ...flags] = action
|
|
186
|
+
.slice('vlt install '.length)
|
|
187
|
+
.split(' ');
|
|
188
|
+
const key = flags.join(' ');
|
|
189
|
+
const specs = installs.get(key) ?? new Set();
|
|
190
|
+
specs.add(spec);
|
|
191
|
+
installs.set(key, specs);
|
|
192
|
+
}
|
|
193
|
+
else if (!action.startsWith('vlt ')) {
|
|
194
|
+
edits.add(`${action.charAt(0).toUpperCase()}${action.slice(1)}.`);
|
|
195
|
+
}
|
|
188
196
|
}
|
|
189
197
|
for (const [flags, specs] of installs) {
|
|
190
198
|
const args = [...specs, ...(flags ? [flags] : [])].join(' ');
|
|
191
199
|
lines.push(`Run \`vlt install ${args}\` to move to latest.`);
|
|
192
200
|
}
|
|
193
|
-
|
|
194
|
-
if (action && !action.startsWith('vlt ')) {
|
|
195
|
-
lines.push(`${action.charAt(0).toUpperCase()}${action.slice(1)}.`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
201
|
+
lines.push(...edits);
|
|
198
202
|
return lines;
|
|
199
203
|
};
|
|
200
204
|
export const views = {
|
|
@@ -336,7 +340,10 @@ const actionFor = (edge, entry, { savePrefix }) => {
|
|
|
336
340
|
return 'vlt update';
|
|
337
341
|
const newRange = `${savePrefix}${latest}`;
|
|
338
342
|
if (edge.spec.type === 'catalog') {
|
|
339
|
-
|
|
343
|
+
const catalog = edge.spec.catalog ?
|
|
344
|
+
`"${edge.spec.catalog}" catalog`
|
|
345
|
+
: 'catalog';
|
|
346
|
+
return `set the ${catalog} entry for ${edge.name} in vlt.json to ${newRange}`;
|
|
340
347
|
}
|
|
341
348
|
const workspace = edge.from.mainImporter ?
|
|
342
349
|
''
|
package/dist/commands/pack.js
CHANGED
|
@@ -9,6 +9,7 @@ import { actual } from '@vltpkg/graph';
|
|
|
9
9
|
import { Query } from '@vltpkg/query';
|
|
10
10
|
import { error } from '@vltpkg/error-cause';
|
|
11
11
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
12
|
+
import { sortScopeLocations } from "../sort-scope-locations.js";
|
|
12
13
|
import { minimatch } from 'minimatch';
|
|
13
14
|
export const needsRegistry = true;
|
|
14
15
|
export const usage = () => commandUsage({
|
|
@@ -97,7 +98,7 @@ const scopeLocations = async (queryString, conf) => {
|
|
|
97
98
|
}));
|
|
98
99
|
locations.push(resolve(projectRoot, location));
|
|
99
100
|
}
|
|
100
|
-
return locations;
|
|
101
|
+
return sortScopeLocations(locations, options.monorepo);
|
|
101
102
|
};
|
|
102
103
|
export const command = async (conf) => {
|
|
103
104
|
const { options, projectRoot } = conf;
|
package/dist/commands/pkg.js
CHANGED
|
@@ -9,6 +9,7 @@ import { SecurityArchive } from '@vltpkg/security-archive';
|
|
|
9
9
|
import { views as initViews } from "./init.js";
|
|
10
10
|
import { commandUsage } from "../config/usage.js";
|
|
11
11
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
12
|
+
import { sortScopeLocations } from "../sort-scope-locations.js";
|
|
12
13
|
import { minimatch } from 'minimatch';
|
|
13
14
|
export const views = {
|
|
14
15
|
human: (results, _, config) => {
|
|
@@ -129,15 +130,17 @@ export const command = async (conf) => {
|
|
|
129
130
|
const { nodes: resultNodes } = await query.search(queryString, {
|
|
130
131
|
signal: new AbortController().signal,
|
|
131
132
|
});
|
|
133
|
+
const found = [];
|
|
132
134
|
for (const node of resultNodes) {
|
|
133
135
|
const location = node.location;
|
|
134
136
|
assert(location, error(`node ${node.id} has no location`, {
|
|
135
137
|
found: node,
|
|
136
138
|
}));
|
|
137
139
|
if (node.id !== VIRTUAL_ROOT_ID) {
|
|
138
|
-
|
|
140
|
+
found.push(resolve(node.projectRoot, location));
|
|
139
141
|
}
|
|
140
142
|
}
|
|
143
|
+
locations.push(...sortScopeLocations(found, options.monorepo));
|
|
141
144
|
}
|
|
142
145
|
else if (paths?.length || groups?.length || recursive) {
|
|
143
146
|
for (const workspace of options.monorepo ?? []) {
|
package/dist/commands/publish.js
CHANGED
|
@@ -10,6 +10,7 @@ import prettyBytes from 'pretty-bytes';
|
|
|
10
10
|
import { actual } from '@vltpkg/graph';
|
|
11
11
|
import { Query } from '@vltpkg/query';
|
|
12
12
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
13
|
+
import { sortScopeLocations } from "../sort-scope-locations.js";
|
|
13
14
|
import { minimatch } from 'minimatch';
|
|
14
15
|
import { resolveRegistry } from "../require-registry.js";
|
|
15
16
|
import { stderr } from "../output.js";
|
|
@@ -114,13 +115,15 @@ export const command = async (conf) => {
|
|
|
114
115
|
const { nodes } = await query.search(queryString, {
|
|
115
116
|
signal: new AbortController().signal,
|
|
116
117
|
});
|
|
118
|
+
const found = [];
|
|
117
119
|
for (const node of nodes) {
|
|
118
120
|
const { location } = node.toJSON();
|
|
119
121
|
assert(location, error(`node ${node.id} has no location`, {
|
|
120
122
|
found: node,
|
|
121
123
|
}));
|
|
122
|
-
|
|
124
|
+
found.push(resolve(projectRoot, location));
|
|
123
125
|
}
|
|
126
|
+
locations.push(...sortScopeLocations(found, options.monorepo));
|
|
124
127
|
}
|
|
125
128
|
else if (paths?.length || groups?.length || recursive) {
|
|
126
129
|
for (const workspace of options.monorepo ?? []) {
|
package/dist/commands/update.js
CHANGED
|
@@ -10,8 +10,17 @@ export const usage = () => commandUsage({
|
|
|
10
10
|
command: 'update',
|
|
11
11
|
usage: '',
|
|
12
12
|
description: `Update dependencies to their latest in-range versions.
|
|
13
|
-
Discards the lockfile and resolves dependencies from scratch
|
|
13
|
+
Discards the lockfile and resolves dependencies from scratch
|
|
14
|
+
(with -w: only matching workspaces' dependencies).`,
|
|
14
15
|
options: {
|
|
16
|
+
workspace: {
|
|
17
|
+
value: '<path|glob>',
|
|
18
|
+
description: 'Limit update to matching workspaces. Others, and deps they share, keep their locked versions.',
|
|
19
|
+
},
|
|
20
|
+
'workspace-group': {
|
|
21
|
+
value: '<name>',
|
|
22
|
+
description: 'Limit update to workspace groups. Others, and deps they share, keep their locked versions.',
|
|
23
|
+
},
|
|
15
24
|
'save-config': {
|
|
16
25
|
description: 'Save registry and git host options given on the command line or via env (e.g. `--registries name=url`) to the project vlt.json, or the user vlt.json with --config=user.',
|
|
17
26
|
},
|
package/dist/commands/version.js
CHANGED
|
@@ -8,6 +8,7 @@ import assert from 'node:assert';
|
|
|
8
8
|
import { actual } from '@vltpkg/graph';
|
|
9
9
|
import { Query } from '@vltpkg/query';
|
|
10
10
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
11
|
+
import { sortScopeLocations } from "../sort-scope-locations.js";
|
|
11
12
|
import { minimatch } from 'minimatch';
|
|
12
13
|
/**
|
|
13
14
|
* `--no-git-tag-version` skips the whole git dance, matching npm.
|
|
@@ -223,13 +224,15 @@ export const command = async (conf) => {
|
|
|
223
224
|
const { nodes } = await query.search(queryString, {
|
|
224
225
|
signal: new AbortController().signal,
|
|
225
226
|
});
|
|
227
|
+
const found = [];
|
|
226
228
|
for (const node of nodes) {
|
|
227
229
|
const location = node.location;
|
|
228
230
|
assert(location, error(`node ${node.id} has no location`, {
|
|
229
231
|
found: node,
|
|
230
232
|
}));
|
|
231
|
-
|
|
233
|
+
found.push(resolve(node.projectRoot, location));
|
|
232
234
|
}
|
|
235
|
+
locations.push(...sortScopeLocations(found, options.monorepo));
|
|
233
236
|
}
|
|
234
237
|
else if (paths?.length || groups?.length || recursive) {
|
|
235
238
|
for (const workspace of options.monorepo ?? []) {
|
package/dist/commands/view.js
CHANGED
|
@@ -22,7 +22,11 @@ export const usage = () => commandUsage({
|
|
|
22
22
|
(e.g., \`dist-tags.latest\`, \`dependencies.lodash\`).
|
|
23
23
|
|
|
24
24
|
Security data from the vlt security archive is shown when
|
|
25
|
-
available, including scores and alerts
|
|
25
|
+
available, including scores and alerts.
|
|
26
|
+
|
|
27
|
+
Use \`.\` (or no args) for the package in the current directory
|
|
28
|
+
(nearest package.json), \`.@<version>\` to pick a version.
|
|
29
|
+
Private packages show local package.json info.`,
|
|
26
30
|
examples: {
|
|
27
31
|
express: {
|
|
28
32
|
description: 'View info about the latest version of express',
|
|
@@ -42,6 +46,12 @@ export const usage = () => commandUsage({
|
|
|
42
46
|
'express dist-tags.latest': {
|
|
43
47
|
description: 'Show the latest dist-tag value',
|
|
44
48
|
},
|
|
49
|
+
'.': {
|
|
50
|
+
description: 'View the package in the current directory',
|
|
51
|
+
},
|
|
52
|
+
'. version': {
|
|
53
|
+
description: 'Show the latest published version of the current package',
|
|
54
|
+
},
|
|
45
55
|
},
|
|
46
56
|
options: {
|
|
47
57
|
view: {
|
|
@@ -114,7 +124,7 @@ const formatHuman = (result) => {
|
|
|
114
124
|
// Name and version
|
|
115
125
|
const name = manifest.name ?? packument.name;
|
|
116
126
|
const version = manifest.version ?? '';
|
|
117
|
-
lines.push(`${name}@${version}`);
|
|
127
|
+
lines.push(version ? `${name}@${version}` : name);
|
|
118
128
|
// Description
|
|
119
129
|
if (manifest.description) {
|
|
120
130
|
lines.push(manifest.description);
|
|
@@ -263,15 +273,30 @@ const lookupField = (result, path) => {
|
|
|
263
273
|
// Default: look up in manifest
|
|
264
274
|
return dotProp.get(result.manifest, path);
|
|
265
275
|
};
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
if (!
|
|
269
|
-
throw error('
|
|
276
|
+
const readLocalManifest = (conf) => {
|
|
277
|
+
const file = conf.options.packageJson.find(process.cwd());
|
|
278
|
+
if (!file) {
|
|
279
|
+
throw error('No local package.json found', {
|
|
270
280
|
code: 'EUSAGE',
|
|
281
|
+
path: process.cwd(),
|
|
271
282
|
});
|
|
272
283
|
}
|
|
273
|
-
const
|
|
274
|
-
const
|
|
284
|
+
const manifest = conf.options.packageJson.read(file);
|
|
285
|
+
const { name } = manifest;
|
|
286
|
+
if (!name) {
|
|
287
|
+
throw error('No package name found in package.json', {
|
|
288
|
+
code: 'EUSAGE',
|
|
289
|
+
found: file,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return { name, manifest };
|
|
293
|
+
};
|
|
294
|
+
// nothing published: no dist-tags or versions
|
|
295
|
+
const localView = (name, manifest) => ({
|
|
296
|
+
manifest,
|
|
297
|
+
packument: { name, 'dist-tags': {}, versions: {} },
|
|
298
|
+
});
|
|
299
|
+
const fetchView = async (spec, conf) => {
|
|
275
300
|
const pic = new PackageInfoClient(conf.options);
|
|
276
301
|
// Fetch the full packument (needs time, maintainers) and resolved manifest
|
|
277
302
|
const [packument, resolvedManifest] = await Promise.all([
|
|
@@ -295,11 +320,34 @@ export const command = async (conf) => {
|
|
|
295
320
|
// Security data is optional, don't fail the command
|
|
296
321
|
}
|
|
297
322
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
323
|
+
return { packument, manifest, security };
|
|
324
|
+
};
|
|
325
|
+
export const command = async (conf) => {
|
|
326
|
+
// `.` (or no args) is the package in the current directory
|
|
327
|
+
const specArg = conf.positionals[0] || '.';
|
|
328
|
+
const fieldPath = conf.positionals[1];
|
|
329
|
+
let result;
|
|
330
|
+
if (specArg === '.' || specArg.startsWith('.@')) {
|
|
331
|
+
const { name, manifest } = readLocalManifest(conf);
|
|
332
|
+
const rest = specArg.slice(1);
|
|
333
|
+
if (manifest.private) {
|
|
334
|
+
// never query the registry for private pkgs
|
|
335
|
+
if (rest) {
|
|
336
|
+
throw error('Cannot select a version of a private package', {
|
|
337
|
+
code: 'EUSAGE',
|
|
338
|
+
found: specArg,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
result = localView(name, manifest);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// keep name literal; rest is '' or '@<spec>'
|
|
345
|
+
result = await fetchView(Spec.parse(name, rest.slice(1), conf.options), conf);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
result = await fetchView(Spec.parseArgs(specArg, conf.options), conf);
|
|
350
|
+
}
|
|
303
351
|
// If a field path is provided, resolve it
|
|
304
352
|
if (fieldPath !== undefined) {
|
|
305
353
|
result.fieldPath = fieldPath;
|
|
@@ -75,7 +75,7 @@ export declare const recordFields: readonly ["git-hosts", "registries", "git-hos
|
|
|
75
75
|
export type RecordField = (typeof recordFields)[number];
|
|
76
76
|
export declare const isRecordField: (s: string) => s is RecordField;
|
|
77
77
|
/** Valid `store-linker` values. */
|
|
78
|
-
export declare const storeLinkers: readonly ["auto", "hardlink", "copy", "unpack"];
|
|
78
|
+
export declare const storeLinkers: readonly ["auto", "hardlink", "clone", "copy", "unpack"];
|
|
79
79
|
export declare const definition: import("jackspeak").Jack<{
|
|
80
80
|
color: {
|
|
81
81
|
short: string;
|
|
@@ -139,7 +139,7 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
139
139
|
'store-linker': {
|
|
140
140
|
hint: string;
|
|
141
141
|
description: string;
|
|
142
|
-
validOptions: readonly ["auto", "hardlink", "copy", "unpack"];
|
|
142
|
+
validOptions: readonly ["auto", "hardlink", "clone", "copy", "unpack"];
|
|
143
143
|
default: string;
|
|
144
144
|
};
|
|
145
145
|
tag: {
|
|
@@ -365,6 +365,9 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
365
365
|
short: string;
|
|
366
366
|
description: string;
|
|
367
367
|
};
|
|
368
|
+
'save-types': {
|
|
369
|
+
description: string;
|
|
370
|
+
};
|
|
368
371
|
'save-config': {
|
|
369
372
|
description: string;
|
|
370
373
|
};
|
|
@@ -374,6 +377,7 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
374
377
|
'save-optional': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
375
378
|
'save-peer': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
376
379
|
'save-prod': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
380
|
+
'save-types': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
377
381
|
'save-config': import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
378
382
|
} & {
|
|
379
383
|
'save-prefix': {
|
|
@@ -114,6 +114,7 @@ export const isRecordField = (s) => recordFields.includes(s);
|
|
|
114
114
|
export const storeLinkers = [
|
|
115
115
|
'auto',
|
|
116
116
|
'hardlink',
|
|
117
|
+
'clone',
|
|
117
118
|
'copy',
|
|
118
119
|
'unpack',
|
|
119
120
|
];
|
|
@@ -226,8 +227,11 @@ export const definition = j
|
|
|
226
227
|
|
|
227
228
|
There is **no** built-in \`npm\` registry URL; the \`npm\`
|
|
228
229
|
alias must be configured (e.g. via \`vlt setup\`) before it
|
|
229
|
-
can be used. The \`gh\`
|
|
230
|
-
|
|
230
|
+
can be used. The \`gh\` alias has a built-in default
|
|
231
|
+
that can be overridden here. A name used by
|
|
232
|
+
\`--jsr-registries\` or \`--git-hosts\` (e.g. \`jsr\`,
|
|
233
|
+
\`github\`), or a built-in protocol (e.g. \`file\`), is
|
|
234
|
+
an error.
|
|
231
235
|
|
|
232
236
|
Aliases given on the command line or in
|
|
233
237
|
\`VLT_REGISTRIES\` (newline-delimited) merge with the
|
|
@@ -317,28 +321,35 @@ export const definition = j
|
|
|
317
321
|
default: cacheDir,
|
|
318
322
|
},
|
|
319
323
|
'store-linker': {
|
|
320
|
-
hint: 'auto | hardlink | copy | unpack',
|
|
324
|
+
hint: 'auto | hardlink | clone | copy | unpack',
|
|
321
325
|
description: `How packages are placed in \`node_modules\`. Every
|
|
322
326
|
linker but \`unpack\` fills a global store under
|
|
323
327
|
\`cache\` in the background, then places packages
|
|
324
328
|
from it.
|
|
325
329
|
|
|
326
330
|
- auto: Default. On Linux, hardlink from the global
|
|
327
|
-
store, copying any file that cannot be linked.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
store, copying any file that cannot be linked. On
|
|
332
|
+
macOS, clone from it, on a Node with \`node:ffi\`
|
|
333
|
+
(26.1 and later, and \`--allow-ffi\` under
|
|
334
|
+
\`--permission\`); a hardlink is the slowest way
|
|
335
|
+
to place a file on APFS, so macOS without it, and
|
|
336
|
+
Windows, unpack by default.
|
|
331
337
|
- hardlink: Hardlink from the global store on every
|
|
332
338
|
platform.
|
|
339
|
+
- clone: Clone each package directory from the
|
|
340
|
+
global store copy-on-write (\`clonefile\`, macOS
|
|
341
|
+
on APFS). Files share storage with the store
|
|
342
|
+
until edited, so editing in place is safe.
|
|
343
|
+
Unpacks where cloning fails.
|
|
333
344
|
- copy: Copy from the global store. Use this when
|
|
334
345
|
editing files in \`node_modules\` in place, since
|
|
335
346
|
a hardlink shares its content with every project.
|
|
336
347
|
- unpack: Unpack each package tarball, skipping the
|
|
337
348
|
global store.
|
|
338
349
|
|
|
339
|
-
A package is copied right before its
|
|
340
|
-
scripts run, so they don't change its files
|
|
341
|
-
store.
|
|
350
|
+
A hardlinked package is copied right before its
|
|
351
|
+
install scripts run, so they don't change its files
|
|
352
|
+
in the store; a cloned one needs no copy.
|
|
342
353
|
An invalid \`VLT_STORE_LINKER\` warns and uses
|
|
343
354
|
\`unpack\`.`,
|
|
344
355
|
validOptions: storeLinkers,
|
|
@@ -702,6 +713,11 @@ export const definition = j
|
|
|
702
713
|
devDependencies or optionalDependencies, but you want to
|
|
703
714
|
move it to be a non-optional production dependency.`,
|
|
704
715
|
},
|
|
716
|
+
'save-types': {
|
|
717
|
+
description: `Also save the matching \`@types/*\` package as a
|
|
718
|
+
devDependency for each added registry package that
|
|
719
|
+
ships no TypeScript types of its own.`,
|
|
720
|
+
},
|
|
705
721
|
'save-config': {
|
|
706
722
|
description: `Save the registry and git host options given on the
|
|
707
723
|
command line or via \`VLT_*\` env (\`--registry\`,
|
package/dist/config/index.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export type ConfigOptions = ConfigOptionsNoExtras & Pick<SpecOptions, 'catalog'
|
|
|
85
85
|
storeRoot: string;
|
|
86
86
|
monorepo?: Monorepo;
|
|
87
87
|
packageInfo: PackageInfoClient;
|
|
88
|
+
/** aborted when the command fails */
|
|
89
|
+
signal: AbortSignal;
|
|
88
90
|
};
|
|
89
91
|
/**
|
|
90
92
|
* The base config definition set as a type
|
|
@@ -125,6 +127,8 @@ export declare class Config {
|
|
|
125
127
|
* to recalculate the options.
|
|
126
128
|
*/
|
|
127
129
|
resetOptions(projectRoot?: string): void;
|
|
130
|
+
/** Abort requests still in flight */
|
|
131
|
+
abort(): void;
|
|
128
132
|
/**
|
|
129
133
|
* positional arguments to the vlt process
|
|
130
134
|
*/
|
package/dist/config/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { error } from '@vltpkg/error-cause';
|
|
|
24
24
|
import { PackageInfoClient } from '@vltpkg/package-info';
|
|
25
25
|
import { PackageJson } from '@vltpkg/package-json';
|
|
26
26
|
import { storeRoot } from '@vltpkg/registry-client/store-root';
|
|
27
|
+
import { cloneAvailable } from '@vltpkg/tar/clonefile';
|
|
27
28
|
import { resetCaches } from '@vltpkg/dep-id';
|
|
28
29
|
import { getOptions } from '@vltpkg/spec';
|
|
29
30
|
import { assertRecordStringString, assertRecordStringT, isObject, isRecordStringString, } from '@vltpkg/types';
|
|
@@ -35,6 +36,7 @@ import { PathScurry } from 'path-scurry';
|
|
|
35
36
|
import { commands, definition, defaultValues, getCommand, isRecordField, recordFields, storeLinkers, } from "./definition.js";
|
|
36
37
|
import { merge } from "./merge.js";
|
|
37
38
|
import { cloneLayer, mergeLayers } from "./merge-layers.js";
|
|
39
|
+
import { assertSpecAliases } from "./spec-aliases.js";
|
|
38
40
|
export { commands, definition, isRecordField, recordFields, };
|
|
39
41
|
export const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
|
|
40
42
|
// turn a set of pairs into a Record object.
|
|
@@ -172,6 +174,7 @@ export class Config {
|
|
|
172
174
|
const asRecords = pairsToRecords(this.parse().values);
|
|
173
175
|
const extras = {
|
|
174
176
|
projectRoot: this.projectRoot,
|
|
177
|
+
signal: this.#abort.signal,
|
|
175
178
|
storeRoot: storeRoot(asRecords.cache),
|
|
176
179
|
scurry,
|
|
177
180
|
packageJson,
|
|
@@ -197,7 +200,8 @@ export class Config {
|
|
|
197
200
|
return Object.fromEntries(Object.entries(options).filter(([k]) => k !== 'monorepo' &&
|
|
198
201
|
k !== 'scurry' &&
|
|
199
202
|
k !== 'packageJson' &&
|
|
200
|
-
k !== 'packageInfo'
|
|
203
|
+
k !== 'packageInfo' &&
|
|
204
|
+
k !== 'signal'));
|
|
201
205
|
},
|
|
202
206
|
});
|
|
203
207
|
return this.#options;
|
|
@@ -213,6 +217,11 @@ export class Config {
|
|
|
213
217
|
}
|
|
214
218
|
// memoized options() getter value
|
|
215
219
|
#options;
|
|
220
|
+
#abort = new AbortController();
|
|
221
|
+
/** Abort requests still in flight */
|
|
222
|
+
abort() {
|
|
223
|
+
this.#abort.abort();
|
|
224
|
+
}
|
|
216
225
|
/**
|
|
217
226
|
* positional arguments to the vlt process
|
|
218
227
|
*/
|
|
@@ -277,13 +286,16 @@ export class Config {
|
|
|
277
286
|
// ok, applied cmd-specific defaults, do rest of the parse
|
|
278
287
|
this.jack.applyDefaults(p);
|
|
279
288
|
// `auto` only hardlinks from the global store on linux: on APFS a
|
|
280
|
-
// hardlink is the slowest way to place a file, so darwin
|
|
281
|
-
//
|
|
282
|
-
//
|
|
289
|
+
// hardlink is the slowest way to place a file, so darwin clones
|
|
290
|
+
// instead, when its node can load `node:ffi` to call clonefile(2)
|
|
291
|
+
// with, and win32 (and darwin without it) unpack by default. the
|
|
292
|
+
// env layer is rewritten too, so `explicit` records the resolved
|
|
293
|
+
// value.
|
|
283
294
|
if (p.values['store-linker'] === 'auto' &&
|
|
284
295
|
process.platform !== 'linux') {
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
const linker = cloneAvailable() ? 'clone' : 'unpack';
|
|
297
|
+
p.values['store-linker'] = linker;
|
|
298
|
+
envValues['store-linker'] = linker;
|
|
287
299
|
}
|
|
288
300
|
// what was set on the cli or env for this run. record fields hold
|
|
289
301
|
// only those pairs, env then cli, so later ones win per key.
|
|
@@ -317,6 +329,10 @@ export class Config {
|
|
|
317
329
|
const base = (k in cmdPairs ? cmdPairs[k] : fileValues[k]) ?? [];
|
|
318
330
|
values[k] = dedupePairs([...base, ...pairs]);
|
|
319
331
|
}
|
|
332
|
+
// skipped for `vlt config`, so a bad alias can be removed
|
|
333
|
+
if (cmdOrFallback !== 'config') {
|
|
334
|
+
assertSpecAliases(pairsToRecords(p.values));
|
|
335
|
+
}
|
|
320
336
|
this.jack.writeEnv(p);
|
|
321
337
|
process.env[kParentEnv] = JSON.stringify({
|
|
322
338
|
explicit,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const builtins: {
|
|
2
|
+
registries: {
|
|
3
|
+
gh: string;
|
|
4
|
+
};
|
|
5
|
+
'jsr-registries': {
|
|
6
|
+
jsr: string;
|
|
7
|
+
};
|
|
8
|
+
'git-hosts': {
|
|
9
|
+
github: string;
|
|
10
|
+
bitbucket: string;
|
|
11
|
+
gitlab: string;
|
|
12
|
+
gist: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
type SpecAliasField = keyof typeof builtins;
|
|
16
|
+
/**
|
|
17
|
+
* Throw `ECONFIG` if a spec prefix alias is in more than one of
|
|
18
|
+
* `registries` / `jsr-registries` / `git-hosts` (built-ins included),
|
|
19
|
+
* or is a built-in protocol. Overriding a built-in in its own field is ok.
|
|
20
|
+
*/
|
|
21
|
+
export declare const assertSpecAliases: (values: Partial<Record<SpecAliasField, Record<string, string>>>) => void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { builtinProtocols, defaultGitHosts, defaultJsrRegistries, defaultRegistries, } from '@vltpkg/spec';
|
|
3
|
+
const builtins = {
|
|
4
|
+
registries: defaultRegistries,
|
|
5
|
+
'jsr-registries': defaultJsrRegistries,
|
|
6
|
+
'git-hosts': defaultGitHosts,
|
|
7
|
+
};
|
|
8
|
+
const fields = Object.keys(builtins);
|
|
9
|
+
/**
|
|
10
|
+
* Throw `ECONFIG` if a spec prefix alias is in more than one of
|
|
11
|
+
* `registries` / `jsr-registries` / `git-hosts` (built-ins included),
|
|
12
|
+
* or is a built-in protocol. Overriding a built-in in its own field is ok.
|
|
13
|
+
*/
|
|
14
|
+
export const assertSpecAliases = (values) => {
|
|
15
|
+
const owners = new Map();
|
|
16
|
+
for (const field of fields) {
|
|
17
|
+
for (const key of Object.keys(builtins[field])) {
|
|
18
|
+
if (values[field]?.[key] === undefined) {
|
|
19
|
+
owners.set(key, { field, builtin: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const field of fields) {
|
|
24
|
+
for (const key of Object.keys(values[field] ?? {})) {
|
|
25
|
+
const name = `${field}.${key}`;
|
|
26
|
+
if (builtinProtocols.has(key)) {
|
|
27
|
+
throw error(`${name} shadows the built-in \`${key}:\` protocol`, {
|
|
28
|
+
code: 'ECONFIG',
|
|
29
|
+
name,
|
|
30
|
+
found: key,
|
|
31
|
+
wanted: 'a name that is not a built-in protocol',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const owner = owners.get(key);
|
|
35
|
+
if (owner) {
|
|
36
|
+
const where = `${owner.field}.${key}${owner.builtin ? ' (built-in)' : ''}`;
|
|
37
|
+
throw error(`\`${key}:\` spec prefix defined in both ${where} and ${name}`, {
|
|
38
|
+
code: 'ECONFIG',
|
|
39
|
+
name,
|
|
40
|
+
found: key,
|
|
41
|
+
wanted: `a ${field} name not used by ${owner.field}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
owners.set(key, { field, builtin: false });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
package/dist/exec-command.js
CHANGED
|
@@ -14,6 +14,7 @@ import { stderr, stdout, styleTextStdout } from "./output.js";
|
|
|
14
14
|
import assert from 'node:assert';
|
|
15
15
|
import { resolve } from 'node:path';
|
|
16
16
|
import { createHostContextsMap } from "./query-host-contexts.js";
|
|
17
|
+
import { sortScopeLocations } from "./sort-scope-locations.js";
|
|
17
18
|
const isScriptSet = (o) => {
|
|
18
19
|
if (!o || typeof o !== 'object')
|
|
19
20
|
return false;
|
|
@@ -98,11 +99,13 @@ export class ExecCommand {
|
|
|
98
99
|
this.#defaultIgnoreMissing = true;
|
|
99
100
|
const mainManifest = conf.options.packageJson.maybeRead(this.projectRoot);
|
|
100
101
|
let graph;
|
|
102
|
+
let monorepo;
|
|
101
103
|
if (mainManifest) {
|
|
104
|
+
monorepo = Monorepo.load(this.projectRoot);
|
|
102
105
|
graph = actual.load({
|
|
103
106
|
...conf.options,
|
|
104
107
|
mainManifest,
|
|
105
|
-
monorepo
|
|
108
|
+
monorepo,
|
|
106
109
|
loadManifests: false,
|
|
107
110
|
});
|
|
108
111
|
}
|
|
@@ -120,14 +123,15 @@ export class ExecCommand {
|
|
|
120
123
|
const { nodes } = await query.search(queryString, {
|
|
121
124
|
signal: new AbortController().signal,
|
|
122
125
|
});
|
|
123
|
-
|
|
126
|
+
const locations = [];
|
|
124
127
|
for (const node of nodes) {
|
|
125
128
|
const { location } = node.toJSON();
|
|
126
129
|
assert(location, error(`node ${node.id} has no location`, {
|
|
127
130
|
found: node,
|
|
128
131
|
}));
|
|
129
|
-
|
|
132
|
+
locations.push(location);
|
|
130
133
|
}
|
|
134
|
+
this.#nodes = sortScopeLocations(locations, monorepo);
|
|
131
135
|
}
|
|
132
136
|
else if (paths?.length || groups?.length || recursive) {
|
|
133
137
|
this.#defaultIgnoreMissing = true;
|
|
@@ -163,8 +167,9 @@ export class ExecCommand {
|
|
|
163
167
|
}
|
|
164
168
|
// run across workspaces
|
|
165
169
|
let failed = false;
|
|
166
|
-
const runInDir = async (cwd, label) => {
|
|
167
|
-
const
|
|
170
|
+
const runInDir = async (cwd, label, signal) => {
|
|
171
|
+
const arg = { ...this.bgArg(cwd), signal };
|
|
172
|
+
const result = await this.bg(arg).catch((er) => {
|
|
168
173
|
if (isErrorWithCause(er) && isRunResult(er.cause)) {
|
|
169
174
|
this.printResult(label, er.cause);
|
|
170
175
|
}
|
|
@@ -189,7 +194,8 @@ export class ExecCommand {
|
|
|
189
194
|
}
|
|
190
195
|
}
|
|
191
196
|
else if (this.#monorepo) {
|
|
192
|
-
|
|
197
|
+
// aborted on the first failure, killing the rest
|
|
198
|
+
const wsResultMap = await this.#monorepo.run((ws, signal) => runInDir(ws.fullpath, ws.path, signal));
|
|
193
199
|
for (const [ws, result] of wsResultMap) {
|
|
194
200
|
if (result)
|
|
195
201
|
resultMap.set(ws.path, result);
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { format } from 'node:util';
|
|
2
|
-
import { asRootError } from '@vltpkg/output/error';
|
|
2
|
+
import { asRootError, findRootError } from '@vltpkg/output/error';
|
|
3
3
|
import { loadPackageJson } from 'package-json-from-dist';
|
|
4
4
|
import { getSortedCliOptions, getSortedKeys, } from "./config/definition.js";
|
|
5
5
|
import { Config } from "./config/index.js";
|
|
@@ -12,13 +12,14 @@ const loadVlt = async (cwd, argv) => {
|
|
|
12
12
|
return await Config.load(cwd, argv);
|
|
13
13
|
}
|
|
14
14
|
catch (e) {
|
|
15
|
-
const
|
|
15
|
+
const code = findRootError(e, { code: 'ECONFIG' }) ? 'ECONFIG' : 'JACKSPEAK';
|
|
16
|
+
const err = asRootError(e, { code });
|
|
16
17
|
const { found, path, wanted, name } = err.cause;
|
|
17
18
|
const isConfigFile = typeof path === 'string';
|
|
18
|
-
const msg = isConfigFile ?
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const validOptions = wanted ? undefined
|
|
19
|
+
const msg = isConfigFile ? `Problem in Config File ${path}`
|
|
20
|
+
: code === 'ECONFIG' ? 'Config Error'
|
|
21
|
+
: 'Invalid Option Flag';
|
|
22
|
+
const validOptions = wanted || code === 'ECONFIG' ? undefined
|
|
22
23
|
: isConfigFile ? getSortedKeys()
|
|
23
24
|
: getSortedCliOptions();
|
|
24
25
|
stderr(msg);
|
|
@@ -34,7 +35,10 @@ const loadVlt = async (cwd, argv) => {
|
|
|
34
35
|
stderr(indent('Valid Options:'));
|
|
35
36
|
stderr(indent(validOptions.join('\n'), 4));
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
// `vlt help` would fail the same way
|
|
39
|
+
if (code !== 'ECONFIG') {
|
|
40
|
+
stderr(indent(`Run 'vlt help' for more information about available options.`));
|
|
41
|
+
}
|
|
38
42
|
return flushAndExit(process.exitCode || 1);
|
|
39
43
|
}
|
|
40
44
|
};
|
|
@@ -57,7 +61,7 @@ const run = async () => {
|
|
|
57
61
|
if (vlt.command !== 'init' &&
|
|
58
62
|
(vlt.get('workspace') || vlt.get('workspace-group')) &&
|
|
59
63
|
![...(monorepo?.values() ?? [])].length) {
|
|
60
|
-
stderr(`Error: No matching workspaces found. Make sure the vlt.json config,
|
|
64
|
+
stderr(`Error: No matching workspaces found. Make sure the vlt.json config, the "workspaces" field of the root package.json, or the "packages" list of pnpm-workspace.yaml contains the correct workspaces.`);
|
|
61
65
|
if (vlt.get('workspace')) {
|
|
62
66
|
stderr(indent(`Workspace: ${format(vlt.get('workspace'))}`));
|
|
63
67
|
}
|
package/dist/output.js
CHANGED
|
@@ -227,6 +227,7 @@ export const outputCommand = async (cliCommand, conf, { start, vltVersion } = {
|
|
|
227
227
|
...formatOptions,
|
|
228
228
|
colors: stderrColor,
|
|
229
229
|
});
|
|
230
|
-
|
|
230
|
+
// stop pending requests, so the process can exit on its own
|
|
231
|
+
conf.abort();
|
|
231
232
|
}
|
|
232
233
|
};
|
package/dist/query-auth.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { GetAuthHeader } from '@vltpkg/query';
|
|
|
2
2
|
import type { LoadedConfig } from './config/index.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Returns a {@link GetAuthHeader} implementation that looks up
|
|
5
|
-
* authorization tokens from the local keychain / env
|
|
5
|
+
* authorization tokens from the local keychain / env (incl.
|
|
6
|
+
* `VLT_TOKEN_<key>` for configured registries), used by
|
|
6
7
|
* network-reliant query selectors, e.g: `:outdated()`.
|
|
7
8
|
*/
|
|
8
9
|
export declare const createGetAuthHeader: (conf: LoadedConfig) => GetAuthHeader;
|
package/dist/query-auth.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { getTokenByURL } from '@vltpkg/registry-client';
|
|
1
|
+
import { getTokenByURL, registryKeys } from '@vltpkg/registry-client';
|
|
2
2
|
/**
|
|
3
3
|
* Returns a {@link GetAuthHeader} implementation that looks up
|
|
4
|
-
* authorization tokens from the local keychain / env
|
|
4
|
+
* authorization tokens from the local keychain / env (incl.
|
|
5
|
+
* `VLT_TOKEN_<key>` for configured registries), used by
|
|
5
6
|
* network-reliant query selectors, e.g: `:outdated()`.
|
|
6
7
|
*/
|
|
7
|
-
export const createGetAuthHeader = (conf) =>
|
|
8
|
+
export const createGetAuthHeader = (conf) => {
|
|
9
|
+
const keys = registryKeys(conf.options);
|
|
10
|
+
return async (url) => getTokenByURL(url, conf.options.identity, keys);
|
|
11
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AddImportersDependenciesMap } from '@vltpkg/graph';
|
|
2
|
+
import type { Manifest } from '@vltpkg/types';
|
|
3
|
+
import type { LoadedConfig } from './config/index.ts';
|
|
4
|
+
type TypedManifest = Pick<Manifest, 'exports'> & {
|
|
5
|
+
types?: unknown;
|
|
6
|
+
typings?: unknown;
|
|
7
|
+
typesVersions?: unknown;
|
|
8
|
+
};
|
|
9
|
+
/** `foo` → `@types/foo`, `@scope/foo` → `@types/scope__foo` */
|
|
10
|
+
export declare const typesName: (name: string) => string;
|
|
11
|
+
/** Does the manifest declare its own TypeScript types? */
|
|
12
|
+
export declare const hasOwnTypes: (m: TypedManifest) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* For each added registry package that ships no types, add the newest
|
|
15
|
+
* non-deprecated `@types/*` package in the same major (major.minor for
|
|
16
|
+
* 0.x), else its older-major `latest`, to that importer's
|
|
17
|
+
* devDependencies.
|
|
18
|
+
*/
|
|
19
|
+
export declare const addTypesDeps: (conf: LoadedConfig, add: AddImportersDependenciesMap) => Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { splitDepID } from '@vltpkg/dep-id';
|
|
2
|
+
import { addKey, asDependency } from '@vltpkg/graph';
|
|
3
|
+
import { parse } from '@vltpkg/semver';
|
|
4
|
+
import { Spec } from '@vltpkg/spec';
|
|
5
|
+
import { isErrorWithCause, isObject, longDependencyTypes, } from '@vltpkg/types';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
/** `foo` → `@types/foo`, `@scope/foo` → `@types/scope__foo` */
|
|
8
|
+
export const typesName = (name) => `@types/${name.startsWith('@') ? name.slice(1).replace('/', '__') : name}`;
|
|
9
|
+
const exportsHaveTypes = (e) => !!e &&
|
|
10
|
+
typeof e === 'object' &&
|
|
11
|
+
Object.entries(e).some(([k, v]) => k === 'types' || k.startsWith('types@') || exportsHaveTypes(v));
|
|
12
|
+
// root entry only: typed subpaths don't type `import 'pkg'`
|
|
13
|
+
const rootExport = (e) => isObject(e) && Object.keys(e).some(k => k.startsWith('.')) ?
|
|
14
|
+
e['.']
|
|
15
|
+
: e;
|
|
16
|
+
/** Does the manifest declare its own TypeScript types? */
|
|
17
|
+
export const hasOwnTypes = (m) => !!(m.types || m.typings || m.typesVersions) ||
|
|
18
|
+
exportsHaveTypes(rootExport(m.exports));
|
|
19
|
+
const isResolveError = (er) => isErrorWithCause(er) &&
|
|
20
|
+
isObject(er.cause) &&
|
|
21
|
+
er.cause.code === 'ERESOLVE';
|
|
22
|
+
/**
|
|
23
|
+
* For each added registry package that ships no types, add the newest
|
|
24
|
+
* non-deprecated `@types/*` package in the same major (major.minor for
|
|
25
|
+
* 0.x), else its older-major `latest`, to that importer's
|
|
26
|
+
* devDependencies.
|
|
27
|
+
*/
|
|
28
|
+
export const addTypesDeps = async (conf, add) => {
|
|
29
|
+
const { options } = conf;
|
|
30
|
+
const { packageInfo, packageJson, projectRoot } = options;
|
|
31
|
+
const exact = conf.values['save-exact'];
|
|
32
|
+
const prefix = conf.values['save-prefix'];
|
|
33
|
+
const lookups = new Map();
|
|
34
|
+
const lookup = async (spec, name) => {
|
|
35
|
+
const types = (range) => packageInfo.manifest(Spec.parse(name, range, options));
|
|
36
|
+
try {
|
|
37
|
+
const m = await packageInfo.manifest(spec);
|
|
38
|
+
const { version = '' } = m;
|
|
39
|
+
const v = parse(version);
|
|
40
|
+
if (!v || hasOwnTypes(m))
|
|
41
|
+
return;
|
|
42
|
+
let tm;
|
|
43
|
+
try {
|
|
44
|
+
// @types track lib major (major.minor for 0.x); x-range, `^0`
|
|
45
|
+
// would only match 0.0.0
|
|
46
|
+
tm = await types(v.major ? String(v.major) : `0.${v.minor}`);
|
|
47
|
+
}
|
|
48
|
+
catch (er) {
|
|
49
|
+
if (!isResolveError(er))
|
|
50
|
+
throw er;
|
|
51
|
+
// @types often lag lib majors: take `latest` if older
|
|
52
|
+
tm = await types('latest');
|
|
53
|
+
if (!parse(tm.version ?? '')?.lessThan(v))
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!tm.version || tm.deprecated)
|
|
57
|
+
return;
|
|
58
|
+
// abbreviated packuments omit types fields: recheck on full one
|
|
59
|
+
const { versions } = await packageInfo.packument(spec, {
|
|
60
|
+
full: true,
|
|
61
|
+
});
|
|
62
|
+
if (hasOwnTypes({ ...versions[version] }))
|
|
63
|
+
return;
|
|
64
|
+
return Spec.parse(name, exact ? tm.version : `${prefix}${tm.version}`, options);
|
|
65
|
+
}
|
|
66
|
+
catch (er) {
|
|
67
|
+
if (isResolveError(er))
|
|
68
|
+
return;
|
|
69
|
+
throw er;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await Promise.all([...add].map(async ([importer, deps]) => {
|
|
73
|
+
const mani = packageJson.maybeRead(resolve(projectRoot, splitDepID(importer)[1])) ?? {};
|
|
74
|
+
const found = await Promise.all([...deps.values()].map(async ({ spec }) => {
|
|
75
|
+
const { final } = spec;
|
|
76
|
+
const name = typesName(spec.name);
|
|
77
|
+
if (final.type !== 'registry' ||
|
|
78
|
+
final.name !== spec.name ||
|
|
79
|
+
spec.name.startsWith('@types/') ||
|
|
80
|
+
deps.has(name) ||
|
|
81
|
+
[...longDependencyTypes].some(t => mani[t]?.[name] !== undefined) ||
|
|
82
|
+
// other registry: its @types/<name> is a different pkg
|
|
83
|
+
final.registry !==
|
|
84
|
+
Spec.parse(name, '', options).final.registry) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const key = String(spec);
|
|
88
|
+
let p = lookups.get(key);
|
|
89
|
+
if (!p)
|
|
90
|
+
lookups.set(key, (p = lookup(spec, name)));
|
|
91
|
+
return p;
|
|
92
|
+
}));
|
|
93
|
+
const types = found.filter(s => s !== undefined);
|
|
94
|
+
if (!types.length)
|
|
95
|
+
return;
|
|
96
|
+
// fresh Map: importers may share one deps Map
|
|
97
|
+
add.set(importer, new Map([
|
|
98
|
+
...deps,
|
|
99
|
+
...types.map(spec => [
|
|
100
|
+
addKey(spec),
|
|
101
|
+
asDependency({ spec, type: 'dev' }),
|
|
102
|
+
]),
|
|
103
|
+
]));
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Monorepo } from '@vltpkg/workspaces';
|
|
2
|
+
/**
|
|
3
|
+
* Reorder `--scope` target locations so each workspace follows the
|
|
4
|
+
* workspaces it depends on (directly or via unmatched workspaces),
|
|
5
|
+
* matching --workspace/--recursive. Ties broken by query order (a dep
|
|
6
|
+
* may move ahead of unrelated matches); cycles broken
|
|
7
|
+
* deterministically. Non-workspace
|
|
8
|
+
* locations (root, installed deps) keep their index. Accepts absolute
|
|
9
|
+
* or projectRoot-relative locations; returns the original strings.
|
|
10
|
+
*/
|
|
11
|
+
export declare const sortScopeLocations: (locations: string[], monorepo?: Monorepo) => string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { graphRunSync } from '@vltpkg/graph-run';
|
|
3
|
+
/**
|
|
4
|
+
* Reorder `--scope` target locations so each workspace follows the
|
|
5
|
+
* workspaces it depends on (directly or via unmatched workspaces),
|
|
6
|
+
* matching --workspace/--recursive. Ties broken by query order (a dep
|
|
7
|
+
* may move ahead of unrelated matches); cycles broken
|
|
8
|
+
* deterministically. Non-workspace
|
|
9
|
+
* locations (root, installed deps) keep their index. Accepts absolute
|
|
10
|
+
* or projectRoot-relative locations; returns the original strings.
|
|
11
|
+
*/
|
|
12
|
+
export const sortScopeLocations = (locations, monorepo) => {
|
|
13
|
+
if (!monorepo || locations.length < 2)
|
|
14
|
+
return locations;
|
|
15
|
+
// resolve first: workspaces are also keyed by name and rel path
|
|
16
|
+
const wsAt = (loc) => monorepo.get(resolve(monorepo.projectRoot, loc));
|
|
17
|
+
const slots = locations.map(loc => [loc, wsAt(loc)]);
|
|
18
|
+
const locOf = new Map();
|
|
19
|
+
for (const [loc, ws] of slots) {
|
|
20
|
+
if (ws && !locOf.has(ws))
|
|
21
|
+
locOf.set(ws, loc);
|
|
22
|
+
}
|
|
23
|
+
const [first, ...rest] = locOf.keys();
|
|
24
|
+
if (!first || !rest.length)
|
|
25
|
+
return locations;
|
|
26
|
+
// matched deps, walking through unmatched workspaces
|
|
27
|
+
const getDeps = (ws) => {
|
|
28
|
+
const found = [];
|
|
29
|
+
const seen = new Set([ws]);
|
|
30
|
+
const queue = monorepo.getDeps(ws);
|
|
31
|
+
for (const dep of queue) {
|
|
32
|
+
if (seen.has(dep))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(dep);
|
|
35
|
+
if (locOf.has(dep))
|
|
36
|
+
found.push(dep);
|
|
37
|
+
else
|
|
38
|
+
queue.push(...monorepo.getDeps(dep));
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
};
|
|
42
|
+
// DFS post-order: deps first, ties in query order
|
|
43
|
+
const sorted = graphRunSync({
|
|
44
|
+
graph: [first, ...rest],
|
|
45
|
+
getDeps,
|
|
46
|
+
visit: () => { },
|
|
47
|
+
}).keys();
|
|
48
|
+
const placed = new Set();
|
|
49
|
+
return slots.map(([loc, ws]) => {
|
|
50
|
+
if (!ws || placed.has(ws))
|
|
51
|
+
return loc;
|
|
52
|
+
placed.add(ws);
|
|
53
|
+
const next = sorted.next().value;
|
|
54
|
+
/* c8 ignore next - same count as placed slots */
|
|
55
|
+
return (next && locOf.get(next)) ?? loc;
|
|
56
|
+
});
|
|
57
|
+
};
|
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.4.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -14,33 +14,33 @@
|
|
|
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/pick-manifest": "1.
|
|
29
|
-
"@vltpkg/promise-spawn": "1.
|
|
30
|
-
"@vltpkg/query": "1.
|
|
31
|
-
"@vltpkg/registry-client": "1.
|
|
32
|
-
"@vltpkg/rollback-remove": "1.
|
|
33
|
-
"@vltpkg/run": "1.
|
|
34
|
-
"@vltpkg/security-archive": "1.
|
|
35
|
-
"@vltpkg/semver": "1.
|
|
36
|
-
"@vltpkg/spec": "1.
|
|
37
|
-
"@vltpkg/tar": "1.
|
|
38
|
-
"@vltpkg/types": "1.
|
|
39
|
-
"@vltpkg/url-open": "1.
|
|
40
|
-
"@vltpkg/vlt-json": "1.
|
|
41
|
-
"@vltpkg/vlx": "1.
|
|
42
|
-
"@vltpkg/workspaces": "1.
|
|
43
|
-
"@vltpkg/xdg": "1.
|
|
17
|
+
"@vltpkg/config": "1.4.0",
|
|
18
|
+
"@vltpkg/dep-id": "1.4.0",
|
|
19
|
+
"@vltpkg/dot-prop": "1.4.0",
|
|
20
|
+
"@vltpkg/error-cause": "1.4.0",
|
|
21
|
+
"@vltpkg/git": "1.4.0",
|
|
22
|
+
"@vltpkg/graph": "1.4.0",
|
|
23
|
+
"@vltpkg/graph-run": "1.4.0",
|
|
24
|
+
"@vltpkg/init": "1.4.0",
|
|
25
|
+
"@vltpkg/output": "1.4.0",
|
|
26
|
+
"@vltpkg/package-info": "1.4.0",
|
|
27
|
+
"@vltpkg/package-json": "1.4.0",
|
|
28
|
+
"@vltpkg/pick-manifest": "1.4.0",
|
|
29
|
+
"@vltpkg/promise-spawn": "1.4.0",
|
|
30
|
+
"@vltpkg/query": "1.4.0",
|
|
31
|
+
"@vltpkg/registry-client": "1.4.0",
|
|
32
|
+
"@vltpkg/rollback-remove": "1.4.0",
|
|
33
|
+
"@vltpkg/run": "1.4.0",
|
|
34
|
+
"@vltpkg/security-archive": "1.4.0",
|
|
35
|
+
"@vltpkg/semver": "1.4.0",
|
|
36
|
+
"@vltpkg/spec": "1.4.0",
|
|
37
|
+
"@vltpkg/tar": "1.4.0",
|
|
38
|
+
"@vltpkg/types": "1.4.0",
|
|
39
|
+
"@vltpkg/url-open": "1.4.0",
|
|
40
|
+
"@vltpkg/vlt-json": "1.4.0",
|
|
41
|
+
"@vltpkg/vlx": "1.4.0",
|
|
42
|
+
"@vltpkg/workspaces": "1.4.0",
|
|
43
|
+
"@vltpkg/xdg": "1.4.0",
|
|
44
44
|
"ansi-to-pre": "^1.0.6",
|
|
45
45
|
"beautiful-mermaid": "^1.1.3",
|
|
46
46
|
"chalk": "^5.6.2",
|