vercel-vm-factory 0.9.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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/deploy-vm.mjs +93 -79
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -78,10 +78,12 @@ Choosing bash or zsh adds the matching package to the generated Dockerfile when
78
78
 
79
79
  Generated shell setup examples:
80
80
 
81
- - Alpine + `/bin/zsh`: installs `zsh curl git`, then installs oh-my-zsh unattended.
81
+ - Alpine + `/bin/zsh`: installs `zsh curl git`, installs oh-my-zsh unattended, and writes `/root/.zshrc`.
82
82
  - Ubuntu/Debian + `/bin/bash`: installs `bash` with `apt-get`.
83
83
  - `/bin/sh`: no extra shell package is installed.
84
84
 
85
+ The generated Dockerfile sets `HOME=/root` and `SHELL` to the selected shell path.
86
+
85
87
  Preinstall tools:
86
88
 
87
89
  - `nodejs`
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
 
@@ -294,15 +294,14 @@ async function installVercelCli() {
294
294
  );
295
295
 
296
296
  const install = await choosePackageInstall();
297
- const answer = (
298
- await askText(
299
- `Vercel CLI is not installed. Install with ${install.command} ${install.args.join(" ")}?`,
300
- "N",
301
- )
302
- ).toLowerCase();
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
+ );
303
303
 
304
- if (answer !== "y" && answer !== "yes")
305
- throw new Error("Vercel CLI is required. Exiting.");
304
+ if (!answer) throw new Error("Vercel CLI is required. Exiting.");
306
305
 
307
306
  step(`Installing Vercel CLI with ${install.command}`);
308
307
  await runNoUrl(install.command, install.args);
@@ -367,7 +366,13 @@ function makeDockerfile({ shell, tools, vmImage, vmImageName, wsShellImage }) {
367
366
  const shellInstall = makeShellInstall({ shell, vmImageName });
368
367
  const ohMyZshInstall =
369
368
  path.basename(shell) === "zsh"
370
- ? `RUN RUNZSH=no CHSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended`
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`
371
376
  : "";
372
377
  const toolInstall = makeToolInstall({ tools, vmImageName });
373
378
  const shellSetup = [shellInstall, ohMyZshInstall, toolInstall]
@@ -384,6 +389,8 @@ COPY --from=ws-shell /app/bin/wsterm /app/bin/wsterm
384
389
 
385
390
  WORKDIR /app
386
391
  ENV ENABLE_SSL=false
392
+ ENV HOME=/root
393
+ ENV SHELL=${shell}
387
394
  EXPOSE 80
388
395
  CMD ["/app/bin/wsterm","-bind",":80","-fork","${shell}"]
389
396
  `;
@@ -468,7 +475,7 @@ async function secret(name, question, fallback) {
468
475
  if (!input.isTTY) return fallback || "";
469
476
 
470
477
  const placeholder = fallback ? mask(fallback) : "skip";
471
- const answer = await askText(question, placeholder);
478
+ const answer = await askSecret(question, placeholder);
472
479
  return answer || fallback || "";
473
480
  }
474
481
 
@@ -513,22 +520,18 @@ async function chooseAuthMode(fallback) {
513
520
  }
514
521
  if (!input.isTTY) return modes.has(fallback) ? fallback : "basic";
515
522
 
516
- printChoices("Authentication", [
517
- ["1", "basic", "username/password"],
518
- ["2", "github", "GitHub OAuth"],
519
- ["3", "both", "basic + GitHub OAuth"],
520
- ["4", "none", "no app auth"],
521
- ]);
522
-
523
- const answer = await askText("Authentication", fallback);
524
-
525
- if (!answer) return modes.has(fallback) ? fallback : "basic";
526
- if (modes.has(answer)) return answer;
527
- if (answer === "1") return "basic";
528
- if (answer === "2") return "github";
529
- if (answer === "3") return "both";
530
- if (answer === "4") return "none";
531
- 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
+ );
532
535
  }
533
536
 
534
537
  function usesBasicAuth(mode) {
@@ -545,25 +548,23 @@ async function chooseVmImage(fallback) {
545
548
  if (!input.isTTY) return fallback;
546
549
 
547
550
  const names = Object.keys(vmImages);
548
- printChoices(
549
- "VM image",
550
- names.map((name, index) => [
551
- String(index + 1),
552
- name,
553
- vmImages[name],
554
- ]).concat([["4", "custom", "enter a full image name"]]),
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
+ }),
555
561
  );
556
562
 
557
- const answer = await askText("VM image", fallback);
558
-
559
- if (!answer) return fallback;
560
- if (vmImages[answer]) return answer;
561
- if (/^[1-3]$/.test(answer)) return names[Number(answer) - 1];
562
- if (answer === "4")
563
+ if (answer === "custom")
563
564
  return value(
564
565
  "custom-vm-image",
565
566
  "Custom VM image",
566
- defaults["custom-vm-image"],
567
+ vmImages[fallback] ? defaults["custom-vm-image"] : fallback,
567
568
  );
568
569
  return answer;
569
570
  }
@@ -572,21 +573,17 @@ async function chooseShell(fallback) {
572
573
  if (args.shell) return args.shell;
573
574
  if (!input.isTTY) return fallback;
574
575
 
575
- printChoices(
576
- "Shell",
577
- shells.map((shell, index) => [
578
- String(index + 1),
579
- shell,
580
- index === shells.length - 1 ? "default" : "",
581
- ]),
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
+ }),
582
586
  );
583
-
584
- const answer = await askText("Shell", fallback);
585
-
586
- if (!answer) return fallback;
587
- if (shells.includes(answer)) return answer;
588
- if (/^[1-3]$/.test(answer)) return shells[Number(answer) - 1];
589
- return answer;
590
587
  }
591
588
 
592
589
  async function chooseTools(fallback) {
@@ -594,17 +591,20 @@ async function chooseTools(fallback) {
594
591
  if (!input.isTTY) return parseTools(fallback).join(",");
595
592
 
596
593
  const names = Object.keys(toolChoices);
597
- printChoices(
598
- "Preinstall tools",
599
- names.map((name, index) => [
600
- String(index + 1),
601
- name,
602
- toolChoices[name],
603
- ]).concat([["0", "none", "default"]]),
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
+ }),
604
605
  );
605
606
 
606
- const answer = await askText("Tools (comma separated)", fallback || "none");
607
- return parseTools(answer || fallback).join(",");
607
+ return selected.join(",");
608
608
  }
609
609
 
610
610
  function parseTools(value) {
@@ -779,6 +779,10 @@ function findLastUrl(text) {
779
779
  }
780
780
 
781
781
  function printHeader() {
782
+ if (input.isTTY) {
783
+ p.intro("Vercel VM Factory");
784
+ return;
785
+ }
782
786
  console.log("");
783
787
  console.log(color.cyan(" __ ____ __ _____ _ "));
784
788
  console.log(color.cyan(" \\ \\ / / \\/ | | ___|_ _ ___| |_ ___ _ __ _ _ "));
@@ -830,25 +834,35 @@ function printKeyValue(key, value) {
830
834
  }
831
835
 
832
836
  async function askText(question, fallback = "") {
833
- const suffix = fallback ? color.dim(` [${fallback}]`) : "";
834
- const rl = createInterface({ input, output });
835
- const answer = (
836
- await rl.question(
837
- `${color.cyan("?")} ${color.bold(question)}${suffix}\n${color.dim("> ")} `,
838
- )
837
+ return String(
838
+ await promptResult(
839
+ p.text({
840
+ message: question,
841
+ placeholder: fallback || undefined,
842
+ }),
843
+ ),
839
844
  ).trim();
840
- rl.close();
841
- return answer;
842
845
  }
843
846
 
844
- function printChoices(title, choices) {
845
- console.log("");
846
- console.log(color.bold(title));
847
- for (const [key, label, detail] of choices) {
848
- const hint = detail ? ` ${color.dim(detail)}` : "";
849
- console.log(` ${color.cyan(key)} ${label}${hint}`);
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);
850
864
  }
851
- console.log("");
865
+ return result;
852
866
  }
853
867
 
854
868
  function step(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vercel-vm-factory",
3
- "version": "0.9.8",
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",