vercel-vm-factory 0.6.8 → 0.7.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 +7 -0
- package/deploy-vm.mjs +99 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Create a tiny Vercel Container deployment: copy `wsterm` from `ghcr.io/v1xingyue
|
|
|
5
5
|
```bash
|
|
6
6
|
npx vercel-vm-factory create \
|
|
7
7
|
--vm-image ubuntu \
|
|
8
|
+
--shell /bin/bash \
|
|
8
9
|
--project ws-shell-ubuntu \
|
|
9
10
|
--auth-mode basic \
|
|
10
11
|
--auth-user admin \
|
|
@@ -56,6 +57,12 @@ VM image presets:
|
|
|
56
57
|
- `ubuntu` -> `ubuntu:24.04`
|
|
57
58
|
- `debian` -> `debian:13-slim`
|
|
58
59
|
|
|
60
|
+
Shell options:
|
|
61
|
+
|
|
62
|
+
- `/bin/bash`
|
|
63
|
+
- `/bin/zsh`
|
|
64
|
+
- `/bin/sh`
|
|
65
|
+
|
|
59
66
|
Custom VM image:
|
|
60
67
|
|
|
61
68
|
```bash
|
package/deploy-vm.mjs
CHANGED
|
@@ -13,6 +13,7 @@ const vmImages = {
|
|
|
13
13
|
ubuntu: "ubuntu:24.04",
|
|
14
14
|
debian: "debian:13-slim",
|
|
15
15
|
};
|
|
16
|
+
const shells = ["/bin/bash", "/bin/zsh", "/bin/sh"];
|
|
16
17
|
|
|
17
18
|
const { command, args } = parseCommand(process.argv.slice(2));
|
|
18
19
|
const scriptRoot = path.resolve(import.meta.dirname);
|
|
@@ -20,7 +21,11 @@ const workspaceRoot = process.cwd();
|
|
|
20
21
|
const stateRoot = path.join(homedir(), ".vercel-vm-factory");
|
|
21
22
|
const defaultsPath = path.join(stateRoot, "defaults.json");
|
|
22
23
|
const legacyDefaultsPath = path.join(scriptRoot, ".defaults.json");
|
|
23
|
-
const codeDefaults = {
|
|
24
|
+
const codeDefaults = {
|
|
25
|
+
"vm-image": "alpine",
|
|
26
|
+
from: defaultWsShellImage,
|
|
27
|
+
shell: "/bin/sh",
|
|
28
|
+
};
|
|
24
29
|
const packagedDefaults = {
|
|
25
30
|
...(await readDefaults(legacyDefaultsPath)),
|
|
26
31
|
...codeDefaults,
|
|
@@ -90,6 +95,7 @@ async function main() {
|
|
|
90
95
|
process.env.WS_SHELL_IMAGE ??
|
|
91
96
|
defaults.from ??
|
|
92
97
|
defaultWsShellImage;
|
|
98
|
+
const shell = await chooseShell(args.shell ?? defaults.shell ?? "/bin/sh");
|
|
93
99
|
const prod = args.prod !== "false";
|
|
94
100
|
const dryRun = Boolean(args["dry-run"]);
|
|
95
101
|
const skipLink = Boolean(args["skip-link"]);
|
|
@@ -107,7 +113,7 @@ async function main() {
|
|
|
107
113
|
".generated",
|
|
108
114
|
project,
|
|
109
115
|
);
|
|
110
|
-
const dockerfile = makeDockerfile({ vmImage, wsShellImage });
|
|
116
|
+
const dockerfile = makeDockerfile({ shell, vmImage, wsShellImage });
|
|
111
117
|
|
|
112
118
|
await rm(appDir, { recursive: true, force: true });
|
|
113
119
|
await mkdir(appDir, { recursive: true });
|
|
@@ -118,6 +124,7 @@ async function main() {
|
|
|
118
124
|
project,
|
|
119
125
|
scope: scope || "default",
|
|
120
126
|
source: wsShellImage,
|
|
127
|
+
shell,
|
|
121
128
|
auth: authMode,
|
|
122
129
|
...(usesGitHubAuth(authMode) ? { callback: oauthRedirectUrl } : {}),
|
|
123
130
|
dockerfile: path.join(appDir, "Dockerfile.vercel"),
|
|
@@ -173,6 +180,7 @@ async function main() {
|
|
|
173
180
|
scope: scope || undefined,
|
|
174
181
|
project,
|
|
175
182
|
from: wsShellImage,
|
|
183
|
+
shell,
|
|
176
184
|
"auth-mode": authMode,
|
|
177
185
|
"auth-user": authUsername,
|
|
178
186
|
"auth-password": authPassword,
|
|
@@ -272,15 +280,12 @@ async function installVercelCli() {
|
|
|
272
280
|
);
|
|
273
281
|
|
|
274
282
|
const install = await choosePackageInstall();
|
|
275
|
-
const rl = createInterface({ input, output });
|
|
276
283
|
const answer = (
|
|
277
|
-
await
|
|
278
|
-
`Vercel CLI is not installed. Install
|
|
284
|
+
await askText(
|
|
285
|
+
`Vercel CLI is not installed. Install with ${install.command} ${install.args.join(" ")}?`,
|
|
286
|
+
"N",
|
|
279
287
|
)
|
|
280
|
-
)
|
|
281
|
-
.trim()
|
|
282
|
-
.toLowerCase();
|
|
283
|
-
rl.close();
|
|
288
|
+
).toLowerCase();
|
|
284
289
|
|
|
285
290
|
if (answer !== "y" && answer !== "yes")
|
|
286
291
|
throw new Error("Vercel CLI is required. Exiting.");
|
|
@@ -319,6 +324,7 @@ async function doctor() {
|
|
|
319
324
|
printKeyValue("project", defaults.project || "not set");
|
|
320
325
|
printKeyValue("scope", defaults.scope || "not set");
|
|
321
326
|
printKeyValue("source image", defaults.from || defaultWsShellImage);
|
|
327
|
+
printKeyValue("shell", defaults.shell || "/bin/sh");
|
|
322
328
|
printKeyValue("auth mode", defaults["auth-mode"] || "not set");
|
|
323
329
|
printKeyValue(
|
|
324
330
|
"auth user",
|
|
@@ -342,7 +348,7 @@ async function doctor() {
|
|
|
342
348
|
);
|
|
343
349
|
}
|
|
344
350
|
|
|
345
|
-
function makeDockerfile({ vmImage, wsShellImage }) {
|
|
351
|
+
function makeDockerfile({ shell, vmImage, wsShellImage }) {
|
|
346
352
|
return `ARG WS_SHELL_IMAGE=${wsShellImage}
|
|
347
353
|
ARG VM_IMAGE=${vmImage}
|
|
348
354
|
|
|
@@ -355,7 +361,7 @@ COPY --from=ws-shell /app/bin/wsterm /app/bin/wsterm
|
|
|
355
361
|
WORKDIR /app
|
|
356
362
|
ENV ENABLE_SSL=false
|
|
357
363
|
EXPOSE 80
|
|
358
|
-
CMD ["/app/bin/wsterm","-bind",":80","-fork","
|
|
364
|
+
CMD ["/app/bin/wsterm","-bind",":80","-fork","${shell}"]
|
|
359
365
|
`;
|
|
360
366
|
}
|
|
361
367
|
|
|
@@ -364,11 +370,7 @@ async function value(name, question, fallback) {
|
|
|
364
370
|
if (args[name]) return args[name];
|
|
365
371
|
if (!input.isTTY) return current || "";
|
|
366
372
|
|
|
367
|
-
const
|
|
368
|
-
const answer = (
|
|
369
|
-
await rl.question(`${question}${fallback ? ` [${fallback}]` : ""}: `)
|
|
370
|
-
).trim();
|
|
371
|
-
rl.close();
|
|
373
|
+
const answer = await askText(question, fallback);
|
|
372
374
|
return answer || current || "";
|
|
373
375
|
}
|
|
374
376
|
|
|
@@ -376,10 +378,8 @@ async function secret(name, question, fallback) {
|
|
|
376
378
|
if (args[name]) return args[name];
|
|
377
379
|
if (!input.isTTY) return fallback || "";
|
|
378
380
|
|
|
379
|
-
const rl = createInterface({ input, output });
|
|
380
381
|
const placeholder = fallback ? mask(fallback) : "skip";
|
|
381
|
-
const answer =
|
|
382
|
-
rl.close();
|
|
382
|
+
const answer = await askText(question, placeholder);
|
|
383
383
|
return answer || fallback || "";
|
|
384
384
|
}
|
|
385
385
|
|
|
@@ -387,10 +387,10 @@ async function optionalValue(name, question, fallback) {
|
|
|
387
387
|
if (args[name] !== undefined) return args[name];
|
|
388
388
|
if (!input.isTTY) return "";
|
|
389
389
|
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
const answer = await askText(
|
|
391
|
+
question,
|
|
392
|
+
fallback ? `${fallback}; Enter to skip` : "skip",
|
|
393
|
+
);
|
|
394
394
|
return answer;
|
|
395
395
|
}
|
|
396
396
|
|
|
@@ -424,15 +424,14 @@ async function chooseAuthMode(fallback) {
|
|
|
424
424
|
}
|
|
425
425
|
if (!input.isTTY) return modes.has(fallback) ? fallback : "basic";
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
427
|
+
printChoices("Authentication", [
|
|
428
|
+
["1", "basic", "username/password"],
|
|
429
|
+
["2", "github", "GitHub OAuth"],
|
|
430
|
+
["3", "both", "basic + GitHub OAuth"],
|
|
431
|
+
["4", "none", "no app auth"],
|
|
432
|
+
]);
|
|
432
433
|
|
|
433
|
-
const
|
|
434
|
-
const answer = (await rl.question(`Authentication [${fallback}]: `)).trim();
|
|
435
|
-
rl.close();
|
|
434
|
+
const answer = await askText("Authentication", fallback);
|
|
436
435
|
|
|
437
436
|
if (!answer) return modes.has(fallback) ? fallback : "basic";
|
|
438
437
|
if (modes.has(answer)) return answer;
|
|
@@ -457,17 +456,16 @@ async function chooseVmImage(fallback) {
|
|
|
457
456
|
if (!input.isTTY) return fallback;
|
|
458
457
|
|
|
459
458
|
const names = Object.keys(vmImages);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
459
|
+
printChoices(
|
|
460
|
+
"VM image",
|
|
461
|
+
names.map((name, index) => [
|
|
462
|
+
String(index + 1),
|
|
463
|
+
name,
|
|
464
|
+
vmImages[name],
|
|
465
|
+
]).concat([["4", "custom", "enter a full image name"]]),
|
|
465
466
|
);
|
|
466
|
-
console.log(` ${color.cyan("4")}. custom image`);
|
|
467
467
|
|
|
468
|
-
const
|
|
469
|
-
const answer = (await rl.question(`VM image [${fallback}]: `)).trim();
|
|
470
|
-
rl.close();
|
|
468
|
+
const answer = await askText("VM image", fallback);
|
|
471
469
|
|
|
472
470
|
if (!answer) return fallback;
|
|
473
471
|
if (vmImages[answer]) return answer;
|
|
@@ -481,6 +479,27 @@ async function chooseVmImage(fallback) {
|
|
|
481
479
|
return answer;
|
|
482
480
|
}
|
|
483
481
|
|
|
482
|
+
async function chooseShell(fallback) {
|
|
483
|
+
if (args.shell) return args.shell;
|
|
484
|
+
if (!input.isTTY) return fallback;
|
|
485
|
+
|
|
486
|
+
printChoices(
|
|
487
|
+
"Shell",
|
|
488
|
+
shells.map((shell, index) => [
|
|
489
|
+
String(index + 1),
|
|
490
|
+
shell,
|
|
491
|
+
index === shells.length - 1 ? "default" : "",
|
|
492
|
+
]),
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
const answer = await askText("Shell", fallback);
|
|
496
|
+
|
|
497
|
+
if (!answer) return fallback;
|
|
498
|
+
if (shells.includes(answer)) return answer;
|
|
499
|
+
if (/^[1-3]$/.test(answer)) return shells[Number(answer) - 1];
|
|
500
|
+
return answer;
|
|
501
|
+
}
|
|
502
|
+
|
|
484
503
|
async function readDefaults(file) {
|
|
485
504
|
try {
|
|
486
505
|
return JSON.parse(await readFile(file, "utf8"));
|
|
@@ -638,18 +657,30 @@ function findLastUrl(text) {
|
|
|
638
657
|
}
|
|
639
658
|
|
|
640
659
|
function printHeader() {
|
|
641
|
-
console.log(
|
|
642
|
-
console.log(
|
|
643
|
-
|
|
644
|
-
);
|
|
660
|
+
console.log("");
|
|
661
|
+
console.log(color.cyan(" __ ____ __ _____ _ "));
|
|
662
|
+
console.log(color.cyan(" \\ \\ / / \\/ | | ___|_ _ ___| |_ ___ _ __ _ _ "));
|
|
663
|
+
console.log(color.cyan(" \\ \\ / /| |\\/| | | |_ / _` |/ __| __/ _ \\| '__| | | |"));
|
|
664
|
+
console.log(color.cyan(" \\ V / | | | | | _| (_| | (__| || (_) | | | |_| |"));
|
|
665
|
+
console.log(color.cyan(" \\_/ |_| |_| |_| \\__,_|\\___|\\__\\___/|_| \\__, |"));
|
|
666
|
+
console.log(color.cyan(" |___/ "));
|
|
667
|
+
console.log(color.dim(" Vercel Container VM deployment helper"));
|
|
645
668
|
console.log("");
|
|
646
669
|
}
|
|
647
670
|
|
|
648
671
|
function printSummary(items) {
|
|
672
|
+
const entries = Object.entries(items);
|
|
673
|
+
const keyWidth = Math.max(...entries.map(([key]) => key.length), 1);
|
|
674
|
+
const valueWidth = Math.max(...entries.map(([, value]) => String(value).length), 1);
|
|
675
|
+
const width = keyWidth + valueWidth + 5;
|
|
676
|
+
|
|
649
677
|
console.log(color.bold("Deployment plan"));
|
|
678
|
+
console.log(color.cyan(`+${"-".repeat(width)}+`));
|
|
650
679
|
for (const [key, value] of Object.entries(items)) {
|
|
651
|
-
|
|
680
|
+
const line = `${key.padEnd(keyWidth)} ${String(value).padEnd(valueWidth)}`;
|
|
681
|
+
console.log(`${color.cyan("|")} ${line} ${color.cyan("|")}`);
|
|
652
682
|
}
|
|
683
|
+
console.log(color.cyan(`+${"-".repeat(width)}+`));
|
|
653
684
|
console.log("");
|
|
654
685
|
}
|
|
655
686
|
|
|
@@ -676,6 +707,28 @@ function printKeyValue(key, value) {
|
|
|
676
707
|
console.log(`${color.dim(`${key.padEnd(14)} `)}${value}`);
|
|
677
708
|
}
|
|
678
709
|
|
|
710
|
+
async function askText(question, fallback = "") {
|
|
711
|
+
const suffix = fallback ? color.dim(` [${fallback}]`) : "";
|
|
712
|
+
const rl = createInterface({ input, output });
|
|
713
|
+
const answer = (
|
|
714
|
+
await rl.question(
|
|
715
|
+
`${color.cyan("?")} ${color.bold(question)}${suffix}\n${color.dim("> ")} `,
|
|
716
|
+
)
|
|
717
|
+
).trim();
|
|
718
|
+
rl.close();
|
|
719
|
+
return answer;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function printChoices(title, choices) {
|
|
723
|
+
console.log("");
|
|
724
|
+
console.log(color.bold(title));
|
|
725
|
+
for (const [key, label, detail] of choices) {
|
|
726
|
+
const hint = detail ? ` ${color.dim(detail)}` : "";
|
|
727
|
+
console.log(` ${color.cyan(key)} ${label}${hint}`);
|
|
728
|
+
}
|
|
729
|
+
console.log("");
|
|
730
|
+
}
|
|
731
|
+
|
|
679
732
|
function step(text) {
|
|
680
733
|
console.log(`${color.cyan("->")} ${text}`);
|
|
681
734
|
}
|
|
@@ -706,6 +759,7 @@ Options:
|
|
|
706
759
|
--project NAME Vercel project name
|
|
707
760
|
--scope SLUG Optional Vercel team/user scope slug
|
|
708
761
|
--from IMAGE Source image for /app/bin/wsterm
|
|
762
|
+
--shell PATH /bin/bash, /bin/zsh, or /bin/sh
|
|
709
763
|
--auth-mode MODE basic, github, both, or none
|
|
710
764
|
--auth-user VALUE Username/password auth user
|
|
711
765
|
--auth-password VAL Username/password auth password
|