vercel-vm-factory 0.15.8 → 0.17.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 +3 -3
- package/config.mjs +21 -0
- package/deploy-vm.mjs +86 -60
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Run without flags for prompts:
|
|
|
29
29
|
npx vercel-vm-factory create
|
|
30
30
|
```
|
|
31
31
|
|
|
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
|
+
The prompt walks through VM image, Vercel team, project, shell, optional preinstalled tools, and authentication. Teams are loaded from `vercel teams list --format json`; choose `Default` to use the current Vercel CLI scope. For list prompts, enter either names or numbers; tool choices can be comma-separated, for example `1,3` or `nodejs,claude-code`.
|
|
33
33
|
|
|
34
34
|
Check local setup:
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@ Common flags:
|
|
|
47
47
|
- `--shell /bin/bash|/bin/zsh|/bin/sh`
|
|
48
48
|
- `--tools nodejs,codex,claude-code`
|
|
49
49
|
- `--project NAME`
|
|
50
|
-
- `--scope TEAM_SLUG`
|
|
50
|
+
- `--scope TEAM_SLUG` to skip team selection
|
|
51
51
|
- `--auth-mode basic|github|both|none`
|
|
52
52
|
- `--dry-run`
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ The generated project contains only `Dockerfile.vercel`.
|
|
|
57
57
|
|
|
58
58
|
CLI mapping:
|
|
59
59
|
|
|
60
|
-
- Vercel Team ->
|
|
60
|
+
- Vercel Team -> loaded from `vercel teams list --format json`; `--scope TEAM_SLUG` skips the selector
|
|
61
61
|
- Project Name -> `--project x-shell`
|
|
62
62
|
- Application Preset -> patched through Vercel API as `framework=container`
|
|
63
63
|
- Root Directory -> generated project directory
|
package/config.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const defaultWsShellImage = "ghcr.io/v1xingyue/ws-shell:v1.8.alpine";
|
|
2
|
+
|
|
3
|
+
export const vmImages = {
|
|
4
|
+
alpine: "alpine:3.23",
|
|
5
|
+
ubuntu: "ubuntu:24.04",
|
|
6
|
+
debian: "debian:13-slim",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const shells = ["/bin/bash", "/bin/zsh", "/bin/sh"];
|
|
10
|
+
|
|
11
|
+
export const toolChoices = {
|
|
12
|
+
nodejs: "Node.js + npm",
|
|
13
|
+
codex: "OpenAI Codex CLI",
|
|
14
|
+
"claude-code": "Claude Code",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const codeDefaults = {
|
|
18
|
+
"vm-image": "alpine",
|
|
19
|
+
from: defaultWsShellImage,
|
|
20
|
+
shell: "/bin/sh",
|
|
21
|
+
};
|
package/deploy-vm.mjs
CHANGED
|
@@ -6,32 +6,21 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import * as p from "@clack/prompts";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
const shells = ["/bin/bash", "/bin/zsh", "/bin/sh"];
|
|
18
|
-
const toolChoices = {
|
|
19
|
-
nodejs: "Node.js + npm",
|
|
20
|
-
codex: "OpenAI Codex CLI",
|
|
21
|
-
"claude-code": "Claude Code",
|
|
22
|
-
};
|
|
9
|
+
import {
|
|
10
|
+
codeDefaults,
|
|
11
|
+
defaultWsShellImage,
|
|
12
|
+
shells,
|
|
13
|
+
toolChoices,
|
|
14
|
+
vmImages,
|
|
15
|
+
} from "./config.mjs";
|
|
23
16
|
|
|
24
17
|
const { command, args } = parseCommand(process.argv.slice(2));
|
|
25
|
-
const
|
|
18
|
+
const scriptFile = fileURLToPath(import.meta.url);
|
|
19
|
+
const scriptRoot = path.dirname(scriptFile);
|
|
26
20
|
const workspaceRoot = process.cwd();
|
|
27
21
|
const stateRoot = path.join(homedir(), ".vercel-vm-factory");
|
|
28
22
|
const defaultsPath = path.join(stateRoot, "defaults.json");
|
|
29
23
|
const legacyDefaultsPath = path.join(scriptRoot, ".defaults.json");
|
|
30
|
-
const codeDefaults = {
|
|
31
|
-
"vm-image": "alpine",
|
|
32
|
-
from: defaultWsShellImage,
|
|
33
|
-
shell: "/bin/sh",
|
|
34
|
-
};
|
|
35
24
|
const packagedDefaults = {
|
|
36
25
|
...(await readDefaults(legacyDefaultsPath)),
|
|
37
26
|
...codeDefaults,
|
|
@@ -86,11 +75,7 @@ async function main() {
|
|
|
86
75
|
args["vm-image"] ?? args.base ?? defaults["vm-image"] ?? "alpine",
|
|
87
76
|
);
|
|
88
77
|
const vmImage = vmImages[vmImageName] ?? vmImageName;
|
|
89
|
-
const scope = await
|
|
90
|
-
"scope",
|
|
91
|
-
"Vercel team/scope",
|
|
92
|
-
defaults.scope,
|
|
93
|
-
);
|
|
78
|
+
const scope = await chooseVercelScope(defaults.scope);
|
|
94
79
|
const project = await value(
|
|
95
80
|
"project",
|
|
96
81
|
"Vercel project name",
|
|
@@ -153,7 +138,7 @@ async function main() {
|
|
|
153
138
|
if (usesGitHubAuth(authMode)) printOAuthGuide(oauthRedirectUrl);
|
|
154
139
|
|
|
155
140
|
const authUsername = usesBasicAuth(authMode)
|
|
156
|
-
? await
|
|
141
|
+
? await value(
|
|
157
142
|
"auth-user",
|
|
158
143
|
"Basic auth username",
|
|
159
144
|
process.env.AUTH_USERNAME ?? defaults["auth-user"],
|
|
@@ -355,10 +340,7 @@ async function doctor() {
|
|
|
355
340
|
printKeyValue("shell", defaults.shell || "/bin/sh");
|
|
356
341
|
printKeyValue("tools", defaults.tools || "none");
|
|
357
342
|
printKeyValue("auth mode", defaults["auth-mode"] || "not set");
|
|
358
|
-
printKeyValue(
|
|
359
|
-
"auth user",
|
|
360
|
-
defaults["auth-user"] ? mask(defaults["auth-user"]) : "not set",
|
|
361
|
-
);
|
|
343
|
+
printKeyValue("auth user", defaults["auth-user"] || "not set");
|
|
362
344
|
printKeyValue(
|
|
363
345
|
"auth password",
|
|
364
346
|
defaults["auth-password"] ? mask(defaults["auth-password"]) : "not set",
|
|
@@ -494,17 +476,6 @@ async function secret(name, question, fallback) {
|
|
|
494
476
|
return answer || fallback || "";
|
|
495
477
|
}
|
|
496
478
|
|
|
497
|
-
async function optionalValue(name, question, fallback) {
|
|
498
|
-
if (args[name] !== undefined) return args[name];
|
|
499
|
-
if (!input.isTTY) return "";
|
|
500
|
-
|
|
501
|
-
const answer = await askText(
|
|
502
|
-
question,
|
|
503
|
-
fallback ? `${fallback}; Enter to skip` : "skip",
|
|
504
|
-
);
|
|
505
|
-
return answer;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
479
|
function defaultAuthMode() {
|
|
509
480
|
if (args["auth-mode"]) return args["auth-mode"];
|
|
510
481
|
const hasBasic = Boolean(
|
|
@@ -622,6 +593,62 @@ async function chooseTools(fallback) {
|
|
|
622
593
|
return selected.join(",");
|
|
623
594
|
}
|
|
624
595
|
|
|
596
|
+
async function chooseVercelScope(fallback) {
|
|
597
|
+
if (args.scope !== undefined) return args.scope;
|
|
598
|
+
if (!input.isTTY) return fallback || "";
|
|
599
|
+
|
|
600
|
+
const teams = await getVercelTeams();
|
|
601
|
+
const options = [
|
|
602
|
+
{ value: "", label: "Default", hint: "current Vercel CLI scope" },
|
|
603
|
+
...teams.map((team) => ({
|
|
604
|
+
value: team.slug,
|
|
605
|
+
label: team.name || team.slug,
|
|
606
|
+
hint: team.slug,
|
|
607
|
+
})),
|
|
608
|
+
];
|
|
609
|
+
|
|
610
|
+
return promptResult(
|
|
611
|
+
p.select({
|
|
612
|
+
message: "Vercel team",
|
|
613
|
+
initialValue: teams.some((team) => team.slug === fallback)
|
|
614
|
+
? fallback
|
|
615
|
+
: "",
|
|
616
|
+
options,
|
|
617
|
+
}),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function getVercelTeams() {
|
|
622
|
+
try {
|
|
623
|
+
const commandArgs = ["teams", "list", "--format", "json"];
|
|
624
|
+
if (args.token) commandArgs.push("--token", args.token);
|
|
625
|
+
const text = await runCapture("vercel", commandArgs);
|
|
626
|
+
return extractVercelTeams(text);
|
|
627
|
+
} catch {
|
|
628
|
+
warn("could not load Vercel teams; using default scope");
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function extractVercelTeams(text) {
|
|
634
|
+
const start = text.indexOf("[");
|
|
635
|
+
const end = text.lastIndexOf("]");
|
|
636
|
+
if (start === -1 || end === -1 || end < start) return [];
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
const data = JSON.parse(text.slice(start, end + 1));
|
|
640
|
+
const teams = Array.isArray(data) ? data : data.teams || [];
|
|
641
|
+
return teams
|
|
642
|
+
.map((team) => ({
|
|
643
|
+
name: team.name || team.slug || "",
|
|
644
|
+
slug: team.slug || "",
|
|
645
|
+
}))
|
|
646
|
+
.filter((team) => team.slug);
|
|
647
|
+
} catch {
|
|
648
|
+
return [];
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
625
652
|
function parseTools(value) {
|
|
626
653
|
const names = Object.keys(toolChoices);
|
|
627
654
|
const selected = String(value || "")
|
|
@@ -648,7 +675,7 @@ async function readDefaults(file) {
|
|
|
648
675
|
async function syncDefaults() {
|
|
649
676
|
const current = await readDefaults(defaultsPath);
|
|
650
677
|
const codeMtime = Math.max(
|
|
651
|
-
await readMtime(
|
|
678
|
+
await readMtime(scriptFile),
|
|
652
679
|
await readMtime(legacyDefaultsPath),
|
|
653
680
|
);
|
|
654
681
|
const homeMtime = await readMtime(defaultsPath);
|
|
@@ -876,26 +903,25 @@ function printKeyValue(key, value) {
|
|
|
876
903
|
}
|
|
877
904
|
|
|
878
905
|
async function askText(question, fallback = "") {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
).trim();
|
|
906
|
+
const answer = await promptResult(
|
|
907
|
+
p.text({
|
|
908
|
+
message: question,
|
|
909
|
+
initialValue: fallback || undefined,
|
|
910
|
+
placeholder: fallback || undefined,
|
|
911
|
+
}),
|
|
912
|
+
);
|
|
913
|
+
return answer === undefined ? "" : String(answer).trim();
|
|
887
914
|
}
|
|
888
915
|
|
|
889
916
|
async function askSecret(question, fallback = "") {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
).trim();
|
|
917
|
+
const answer = await promptResult(
|
|
918
|
+
p.password({
|
|
919
|
+
message: question,
|
|
920
|
+
mask: "*",
|
|
921
|
+
placeholder: fallback || undefined,
|
|
922
|
+
}),
|
|
923
|
+
);
|
|
924
|
+
return answer === undefined ? "" : String(answer).trim();
|
|
899
925
|
}
|
|
900
926
|
|
|
901
927
|
async function promptResult(resultPromise) {
|
|
@@ -935,7 +961,7 @@ Options:
|
|
|
935
961
|
--vm-image NAME alpine, ubuntu, debian, or a custom VM image
|
|
936
962
|
--base NAME Alias for --vm-image
|
|
937
963
|
--project NAME Vercel project name
|
|
938
|
-
--scope SLUG
|
|
964
|
+
--scope SLUG Vercel team/user scope slug; skips team selector
|
|
939
965
|
--from IMAGE Source image for /app/bin/wsterm
|
|
940
966
|
--shell PATH /bin/bash, /bin/zsh, or /bin/sh
|
|
941
967
|
--tools LIST nodejs,codex,claude-code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vercel-vm-factory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.8",
|
|
4
4
|
"description": "Create Vercel Container deployments for ws-shell from selectable VM images.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
|
+
"config.mjs",
|
|
12
13
|
"deploy-vm.mjs",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|