@vltpkg/cli-sdk 1.4.0 → 1.6.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/allow-skills.d.ts +36 -0
- package/dist/allow-skills.js +49 -0
- package/dist/commands/build.d.ts +15 -4
- package/dist/commands/build.js +50 -12
- package/dist/commands/ci.d.ts +2 -2
- package/dist/commands/ci.js +10 -2
- package/dist/commands/create.d.ts +2 -1
- package/dist/commands/create.js +2 -3
- package/dist/commands/exec.d.ts +2 -1
- package/dist/commands/exec.js +2 -3
- package/dist/commands/init.js +4 -3
- package/dist/commands/install/reporter.d.ts +1 -1
- package/dist/commands/install/reporter.js +91 -5
- package/dist/commands/install.d.ts +50 -4
- package/dist/commands/install.js +107 -25
- package/dist/commands/list.js +8 -2
- package/dist/commands/publish.d.ts +0 -1
- package/dist/commands/publish.js +27 -16
- package/dist/commands/query.js +8 -2
- package/dist/commands/setup.d.ts +28 -2
- package/dist/commands/setup.js +185 -3
- package/dist/commands/skills.d.ts +56 -0
- package/dist/commands/skills.js +267 -0
- package/dist/commands/token.js +1 -1
- package/dist/commands/uninstall.d.ts +15 -4
- package/dist/commands/uninstall.js +32 -4
- package/dist/commands/unpublish.d.ts +0 -1
- package/dist/commands/unpublish.js +17 -4
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +18 -1
- package/dist/config/definition.d.ts +52 -0
- package/dist/config/definition.js +109 -1
- package/dist/config/index.d.ts +26 -3
- package/dist/config/index.js +149 -7
- package/dist/custom-help.js +4 -0
- package/dist/global-bin-dir.d.ts +37 -0
- package/dist/global-bin-dir.js +138 -0
- package/dist/global-project.d.ts +18 -0
- package/dist/global-project.js +67 -0
- package/dist/global-state.d.ts +24 -0
- package/dist/global-state.js +94 -0
- package/dist/global.d.ts +78 -0
- package/dist/global.js +409 -0
- package/dist/index.js +14 -3
- package/dist/install-receipt.d.ts +40 -0
- package/dist/install-receipt.js +69 -0
- package/dist/load-command.d.ts +2 -0
- package/dist/migrate-workspaces.d.ts +16 -0
- package/dist/migrate-workspaces.js +44 -0
- package/dist/output.js +13 -0
- package/dist/parse-add-remove-args.js +2 -14
- package/dist/print-err.js +4 -1
- package/dist/publish-config.d.ts +9 -0
- package/dist/publish-config.js +8 -0
- package/dist/query-auth.d.ts +3 -2
- package/dist/query-auth.js +6 -4
- package/dist/reload-config.js +2 -1
- package/dist/require-registry.d.ts +7 -0
- package/dist/require-registry.js +13 -1
- package/dist/self-update.d.ts +74 -0
- package/dist/self-update.js +401 -0
- package/dist/shell-setup/env.d.ts +9 -0
- package/dist/shell-setup/env.js +25 -0
- package/dist/shell-setup/index.d.ts +69 -0
- package/dist/shell-setup/index.js +213 -0
- package/dist/shell-setup/posix.d.ts +36 -0
- package/dist/shell-setup/posix.js +158 -0
- package/dist/shell-setup/windows.d.ts +39 -0
- package/dist/shell-setup/windows.js +151 -0
- package/dist/shell.d.ts +18 -0
- package/dist/shell.js +110 -0
- package/dist/skills-prompt.d.ts +28 -0
- package/dist/skills-prompt.js +78 -0
- package/package.json +30 -28
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { findCmdShimIfExists } from '@vltpkg/cmd-shim';
|
|
2
|
+
import { isDepID } from '@vltpkg/dep-id';
|
|
3
|
+
import { error } from '@vltpkg/error-cause';
|
|
4
|
+
import { lt, valid } from '@vltpkg/semver';
|
|
5
|
+
import { Spec } from '@vltpkg/spec';
|
|
6
|
+
import { which } from '@vltpkg/which';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { constants, existsSync } from 'node:fs';
|
|
10
|
+
import { access, chmod, lstat, readFile, realpath, rename, rm, stat, } from 'node:fs/promises';
|
|
11
|
+
import { basename, dirname, extname, join, relative, resolve, sep, } from 'node:path';
|
|
12
|
+
import { loadPackageJson } from 'package-json-from-dist';
|
|
13
|
+
import { findSelf } from "./global-bin-dir.js";
|
|
14
|
+
import { globalSpec } from "./global.js";
|
|
15
|
+
import { globalWorkspacePath, isInside, samePath, } from "./global-project.js";
|
|
16
|
+
import { readGlobalState, updateGlobalState } from "./global-state.js";
|
|
17
|
+
import { writeInstallReceipt } from "./install-receipt.js";
|
|
18
|
+
import { parseSpecArg } from "./require-registry.js";
|
|
19
|
+
import { prettyPath } from "./shell.js";
|
|
20
|
+
const { version: running } = loadPackageJson(import.meta.filename, process.env.__VLT_INTERNAL_CLI_PACKAGE_JSON);
|
|
21
|
+
const installerLine = 'Re-run the installer to update vlt:\n curl -fsSL https://install.vlt.sh | bash';
|
|
22
|
+
const writable = (dir) => access(dir, constants.W_OK).then(() => true, () => false);
|
|
23
|
+
// exit 1 without usage: the usage was fine
|
|
24
|
+
const refuse = (self, message) => {
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return { status: 'refused', from: running, dir: self.dir, message };
|
|
27
|
+
};
|
|
28
|
+
const isFile = (f) => stat(f).then(s => s.isFile(), () => false);
|
|
29
|
+
const cellars = () => [
|
|
30
|
+
process.env.HOMEBREW_CELLAR,
|
|
31
|
+
'/opt/homebrew/Cellar',
|
|
32
|
+
'/usr/local/Cellar',
|
|
33
|
+
'/home/linuxbrew/.linuxbrew/Cellar',
|
|
34
|
+
].filter((c) => !!c);
|
|
35
|
+
const whichNpm = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const found = await which('npm', { nothrow: true });
|
|
38
|
+
if (!found)
|
|
39
|
+
return;
|
|
40
|
+
const real = await realpath((await findCmdShimIfExists(found))?.[1] ?? found);
|
|
41
|
+
return basename(real) === 'npm-cli.js' ? real : undefined;
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
};
|
|
45
|
+
// npm's -g layout: <prefix>/lib/node_modules, win32 <prefix>/node_modules
|
|
46
|
+
const findNpm = async (self) => {
|
|
47
|
+
const i = self.target.lastIndexOf(`${sep}node_modules${sep}vlt${sep}`);
|
|
48
|
+
if (i === -1)
|
|
49
|
+
return;
|
|
50
|
+
const nm = self.target.slice(0, i) + sep + 'node_modules';
|
|
51
|
+
const win32 = process.platform === 'win32';
|
|
52
|
+
const up = dirname(nm);
|
|
53
|
+
const prefix = win32 ?
|
|
54
|
+
// vlt.cmd is in the prefix
|
|
55
|
+
samePath(up, self.dir) && up
|
|
56
|
+
: basename(up) === 'lib' && dirname(up);
|
|
57
|
+
if (!prefix)
|
|
58
|
+
return;
|
|
59
|
+
const node = dirname(process.execPath);
|
|
60
|
+
const candidates = [
|
|
61
|
+
join(nm, 'npm/bin/npm-cli.js'),
|
|
62
|
+
win32 ?
|
|
63
|
+
join(node, 'node_modules/npm/bin/npm-cli.js')
|
|
64
|
+
: join(dirname(node), 'lib/node_modules/npm/bin/npm-cli.js'),
|
|
65
|
+
];
|
|
66
|
+
for (const c of candidates) {
|
|
67
|
+
if (await isFile(c))
|
|
68
|
+
return { npmCli: c, prefix };
|
|
69
|
+
}
|
|
70
|
+
const npmCli = await whichNpm();
|
|
71
|
+
return npmCli && { npmCli, prefix };
|
|
72
|
+
};
|
|
73
|
+
const classify = async (self, root) => {
|
|
74
|
+
// before the receipt: it can outlive the vlt it was written for
|
|
75
|
+
const g = await realpath(root).catch(() => resolve(root));
|
|
76
|
+
if (isInside(g, self.target))
|
|
77
|
+
return { kind: 'global-project' };
|
|
78
|
+
const { receipt } = self;
|
|
79
|
+
if (receipt?.data.method === 'standalone') {
|
|
80
|
+
if (self.standalone)
|
|
81
|
+
return { kind: 'standalone-receipt', receipt };
|
|
82
|
+
}
|
|
83
|
+
else if (receipt) {
|
|
84
|
+
const npm = receipt.data.method === 'npm' && (await findNpm(self));
|
|
85
|
+
return npm ?
|
|
86
|
+
{ kind: 'npm', ...npm, receipt }
|
|
87
|
+
: { kind: 'installer' };
|
|
88
|
+
}
|
|
89
|
+
// before npm: a node formula has npm's layout
|
|
90
|
+
if (cellars().some(c => isInside(c, self.target))) {
|
|
91
|
+
return { kind: 'homebrew' };
|
|
92
|
+
}
|
|
93
|
+
if (self.standalone)
|
|
94
|
+
return { kind: 'standalone' };
|
|
95
|
+
const npm = await findNpm(self);
|
|
96
|
+
return npm ? { kind: 'npm', ...npm } : { kind: 'unknown' };
|
|
97
|
+
};
|
|
98
|
+
const sha512 = async (file) => `sha512-${createHash('sha512')
|
|
99
|
+
.update(await readFile(file))
|
|
100
|
+
.digest('base64')}`;
|
|
101
|
+
const safeBin = (b) => /^[a-z0-9][\w.-]*$/i.test(b);
|
|
102
|
+
/** a refusal message, or undefined once replaced */
|
|
103
|
+
const updateStandalone = async (self, receipt, to, fetchRelease) => {
|
|
104
|
+
const { target, channel, binaries = [] } = receipt.data;
|
|
105
|
+
if (!fetchRelease || !target)
|
|
106
|
+
return installerLine;
|
|
107
|
+
const win32 = process.platform === 'win32';
|
|
108
|
+
const at = prettyPath(self.dir);
|
|
109
|
+
// stat().uid is 0 on win32
|
|
110
|
+
if (!win32 &&
|
|
111
|
+
(await stat(self.found)).uid === 0 &&
|
|
112
|
+
process.getuid?.() !== 0) {
|
|
113
|
+
return `vlt at ${at} is owned by root. Update it the way you installed it, or run this as root.`;
|
|
114
|
+
}
|
|
115
|
+
if (!(await writable(self.dir))) {
|
|
116
|
+
return `vlt at ${at} is not writable. Update it the way you installed it.`;
|
|
117
|
+
}
|
|
118
|
+
const ext = extname(self.found).toLowerCase() === '.exe' ? '.exe' : '';
|
|
119
|
+
const entries = [];
|
|
120
|
+
for (const bin of new Set(binaries)) {
|
|
121
|
+
if (bin === 'vlt' || !safeBin(bin))
|
|
122
|
+
continue;
|
|
123
|
+
const file = join(self.dir, bin + ext);
|
|
124
|
+
const st = await lstat(file).catch(() => undefined);
|
|
125
|
+
// links follow vlt
|
|
126
|
+
if (st && !st.isSymbolicLink())
|
|
127
|
+
entries.push({ bin, file });
|
|
128
|
+
}
|
|
129
|
+
// last, so a failure part-way leaves a working vlt
|
|
130
|
+
entries.push({
|
|
131
|
+
bin: 'vlt',
|
|
132
|
+
file: join(self.dir, basename(self.found)),
|
|
133
|
+
});
|
|
134
|
+
const tmps = [];
|
|
135
|
+
const cleanup = () => Promise.all(tmps.map(f => rm(f, { force: true })));
|
|
136
|
+
const ready = [];
|
|
137
|
+
try {
|
|
138
|
+
for (const { bin, file } of entries) {
|
|
139
|
+
// a running vlt.exe.old: the rename below says so
|
|
140
|
+
if (win32)
|
|
141
|
+
await rm(`${file}.old`, { force: true }).catch(() => { });
|
|
142
|
+
// same dir: rename is atomic
|
|
143
|
+
const dest = join(self.dir, `.${bin}-${to}.tmp`);
|
|
144
|
+
tmps.push(dest);
|
|
145
|
+
const { checksum } = await fetchRelease({
|
|
146
|
+
version: to,
|
|
147
|
+
target,
|
|
148
|
+
channel,
|
|
149
|
+
bin,
|
|
150
|
+
dest,
|
|
151
|
+
});
|
|
152
|
+
const found = await sha512(dest);
|
|
153
|
+
if (found !== checksum) {
|
|
154
|
+
throw error(`Checksum mismatch for ${bin} ${to}`, {
|
|
155
|
+
code: 'EINTEGRITY',
|
|
156
|
+
wanted: checksum,
|
|
157
|
+
found,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
await chmod(dest, (await stat(file)).mode & 0o777);
|
|
161
|
+
ready.push({ file, dest });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (er) {
|
|
165
|
+
await cleanup();
|
|
166
|
+
throw er;
|
|
167
|
+
}
|
|
168
|
+
// never write into a running file: rename only
|
|
169
|
+
for (const { file, dest } of ready) {
|
|
170
|
+
try {
|
|
171
|
+
if (win32) {
|
|
172
|
+
// a running .exe can be renamed, not replaced
|
|
173
|
+
await rename(file, `${file}.old`);
|
|
174
|
+
await rename(dest, file).catch(async (er) => {
|
|
175
|
+
await rename(`${file}.old`, file).catch(() => { });
|
|
176
|
+
throw er;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await rename(dest, file);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (cause) {
|
|
184
|
+
await cleanup();
|
|
185
|
+
throw error(`Could not replace ${file}. Close other vlt processes and editors, and check permissions.`, { path: file, cause });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const runNpm = (npmCli, prefix, arg) => new Promise((res, rej) => {
|
|
190
|
+
// stdout to stderr: keeps --view=json parseable
|
|
191
|
+
const child = spawn(process.execPath, [npmCli, 'install', '--global', '--prefix', prefix, arg], { stdio: ['inherit', 2, 2] });
|
|
192
|
+
child.on('error', rej);
|
|
193
|
+
child.on('close', (code) => res(code ?? 1));
|
|
194
|
+
});
|
|
195
|
+
const rewriteReceipt = (receipt, to) => receipt &&
|
|
196
|
+
writeInstallReceipt(receipt.file, { version: to }).catch(() => { });
|
|
197
|
+
// vlt, vlt@..., a vlt tarball
|
|
198
|
+
const vltArg = /^vlt(@|$)|(^|[/\\:])vlt-\d[^/\\]*\.tgz$/;
|
|
199
|
+
/** `vlt install -g vlt...`, for the human view */
|
|
200
|
+
export const isSelfUpdateArgs = (conf) => conf.command === 'install' &&
|
|
201
|
+
!!conf.globalRoot &&
|
|
202
|
+
conf.positionals.some(p => vltArg.test(p));
|
|
203
|
+
/**
|
|
204
|
+
* `vlt install -g vlt`: update the vlt on the PATH the way it was
|
|
205
|
+
* installed. Undefined when vlt is not a positional.
|
|
206
|
+
*/
|
|
207
|
+
export const selfUpdate = async (conf, { fetchRelease } = {}) => {
|
|
208
|
+
const { globalRoot, positionals } = conf;
|
|
209
|
+
if (!globalRoot || !positionals.length)
|
|
210
|
+
return;
|
|
211
|
+
const specs = await Promise.all(positionals.map(p => globalSpec(p, conf)));
|
|
212
|
+
const [spec] = specs;
|
|
213
|
+
if (!spec || !specs.some(s => s.name === 'vlt'))
|
|
214
|
+
return;
|
|
215
|
+
if (specs.length > 1) {
|
|
216
|
+
throw error('Update vlt on its own, without other packages', {
|
|
217
|
+
code: 'EUSAGE',
|
|
218
|
+
found: positionals.join(' '),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const self = await findSelf();
|
|
222
|
+
if (!self) {
|
|
223
|
+
throw error(`Can't tell which vlt to update: run the vlt on your PATH, not ${process.argv[1] ?? process.execPath}`, { code: 'EUSAGE' });
|
|
224
|
+
}
|
|
225
|
+
const owner = await classify(self, globalRoot);
|
|
226
|
+
const at = prettyPath(self.dir);
|
|
227
|
+
const foreign = `vlt at ${at} is not managed by vlt or npm. Update it the way you installed it.`;
|
|
228
|
+
if (owner.kind === 'installer') {
|
|
229
|
+
return { done: refuse(self, installerLine) };
|
|
230
|
+
}
|
|
231
|
+
if (owner.kind === 'homebrew') {
|
|
232
|
+
return {
|
|
233
|
+
done: refuse(self, `vlt at ${at} was installed by Homebrew. Run \`brew upgrade vlt\`.`),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (owner.kind === 'standalone') {
|
|
237
|
+
return { done: refuse(self, foreign) };
|
|
238
|
+
}
|
|
239
|
+
// npm's node_modules not writable: npm's EACCES says so
|
|
240
|
+
const bootstrap = (owner.kind === 'npm' || owner.kind === 'unknown') &&
|
|
241
|
+
!(await writable(self.dir));
|
|
242
|
+
if (owner.kind === 'unknown' && !bootstrap) {
|
|
243
|
+
return { done: refuse(self, foreign) };
|
|
244
|
+
}
|
|
245
|
+
const raw = String(positionals[0]);
|
|
246
|
+
const implicit = !parseSpecArg(raw, conf.options).bareSpec;
|
|
247
|
+
const receipt = 'receipt' in owner ? owner.receipt : undefined;
|
|
248
|
+
const channel = receipt?.data.channel;
|
|
249
|
+
const tag = channel ?? conf.options.tag;
|
|
250
|
+
const { name, version: to } = await conf.options.packageInfo.manifest(implicit && channel ?
|
|
251
|
+
Spec.parse('vlt', tag, conf.options)
|
|
252
|
+
: spec);
|
|
253
|
+
if (name !== 'vlt' || !to) {
|
|
254
|
+
throw error(`${raw} is not a version of vlt`, {
|
|
255
|
+
code: 'EUSAGE',
|
|
256
|
+
found: name,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const kind = bootstrap ? 'bootstrap'
|
|
260
|
+
: owner.kind === 'standalone-receipt' ? 'standalone'
|
|
261
|
+
: owner.kind === 'npm' ? 'npm'
|
|
262
|
+
: 'global-project';
|
|
263
|
+
const base = { case: kind, from: running, to, dir: self.dir };
|
|
264
|
+
if (to === running)
|
|
265
|
+
return { done: { ...base, status: 'up-to-date' } };
|
|
266
|
+
// `latest` may lag a new major
|
|
267
|
+
if (implicit && lt(to, running)) {
|
|
268
|
+
return { done: { ...base, status: 'newer-running', tag } };
|
|
269
|
+
}
|
|
270
|
+
const registry = spec.final.type === 'registry';
|
|
271
|
+
if (owner.kind === 'standalone-receipt') {
|
|
272
|
+
// releases are registry versions
|
|
273
|
+
if (!registry || !valid(to)) {
|
|
274
|
+
throw error(`${raw} is not a vlt release`, {
|
|
275
|
+
code: 'EUSAGE',
|
|
276
|
+
found: raw,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const refused = await updateStandalone(self, owner.receipt, to, fetchRelease);
|
|
280
|
+
if (refused)
|
|
281
|
+
return { done: refuse(self, refused) };
|
|
282
|
+
await rewriteReceipt(owner.receipt, to);
|
|
283
|
+
return { done: { ...base, status: 'updated' } };
|
|
284
|
+
}
|
|
285
|
+
if (owner.kind === 'npm' && !bootstrap) {
|
|
286
|
+
const code = await runNpm(owner.npmCli, owner.prefix, registry ? `vlt@${to}` : `vlt@${spec.bareSpec}`);
|
|
287
|
+
// npm already said why
|
|
288
|
+
if (code !== 0) {
|
|
289
|
+
process.exitCode = code;
|
|
290
|
+
return { done: { ...base, status: 'failed' } };
|
|
291
|
+
}
|
|
292
|
+
await rewriteReceipt(receipt, to);
|
|
293
|
+
return { done: { ...base, status: 'updated' } };
|
|
294
|
+
}
|
|
295
|
+
// the ws pins what was resolved
|
|
296
|
+
if (registry)
|
|
297
|
+
conf.positionals = [`vlt@${to}`];
|
|
298
|
+
return {
|
|
299
|
+
selfUnwritable: bootstrap,
|
|
300
|
+
finish: async (global) => {
|
|
301
|
+
const u = {
|
|
302
|
+
...base,
|
|
303
|
+
status: 'updated',
|
|
304
|
+
dir: global?.binDir ?? self.dir,
|
|
305
|
+
...(bootstrap ? { notWritable: self.dir } : {}),
|
|
306
|
+
};
|
|
307
|
+
if (bootstrap &&
|
|
308
|
+
global?.inPath &&
|
|
309
|
+
global.bins.includes('vlt')) {
|
|
310
|
+
const now = await which('vlt', { nothrow: true }).catch(() => null);
|
|
311
|
+
const first = now && dirname(now);
|
|
312
|
+
if (first &&
|
|
313
|
+
!samePath(await realpath(first).catch(() => first), global.binDir)) {
|
|
314
|
+
u.shadowedBy = first;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return u;
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
/** `vlt uninstall -g vlt` of a vlt that is not a global package */
|
|
322
|
+
export const refuseSelfUninstall = async (conf) => {
|
|
323
|
+
const { globalRoot, positionals } = conf;
|
|
324
|
+
if (!globalRoot || !positionals.includes('vlt'))
|
|
325
|
+
return;
|
|
326
|
+
const ws = resolve(globalRoot, globalWorkspacePath('vlt'));
|
|
327
|
+
if (existsSync(resolve(ws, 'package.json')))
|
|
328
|
+
return;
|
|
329
|
+
const self = await findSelf();
|
|
330
|
+
if (!self)
|
|
331
|
+
return;
|
|
332
|
+
const { kind } = await classify(self, globalRoot);
|
|
333
|
+
return refuse(self, kind === 'npm' ?
|
|
334
|
+
'vlt was installed by npm. Run `npm uninstall -g vlt` to remove it.'
|
|
335
|
+
: kind === 'homebrew' ?
|
|
336
|
+
'vlt was installed by Homebrew. Run `brew uninstall vlt` to remove it.'
|
|
337
|
+
: `vlt at ${prettyPath(self.dir)} is not managed by vlt. Remove it the way you installed it.`);
|
|
338
|
+
};
|
|
339
|
+
/** store id of the running vlt, when it runs from the global project */
|
|
340
|
+
export const runningStoreDep = async (root) => {
|
|
341
|
+
const script = process.argv[1];
|
|
342
|
+
if (!script)
|
|
343
|
+
return;
|
|
344
|
+
try {
|
|
345
|
+
const [real, store] = await Promise.all([
|
|
346
|
+
realpath(script),
|
|
347
|
+
realpath(resolve(root, 'node_modules/.vlt')),
|
|
348
|
+
]);
|
|
349
|
+
const [id] = relative(store, real).split(sep);
|
|
350
|
+
return isDepID(id) ? id : undefined;
|
|
351
|
+
}
|
|
352
|
+
catch { }
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Record the running vlt's store dir for removal by a later -g
|
|
356
|
+
* command when the graph no longer has it, and forget listed dirs
|
|
357
|
+
* the graph has again.
|
|
358
|
+
*/
|
|
359
|
+
export const deferSelfRemove = async (root, graph, keep) => {
|
|
360
|
+
const store = resolve(await realpath(root), 'node_modules/.vlt');
|
|
361
|
+
const add = keep && !graph.nodes.has(keep) ? [resolve(store, keep)] : [];
|
|
362
|
+
const { pendingRemove = [] } = await readGlobalState(root);
|
|
363
|
+
const kept = pendingRemove.filter(p => {
|
|
364
|
+
const id = basename(p);
|
|
365
|
+
return !(isDepID(id) && graph.nodes.has(id));
|
|
366
|
+
});
|
|
367
|
+
if (!add.length && kept.length === pendingRemove.length)
|
|
368
|
+
return;
|
|
369
|
+
await updateGlobalState(root, s => ({
|
|
370
|
+
...s,
|
|
371
|
+
pendingRemove: [...new Set([...kept, ...add])],
|
|
372
|
+
}));
|
|
373
|
+
};
|
|
374
|
+
/** human lines for a self-update */
|
|
375
|
+
export const selfUpdateLines = (s) => {
|
|
376
|
+
if (s.status === 'refused')
|
|
377
|
+
return [s.message];
|
|
378
|
+
if (s.status === 'up-to-date') {
|
|
379
|
+
return [`✅ vlt ${s.to} is already up to date`];
|
|
380
|
+
}
|
|
381
|
+
if (s.status === 'newer-running') {
|
|
382
|
+
return [
|
|
383
|
+
`ℹ️ vlt ${s.from} is running but "${s.tag}" is ${s.to}; run \`vlt i -g vlt@${s.tag}\` to downgrade`,
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
if (s.status === 'failed')
|
|
387
|
+
return [];
|
|
388
|
+
if (s.case === 'bootstrap') {
|
|
389
|
+
return [
|
|
390
|
+
`ℹ️ ${prettyPath(s.notWritable ?? s.dir)} is not writable, so vlt ${s.to} was installed as a global package.`,
|
|
391
|
+
...(s.shadowedBy ?
|
|
392
|
+
[
|
|
393
|
+
`⚠️ ${prettyPath(s.shadowedBy)} is earlier in your PATH, so \`vlt\` still runs vlt ${s.from}.`,
|
|
394
|
+
]
|
|
395
|
+
: []),
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
return [
|
|
399
|
+
`⬆️ Updated vlt ${s.from} → ${s.to} in ${prettyPath(s.dir)}${s.case === 'npm' ? ' (via npm)' : ''}`,
|
|
400
|
+
];
|
|
401
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShellOptions } from '../shell.ts';
|
|
2
|
+
/** `<global-dir>/env`, sourced by sh-family rc files */
|
|
3
|
+
export declare const envFile: (globalDir: string) => string;
|
|
4
|
+
/** `<global-dir>/env.fish`, sourced by fish */
|
|
5
|
+
export declare const envFishFile: (globalDir: string) => string;
|
|
6
|
+
/** sh script adding `dir` to the `PATH` once */
|
|
7
|
+
export declare const renderEnv: (dir: string, o?: ShellOptions) => string;
|
|
8
|
+
/** fish script adding `dir` to the `PATH` of the session */
|
|
9
|
+
export declare const renderEnvFish: (dir: string, o?: ShellOptions) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { fishPath, posixPath, shellPathCommand } from "../shell.js";
|
|
3
|
+
/** `<global-dir>/env`, sourced by sh-family rc files */
|
|
4
|
+
export const envFile = (globalDir) => resolve(globalDir, 'env');
|
|
5
|
+
/** `<global-dir>/env.fish`, sourced by fish */
|
|
6
|
+
export const envFishFile = (globalDir) => resolve(globalDir, 'env.fish');
|
|
7
|
+
/** sh script adding `dir` to the `PATH` once */
|
|
8
|
+
export const renderEnv = (dir, o = {}) => [
|
|
9
|
+
'# vlt global bin dir; sourced from the shell rc file. Edit with `vlt setup --path`.',
|
|
10
|
+
'case ":$PATH:" in',
|
|
11
|
+
` *:${posixPath(dir, o)}:*) ;;`,
|
|
12
|
+
` *) ${shellPathCommand('sh', dir, o)} ;;`,
|
|
13
|
+
'esac',
|
|
14
|
+
'',
|
|
15
|
+
].join('\n');
|
|
16
|
+
/** fish script adding `dir` to the `PATH` of the session */
|
|
17
|
+
export const renderEnvFish = (dir, o = {}) => {
|
|
18
|
+
const d = fishPath(dir, o);
|
|
19
|
+
return [
|
|
20
|
+
'# vlt global bin dir; sourced from fish config. Edit with `vlt setup --path`.',
|
|
21
|
+
// session only, and fish_add_path needs fish 3.2
|
|
22
|
+
`contains -- ${d} $PATH; or set -gx PATH ${d} $PATH`,
|
|
23
|
+
'',
|
|
24
|
+
].join('\n');
|
|
25
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { GlobalBins } from '../global.ts';
|
|
2
|
+
import type { Shell, ShellOptions } from '../shell.ts';
|
|
3
|
+
import type { FileAction, FileChange } from './posix.ts';
|
|
4
|
+
import type { WindowsPathChange } from './windows.ts';
|
|
5
|
+
export type { FileAction } from './posix.ts';
|
|
6
|
+
/** whether `vlt setup --path` can edit config for `shell` */
|
|
7
|
+
export declare const canSetupPath: (shell: Shell, platform?: NodeJS.Platform) => boolean;
|
|
8
|
+
export type PathSetupOptions = ShellOptions & {
|
|
9
|
+
/** holds the env files and backups */
|
|
10
|
+
globalDir: string;
|
|
11
|
+
/** dir to add to the `PATH` */
|
|
12
|
+
dir: string;
|
|
13
|
+
shell: Shell;
|
|
14
|
+
};
|
|
15
|
+
export type PathSetupPlan = {
|
|
16
|
+
globalDir: string;
|
|
17
|
+
dir: string;
|
|
18
|
+
shell: Shell;
|
|
19
|
+
changes: FileChange[];
|
|
20
|
+
/** win32: user Path change, instead of files */
|
|
21
|
+
registry?: WindowsPathChange;
|
|
22
|
+
/** win32: env the user Path was read with */
|
|
23
|
+
env?: NodeJS.ProcessEnv;
|
|
24
|
+
};
|
|
25
|
+
export type PathSetupFile = {
|
|
26
|
+
file: string;
|
|
27
|
+
kind: 'env' | 'rc' | 'registry';
|
|
28
|
+
action: FileAction | 'prepend';
|
|
29
|
+
};
|
|
30
|
+
export type PathSetupResult = {
|
|
31
|
+
dir: string;
|
|
32
|
+
shell: Shell;
|
|
33
|
+
files: PathSetupFile[];
|
|
34
|
+
backups: string[];
|
|
35
|
+
};
|
|
36
|
+
/** what adding `dir` to the `PATH` of new shells would change */
|
|
37
|
+
export declare const planPathSetup: (o: PathSetupOptions) => Promise<PathSetupPlan>;
|
|
38
|
+
export declare const pathSetupChanged: (plan: PathSetupPlan) => boolean;
|
|
39
|
+
export declare const pathSetupFiles: (plan: PathSetupPlan) => PathSetupFile[];
|
|
40
|
+
/** rc files or the user Path, for display */
|
|
41
|
+
export declare const rcList: (files: PathSetupFile[], home?: string) => string;
|
|
42
|
+
/** write the plan: env files, rc files, user Path, backed up */
|
|
43
|
+
export declare const applyPathSetup: (plan: PathSetupPlan) => Promise<PathSetupResult>;
|
|
44
|
+
/** loads the env file into the current shell (win32: adds `dir`) */
|
|
45
|
+
export declare const sourceCommand: (shell: Shell, globalDir: string, dir: string, o?: ShellOptions) => string;
|
|
46
|
+
export declare const pathSetupLines: (r: PathSetupResult, source: string, home?: string) => string[];
|
|
47
|
+
/** one trimmed answer */
|
|
48
|
+
export declare const ask: (question: string, { input, output, }?: {
|
|
49
|
+
input?: NodeJS.ReadableStream;
|
|
50
|
+
output?: NodeJS.WritableStream;
|
|
51
|
+
}) => Promise<string>;
|
|
52
|
+
export type OfferOptions = ShellOptions & {
|
|
53
|
+
globalDir: string;
|
|
54
|
+
yes?: boolean;
|
|
55
|
+
noModifyPath?: boolean;
|
|
56
|
+
input?: NodeJS.ReadableStream & {
|
|
57
|
+
isTTY?: boolean;
|
|
58
|
+
};
|
|
59
|
+
output?: NodeJS.WritableStream & {
|
|
60
|
+
isTTY?: boolean;
|
|
61
|
+
};
|
|
62
|
+
/** once, before the first prompt or line */
|
|
63
|
+
beforeOutput?: () => void;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* After `install -g` linked into a dir not in the `PATH`: offer, once,
|
|
67
|
+
* in a terminal, to add it to the PATH. Never throws.
|
|
68
|
+
*/
|
|
69
|
+
export declare const offerPathSetup: (g: GlobalBins, o: OfferOptions) => Promise<void>;
|