@vltpkg/cli-sdk 1.0.6 → 1.0.8
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/bugs.js +3 -3
- package/dist/commands/docs.js +3 -3
- package/dist/commands/list.js +4 -0
- package/dist/commands/login.js +12 -2
- package/dist/commands/query.js +4 -0
- package/dist/commands/repo.js +3 -3
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +30 -20
- package/dist/commands/uninstall.js +8 -1
- package/dist/config/definition.d.ts +11 -0
- package/dist/config/definition.js +30 -8
- package/dist/config/index.d.ts +23 -2
- package/dist/config/index.js +119 -41
- package/dist/config/merge-layers.d.ts +48 -0
- package/dist/config/merge-layers.js +138 -0
- package/dist/index.js +3 -3
- package/dist/mermaid-image-view.js +3 -3
- package/dist/open-url.d.ts +7 -0
- package/dist/open-url.js +16 -0
- package/dist/output.d.ts +20 -0
- package/dist/output.js +26 -1
- package/dist/parse-add-remove-args.js +3 -7
- package/dist/query-auth.d.ts +8 -0
- package/dist/query-auth.js +7 -0
- package/package.json +25 -25
package/dist/commands/bugs.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { error } from '@vltpkg/error-cause';
|
|
2
2
|
import { PackageInfoClient } from '@vltpkg/package-info';
|
|
3
3
|
import { Spec } from '@vltpkg/spec';
|
|
4
|
-
import { urlOpen } from '@vltpkg/url-open';
|
|
5
4
|
import { actual } from '@vltpkg/graph';
|
|
6
5
|
import { Query } from '@vltpkg/query';
|
|
7
6
|
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
7
|
+
import { openUrl } from "../open-url.js";
|
|
8
8
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
9
9
|
import { commandUsage } from "../config/usage.js";
|
|
10
10
|
import hostedGitInfo from 'hosted-git-info';
|
|
@@ -138,7 +138,7 @@ export const command = async (conf) => {
|
|
|
138
138
|
if (!result) {
|
|
139
139
|
throw error('Unexpected empty result');
|
|
140
140
|
}
|
|
141
|
-
await
|
|
141
|
+
await openUrl(result.url);
|
|
142
142
|
return result;
|
|
143
143
|
}
|
|
144
144
|
// Multiple results, return the list
|
|
@@ -158,6 +158,6 @@ export const command = async (conf) => {
|
|
|
158
158
|
}
|
|
159
159
|
/* c8 ignore stop */
|
|
160
160
|
// Open the URL
|
|
161
|
-
await
|
|
161
|
+
await openUrl(url);
|
|
162
162
|
return { url, name };
|
|
163
163
|
};
|
package/dist/commands/docs.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { error } from '@vltpkg/error-cause';
|
|
2
2
|
import { PackageInfoClient } from '@vltpkg/package-info';
|
|
3
3
|
import { Spec } from '@vltpkg/spec';
|
|
4
|
-
import { urlOpen } from '@vltpkg/url-open';
|
|
5
4
|
import { actual } from '@vltpkg/graph';
|
|
6
5
|
import { Query } from '@vltpkg/query';
|
|
7
6
|
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
7
|
+
import { openUrl } from "../open-url.js";
|
|
8
8
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
9
9
|
import { commandUsage } from "../config/usage.js";
|
|
10
10
|
import hostedGitInfo from 'hosted-git-info';
|
|
@@ -128,7 +128,7 @@ export const command = async (conf) => {
|
|
|
128
128
|
if (!result) {
|
|
129
129
|
throw error('Unexpected empty result');
|
|
130
130
|
}
|
|
131
|
-
await
|
|
131
|
+
await openUrl(result.url);
|
|
132
132
|
return result;
|
|
133
133
|
}
|
|
134
134
|
// Multiple results, return the list
|
|
@@ -148,6 +148,6 @@ export const command = async (conf) => {
|
|
|
148
148
|
}
|
|
149
149
|
/* c8 ignore stop */
|
|
150
150
|
// Open the URL
|
|
151
|
-
await
|
|
151
|
+
await openUrl(url);
|
|
152
152
|
return { url, name };
|
|
153
153
|
};
|
package/dist/commands/list.js
CHANGED
|
@@ -4,6 +4,7 @@ import { SecurityArchive } from '@vltpkg/security-archive';
|
|
|
4
4
|
import { error } from '@vltpkg/error-cause';
|
|
5
5
|
import { commandUsage } from "../config/usage.js";
|
|
6
6
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
7
|
+
import { createGetAuthHeader } from "../query-auth.js";
|
|
7
8
|
import { MermaidImageView } from "../mermaid-image-view.js";
|
|
8
9
|
export const needsRegistry = true;
|
|
9
10
|
export const usage = () => commandUsage({
|
|
@@ -104,6 +105,7 @@ export const command = async (conf) => {
|
|
|
104
105
|
const projectQueryString = ':workspace, :project > *';
|
|
105
106
|
const selectImporters = [];
|
|
106
107
|
const hostContexts = await createHostContextsMap(conf);
|
|
108
|
+
const getAuthHeader = createGetAuthHeader(conf);
|
|
107
109
|
const importers = new Set();
|
|
108
110
|
const scopeIDs = [];
|
|
109
111
|
// handle --scope option to add scope nodes as importers
|
|
@@ -123,6 +125,7 @@ export const command = async (conf) => {
|
|
|
123
125
|
importers,
|
|
124
126
|
securityArchive,
|
|
125
127
|
hostContexts,
|
|
128
|
+
getAuthHeader,
|
|
126
129
|
});
|
|
127
130
|
const { nodes: resultNodes } = await scopeQuery.search(scopeQueryString, {
|
|
128
131
|
signal: new AbortController().signal,
|
|
@@ -174,6 +177,7 @@ export const command = async (conf) => {
|
|
|
174
177
|
importers: importers_,
|
|
175
178
|
securityArchive,
|
|
176
179
|
hostContexts,
|
|
180
|
+
getAuthHeader,
|
|
177
181
|
});
|
|
178
182
|
const query = queryString ||
|
|
179
183
|
/* c8 ignore next */
|
package/dist/commands/login.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RegistryClient } from '@vltpkg/registry-client';
|
|
2
|
+
import { configWriteTarget } from "../config/index.js";
|
|
2
3
|
import { commandUsage } from "../config/usage.js";
|
|
3
4
|
import { missingRegistryError } from "../require-registry.js";
|
|
4
5
|
export const usage = () => commandUsage({
|
|
@@ -10,7 +11,9 @@ export const usage = () => commandUsage({
|
|
|
10
11
|
There is no default registry, so a registry must either
|
|
11
12
|
already be configured or be provided with
|
|
12
13
|
\`--registry=<url>\`. On success the registry is written to
|
|
13
|
-
the project's \`vlt.json
|
|
14
|
+
the project's \`vlt.json\`, so that it applies to this
|
|
15
|
+
project only. Pass \`--config=user\` to configure it for
|
|
16
|
+
every project instead.`,
|
|
14
17
|
options: {
|
|
15
18
|
registry: {
|
|
16
19
|
value: '<url>',
|
|
@@ -20,6 +23,11 @@ export const usage = () => commandUsage({
|
|
|
20
23
|
value: '<name>',
|
|
21
24
|
description: 'Identity namespace used to store auth tokens.',
|
|
22
25
|
},
|
|
26
|
+
config: {
|
|
27
|
+
value: '<user | project>',
|
|
28
|
+
description: 'Which config file to write the registry to. Defaults to ' +
|
|
29
|
+
'`project`.',
|
|
30
|
+
},
|
|
23
31
|
},
|
|
24
32
|
});
|
|
25
33
|
export const command = async (conf) => {
|
|
@@ -31,5 +39,7 @@ export const command = async (conf) => {
|
|
|
31
39
|
const rc = new RegistryClient(conf.options);
|
|
32
40
|
await rc.login(registry);
|
|
33
41
|
// persist the registry so subsequent commands are configured
|
|
34
|
-
await conf.addConfigToFile('project', {
|
|
42
|
+
await conf.addConfigToFile(configWriteTarget(conf, 'project'), {
|
|
43
|
+
registry,
|
|
44
|
+
});
|
|
35
45
|
};
|
package/dist/commands/query.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createDiffFilesProvider } from "../query-diff-files.js";
|
|
|
6
6
|
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
7
7
|
import { commandUsage } from "../config/usage.js";
|
|
8
8
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
9
|
+
import { createGetAuthHeader } from "../query-auth.js";
|
|
9
10
|
import { MermaidImageView } from "../mermaid-image-view.js";
|
|
10
11
|
export const needsRegistry = true;
|
|
11
12
|
export const usage = () => commandUsage({
|
|
@@ -130,6 +131,7 @@ export const command = async (conf) => {
|
|
|
130
131
|
const queryString = targetQueryString || positionalQueryString;
|
|
131
132
|
const hostContexts = await createHostContextsMap(conf);
|
|
132
133
|
const diffFiles = createDiffFilesProvider(conf.options.projectRoot);
|
|
134
|
+
const getAuthHeader = createGetAuthHeader(conf);
|
|
133
135
|
const importers = new Set();
|
|
134
136
|
const scopeIDs = [];
|
|
135
137
|
// Handle --scope option to add scope nodes as importers
|
|
@@ -150,6 +152,7 @@ export const command = async (conf) => {
|
|
|
150
152
|
securityArchive,
|
|
151
153
|
hostContexts,
|
|
152
154
|
diffFiles,
|
|
155
|
+
getAuthHeader,
|
|
153
156
|
});
|
|
154
157
|
const { nodes: resultNodes } = await scopeQuery.search(scopeQueryString, {
|
|
155
158
|
signal: new AbortController().signal,
|
|
@@ -214,6 +217,7 @@ export const command = async (conf) => {
|
|
|
214
217
|
securityArchive,
|
|
215
218
|
hostContexts,
|
|
216
219
|
diffFiles,
|
|
220
|
+
getAuthHeader,
|
|
217
221
|
});
|
|
218
222
|
const query = queryString ||
|
|
219
223
|
(graph ? defaultProjectQueryString : defaultLocalScopeQueryString);
|
package/dist/commands/repo.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { error } from '@vltpkg/error-cause';
|
|
2
2
|
import { PackageInfoClient } from '@vltpkg/package-info';
|
|
3
3
|
import { Spec } from '@vltpkg/spec';
|
|
4
|
-
import { urlOpen } from '@vltpkg/url-open';
|
|
5
4
|
import { actual } from '@vltpkg/graph';
|
|
6
5
|
import { Query } from '@vltpkg/query';
|
|
7
6
|
import { SecurityArchive } from '@vltpkg/security-archive';
|
|
7
|
+
import { openUrl } from "../open-url.js";
|
|
8
8
|
import { createHostContextsMap } from "../query-host-contexts.js";
|
|
9
9
|
import { commandUsage } from "../config/usage.js";
|
|
10
10
|
import hostedGitInfo from 'hosted-git-info';
|
|
@@ -132,7 +132,7 @@ export const command = async (conf) => {
|
|
|
132
132
|
if (!result) {
|
|
133
133
|
throw error('Unexpected empty result');
|
|
134
134
|
}
|
|
135
|
-
await
|
|
135
|
+
await openUrl(result.url);
|
|
136
136
|
return result;
|
|
137
137
|
}
|
|
138
138
|
// Multiple results, return the list
|
|
@@ -152,6 +152,6 @@ export const command = async (conf) => {
|
|
|
152
152
|
}
|
|
153
153
|
/* c8 ignore stop */
|
|
154
154
|
// Open the URL
|
|
155
|
-
await
|
|
155
|
+
await openUrl(url);
|
|
156
156
|
return { url, name };
|
|
157
157
|
};
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export type SetupResult = {
|
|
|
17
17
|
which: 'user' | 'project';
|
|
18
18
|
/** the alias -> url map that was staged into config */
|
|
19
19
|
registries: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* set when the config we wrote to is outranked for registry selection by
|
|
22
|
+
* the project's `vlt.json`, so the result won't take effect here.
|
|
23
|
+
*/
|
|
24
|
+
shadowedByProject?: boolean;
|
|
20
25
|
};
|
|
21
26
|
export declare const views: {
|
|
22
27
|
readonly human: (result: SetupResult) => string;
|
package/dist/commands/setup.js
CHANGED
|
@@ -2,6 +2,8 @@ import { error } from '@vltpkg/error-cause';
|
|
|
2
2
|
import { RegistryClient } from '@vltpkg/registry-client';
|
|
3
3
|
import { defaultRegistries } from '@vltpkg/spec';
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { configWriteTarget } from "../config/index.js";
|
|
6
|
+
import { registrySelectionFields } from "../config/merge-layers.js";
|
|
5
7
|
import { commandUsage } from "../config/usage.js";
|
|
6
8
|
import { stdout } from "../output.js";
|
|
7
9
|
/** Where users sign up for / log into a vlt.io account. */
|
|
@@ -13,17 +15,6 @@ export const VLT_REGISTRY_BASE = 'https://registry.vlt.io';
|
|
|
13
15
|
* `npm` is the {@link https://docs.vlt.sh | default bare-spec alias}.
|
|
14
16
|
*/
|
|
15
17
|
export const accountRegistries = ['npm', 'main'];
|
|
16
|
-
/**
|
|
17
|
-
* What each account registry is, in the order {@link accountRegistries}
|
|
18
|
-
* is authenticated. Every registry has its own credentials, so the
|
|
19
|
-
* web-login flow opens the browser once per registry -- naming what is
|
|
20
|
-
* being authenticated keeps the second prompt from reading like a repeat
|
|
21
|
-
* of the first.
|
|
22
|
-
*/
|
|
23
|
-
const accountRegistryAuthPrompts = {
|
|
24
|
-
npm: `First, let's authenticate your public npm registry mirror`,
|
|
25
|
-
main: `Now let's authenticate your private registry`,
|
|
26
|
-
};
|
|
27
18
|
/** Build the per-account registry URL for a given registry name. */
|
|
28
19
|
export const accountRegistryURL = (account, name) => `${VLT_REGISTRY_BASE}/${encodeURIComponent(account)}/${name}/`;
|
|
29
20
|
/** Ensure a registry URL ends with a single trailing slash. */
|
|
@@ -38,6 +29,9 @@ export const views = {
|
|
|
38
29
|
for (const [name, url] of Object.entries(result.registries)) {
|
|
39
30
|
lines.push(` ${name} -> ${url}`);
|
|
40
31
|
}
|
|
32
|
+
if (result.shadowedByProject) {
|
|
33
|
+
lines.push('', `Note: this project's vlt.json configures its own registries, and`, 'project config takes precedence. Re-run with `--config=project`', 'to configure this project instead.');
|
|
34
|
+
}
|
|
41
35
|
lines.push('', 'You can now install packages, e.g.:', ' vlt install <pkg>');
|
|
42
36
|
return lines.join('\n');
|
|
43
37
|
},
|
|
@@ -71,7 +65,7 @@ export const usage = () => commandUsage({
|
|
|
71
65
|
});
|
|
72
66
|
export const command = async (conf) => {
|
|
73
67
|
const yes = !!conf.get('yes');
|
|
74
|
-
const which = conf
|
|
68
|
+
const which = configWriteTarget(conf, 'user');
|
|
75
69
|
let rl;
|
|
76
70
|
const ask = async (question) => {
|
|
77
71
|
rl ??= createInterface(process.stdin, process.stdout);
|
|
@@ -96,11 +90,19 @@ export const command = async (conf) => {
|
|
|
96
90
|
for (const name of accountRegistries) {
|
|
97
91
|
registries[name] = accountRegistryURL(account, name);
|
|
98
92
|
}
|
|
99
|
-
// 3. merge
|
|
93
|
+
// 3. merge in registry aliases supplied for this invocation. aliases
|
|
94
|
+
// that came from a config file are left where they are: copying them
|
|
95
|
+
// would turn a project-local alias into a global one (or vice versa).
|
|
100
96
|
const builtinRegistries = defaultRegistries;
|
|
97
|
+
const fileRegistries = {
|
|
98
|
+
...conf.layers.user?.registries,
|
|
99
|
+
...conf.layers.project?.registries,
|
|
100
|
+
};
|
|
101
101
|
for (const [name, url] of Object.entries(conf.options.registries)) {
|
|
102
102
|
if (builtinRegistries[name] === url)
|
|
103
103
|
continue;
|
|
104
|
+
if (fileRegistries[name] === url)
|
|
105
|
+
continue;
|
|
104
106
|
registries[name] = normalizeRegistryURL(url);
|
|
105
107
|
}
|
|
106
108
|
const rc = new RegistryClient(conf.options);
|
|
@@ -108,12 +110,10 @@ export const command = async (conf) => {
|
|
|
108
110
|
if (!yes) {
|
|
109
111
|
const doAuth = await ask('Authenticate with your vlt.io account now? (Y/n) ');
|
|
110
112
|
if (!/^n/i.test(doAuth)) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await rc.login(accountRegistryURL(account, name));
|
|
116
|
-
}
|
|
113
|
+
// one token covers every registry on the account, so the browser
|
|
114
|
+
// opens once and the token is stored for all of them.
|
|
115
|
+
stdout(`Authenticating your account registries (${accountRegistries.join(', ')})...`);
|
|
116
|
+
await rc.login(accountRegistries.map(name => accountRegistryURL(account, name)));
|
|
117
117
|
}
|
|
118
118
|
// 5. offer to add further custom aliases
|
|
119
119
|
for (;;) {
|
|
@@ -144,7 +144,17 @@ export const command = async (conf) => {
|
|
|
144
144
|
}
|
|
145
145
|
// 6. persist the staged registries (merged, not clobbered)
|
|
146
146
|
await conf.addConfigToFile(which, { registries });
|
|
147
|
-
|
|
147
|
+
// writing the user config from inside a project that configures its own
|
|
148
|
+
// registries has no effect here, so say so rather than looking like it
|
|
149
|
+
// worked.
|
|
150
|
+
const shadowedByProject = which === 'user' &&
|
|
151
|
+
registrySelectionFields.some(f => f in (conf.layers.project ?? {}));
|
|
152
|
+
return {
|
|
153
|
+
account,
|
|
154
|
+
which,
|
|
155
|
+
registries,
|
|
156
|
+
...(shadowedByProject ? { shadowedByProject } : undefined),
|
|
157
|
+
};
|
|
148
158
|
}
|
|
149
159
|
finally {
|
|
150
160
|
if (rl) {
|
|
@@ -18,6 +18,9 @@ export const usage = () => commandUsage({
|
|
|
18
18
|
value: '<name>',
|
|
19
19
|
description: 'Limit uninstall targets to workspace groups.',
|
|
20
20
|
},
|
|
21
|
+
'lockfile-only': {
|
|
22
|
+
description: 'Only update lockfile and package.json files; skip node_modules operations.',
|
|
23
|
+
},
|
|
21
24
|
'allow-scripts': {
|
|
22
25
|
value: '<query>',
|
|
23
26
|
description: 'Filter which packages are allowed to run lifecycle scripts using DSS query syntax.',
|
|
@@ -36,6 +39,10 @@ export const command = async (conf) => {
|
|
|
36
39
|
String(conf.get('allow-scripts'))
|
|
37
40
|
: ':not(*)';
|
|
38
41
|
/* c8 ignore stop */
|
|
39
|
-
const { graph } = await uninstall({
|
|
42
|
+
const { graph } = await uninstall({
|
|
43
|
+
...conf.options,
|
|
44
|
+
allowScripts,
|
|
45
|
+
lockfileOnly: conf.options['lockfile-only'],
|
|
46
|
+
}, remove);
|
|
40
47
|
return { graph };
|
|
41
48
|
};
|
|
@@ -454,5 +454,16 @@ export declare const definition: import("jackspeak").Jack<{
|
|
|
454
454
|
help: import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
455
455
|
all: import("jackspeak").ConfigOption<"boolean", false, undefined>;
|
|
456
456
|
}>;
|
|
457
|
+
/**
|
|
458
|
+
* The pristine `default` of every known config field, captured here at
|
|
459
|
+
* module load, before anything can call `jack.setConfigValues()`.
|
|
460
|
+
*
|
|
461
|
+
* Config layers are applied by overwriting jackspeak's defaults, and
|
|
462
|
+
* `setConfigValues()` can only set them. So when a layer stops setting a
|
|
463
|
+
* field (or removes it with `null`), the definitional default has to be
|
|
464
|
+
* put back explicitly, otherwise a value left behind by a previous apply
|
|
465
|
+
* would leak through.
|
|
466
|
+
*/
|
|
467
|
+
export declare const defaultValues: Record<string, unknown>;
|
|
457
468
|
export declare const getSortedCliOptions: () => string[];
|
|
458
469
|
export declare const getSortedKeys: () => string[];
|
|
@@ -132,14 +132,22 @@ export const definition = j
|
|
|
132
132
|
* Definition of all configuration values used by vlt.
|
|
133
133
|
*/
|
|
134
134
|
.heading('Configuration')
|
|
135
|
-
.description(`
|
|
136
|
-
then
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
.description(`The \`vlt.json\` file in the XDG specified config directory is loaded
|
|
136
|
+
first, and then the \`vlt.json\` file in the root of the current
|
|
137
|
+
project, if present, is layered on top of it.
|
|
138
|
+
|
|
139
|
+
Scalar values are overridden by the innermost layer that sets them.
|
|
140
|
+
Object type values are merged together key by key. Set a field, or a
|
|
141
|
+
key within an object field, to \`null\` in the JSON configuration to
|
|
142
|
+
explicitly remove it.
|
|
143
|
+
|
|
144
|
+
Registry *selection* is an exception, since \`--registry\` outranks any
|
|
145
|
+
\`--registries\` alias. A layer that sets any of \`registry\`,
|
|
146
|
+
\`registries\`, or \`default-registry-alias\` owns selection: the
|
|
147
|
+
\`registry\` and \`default-registry-alias\` it does *not* set are
|
|
148
|
+
dropped, rather than inherited from an outer layer. So a project that
|
|
149
|
+
configures its own registry is never sent to the user-level one, while
|
|
150
|
+
the aliases in the user config stay addressable by name.
|
|
143
151
|
|
|
144
152
|
Command-specific fields may be set in a nested \`command\` object that
|
|
145
153
|
overrides any options defined at the top level.
|
|
@@ -750,6 +758,20 @@ export const definition = j
|
|
|
750
758
|
description: 'Show all commands, bins, and flags',
|
|
751
759
|
},
|
|
752
760
|
});
|
|
761
|
+
/**
|
|
762
|
+
* The pristine `default` of every known config field, captured here at
|
|
763
|
+
* module load, before anything can call `jack.setConfigValues()`.
|
|
764
|
+
*
|
|
765
|
+
* Config layers are applied by overwriting jackspeak's defaults, and
|
|
766
|
+
* `setConfigValues()` can only set them. So when a layer stops setting a
|
|
767
|
+
* field (or removes it with `null`), the definitional default has to be
|
|
768
|
+
* put back explicitly, otherwise a value left behind by a previous apply
|
|
769
|
+
* would leak through.
|
|
770
|
+
*/
|
|
771
|
+
export const defaultValues = Object.fromEntries(Object.entries(definition.toJSON()).map(([field, def]) => [
|
|
772
|
+
field,
|
|
773
|
+
def.default,
|
|
774
|
+
]));
|
|
753
775
|
export const getSortedCliOptions = () => {
|
|
754
776
|
const defs = definition.toJSON();
|
|
755
777
|
return getSortedKeys().map((k) => {
|
package/dist/config/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Module that handles all vlt configuration needs
|
|
3
3
|
*
|
|
4
4
|
* Project-level configs are set in a `vlt.json` file in the local project
|
|
5
|
-
* if present
|
|
6
|
-
* XDG config path.
|
|
5
|
+
* if present, and are layered over the user-level configs in the appropriate
|
|
6
|
+
* XDG config path. See `./merge-layers.ts` for the layering rules.
|
|
7
7
|
*
|
|
8
8
|
* Command-specific configuration can be specified by putting options in a
|
|
9
9
|
* field in the `command` object. For example:
|
|
@@ -38,6 +38,11 @@ export type PairsAsRecords = ConfigOptionsNoExtras & {
|
|
|
38
38
|
[k in keyof Commands]?: ConfigOptionsNoExtras;
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* One `vlt.json` file's `config` object, in record form. Every field is
|
|
43
|
+
* optional, since a config file only sets what it sets.
|
|
44
|
+
*/
|
|
45
|
+
export type ConfigFileLayer = Partial<PairsAsRecords>;
|
|
41
46
|
export declare const pairsToRecords: (obj: NonNullable<ConfigFileData> | OptionsResults<ConfigDefinitions>) => PairsAsRecords;
|
|
42
47
|
export declare const recordsToPairs: (obj: RecordPairs) => RecordPairs;
|
|
43
48
|
export type ConfigDataNoCommand = {
|
|
@@ -169,6 +174,14 @@ export declare class Config {
|
|
|
169
174
|
* in the config file.
|
|
170
175
|
*/
|
|
171
176
|
addConfigToFile(this: LoadedConfig, which: WhichConfig, values: NonNullable<ConfigFileData>): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* The raw config data from each `vlt.json` file, normalized to record
|
|
179
|
+
* form, before the layers are merged together. `undefined` for a layer
|
|
180
|
+
* with no file, or a file with no `config` field.
|
|
181
|
+
*/
|
|
182
|
+
get layers(): Readonly<{
|
|
183
|
+
[k in WhichConfig]?: ConfigFileLayer;
|
|
184
|
+
}>;
|
|
172
185
|
/**
|
|
173
186
|
* Deletes the specified config fields from the named file
|
|
174
187
|
* Returns `true` if anything was changed.
|
|
@@ -216,3 +229,11 @@ export type ParsedConfig = Config & {
|
|
|
216
229
|
* A fully loaded {@link Config} object
|
|
217
230
|
*/
|
|
218
231
|
export type LoadedConfig = ParsedConfig;
|
|
232
|
+
/**
|
|
233
|
+
* Which `vlt.json` a command that persists settings should write to.
|
|
234
|
+
*
|
|
235
|
+
* `--config=user` and `--config=project` are explicit. Anything else means
|
|
236
|
+
* the caller's own default, since `--config` also takes `all` (its default,
|
|
237
|
+
* meaningful only for `vlt config` reads).
|
|
238
|
+
*/
|
|
239
|
+
export declare const configWriteTarget: (conf: Pick<LoadedConfig, "get">, fallback: WhichConfig) => WhichConfig;
|
package/dist/config/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Module that handles all vlt configuration needs
|
|
3
3
|
*
|
|
4
4
|
* Project-level configs are set in a `vlt.json` file in the local project
|
|
5
|
-
* if present
|
|
6
|
-
* XDG config path.
|
|
5
|
+
* if present, and are layered over the user-level configs in the appropriate
|
|
6
|
+
* XDG config path. See `./merge-layers.ts` for the layering rules.
|
|
7
7
|
*
|
|
8
8
|
* Command-specific configuration can be specified by putting options in a
|
|
9
9
|
* field in the `command` object. For example:
|
|
@@ -31,8 +31,9 @@ import { Monorepo } from '@vltpkg/workspaces';
|
|
|
31
31
|
import { readFile, rm, writeFile } from 'node:fs/promises';
|
|
32
32
|
import { dirname } from 'node:path';
|
|
33
33
|
import { PathScurry } from 'path-scurry';
|
|
34
|
-
import { commands, definition, getCommand, isRecordField, recordFields, } from "./definition.js";
|
|
34
|
+
import { commands, definition, defaultValues, getCommand, isRecordField, recordFields, } from "./definition.js";
|
|
35
35
|
import { merge } from "./merge.js";
|
|
36
|
+
import { cloneLayer, mergeLayers } from "./merge-layers.js";
|
|
36
37
|
export { commands, definition, isRecordField, recordFields, };
|
|
37
38
|
export const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
|
|
38
39
|
// turn a set of pairs into a Record object.
|
|
@@ -53,6 +54,25 @@ const reducePairs = (pairs) => {
|
|
|
53
54
|
};
|
|
54
55
|
const isRecordFieldValue = (k, v) => Array.isArray(v) &&
|
|
55
56
|
recordFields.includes(k);
|
|
57
|
+
/**
|
|
58
|
+
* `registries` alias names are used as spec prefixes, where `~` is
|
|
59
|
+
* reserved, and an empty name has nothing to prefix with.
|
|
60
|
+
*/
|
|
61
|
+
const assertRegistryKeys = (registries, file) => {
|
|
62
|
+
if (!registries || typeof registries !== 'object')
|
|
63
|
+
return;
|
|
64
|
+
const obj = Array.isArray(registries) ?
|
|
65
|
+
reducePairs(registries)
|
|
66
|
+
: registries;
|
|
67
|
+
for (const key of Object.keys(obj)) {
|
|
68
|
+
if (key === '' || key.includes('~')) {
|
|
69
|
+
throw error('Reserved character found in registries name', {
|
|
70
|
+
path: file,
|
|
71
|
+
found: key,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
56
76
|
export const pairsToRecords = (obj) => {
|
|
57
77
|
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [
|
|
58
78
|
k,
|
|
@@ -75,7 +95,12 @@ export const recordsToPairs = (obj) => {
|
|
|
75
95
|
.map(([k, v]) => [
|
|
76
96
|
k,
|
|
77
97
|
k === 'command' && v && typeof v === 'object' ?
|
|
78
|
-
|
|
98
|
+
// each command's own block gets converted too, mirroring
|
|
99
|
+
// pairsToRecords
|
|
100
|
+
Object.fromEntries(Object.entries(v).map(([k, v]) => [
|
|
101
|
+
k,
|
|
102
|
+
recordsToPairs(v),
|
|
103
|
+
]))
|
|
79
104
|
: (!v ||
|
|
80
105
|
typeof v !== 'object' ||
|
|
81
106
|
Array.isArray(v) ||
|
|
@@ -277,7 +302,10 @@ export class Config {
|
|
|
277
302
|
* in the config file.
|
|
278
303
|
*/
|
|
279
304
|
async addConfigToFile(which, values) {
|
|
280
|
-
|
|
305
|
+
// normalize both sides to record form first, so that a record field
|
|
306
|
+
// written as a `key=value` list in an existing file is still merged key
|
|
307
|
+
// by key, rather than replaced wholesale.
|
|
308
|
+
return this.writeConfigFile(which, merge(pairsToRecords((await this.#maybeLoadConfigFile(which)) ?? {}), pairsToRecords(values)));
|
|
281
309
|
}
|
|
282
310
|
// called in this weird bound way so that it can be used by the
|
|
283
311
|
// vlt-json config loading module.
|
|
@@ -292,53 +320,92 @@ export class Config {
|
|
|
292
320
|
wanted: 'ConfigFileData',
|
|
293
321
|
});
|
|
294
322
|
}
|
|
295
|
-
const { command, ...values } =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
: registries;
|
|
302
|
-
for (const key of Object.keys(registriesObj)) {
|
|
303
|
-
if (key === '' || key.includes('~')) {
|
|
304
|
-
throw error('Reserved character found in registries name', {
|
|
305
|
-
path: file,
|
|
306
|
-
found: key,
|
|
307
|
-
});
|
|
323
|
+
const { command, ...values } = c;
|
|
324
|
+
assertRegistryKeys(values.registries, file);
|
|
325
|
+
if (command && typeof command === 'object') {
|
|
326
|
+
for (const opts of Object.values(command)) {
|
|
327
|
+
if (opts && typeof opts === 'object') {
|
|
328
|
+
assertRegistryKeys(opts.registries, file);
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
+
// validate this layer on its own, so that an invalid value is blamed on
|
|
333
|
+
// the file it actually came from. `mergeLayers` on a single layer just
|
|
334
|
+
// strips the `null`s, which mean "remove this field" rather than being
|
|
335
|
+
// a value jack should ever see.
|
|
336
|
+
const layer = cloneLayer(pairsToRecords(c));
|
|
337
|
+
const { command: _cmd, ...single } = mergeLayers([layer]);
|
|
338
|
+
try {
|
|
339
|
+
this.jack.validate(recordsToPairs(single));
|
|
340
|
+
}
|
|
341
|
+
catch (er) {
|
|
342
|
+
/* c8 ignore next - for TS */
|
|
343
|
+
if (er instanceof Error) {
|
|
344
|
+
// jack only attaches the source path when going through
|
|
345
|
+
// setConfigValues(), and the CLI's "Problem in Config File" banner
|
|
346
|
+
// keys off it.
|
|
347
|
+
/* c8 ignore next - jack always throws with a cause object */
|
|
348
|
+
const cause = typeof er.cause === 'object' ? er.cause : {};
|
|
349
|
+
er.cause = { ...cause, path: file };
|
|
350
|
+
}
|
|
351
|
+
throw er;
|
|
352
|
+
}
|
|
353
|
+
this.#layers[file === find('user') ? 'user' : 'project'] = layer;
|
|
354
|
+
this.#applyLayers();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The raw config data from each `vlt.json` file, normalized to record
|
|
358
|
+
* form, before the layers are merged together. `undefined` for a layer
|
|
359
|
+
* with no file, or a file with no `config` field.
|
|
360
|
+
*/
|
|
361
|
+
get layers() {
|
|
362
|
+
return this.#layers;
|
|
363
|
+
}
|
|
364
|
+
#layers = {};
|
|
365
|
+
/**
|
|
366
|
+
* Recompute the jack defaults and {@link Config#commandValues} from
|
|
367
|
+
* {@link Config#layers}.
|
|
368
|
+
*
|
|
369
|
+
* Everything is derived from `#layers`, and every known field is applied,
|
|
370
|
+
* so this is idempotent: re-reading a config file (which
|
|
371
|
+
* `addConfigToFile` and `deleteConfigKeys` both do) can't stack the user
|
|
372
|
+
* layer back on top of the project layer, and a field a layer stopped
|
|
373
|
+
* setting goes back to its definitional default.
|
|
374
|
+
*/
|
|
375
|
+
#applyLayers() {
|
|
376
|
+
const { command, ...values } = mergeLayers([
|
|
377
|
+
this.#layers.user,
|
|
378
|
+
this.#layers.project,
|
|
379
|
+
]);
|
|
380
|
+
this.commandValues = {};
|
|
381
|
+
for (const [c, opts] of Object.entries(command ?? {})) {
|
|
382
|
+
const cmd = getCommand(c);
|
|
383
|
+
if (cmd && opts && typeof opts === 'object') {
|
|
384
|
+
this.commandValues[cmd] = opts;
|
|
332
385
|
}
|
|
333
386
|
}
|
|
334
|
-
|
|
387
|
+
const pairs = recordsToPairs(values);
|
|
388
|
+
const applied = {};
|
|
389
|
+
for (const [field, dflt] of Object.entries(defaultValues)) {
|
|
390
|
+
applied[field] = field in pairs ? pairs[field] : dflt;
|
|
391
|
+
}
|
|
392
|
+
this.jack.setConfigValues(applied);
|
|
393
|
+
this.#options = undefined;
|
|
335
394
|
}
|
|
336
395
|
/**
|
|
337
396
|
* if the file exists, parse and load it. returns object if data was
|
|
338
397
|
* loaded, or undefined if not.
|
|
339
398
|
*/
|
|
340
399
|
async #maybeLoadConfigFile(whichConfig) {
|
|
341
|
-
|
|
400
|
+
const data = load('config', this.#validator, whichConfig);
|
|
401
|
+
if (data === undefined &&
|
|
402
|
+
this.#layers[whichConfig] !== undefined) {
|
|
403
|
+
// the file (or its `config` field) went away, so the validator never
|
|
404
|
+
// ran. drop the layer it left behind.
|
|
405
|
+
delete this.#layers[whichConfig];
|
|
406
|
+
this.#applyLayers();
|
|
407
|
+
}
|
|
408
|
+
return data;
|
|
342
409
|
}
|
|
343
410
|
/**
|
|
344
411
|
* Deletes the specified config fields from the named file
|
|
@@ -493,3 +560,14 @@ export class Config {
|
|
|
493
560
|
}
|
|
494
561
|
}
|
|
495
562
|
const isParsed = (c) => !!(c.values && c.positionals && c.command);
|
|
563
|
+
/**
|
|
564
|
+
* Which `vlt.json` a command that persists settings should write to.
|
|
565
|
+
*
|
|
566
|
+
* `--config=user` and `--config=project` are explicit. Anything else means
|
|
567
|
+
* the caller's own default, since `--config` also takes `all` (its default,
|
|
568
|
+
* meaningful only for `vlt config` reads).
|
|
569
|
+
*/
|
|
570
|
+
export const configWriteTarget = (conf, fallback) => {
|
|
571
|
+
const which = conf.get('config');
|
|
572
|
+
return which === 'user' || which === 'project' ? which : fallback;
|
|
573
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge the `config` objects found in the user and project `vlt.json`
|
|
3
|
+
* files into the single set of values that gets applied to the
|
|
4
|
+
* {@link https://npmjs.com/jackspeak | jackspeak} definition.
|
|
5
|
+
*
|
|
6
|
+
* Layers are supplied outermost first (user, then project), and follow the
|
|
7
|
+
* rules documented in `vlt help` and at
|
|
8
|
+
* <https://docs.vlt.sh/cli/configuring>:
|
|
9
|
+
*
|
|
10
|
+
* - Scalar fields come from the innermost layer that sets them.
|
|
11
|
+
* - Object (`key=value` record) fields are merged key by key.
|
|
12
|
+
* - A `null` value removes a field, or a single key within a record field.
|
|
13
|
+
* - Registry *selection* belongs to a single layer, see
|
|
14
|
+
* {@link registrySelectionFields}.
|
|
15
|
+
*
|
|
16
|
+
* Note that this operates on the record form of a config object (the shape
|
|
17
|
+
* it has in `vlt.json`), not on jackspeak's `key=value` string lists, so
|
|
18
|
+
* record fields can be merged per key. Run values through
|
|
19
|
+
* `pairsToRecords` first.
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
/** One config layer, in record (not `key=value` pair) form. */
|
|
23
|
+
export type ConfigLayer = Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* The fields that *select* which registry is used, as opposed to the
|
|
26
|
+
* catalogs of aliases that can be selected from.
|
|
27
|
+
*/
|
|
28
|
+
export declare const registrySelectors: readonly ["registry", "default-registry-alias"];
|
|
29
|
+
/**
|
|
30
|
+
* When a layer sets any of these, that layer owns registry selection: the
|
|
31
|
+
* {@link registrySelectors} it does *not* set are removed from the merged
|
|
32
|
+
* result, rather than inherited from an outer layer.
|
|
33
|
+
*
|
|
34
|
+
* Without this, a `registry` in the user `vlt.json` would outrank a
|
|
35
|
+
* project's `registries.npm`, since the `registry` scalar beats any alias
|
|
36
|
+
* in `requireRegistry()`. The alias catalogs are still merged, so aliases
|
|
37
|
+
* configured at the user level stay addressable by name.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately narrow: `scoped-registries` and `jsr-registries` are purely
|
|
40
|
+
* additive, so adding one of those in a project must not drop the user's
|
|
41
|
+
* default registry.
|
|
42
|
+
*/
|
|
43
|
+
export declare const registrySelectionFields: readonly ["registry", "registries", "default-registry-alias"];
|
|
44
|
+
export declare const cloneLayer: <T>(data: T) => T;
|
|
45
|
+
/**
|
|
46
|
+
* Merge config layers, outermost first. Layers are never mutated.
|
|
47
|
+
*/
|
|
48
|
+
export declare const mergeLayers: (layers: (ConfigLayer | undefined)[]) => ConfigLayer;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge the `config` objects found in the user and project `vlt.json`
|
|
3
|
+
* files into the single set of values that gets applied to the
|
|
4
|
+
* {@link https://npmjs.com/jackspeak | jackspeak} definition.
|
|
5
|
+
*
|
|
6
|
+
* Layers are supplied outermost first (user, then project), and follow the
|
|
7
|
+
* rules documented in `vlt help` and at
|
|
8
|
+
* <https://docs.vlt.sh/cli/configuring>:
|
|
9
|
+
*
|
|
10
|
+
* - Scalar fields come from the innermost layer that sets them.
|
|
11
|
+
* - Object (`key=value` record) fields are merged key by key.
|
|
12
|
+
* - A `null` value removes a field, or a single key within a record field.
|
|
13
|
+
* - Registry *selection* belongs to a single layer, see
|
|
14
|
+
* {@link registrySelectionFields}.
|
|
15
|
+
*
|
|
16
|
+
* Note that this operates on the record form of a config object (the shape
|
|
17
|
+
* it has in `vlt.json`), not on jackspeak's `key=value` string lists, so
|
|
18
|
+
* record fields can be merged per key. Run values through
|
|
19
|
+
* `pairsToRecords` first.
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { isRecordField } from "./definition.js";
|
|
23
|
+
/**
|
|
24
|
+
* The fields that *select* which registry is used, as opposed to the
|
|
25
|
+
* catalogs of aliases that can be selected from.
|
|
26
|
+
*/
|
|
27
|
+
export const registrySelectors = [
|
|
28
|
+
'registry',
|
|
29
|
+
'default-registry-alias',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* When a layer sets any of these, that layer owns registry selection: the
|
|
33
|
+
* {@link registrySelectors} it does *not* set are removed from the merged
|
|
34
|
+
* result, rather than inherited from an outer layer.
|
|
35
|
+
*
|
|
36
|
+
* Without this, a `registry` in the user `vlt.json` would outrank a
|
|
37
|
+
* project's `registries.npm`, since the `registry` scalar beats any alias
|
|
38
|
+
* in `requireRegistry()`. The alias catalogs are still merged, so aliases
|
|
39
|
+
* configured at the user level stay addressable by name.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately narrow: `scoped-registries` and `jsr-registries` are purely
|
|
42
|
+
* additive, so adding one of those in a project must not drop the user's
|
|
43
|
+
* default registry.
|
|
44
|
+
*/
|
|
45
|
+
export const registrySelectionFields = [
|
|
46
|
+
'registry',
|
|
47
|
+
'registries',
|
|
48
|
+
'default-registry-alias',
|
|
49
|
+
];
|
|
50
|
+
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
51
|
+
/**
|
|
52
|
+
* Deep copy of a config layer, preserving `null` values.
|
|
53
|
+
*
|
|
54
|
+
* Layers are held onto for the life of the `Config` object, but the objects
|
|
55
|
+
* nested in them are shared with the `@vltpkg/vlt-json` cache, which
|
|
56
|
+
* `deleteConfigKeys` mutates in place. Copying keeps a stored layer an
|
|
57
|
+
* accurate snapshot of what was read.
|
|
58
|
+
*/
|
|
59
|
+
const cloneValue = (v) => Array.isArray(v) ? v.map(x => cloneValue(x))
|
|
60
|
+
: isObj(v) ?
|
|
61
|
+
Object.fromEntries(Object.entries(v).map(([k, x]) => [k, cloneValue(x)]))
|
|
62
|
+
: v;
|
|
63
|
+
export const cloneLayer = (data) => cloneValue(data);
|
|
64
|
+
/**
|
|
65
|
+
* Fold one record field's keys into the value from the outer layers.
|
|
66
|
+
* `null` removes a key. Returns `undefined` when nothing is left, so that
|
|
67
|
+
* the field falls back to its definitional default instead of applying an
|
|
68
|
+
* empty record.
|
|
69
|
+
*/
|
|
70
|
+
const mergeRecord = (base, add) => {
|
|
71
|
+
const result = isObj(base) ? { ...base } : {};
|
|
72
|
+
for (const [k, v] of Object.entries(add)) {
|
|
73
|
+
if (v === null)
|
|
74
|
+
delete result[k];
|
|
75
|
+
else
|
|
76
|
+
result[k] = v;
|
|
77
|
+
}
|
|
78
|
+
return Object.keys(result).length ? result : undefined;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Fold the `command` object in, merging each command's block with the same
|
|
82
|
+
* rules as the top level.
|
|
83
|
+
*/
|
|
84
|
+
const mergeCommands = (base, add) => {
|
|
85
|
+
const result = isObj(base) ? { ...base } : {};
|
|
86
|
+
for (const [cmd, opts] of Object.entries(add)) {
|
|
87
|
+
if (opts === null)
|
|
88
|
+
delete result[cmd];
|
|
89
|
+
else if (isObj(opts)) {
|
|
90
|
+
result[cmd] = mergeLayers([
|
|
91
|
+
isObj(result[cmd]) ? result[cmd] : undefined,
|
|
92
|
+
opts,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return Object.keys(result).length ? result : undefined;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Merge config layers, outermost first. Layers are never mutated.
|
|
100
|
+
*/
|
|
101
|
+
export const mergeLayers = (layers) => {
|
|
102
|
+
const result = {};
|
|
103
|
+
for (const layer of layers) {
|
|
104
|
+
if (!isObj(layer))
|
|
105
|
+
continue;
|
|
106
|
+
for (const [k, v] of Object.entries(layer)) {
|
|
107
|
+
if (k === 'command') {
|
|
108
|
+
const merged = isObj(v) ? mergeCommands(result.command, v) : undefined;
|
|
109
|
+
if (merged)
|
|
110
|
+
result.command = merged;
|
|
111
|
+
else
|
|
112
|
+
delete result.command;
|
|
113
|
+
}
|
|
114
|
+
else if (v === null) {
|
|
115
|
+
delete result[k];
|
|
116
|
+
}
|
|
117
|
+
else if (isRecordField(k) && isObj(v)) {
|
|
118
|
+
const merged = mergeRecord(result[k], v);
|
|
119
|
+
if (merged)
|
|
120
|
+
result[k] = merged;
|
|
121
|
+
else
|
|
122
|
+
delete result[k];
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
result[k] = v;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// the innermost layer that configures a registry at all owns which
|
|
129
|
+
// registry is selected.
|
|
130
|
+
if (registrySelectionFields.some(f => f in layer)) {
|
|
131
|
+
for (const f of registrySelectors) {
|
|
132
|
+
if (!(f in layer))
|
|
133
|
+
delete result[f];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { asRootError } 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";
|
|
6
|
-
import { outputCommand, stderr, stdout } from "./output.js";
|
|
6
|
+
import { flushAndExit, outputCommand, stderr, stdout, } from "./output.js";
|
|
7
7
|
import { indent } from "./print-err.js";
|
|
8
8
|
import { loadCommand } from "./load-command.js";
|
|
9
9
|
const { version } = loadPackageJson(import.meta.filename, process.env.__VLT_INTERNAL_CLI_PACKAGE_JSON);
|
|
@@ -35,7 +35,7 @@ const loadVlt = async (cwd, argv) => {
|
|
|
35
35
|
stderr(indent(validOptions.join('\n'), 4));
|
|
36
36
|
}
|
|
37
37
|
stderr(indent(`Run 'vlt help' for more information about available options.`));
|
|
38
|
-
return
|
|
38
|
+
return flushAndExit(process.exitCode || 1);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
const run = async () => {
|
|
@@ -64,7 +64,7 @@ const run = async () => {
|
|
|
64
64
|
if (vlt.get('workspace-group')) {
|
|
65
65
|
stderr(indent(`Workspace Group: ${format(vlt.get('workspace-group'))}`));
|
|
66
66
|
}
|
|
67
|
-
return
|
|
67
|
+
return flushAndExit(process.exitCode || 1);
|
|
68
68
|
}
|
|
69
69
|
if (vlt.command === 'registry') {
|
|
70
70
|
const { dispatchRegistry } = await import("./commands/registry.js");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mermaidOutput } from '@vltpkg/graph';
|
|
2
|
-
import {
|
|
2
|
+
import { openUrl } from "./open-url.js";
|
|
3
3
|
import { stderr } from "./output.js";
|
|
4
4
|
import { ViewClass } from "./view.js";
|
|
5
5
|
/**
|
|
@@ -24,13 +24,13 @@ export class MermaidImageView extends ViewClass {
|
|
|
24
24
|
stderr(`Generating PNG image...`);
|
|
25
25
|
const filePath = await renderMermaidToPng(mermaidText);
|
|
26
26
|
stderr(`Image saved to: ${filePath}`);
|
|
27
|
-
await
|
|
27
|
+
await openUrl(filePath);
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
stderr(`Generating SVG image...`);
|
|
31
31
|
const filePath = await renderMermaidToFile(mermaidText);
|
|
32
32
|
stderr(`Image saved to: ${filePath}`);
|
|
33
|
-
await
|
|
33
|
+
await openUrl(filePath);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a url in the system browser, without failing the command if the
|
|
3
|
+
* opener does. The opener command may exist but still be unable to launch
|
|
4
|
+
* anything, e.g: `xdg-open` on a headless server. The url was already
|
|
5
|
+
* printed, so the user can open it manually.
|
|
6
|
+
*/
|
|
7
|
+
export declare const openUrl: (url: string) => Promise<void>;
|
package/dist/open-url.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { urlOpen } from '@vltpkg/url-open';
|
|
2
|
+
import { stderr } from "./output.js";
|
|
3
|
+
/**
|
|
4
|
+
* Open a url in the system browser, without failing the command if the
|
|
5
|
+
* opener does. The opener command may exist but still be unable to launch
|
|
6
|
+
* anything, e.g: `xdg-open` on a headless server. The url was already
|
|
7
|
+
* printed, so the user can open it manually.
|
|
8
|
+
*/
|
|
9
|
+
export const openUrl = async (url) => {
|
|
10
|
+
try {
|
|
11
|
+
await urlOpen(url);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
stderr(`Could not open a browser. Please open ${url} manually.`);
|
|
15
|
+
}
|
|
16
|
+
};
|
package/dist/output.d.ts
CHANGED
|
@@ -4,6 +4,26 @@ import type { Command } from './index.ts';
|
|
|
4
4
|
import type { View, Views } from './view.ts';
|
|
5
5
|
export declare const stdout: (...args: unknown[]) => void;
|
|
6
6
|
export declare const stderr: (...args: unknown[]) => void;
|
|
7
|
+
/** the part of a stdio stream that {@link flushStream} waits on */
|
|
8
|
+
export type FlushableStream = {
|
|
9
|
+
writableLength: number;
|
|
10
|
+
write: (chunk: string, cb: () => void) => boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Resolve once everything already handed to `stream` has reached the
|
|
14
|
+
* other end. A zero length write is enough: writes complete in order,
|
|
15
|
+
* so its callback cannot run ahead of the ones already queued.
|
|
16
|
+
*/
|
|
17
|
+
export declare const flushStream: (stream: FlushableStream) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Exit, but only once stdout and stderr have drained.
|
|
20
|
+
*
|
|
21
|
+
* Writes to a pipe are async, and `process.exit()` throws away whatever
|
|
22
|
+
* is still queued for one. Piped output is exactly what CI gives us, so
|
|
23
|
+
* exiting right after printing crops the output at the 64k pipe buffer,
|
|
24
|
+
* and since the error is printed last, the error is what gets lost.
|
|
25
|
+
*/
|
|
26
|
+
export declare const flushAndExit: (code?: Parameters<typeof process.exit>[0]) => Promise<never>;
|
|
7
27
|
type StyleTextFn = (format: Parameters<typeof utilStyleText>[0], s: string) => string;
|
|
8
28
|
export declare let styleTextStdout: StyleTextFn;
|
|
9
29
|
export declare let styleTextStderr: StyleTextFn;
|
package/dist/output.js
CHANGED
|
@@ -27,6 +27,31 @@ const supportsColor = (stream) => {
|
|
|
27
27
|
export const stdout = (...args) => console.log(...args);
|
|
28
28
|
// eslint-disable-next-line no-console
|
|
29
29
|
export const stderr = (...args) => console.error(...args);
|
|
30
|
+
/**
|
|
31
|
+
* Resolve once everything already handed to `stream` has reached the
|
|
32
|
+
* other end. A zero length write is enough: writes complete in order,
|
|
33
|
+
* so its callback cannot run ahead of the ones already queued.
|
|
34
|
+
*/
|
|
35
|
+
export const flushStream = (stream) => new Promise(res => {
|
|
36
|
+
if (!stream.writableLength)
|
|
37
|
+
return res();
|
|
38
|
+
stream.write('', () => res());
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Exit, but only once stdout and stderr have drained.
|
|
42
|
+
*
|
|
43
|
+
* Writes to a pipe are async, and `process.exit()` throws away whatever
|
|
44
|
+
* is still queued for one. Piped output is exactly what CI gives us, so
|
|
45
|
+
* exiting right after printing crops the output at the 64k pipe buffer,
|
|
46
|
+
* and since the error is printed last, the error is what gets lost.
|
|
47
|
+
*/
|
|
48
|
+
export const flushAndExit = async (code) => {
|
|
49
|
+
await Promise.all([
|
|
50
|
+
flushStream(process.stdout),
|
|
51
|
+
flushStream(process.stderr),
|
|
52
|
+
]);
|
|
53
|
+
return process.exit(code);
|
|
54
|
+
};
|
|
30
55
|
/* c8 ignore start */
|
|
31
56
|
const styleText = (f, s) => utilStyleText(f, s, { validateStream: false });
|
|
32
57
|
/* c8 ignore stop */
|
|
@@ -202,6 +227,6 @@ export const outputCommand = async (cliCommand, conf, { start, vltVersion } = {
|
|
|
202
227
|
...formatOptions,
|
|
203
228
|
colors: stderrColor,
|
|
204
229
|
});
|
|
205
|
-
|
|
230
|
+
await flushAndExit(process.exitCode);
|
|
206
231
|
}
|
|
207
232
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asDependency } from '@vltpkg/graph';
|
|
1
|
+
import { addKey, asDependency } from '@vltpkg/graph';
|
|
2
2
|
import { joinDepIDTuple, splitDepID } from '@vltpkg/dep-id';
|
|
3
3
|
import { Spec } from '@vltpkg/spec';
|
|
4
4
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
@@ -85,10 +85,6 @@ export const parseAddArgs = (config, scurry, monorepo) => {
|
|
|
85
85
|
const type = getType(config.values);
|
|
86
86
|
const importers = getWorkspaceImporters(config.values, monorepo);
|
|
87
87
|
const specOptions = config.options;
|
|
88
|
-
// nameless spec definitions will need to use their full
|
|
89
|
-
// stringified spec result instead of their name in order
|
|
90
|
-
// to have an unique key name in the resulting Map
|
|
91
|
-
const getName = (s) => s.name === '(unknown)' ? s.spec : s.name;
|
|
92
88
|
// parses each positional argument into a Spec and
|
|
93
89
|
// adds it to the new dependencies Map
|
|
94
90
|
const parsedItems = [];
|
|
@@ -110,7 +106,7 @@ export const parseAddArgs = (config, scurry, monorepo) => {
|
|
|
110
106
|
const importerDeps = new Map();
|
|
111
107
|
for (const { spec, type: depType } of parsedItems) {
|
|
112
108
|
const rebased = rebaseFileSpec(spec, importerPath, scurryCwd, specOptions, monorepo);
|
|
113
|
-
importerDeps.set(
|
|
109
|
+
importerDeps.set(addKey(rebased), asDependency({ spec: rebased, type: depType }));
|
|
114
110
|
}
|
|
115
111
|
add.set(importer, importerDeps);
|
|
116
112
|
}
|
|
@@ -118,7 +114,7 @@ export const parseAddArgs = (config, scurry, monorepo) => {
|
|
|
118
114
|
else {
|
|
119
115
|
const newDependencies = new Map();
|
|
120
116
|
for (const { spec, type: depType } of parsedItems) {
|
|
121
|
-
newDependencies.set(
|
|
117
|
+
newDependencies.set(addKey(spec), asDependency({ spec, type: depType }));
|
|
122
118
|
}
|
|
123
119
|
for (const importer of importers) {
|
|
124
120
|
add.set(importer, newDependencies);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { GetAuthHeader } from '@vltpkg/query';
|
|
2
|
+
import type { LoadedConfig } from './config/index.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a {@link GetAuthHeader} implementation that looks up
|
|
5
|
+
* authorization tokens from the local keychain / env, used by
|
|
6
|
+
* network-reliant query selectors, e.g: `:outdated()`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createGetAuthHeader: (conf: LoadedConfig) => GetAuthHeader;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getTokenByURL } from '@vltpkg/registry-client';
|
|
2
|
+
/**
|
|
3
|
+
* Returns a {@link GetAuthHeader} implementation that looks up
|
|
4
|
+
* authorization tokens from the local keychain / env, used by
|
|
5
|
+
* network-reliant query selectors, e.g: `:outdated()`.
|
|
6
|
+
*/
|
|
7
|
+
export const createGetAuthHeader = (conf) => async (url) => getTokenByURL(url, conf.options.identity);
|
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.0.
|
|
4
|
+
"version": "1.0.8",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/vltpkg/vltpkg.git",
|
|
@@ -14,30 +14,30 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
17
|
-
"@vltpkg/config": "1.0.
|
|
18
|
-
"@vltpkg/dep-id": "1.0.
|
|
19
|
-
"@vltpkg/dot-prop": "1.0.
|
|
20
|
-
"@vltpkg/error-cause": "1.0.
|
|
21
|
-
"@vltpkg/git": "1.0.
|
|
22
|
-
"@vltpkg/graph": "1.0.
|
|
23
|
-
"@vltpkg/graph-run": "1.0.
|
|
24
|
-
"@vltpkg/init": "1.0.
|
|
25
|
-
"@vltpkg/output": "1.0.
|
|
26
|
-
"@vltpkg/package-info": "1.0.
|
|
27
|
-
"@vltpkg/package-json": "1.0.
|
|
28
|
-
"@vltpkg/promise-spawn": "1.0.
|
|
29
|
-
"@vltpkg/query": "1.0.
|
|
30
|
-
"@vltpkg/registry-client": "1.0.
|
|
31
|
-
"@vltpkg/rollback-remove": "1.0.
|
|
32
|
-
"@vltpkg/run": "1.0.
|
|
33
|
-
"@vltpkg/security-archive": "1.0.
|
|
34
|
-
"@vltpkg/spec": "1.0.
|
|
35
|
-
"@vltpkg/types": "1.0.
|
|
36
|
-
"@vltpkg/url-open": "1.0.
|
|
37
|
-
"@vltpkg/vlt-json": "1.0.
|
|
38
|
-
"@vltpkg/vlx": "1.0.
|
|
39
|
-
"@vltpkg/workspaces": "1.0.
|
|
40
|
-
"@vltpkg/xdg": "1.0.
|
|
17
|
+
"@vltpkg/config": "1.0.8",
|
|
18
|
+
"@vltpkg/dep-id": "1.0.8",
|
|
19
|
+
"@vltpkg/dot-prop": "1.0.8",
|
|
20
|
+
"@vltpkg/error-cause": "1.0.8",
|
|
21
|
+
"@vltpkg/git": "1.0.8",
|
|
22
|
+
"@vltpkg/graph": "1.0.8",
|
|
23
|
+
"@vltpkg/graph-run": "1.0.8",
|
|
24
|
+
"@vltpkg/init": "1.0.8",
|
|
25
|
+
"@vltpkg/output": "1.0.8",
|
|
26
|
+
"@vltpkg/package-info": "1.0.8",
|
|
27
|
+
"@vltpkg/package-json": "1.0.8",
|
|
28
|
+
"@vltpkg/promise-spawn": "1.0.8",
|
|
29
|
+
"@vltpkg/query": "1.0.8",
|
|
30
|
+
"@vltpkg/registry-client": "1.0.8",
|
|
31
|
+
"@vltpkg/rollback-remove": "1.0.8",
|
|
32
|
+
"@vltpkg/run": "1.0.8",
|
|
33
|
+
"@vltpkg/security-archive": "1.0.8",
|
|
34
|
+
"@vltpkg/spec": "1.0.8",
|
|
35
|
+
"@vltpkg/types": "1.0.8",
|
|
36
|
+
"@vltpkg/url-open": "1.0.8",
|
|
37
|
+
"@vltpkg/vlt-json": "1.0.8",
|
|
38
|
+
"@vltpkg/vlx": "1.0.8",
|
|
39
|
+
"@vltpkg/workspaces": "1.0.8",
|
|
40
|
+
"@vltpkg/xdg": "1.0.8",
|
|
41
41
|
"ansi-to-pre": "^1.0.6",
|
|
42
42
|
"beautiful-mermaid": "^1.1.3",
|
|
43
43
|
"chalk": "^5.6.2",
|