vercel-vm-factory 0.8.8 → 0.10.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/README.md +36 -2
- package/deploy-vm.mjs +178 -73
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ Create a tiny Vercel Container deployment: copy `wsterm` from `ghcr.io/v1xingyue
|
|
|
6
6
|
npx vercel-vm-factory create \
|
|
7
7
|
--vm-image ubuntu \
|
|
8
8
|
--shell /bin/bash \
|
|
9
|
+
--tools nodejs,codex \
|
|
9
10
|
--project ws-shell-ubuntu \
|
|
10
11
|
--auth-mode basic \
|
|
11
12
|
--auth-user admin \
|
|
@@ -28,7 +29,7 @@ Run without flags for prompts:
|
|
|
28
29
|
npx vercel-vm-factory create
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
The prompt
|
|
32
|
+
The prompt walks through VM image, project, shell, optional preinstalled tools, and authentication. For list prompts, enter either names or numbers; tool choices can be comma-separated, for example `1,3` or `nodejs,claude-code`.
|
|
32
33
|
|
|
33
34
|
Check local setup:
|
|
34
35
|
|
|
@@ -40,6 +41,16 @@ The script checks `vercel --version` and `vercel whoami`; if you are not logged
|
|
|
40
41
|
|
|
41
42
|
Use `--help` to show all flags.
|
|
42
43
|
|
|
44
|
+
Common flags:
|
|
45
|
+
|
|
46
|
+
- `--vm-image alpine|ubuntu|debian|IMAGE`
|
|
47
|
+
- `--shell /bin/bash|/bin/zsh|/bin/sh`
|
|
48
|
+
- `--tools nodejs,codex,claude-code`
|
|
49
|
+
- `--project NAME`
|
|
50
|
+
- `--scope TEAM_SLUG`
|
|
51
|
+
- `--auth-mode basic|github|both|none`
|
|
52
|
+
- `--dry-run`
|
|
53
|
+
|
|
43
54
|
Entered auth values are reused from `~/.vercel-vm-factory/defaults.json`; press Enter to keep the placeholder value or skip an empty one.
|
|
44
55
|
|
|
45
56
|
The generated project contains only `Dockerfile.vercel`.
|
|
@@ -63,7 +74,30 @@ Shell options:
|
|
|
63
74
|
- `/bin/zsh`
|
|
64
75
|
- `/bin/sh`
|
|
65
76
|
|
|
66
|
-
Choosing bash or zsh adds the matching package to the generated Dockerfile when the VM image does not already include it.
|
|
77
|
+
Choosing bash or zsh adds the matching package to the generated Dockerfile when the VM image does not already include it. Choosing zsh also installs oh-my-zsh.
|
|
78
|
+
|
|
79
|
+
Generated shell setup examples:
|
|
80
|
+
|
|
81
|
+
- Alpine + `/bin/zsh`: installs `zsh curl git`, installs oh-my-zsh unattended, and writes `/root/.zshrc`.
|
|
82
|
+
- Ubuntu/Debian + `/bin/bash`: installs `bash` with `apt-get`.
|
|
83
|
+
- `/bin/sh`: no extra shell package is installed.
|
|
84
|
+
|
|
85
|
+
The generated Dockerfile sets `HOME=/root` and `SHELL` to the selected shell path.
|
|
86
|
+
|
|
87
|
+
Preinstall tools:
|
|
88
|
+
|
|
89
|
+
- `nodejs`
|
|
90
|
+
- `codex`
|
|
91
|
+
- `claude-code`
|
|
92
|
+
|
|
93
|
+
Choosing codex or claude-code also installs Node.js/npm.
|
|
94
|
+
|
|
95
|
+
Generated tool setup examples:
|
|
96
|
+
|
|
97
|
+
- `--tools nodejs`: installs `nodejs npm`
|
|
98
|
+
- `--tools codex`: installs `nodejs npm`, then `npm install -g @openai/codex`
|
|
99
|
+
- `--tools claude-code`: installs `nodejs npm`, then `npm install -g @anthropic-ai/claude-code`
|
|
100
|
+
- `--tools codex,claude-code`: installs both CLIs
|
|
67
101
|
|
|
68
102
|
Custom VM image:
|
|
69
103
|
|
package/deploy-vm.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { createInterface } from "node:readline/promises";
|
|
5
4
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
5
|
import { homedir } from "node:os";
|
|
7
6
|
import path from "node:path";
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
8
|
|
|
9
9
|
const defaultWsShellImage = "ghcr.io/v1xingyue/ws-shell:v1.8.alpine";
|
|
10
10
|
|
|
@@ -14,6 +14,11 @@ const vmImages = {
|
|
|
14
14
|
debian: "debian:13-slim",
|
|
15
15
|
};
|
|
16
16
|
const shells = ["/bin/bash", "/bin/zsh", "/bin/sh"];
|
|
17
|
+
const toolChoices = {
|
|
18
|
+
nodejs: "Node.js + npm",
|
|
19
|
+
codex: "OpenAI Codex CLI",
|
|
20
|
+
"claude-code": "Claude Code",
|
|
21
|
+
};
|
|
17
22
|
|
|
18
23
|
const { command, args } = parseCommand(process.argv.slice(2));
|
|
19
24
|
const scriptRoot = path.resolve(import.meta.dirname);
|
|
@@ -96,6 +101,7 @@ async function main() {
|
|
|
96
101
|
defaults.from ??
|
|
97
102
|
defaultWsShellImage;
|
|
98
103
|
const shell = await chooseShell(args.shell ?? defaults.shell ?? "/bin/sh");
|
|
104
|
+
const tools = await chooseTools(args.tools ?? defaults.tools ?? "");
|
|
99
105
|
const prod = args.prod !== "false";
|
|
100
106
|
const dryRun = Boolean(args["dry-run"]);
|
|
101
107
|
const skipLink = Boolean(args["skip-link"]);
|
|
@@ -115,6 +121,7 @@ async function main() {
|
|
|
115
121
|
);
|
|
116
122
|
const dockerfile = makeDockerfile({
|
|
117
123
|
shell,
|
|
124
|
+
tools,
|
|
118
125
|
vmImage,
|
|
119
126
|
vmImageName,
|
|
120
127
|
wsShellImage,
|
|
@@ -130,6 +137,7 @@ async function main() {
|
|
|
130
137
|
scope: scope || "default",
|
|
131
138
|
source: wsShellImage,
|
|
132
139
|
shell,
|
|
140
|
+
tools: tools || "none",
|
|
133
141
|
auth: authMode,
|
|
134
142
|
...(usesGitHubAuth(authMode) ? { callback: oauthRedirectUrl } : {}),
|
|
135
143
|
dockerfile: path.join(appDir, "Dockerfile.vercel"),
|
|
@@ -186,6 +194,7 @@ async function main() {
|
|
|
186
194
|
project,
|
|
187
195
|
from: wsShellImage,
|
|
188
196
|
shell,
|
|
197
|
+
tools,
|
|
189
198
|
"auth-mode": authMode,
|
|
190
199
|
"auth-user": authUsername,
|
|
191
200
|
"auth-password": authPassword,
|
|
@@ -285,15 +294,14 @@ async function installVercelCli() {
|
|
|
285
294
|
);
|
|
286
295
|
|
|
287
296
|
const install = await choosePackageInstall();
|
|
288
|
-
const answer = (
|
|
289
|
-
|
|
290
|
-
`Vercel CLI is not installed. Install with ${install.command} ${install.args.join(" ")}?`,
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
)
|
|
297
|
+
const answer = await promptResult(
|
|
298
|
+
p.confirm({
|
|
299
|
+
message: `Vercel CLI is not installed. Install with ${install.command} ${install.args.join(" ")}?`,
|
|
300
|
+
initialValue: false,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
294
303
|
|
|
295
|
-
if (answer
|
|
296
|
-
throw new Error("Vercel CLI is required. Exiting.");
|
|
304
|
+
if (!answer) throw new Error("Vercel CLI is required. Exiting.");
|
|
297
305
|
|
|
298
306
|
step(`Installing Vercel CLI with ${install.command}`);
|
|
299
307
|
await runNoUrl(install.command, install.args);
|
|
@@ -330,6 +338,7 @@ async function doctor() {
|
|
|
330
338
|
printKeyValue("scope", defaults.scope || "not set");
|
|
331
339
|
printKeyValue("source image", defaults.from || defaultWsShellImage);
|
|
332
340
|
printKeyValue("shell", defaults.shell || "/bin/sh");
|
|
341
|
+
printKeyValue("tools", defaults.tools || "none");
|
|
333
342
|
printKeyValue("auth mode", defaults["auth-mode"] || "not set");
|
|
334
343
|
printKeyValue(
|
|
335
344
|
"auth user",
|
|
@@ -353,43 +362,98 @@ async function doctor() {
|
|
|
353
362
|
);
|
|
354
363
|
}
|
|
355
364
|
|
|
356
|
-
function makeDockerfile({ shell, vmImage, vmImageName, wsShellImage }) {
|
|
365
|
+
function makeDockerfile({ shell, tools, vmImage, vmImageName, wsShellImage }) {
|
|
357
366
|
const shellInstall = makeShellInstall({ shell, vmImageName });
|
|
367
|
+
const ohMyZshInstall =
|
|
368
|
+
path.basename(shell) === "zsh"
|
|
369
|
+
? `RUN RUNZSH=no CHSH=no KEEP_ZSHRC=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \\
|
|
370
|
+
&& printf '%s\\n' \\
|
|
371
|
+
'export ZSH="$HOME/.oh-my-zsh"' \\
|
|
372
|
+
'ZSH_THEME="robbyrussell"' \\
|
|
373
|
+
'plugins=(git)' \\
|
|
374
|
+
'source "$ZSH/oh-my-zsh.sh"' \\
|
|
375
|
+
> /root/.zshrc`
|
|
376
|
+
: "";
|
|
377
|
+
const toolInstall = makeToolInstall({ tools, vmImageName });
|
|
378
|
+
const shellSetup = [shellInstall, ohMyZshInstall, toolInstall]
|
|
379
|
+
.filter(Boolean)
|
|
380
|
+
.join("\n");
|
|
358
381
|
return `ARG WS_SHELL_IMAGE=${wsShellImage}
|
|
359
382
|
ARG VM_IMAGE=${vmImage}
|
|
360
383
|
|
|
361
384
|
FROM \${WS_SHELL_IMAGE} AS ws-shell
|
|
362
385
|
FROM \${VM_IMAGE} AS vm
|
|
363
|
-
${
|
|
386
|
+
${shellSetup ? `\n${shellSetup}\n` : ""}
|
|
364
387
|
# wsterm already embeds the web UI; runtime config comes from environment variables.
|
|
365
388
|
COPY --from=ws-shell /app/bin/wsterm /app/bin/wsterm
|
|
366
389
|
|
|
367
390
|
WORKDIR /app
|
|
368
391
|
ENV ENABLE_SSL=false
|
|
392
|
+
ENV HOME=/root
|
|
393
|
+
ENV SHELL=${shell}
|
|
369
394
|
EXPOSE 80
|
|
370
395
|
CMD ["/app/bin/wsterm","-bind",":80","-fork","${shell}"]
|
|
371
396
|
`;
|
|
372
397
|
}
|
|
373
398
|
|
|
399
|
+
function makeToolInstall({ tools, vmImageName }) {
|
|
400
|
+
const selected = new Set(parseTools(tools));
|
|
401
|
+
if (selected.has("codex") || selected.has("claude-code"))
|
|
402
|
+
selected.add("nodejs");
|
|
403
|
+
if (!selected.size) return "";
|
|
404
|
+
|
|
405
|
+
const packages = [];
|
|
406
|
+
if (selected.has("nodejs")) packages.push("nodejs", "npm");
|
|
407
|
+
const npmPackages = [];
|
|
408
|
+
if (selected.has("codex")) npmPackages.push("@openai/codex");
|
|
409
|
+
if (selected.has("claude-code")) npmPackages.push("@anthropic-ai/claude-code");
|
|
410
|
+
|
|
411
|
+
const installNode =
|
|
412
|
+
packages.length && vmImageName === "alpine"
|
|
413
|
+
? `RUN apk add --no-cache ${packages.join(" ")}`
|
|
414
|
+
: packages.length && (vmImageName === "ubuntu" || vmImageName === "debian")
|
|
415
|
+
? `RUN apt-get update \\
|
|
416
|
+
&& apt-get install -y --no-install-recommends ${packages.join(" ")} \\
|
|
417
|
+
&& rm -rf /var/lib/apt/lists/*`
|
|
418
|
+
: packages.length
|
|
419
|
+
? `RUN if command -v apk >/dev/null 2>&1; then \\
|
|
420
|
+
apk add --no-cache ${packages.join(" ")}; \\
|
|
421
|
+
elif command -v apt-get >/dev/null 2>&1; then \\
|
|
422
|
+
apt-get update \\
|
|
423
|
+
&& apt-get install -y --no-install-recommends ${packages.join(" ")} \\
|
|
424
|
+
&& rm -rf /var/lib/apt/lists/*; \\
|
|
425
|
+
else \\
|
|
426
|
+
echo "Cannot install nodejs/npm: unsupported VM image package manager" >&2; \\
|
|
427
|
+
exit 1; \\
|
|
428
|
+
fi`
|
|
429
|
+
: "";
|
|
430
|
+
|
|
431
|
+
const installCli = npmPackages.length
|
|
432
|
+
? `RUN npm install -g ${npmPackages.join(" ")}`
|
|
433
|
+
: "";
|
|
434
|
+
return [installNode, installCli].filter(Boolean).join("\n");
|
|
435
|
+
}
|
|
436
|
+
|
|
374
437
|
function makeShellInstall({ shell, vmImageName }) {
|
|
375
438
|
const packageName = path.basename(shell);
|
|
376
439
|
if (packageName === "sh") return "";
|
|
440
|
+
const packages = packageName === "zsh" ? "zsh curl git" : packageName;
|
|
377
441
|
|
|
378
442
|
if (vmImageName === "alpine")
|
|
379
|
-
return `RUN apk add --no-cache ${
|
|
443
|
+
return `RUN apk add --no-cache ${packages}`;
|
|
380
444
|
|
|
381
445
|
if (vmImageName === "ubuntu" || vmImageName === "debian")
|
|
382
446
|
return `RUN apt-get update \\
|
|
383
|
-
&& apt-get install -y --no-install-recommends ${
|
|
447
|
+
&& apt-get install -y --no-install-recommends ${packages} \\
|
|
384
448
|
&& rm -rf /var/lib/apt/lists/*`;
|
|
385
449
|
|
|
386
450
|
return `RUN if command -v ${shell} >/dev/null 2>&1; then \\
|
|
387
451
|
true; \\
|
|
388
452
|
elif command -v apk >/dev/null 2>&1; then \\
|
|
389
|
-
apk add --no-cache ${
|
|
453
|
+
apk add --no-cache ${packages}; \\
|
|
390
454
|
elif command -v apt-get >/dev/null 2>&1; then \\
|
|
391
455
|
apt-get update \\
|
|
392
|
-
&& apt-get install -y --no-install-recommends ${
|
|
456
|
+
&& apt-get install -y --no-install-recommends ${packages} \\
|
|
393
457
|
&& rm -rf /var/lib/apt/lists/*; \\
|
|
394
458
|
else \\
|
|
395
459
|
echo "Cannot install ${packageName}: unsupported VM image package manager" >&2; \\
|
|
@@ -411,7 +475,7 @@ async function secret(name, question, fallback) {
|
|
|
411
475
|
if (!input.isTTY) return fallback || "";
|
|
412
476
|
|
|
413
477
|
const placeholder = fallback ? mask(fallback) : "skip";
|
|
414
|
-
const answer = await
|
|
478
|
+
const answer = await askSecret(question, placeholder);
|
|
415
479
|
return answer || fallback || "";
|
|
416
480
|
}
|
|
417
481
|
|
|
@@ -456,22 +520,18 @@ async function chooseAuthMode(fallback) {
|
|
|
456
520
|
}
|
|
457
521
|
if (!input.isTTY) return modes.has(fallback) ? fallback : "basic";
|
|
458
522
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (answer === "2") return "github";
|
|
472
|
-
if (answer === "3") return "both";
|
|
473
|
-
if (answer === "4") return "none";
|
|
474
|
-
throw new Error("Authentication must be basic, github, both, or none");
|
|
523
|
+
return promptResult(
|
|
524
|
+
p.select({
|
|
525
|
+
message: "Authentication",
|
|
526
|
+
initialValue: modes.has(fallback) ? fallback : "basic",
|
|
527
|
+
options: [
|
|
528
|
+
{ value: "basic", label: "Basic", hint: "username/password" },
|
|
529
|
+
{ value: "github", label: "GitHub OAuth" },
|
|
530
|
+
{ value: "both", label: "Basic + GitHub OAuth" },
|
|
531
|
+
{ value: "none", label: "None", hint: "no app auth" },
|
|
532
|
+
],
|
|
533
|
+
}),
|
|
534
|
+
);
|
|
475
535
|
}
|
|
476
536
|
|
|
477
537
|
function usesBasicAuth(mode) {
|
|
@@ -488,25 +548,23 @@ async function chooseVmImage(fallback) {
|
|
|
488
548
|
if (!input.isTTY) return fallback;
|
|
489
549
|
|
|
490
550
|
const names = Object.keys(vmImages);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
551
|
+
const answer = await promptResult(
|
|
552
|
+
p.select({
|
|
553
|
+
message: "VM image",
|
|
554
|
+
initialValue: vmImages[fallback] ? fallback : "custom",
|
|
555
|
+
options: names
|
|
556
|
+
.map((name) => ({ value: name, label: name, hint: vmImages[name] }))
|
|
557
|
+
.concat([
|
|
558
|
+
{ value: "custom", label: "Custom", hint: "enter a full image name" },
|
|
559
|
+
]),
|
|
560
|
+
}),
|
|
498
561
|
);
|
|
499
562
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (!answer) return fallback;
|
|
503
|
-
if (vmImages[answer]) return answer;
|
|
504
|
-
if (/^[1-3]$/.test(answer)) return names[Number(answer) - 1];
|
|
505
|
-
if (answer === "4")
|
|
563
|
+
if (answer === "custom")
|
|
506
564
|
return value(
|
|
507
565
|
"custom-vm-image",
|
|
508
566
|
"Custom VM image",
|
|
509
|
-
defaults["custom-vm-image"],
|
|
567
|
+
vmImages[fallback] ? defaults["custom-vm-image"] : fallback,
|
|
510
568
|
);
|
|
511
569
|
return answer;
|
|
512
570
|
}
|
|
@@ -515,21 +573,53 @@ async function chooseShell(fallback) {
|
|
|
515
573
|
if (args.shell) return args.shell;
|
|
516
574
|
if (!input.isTTY) return fallback;
|
|
517
575
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
shell,
|
|
523
|
-
|
|
524
|
-
|
|
576
|
+
return promptResult(
|
|
577
|
+
p.select({
|
|
578
|
+
message: "Shell",
|
|
579
|
+
initialValue: shells.includes(fallback) ? fallback : "/bin/sh",
|
|
580
|
+
options: shells.map((shell, index) => ({
|
|
581
|
+
value: shell,
|
|
582
|
+
label: shell,
|
|
583
|
+
hint: index === shells.length - 1 ? "default" : undefined,
|
|
584
|
+
})),
|
|
585
|
+
}),
|
|
525
586
|
);
|
|
587
|
+
}
|
|
526
588
|
|
|
527
|
-
|
|
589
|
+
async function chooseTools(fallback) {
|
|
590
|
+
if (args.tools !== undefined) return parseTools(args.tools).join(",");
|
|
591
|
+
if (!input.isTTY) return parseTools(fallback).join(",");
|
|
592
|
+
|
|
593
|
+
const names = Object.keys(toolChoices);
|
|
594
|
+
const selected = await promptResult(
|
|
595
|
+
p.multiselect({
|
|
596
|
+
message: "Preinstall tools",
|
|
597
|
+
required: false,
|
|
598
|
+
initialValues: parseTools(fallback),
|
|
599
|
+
options: names.map((name) => ({
|
|
600
|
+
value: name,
|
|
601
|
+
label: name,
|
|
602
|
+
hint: toolChoices[name],
|
|
603
|
+
})),
|
|
604
|
+
}),
|
|
605
|
+
);
|
|
528
606
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
607
|
+
return selected.join(",");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function parseTools(value) {
|
|
611
|
+
const names = Object.keys(toolChoices);
|
|
612
|
+
const selected = String(value || "")
|
|
613
|
+
.split(",")
|
|
614
|
+
.map((item) => item.trim())
|
|
615
|
+
.filter(Boolean)
|
|
616
|
+
.flatMap((item) => {
|
|
617
|
+
if (item === "0" || item === "none") return [];
|
|
618
|
+
if (/^[1-3]$/.test(item)) return [names[Number(item) - 1]];
|
|
619
|
+
return [item];
|
|
620
|
+
})
|
|
621
|
+
.filter((item) => names.includes(item));
|
|
622
|
+
return [...new Set(selected)];
|
|
533
623
|
}
|
|
534
624
|
|
|
535
625
|
async function readDefaults(file) {
|
|
@@ -689,6 +779,10 @@ function findLastUrl(text) {
|
|
|
689
779
|
}
|
|
690
780
|
|
|
691
781
|
function printHeader() {
|
|
782
|
+
if (input.isTTY) {
|
|
783
|
+
p.intro("Vercel VM Factory");
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
692
786
|
console.log("");
|
|
693
787
|
console.log(color.cyan(" __ ____ __ _____ _ "));
|
|
694
788
|
console.log(color.cyan(" \\ \\ / / \\/ | | ___|_ _ ___| |_ ___ _ __ _ _ "));
|
|
@@ -740,25 +834,35 @@ function printKeyValue(key, value) {
|
|
|
740
834
|
}
|
|
741
835
|
|
|
742
836
|
async function askText(question, fallback = "") {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
837
|
+
return String(
|
|
838
|
+
await promptResult(
|
|
839
|
+
p.text({
|
|
840
|
+
message: question,
|
|
841
|
+
placeholder: fallback || undefined,
|
|
842
|
+
}),
|
|
843
|
+
),
|
|
749
844
|
).trim();
|
|
750
|
-
rl.close();
|
|
751
|
-
return answer;
|
|
752
845
|
}
|
|
753
846
|
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
847
|
+
async function askSecret(question, fallback = "") {
|
|
848
|
+
return String(
|
|
849
|
+
await promptResult(
|
|
850
|
+
p.password({
|
|
851
|
+
message: question,
|
|
852
|
+
mask: "*",
|
|
853
|
+
placeholder: fallback || undefined,
|
|
854
|
+
}),
|
|
855
|
+
),
|
|
856
|
+
).trim();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function promptResult(resultPromise) {
|
|
860
|
+
const result = await resultPromise;
|
|
861
|
+
if (p.isCancel(result)) {
|
|
862
|
+
p.cancel("Operation cancelled.");
|
|
863
|
+
process.exit(0);
|
|
760
864
|
}
|
|
761
|
-
|
|
865
|
+
return result;
|
|
762
866
|
}
|
|
763
867
|
|
|
764
868
|
function step(text) {
|
|
@@ -792,6 +896,7 @@ Options:
|
|
|
792
896
|
--scope SLUG Optional Vercel team/user scope slug
|
|
793
897
|
--from IMAGE Source image for /app/bin/wsterm
|
|
794
898
|
--shell PATH /bin/bash, /bin/zsh, or /bin/sh
|
|
899
|
+
--tools LIST nodejs,codex,claude-code
|
|
795
900
|
--auth-mode MODE basic, github, both, or none
|
|
796
901
|
--auth-user VALUE Username/password auth user
|
|
797
902
|
--auth-password VAL Username/password auth password
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vercel-vm-factory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.8",
|
|
4
4
|
"description": "Create Vercel Container deployments for ws-shell from selectable VM images.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"deploy-vm.mjs",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@clack/prompts": "^0.11.0"
|
|
17
|
+
},
|
|
15
18
|
"scripts": {
|
|
16
19
|
"deploy": "node deploy-vm.mjs create",
|
|
17
20
|
"doctor": "node deploy-vm.mjs doctor",
|