vercel-vm-factory 0.1.1 → 0.1.3
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 +5 -0
- package/deploy-vm.mjs +227 -54
- package/package.json +1 -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
|
--base ubuntu \
|
|
8
8
|
--project ws-shell-ubuntu \
|
|
9
|
+
--auth-mode basic \
|
|
9
10
|
--auth-user admin \
|
|
10
11
|
--auth-password change-me
|
|
11
12
|
```
|
|
@@ -14,6 +15,7 @@ GitHub OAuth is optional:
|
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
npx vercel-vm-factory create \
|
|
18
|
+
--auth-mode github \
|
|
17
19
|
--client-id YOUR_GITHUB_CLIENT_ID \
|
|
18
20
|
--client-secret YOUR_GITHUB_CLIENT_SECRET \
|
|
19
21
|
--github-userid 12345678
|
|
@@ -25,6 +27,8 @@ Run without flags for prompts:
|
|
|
25
27
|
npx vercel-vm-factory create
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
The prompt asks for authentication first: `basic`, `github`, `both`, or `none`, then only asks for the fields that mode needs.
|
|
31
|
+
|
|
28
32
|
Check local setup:
|
|
29
33
|
|
|
30
34
|
```bash
|
|
@@ -72,6 +76,7 @@ https://x-shell.vercel.app/auth/github/callback
|
|
|
72
76
|
|
|
73
77
|
GitHub OAuth fields:
|
|
74
78
|
|
|
79
|
+
- Auth mode -> `--auth-mode basic|github|both|none`
|
|
75
80
|
- Username -> `--auth-user`
|
|
76
81
|
- Password -> `--auth-password`
|
|
77
82
|
- Client ID -> `--client-id`
|
package/deploy-vm.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { stdin as input, stdout as output } from "node:process";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
|
|
9
|
+
const defaultWsShellImage = "ghcr.io/v1xingyue/ws-shell:v1.2.alpine";
|
|
10
|
+
|
|
9
11
|
const bases = {
|
|
10
12
|
alpine: "alpine:3.23",
|
|
11
13
|
ubuntu: "ubuntu:24.04",
|
|
@@ -18,7 +20,10 @@ const workspaceRoot = process.cwd();
|
|
|
18
20
|
const stateRoot = path.join(homedir(), ".vercel-vm-factory");
|
|
19
21
|
const defaultsPath = path.join(stateRoot, "defaults.json");
|
|
20
22
|
const legacyDefaultsPath = path.join(scriptRoot, ".defaults.json");
|
|
21
|
-
const defaults = {
|
|
23
|
+
const defaults = {
|
|
24
|
+
...(await readDefaults(legacyDefaultsPath)),
|
|
25
|
+
...(await readDefaults(defaultsPath)),
|
|
26
|
+
};
|
|
22
27
|
const colorEnabled = output.isTTY && !process.env.NO_COLOR;
|
|
23
28
|
const color = {
|
|
24
29
|
dim: (text) => paint(text, "2"),
|
|
@@ -41,7 +46,9 @@ try {
|
|
|
41
46
|
} catch (error) {
|
|
42
47
|
const message = error instanceof Error ? error.message : String(error);
|
|
43
48
|
if (message.includes("scope does not exist")) {
|
|
44
|
-
console.error(
|
|
49
|
+
console.error(
|
|
50
|
+
`\nScope not found. Leave scope empty, or set the real CLI slug with --scope.`,
|
|
51
|
+
);
|
|
45
52
|
console.error(`If it was saved before, edit or delete: ${defaultsPath}`);
|
|
46
53
|
}
|
|
47
54
|
console.error(message);
|
|
@@ -58,12 +65,25 @@ async function main() {
|
|
|
58
65
|
|
|
59
66
|
const baseName = await chooseBase(args.base ?? defaults.base ?? "alpine");
|
|
60
67
|
const baseImage = bases[baseName] ?? baseName;
|
|
61
|
-
const scope = await optionalValue(
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const scope = await optionalValue(
|
|
69
|
+
"scope",
|
|
70
|
+
"Vercel team/scope",
|
|
71
|
+
defaults.scope,
|
|
72
|
+
);
|
|
73
|
+
const project = await value(
|
|
74
|
+
"project",
|
|
75
|
+
"Vercel project name",
|
|
76
|
+
defaults.project ?? `ws-shell-${baseName}`,
|
|
77
|
+
);
|
|
78
|
+
const wsShellImage =
|
|
79
|
+
args.from ??
|
|
80
|
+
process.env.WS_SHELL_IMAGE ??
|
|
81
|
+
defaults.from ??
|
|
82
|
+
defaultWsShellImage;
|
|
64
83
|
const prod = args.prod !== "false";
|
|
65
84
|
const dryRun = Boolean(args["dry-run"]);
|
|
66
85
|
const skipLink = Boolean(args["skip-link"]);
|
|
86
|
+
const authMode = await chooseAuthMode(defaultAuthMode());
|
|
67
87
|
|
|
68
88
|
const oauthRedirectUrl =
|
|
69
89
|
args["redirect-url"] ??
|
|
@@ -71,7 +91,12 @@ async function main() {
|
|
|
71
91
|
defaults["redirect-url"] ??
|
|
72
92
|
`https://${project}.vercel.app/auth/github/callback`;
|
|
73
93
|
|
|
74
|
-
const appDir = path.join(
|
|
94
|
+
const appDir = path.join(
|
|
95
|
+
workspaceRoot,
|
|
96
|
+
".vercel-vm-factory",
|
|
97
|
+
".generated",
|
|
98
|
+
project,
|
|
99
|
+
);
|
|
75
100
|
const dockerfile = makeDockerfile({ baseImage, wsShellImage });
|
|
76
101
|
|
|
77
102
|
await rm(appDir, { recursive: true, force: true });
|
|
@@ -83,7 +108,8 @@ async function main() {
|
|
|
83
108
|
project,
|
|
84
109
|
scope: scope || "default",
|
|
85
110
|
source: wsShellImage,
|
|
86
|
-
|
|
111
|
+
auth: authMode,
|
|
112
|
+
...(usesGitHubAuth(authMode) ? { callback: oauthRedirectUrl } : {}),
|
|
87
113
|
dockerfile: path.join(appDir, "Dockerfile.vercel"),
|
|
88
114
|
target: prod ? "production" : "preview",
|
|
89
115
|
});
|
|
@@ -94,17 +120,43 @@ async function main() {
|
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
await ensureVercelReady();
|
|
97
|
-
printOAuthGuide(oauthRedirectUrl);
|
|
98
|
-
|
|
99
|
-
const authUsername =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
107
|
-
|
|
123
|
+
if (usesGitHubAuth(authMode)) printOAuthGuide(oauthRedirectUrl);
|
|
124
|
+
|
|
125
|
+
const authUsername = usesBasicAuth(authMode)
|
|
126
|
+
? await secret(
|
|
127
|
+
"auth-user",
|
|
128
|
+
"Basic auth username",
|
|
129
|
+
process.env.AUTH_USERNAME ?? defaults["auth-user"],
|
|
130
|
+
)
|
|
131
|
+
: "";
|
|
132
|
+
const authPassword = usesBasicAuth(authMode)
|
|
133
|
+
? await secret(
|
|
134
|
+
"auth-password",
|
|
135
|
+
"Basic auth password",
|
|
136
|
+
process.env.AUTH_PASSWORD ?? defaults["auth-password"],
|
|
137
|
+
)
|
|
138
|
+
: "";
|
|
139
|
+
const githubClientId = usesGitHubAuth(authMode)
|
|
140
|
+
? await secret(
|
|
141
|
+
"client-id",
|
|
142
|
+
"GitHub client id",
|
|
143
|
+
process.env.GITHUB_CLIENT_ID ?? defaults["client-id"],
|
|
144
|
+
)
|
|
145
|
+
: "";
|
|
146
|
+
const githubClientSecret = usesGitHubAuth(authMode)
|
|
147
|
+
? await secret(
|
|
148
|
+
"client-secret",
|
|
149
|
+
"GitHub client secret",
|
|
150
|
+
process.env.GITHUB_CLIENT_SECRET ?? defaults["client-secret"],
|
|
151
|
+
)
|
|
152
|
+
: "";
|
|
153
|
+
const allowedUserIds = usesGitHubAuth(authMode)
|
|
154
|
+
? await secret(
|
|
155
|
+
"github-userid",
|
|
156
|
+
"Allowed GitHub numeric user id(s)",
|
|
157
|
+
process.env.ALLOWED_USER_IDS ?? defaults["github-userid"],
|
|
158
|
+
)
|
|
159
|
+
: "";
|
|
108
160
|
|
|
109
161
|
await writeDefaults(defaultsPath, {
|
|
110
162
|
...defaults,
|
|
@@ -112,6 +164,7 @@ async function main() {
|
|
|
112
164
|
scope: scope || undefined,
|
|
113
165
|
project,
|
|
114
166
|
from: wsShellImage,
|
|
167
|
+
"auth-mode": authMode,
|
|
115
168
|
"auth-user": authUsername,
|
|
116
169
|
"auth-password": authPassword,
|
|
117
170
|
"client-id": githubClientId,
|
|
@@ -125,7 +178,15 @@ async function main() {
|
|
|
125
178
|
|
|
126
179
|
if (!skipLink) {
|
|
127
180
|
step("Linking Vercel project");
|
|
128
|
-
await runNoUrl("vercel", [
|
|
181
|
+
await runNoUrl("vercel", [
|
|
182
|
+
"link",
|
|
183
|
+
"--yes",
|
|
184
|
+
"--project",
|
|
185
|
+
project,
|
|
186
|
+
"--cwd",
|
|
187
|
+
appDir,
|
|
188
|
+
...commonArgs,
|
|
189
|
+
]);
|
|
129
190
|
} else {
|
|
130
191
|
warn("skip-link enabled; using existing .vercel/project.json");
|
|
131
192
|
}
|
|
@@ -133,32 +194,33 @@ async function main() {
|
|
|
133
194
|
step("Setting project framework=container");
|
|
134
195
|
await setContainerFramework(appDir, commonArgs);
|
|
135
196
|
|
|
136
|
-
const vercelArgs = [
|
|
137
|
-
"deploy",
|
|
138
|
-
appDir,
|
|
139
|
-
"--yes",
|
|
140
|
-
"--logs",
|
|
141
|
-
"--env",
|
|
142
|
-
`OAUTH_REDIRECT_URL=${oauthRedirectUrl}`,
|
|
143
|
-
];
|
|
197
|
+
const vercelArgs = ["deploy", appDir, "--yes", "--logs"];
|
|
144
198
|
|
|
145
199
|
if (authUsername) vercelArgs.push("--env", `AUTH_USERNAME=${authUsername}`);
|
|
146
200
|
if (authPassword) vercelArgs.push("--env", `AUTH_PASSWORD=${authPassword}`);
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
if (
|
|
201
|
+
if (usesGitHubAuth(authMode))
|
|
202
|
+
vercelArgs.push("--env", `OAUTH_REDIRECT_URL=${oauthRedirectUrl}`);
|
|
203
|
+
if (githubClientId)
|
|
204
|
+
vercelArgs.push("--env", `GITHUB_CLIENT_ID=${githubClientId}`);
|
|
205
|
+
if (githubClientSecret)
|
|
206
|
+
vercelArgs.push("--env", `GITHUB_CLIENT_SECRET=${githubClientSecret}`);
|
|
207
|
+
if (allowedUserIds)
|
|
208
|
+
vercelArgs.push("--env", `ALLOWED_USER_IDS=${allowedUserIds}`);
|
|
150
209
|
if (prod) vercelArgs.push("--prod");
|
|
151
210
|
vercelArgs.push(...commonArgs);
|
|
152
211
|
|
|
153
212
|
step("Deploying");
|
|
154
213
|
const deploymentUrl = await run("vercel", vercelArgs);
|
|
155
|
-
console.log(
|
|
214
|
+
console.log(
|
|
215
|
+
`\n${color.green("Deployment URL:")} ${color.bold(deploymentUrl)}`,
|
|
216
|
+
);
|
|
156
217
|
}
|
|
157
218
|
|
|
158
219
|
async function setContainerFramework(appDir, commonArgs) {
|
|
159
220
|
const projectFile = path.join(appDir, ".vercel", "project.json");
|
|
160
221
|
const projectConfig = JSON.parse(await readFile(projectFile, "utf8"));
|
|
161
|
-
if (!projectConfig.projectId)
|
|
222
|
+
if (!projectConfig.projectId)
|
|
223
|
+
throw new Error(`Missing projectId in ${projectFile}`);
|
|
162
224
|
|
|
163
225
|
await runNoUrl("vercel", [
|
|
164
226
|
"api",
|
|
@@ -177,7 +239,9 @@ async function ensureVercelReady() {
|
|
|
177
239
|
const version = await runCapture("vercel", ["--version"]);
|
|
178
240
|
ok(version.split("\n").filter(Boolean).at(-1) || "vercel installed");
|
|
179
241
|
} catch {
|
|
180
|
-
throw new Error(
|
|
242
|
+
throw new Error(
|
|
243
|
+
"Vercel CLI is not installed. Install it with: pnpm add -g vercel",
|
|
244
|
+
);
|
|
181
245
|
}
|
|
182
246
|
|
|
183
247
|
step("Checking Vercel login");
|
|
@@ -198,12 +262,31 @@ async function doctor() {
|
|
|
198
262
|
printKeyValue("base", defaults.base || "not set");
|
|
199
263
|
printKeyValue("project", defaults.project || "not set");
|
|
200
264
|
printKeyValue("scope", defaults.scope || "not set");
|
|
201
|
-
printKeyValue(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
printKeyValue("
|
|
206
|
-
printKeyValue(
|
|
265
|
+
printKeyValue(
|
|
266
|
+
"source image",
|
|
267
|
+
defaults.from || "ghcr.io/v1xingyue/ws-shell:v1.1.alpine",
|
|
268
|
+
);
|
|
269
|
+
printKeyValue("auth mode", defaults["auth-mode"] || "not set");
|
|
270
|
+
printKeyValue(
|
|
271
|
+
"auth user",
|
|
272
|
+
defaults["auth-user"] ? mask(defaults["auth-user"]) : "not set",
|
|
273
|
+
);
|
|
274
|
+
printKeyValue(
|
|
275
|
+
"auth password",
|
|
276
|
+
defaults["auth-password"] ? mask(defaults["auth-password"]) : "not set",
|
|
277
|
+
);
|
|
278
|
+
printKeyValue(
|
|
279
|
+
"client id",
|
|
280
|
+
defaults["client-id"] ? mask(defaults["client-id"]) : "not set",
|
|
281
|
+
);
|
|
282
|
+
printKeyValue(
|
|
283
|
+
"client secret",
|
|
284
|
+
defaults["client-secret"] ? mask(defaults["client-secret"]) : "not set",
|
|
285
|
+
);
|
|
286
|
+
printKeyValue(
|
|
287
|
+
"github userid",
|
|
288
|
+
defaults["github-userid"] ? mask(defaults["github-userid"]) : "not set",
|
|
289
|
+
);
|
|
207
290
|
}
|
|
208
291
|
|
|
209
292
|
function makeDockerfile({ baseImage, wsShellImage }) {
|
|
@@ -229,7 +312,9 @@ async function value(name, question, fallback) {
|
|
|
229
312
|
if (!input.isTTY) return current || "";
|
|
230
313
|
|
|
231
314
|
const rl = createInterface({ input, output });
|
|
232
|
-
const answer = (
|
|
315
|
+
const answer = (
|
|
316
|
+
await rl.question(`${question}${fallback ? ` [${fallback}]` : ""}: `)
|
|
317
|
+
).trim();
|
|
233
318
|
rl.close();
|
|
234
319
|
return answer || current || "";
|
|
235
320
|
}
|
|
@@ -256,13 +341,74 @@ async function optionalValue(name, question, fallback) {
|
|
|
256
341
|
return answer;
|
|
257
342
|
}
|
|
258
343
|
|
|
344
|
+
function defaultAuthMode() {
|
|
345
|
+
if (args["auth-mode"]) return args["auth-mode"];
|
|
346
|
+
const hasBasic = Boolean(
|
|
347
|
+
args["auth-user"] ||
|
|
348
|
+
args["auth-password"] ||
|
|
349
|
+
process.env.AUTH_USERNAME ||
|
|
350
|
+
process.env.AUTH_PASSWORD,
|
|
351
|
+
);
|
|
352
|
+
const hasGitHub = Boolean(
|
|
353
|
+
args["client-id"] ||
|
|
354
|
+
args["client-secret"] ||
|
|
355
|
+
args["github-userid"] ||
|
|
356
|
+
process.env.GITHUB_CLIENT_ID ||
|
|
357
|
+
process.env.GITHUB_CLIENT_SECRET,
|
|
358
|
+
);
|
|
359
|
+
if (hasBasic && hasGitHub) return "both";
|
|
360
|
+
if (hasBasic) return "basic";
|
|
361
|
+
if (hasGitHub) return "github";
|
|
362
|
+
return defaults["auth-mode"] || (input.isTTY ? "basic" : "none");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function chooseAuthMode(fallback) {
|
|
366
|
+
const modes = new Set(["basic", "github", "both", "none"]);
|
|
367
|
+
if (args["auth-mode"]) {
|
|
368
|
+
if (!modes.has(args["auth-mode"]))
|
|
369
|
+
throw new Error("--auth-mode must be basic, github, both, or none");
|
|
370
|
+
return args["auth-mode"];
|
|
371
|
+
}
|
|
372
|
+
if (!input.isTTY) return modes.has(fallback) ? fallback : "basic";
|
|
373
|
+
|
|
374
|
+
console.log(color.bold("Choose authentication"));
|
|
375
|
+
console.log(` ${color.cyan("1")}. basic username/password`);
|
|
376
|
+
console.log(` ${color.cyan("2")}. GitHub OAuth`);
|
|
377
|
+
console.log(` ${color.cyan("3")}. both`);
|
|
378
|
+
console.log(` ${color.cyan("4")}. none`);
|
|
379
|
+
|
|
380
|
+
const rl = createInterface({ input, output });
|
|
381
|
+
const answer = (await rl.question(`Authentication [${fallback}]: `)).trim();
|
|
382
|
+
rl.close();
|
|
383
|
+
|
|
384
|
+
if (!answer) return modes.has(fallback) ? fallback : "basic";
|
|
385
|
+
if (modes.has(answer)) return answer;
|
|
386
|
+
if (answer === "1") return "basic";
|
|
387
|
+
if (answer === "2") return "github";
|
|
388
|
+
if (answer === "3") return "both";
|
|
389
|
+
if (answer === "4") return "none";
|
|
390
|
+
throw new Error("Authentication must be basic, github, both, or none");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function usesBasicAuth(mode) {
|
|
394
|
+
return mode === "basic" || mode === "both";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function usesGitHubAuth(mode) {
|
|
398
|
+
return mode === "github" || mode === "both";
|
|
399
|
+
}
|
|
400
|
+
|
|
259
401
|
async function chooseBase(fallback) {
|
|
260
402
|
if (args.base) return args.base;
|
|
261
403
|
if (!input.isTTY) return fallback;
|
|
262
404
|
|
|
263
405
|
const names = Object.keys(bases);
|
|
264
406
|
console.log(color.bold("Choose base image"));
|
|
265
|
-
names.forEach((name, index) =>
|
|
407
|
+
names.forEach((name, index) =>
|
|
408
|
+
console.log(
|
|
409
|
+
` ${color.cyan(String(index + 1))}. ${name} ${color.dim(`(${bases[name]})`)}`,
|
|
410
|
+
),
|
|
411
|
+
);
|
|
266
412
|
console.log(` ${color.cyan("4")}. custom image`);
|
|
267
413
|
|
|
268
414
|
const rl = createInterface({ input, output });
|
|
@@ -272,7 +418,8 @@ async function chooseBase(fallback) {
|
|
|
272
418
|
if (!answer) return fallback;
|
|
273
419
|
if (bases[answer]) return answer;
|
|
274
420
|
if (/^[1-3]$/.test(answer)) return names[Number(answer) - 1];
|
|
275
|
-
if (answer === "4")
|
|
421
|
+
if (answer === "4")
|
|
422
|
+
return value("custom-base", "Custom base image", defaults["custom-base"]);
|
|
276
423
|
return answer;
|
|
277
424
|
}
|
|
278
425
|
|
|
@@ -285,7 +432,9 @@ async function readDefaults(file) {
|
|
|
285
432
|
}
|
|
286
433
|
|
|
287
434
|
async function writeDefaults(file, data) {
|
|
288
|
-
const clean = Object.fromEntries(
|
|
435
|
+
const clean = Object.fromEntries(
|
|
436
|
+
Object.entries(data).filter(([, value]) => value),
|
|
437
|
+
);
|
|
289
438
|
await mkdir(path.dirname(file), { recursive: true });
|
|
290
439
|
await writeFile(file, `${JSON.stringify(clean, null, 2)}\n`);
|
|
291
440
|
}
|
|
@@ -302,8 +451,10 @@ function parseArgs(argv) {
|
|
|
302
451
|
if (!arg.startsWith("--")) continue;
|
|
303
452
|
|
|
304
453
|
const [rawKey, rawValue] = arg.slice(2).split("=", 2);
|
|
305
|
-
out[rawKey] =
|
|
306
|
-
|
|
454
|
+
out[rawKey] =
|
|
455
|
+
rawValue ?? (argv[i + 1]?.startsWith("--") ? true : argv[i + 1]) ?? true;
|
|
456
|
+
if (rawValue === undefined && argv[i + 1] && !argv[i + 1].startsWith("--"))
|
|
457
|
+
i += 1;
|
|
307
458
|
}
|
|
308
459
|
return out;
|
|
309
460
|
}
|
|
@@ -312,7 +463,10 @@ function parseCommand(argv) {
|
|
|
312
463
|
const known = new Set(["create", "doctor", "help"]);
|
|
313
464
|
const first = argv[0];
|
|
314
465
|
if (first && !first.startsWith("--")) {
|
|
315
|
-
return {
|
|
466
|
+
return {
|
|
467
|
+
command: known.has(first) ? first : first,
|
|
468
|
+
args: parseArgs(argv.slice(1)),
|
|
469
|
+
};
|
|
316
470
|
}
|
|
317
471
|
return { command: "create", args: parseArgs(argv) };
|
|
318
472
|
}
|
|
@@ -320,7 +474,9 @@ function parseCommand(argv) {
|
|
|
320
474
|
function run(command, commandArgs) {
|
|
321
475
|
return new Promise((resolve, reject) => {
|
|
322
476
|
let seenUrl = "";
|
|
323
|
-
const child = spawn(command, commandArgs, {
|
|
477
|
+
const child = spawn(command, commandArgs, {
|
|
478
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
479
|
+
});
|
|
324
480
|
|
|
325
481
|
child.stdout.on("data", (chunk) => {
|
|
326
482
|
const text = chunk.toString();
|
|
@@ -337,7 +493,8 @@ function run(command, commandArgs) {
|
|
|
337
493
|
child.on("error", reject);
|
|
338
494
|
child.on("close", (code) => {
|
|
339
495
|
if (code === 0 && seenUrl) resolve(seenUrl);
|
|
340
|
-
else if (code === 0)
|
|
496
|
+
else if (code === 0)
|
|
497
|
+
reject(new Error("vercel finished but no deployment url was found"));
|
|
341
498
|
else reject(new Error(`vercel exited with code ${code}`));
|
|
342
499
|
});
|
|
343
500
|
});
|
|
@@ -346,7 +503,9 @@ function run(command, commandArgs) {
|
|
|
346
503
|
function runCapture(command, commandArgs) {
|
|
347
504
|
return new Promise((resolve, reject) => {
|
|
348
505
|
let text = "";
|
|
349
|
-
const child = spawn(command, commandArgs, {
|
|
506
|
+
const child = spawn(command, commandArgs, {
|
|
507
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
508
|
+
});
|
|
350
509
|
child.stdout.on("data", (chunk) => {
|
|
351
510
|
text += chunk.toString();
|
|
352
511
|
});
|
|
@@ -356,7 +515,8 @@ function runCapture(command, commandArgs) {
|
|
|
356
515
|
child.on("error", reject);
|
|
357
516
|
child.on("close", (code) => {
|
|
358
517
|
if (code === 0) resolve(text.trim());
|
|
359
|
-
else
|
|
518
|
+
else
|
|
519
|
+
reject(new Error(text.trim() || `${command} exited with code ${code}`));
|
|
360
520
|
});
|
|
361
521
|
});
|
|
362
522
|
}
|
|
@@ -378,7 +538,9 @@ function findLastUrl(text) {
|
|
|
378
538
|
|
|
379
539
|
function printHeader() {
|
|
380
540
|
console.log(color.bold(color.cyan("Vercel VM Factory")));
|
|
381
|
-
console.log(
|
|
541
|
+
console.log(
|
|
542
|
+
color.dim("Build a Container deployment from a tiny Dockerfile.vercel"),
|
|
543
|
+
);
|
|
382
544
|
console.log("");
|
|
383
545
|
}
|
|
384
546
|
|
|
@@ -392,10 +554,20 @@ function printSummary(items) {
|
|
|
392
554
|
|
|
393
555
|
function printOAuthGuide(callbackUrl) {
|
|
394
556
|
console.log(color.bold("GitHub OAuth"));
|
|
395
|
-
console.log(
|
|
557
|
+
console.log(
|
|
558
|
+
`${color.yellow("!")} Set this callback URL in your GitHub OAuth App before deploying:`,
|
|
559
|
+
);
|
|
396
560
|
console.log(` ${color.bold(callbackUrl)}`);
|
|
397
|
-
console.log(
|
|
398
|
-
|
|
561
|
+
console.log(
|
|
562
|
+
color.dim(
|
|
563
|
+
" GitHub: Settings -> Developer settings -> OAuth Apps -> Authorization callback URL",
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
console.log(
|
|
567
|
+
color.dim(
|
|
568
|
+
" User ID: open https://api.github.com/users/YOUR_LOGIN and copy the numeric id",
|
|
569
|
+
),
|
|
570
|
+
);
|
|
399
571
|
console.log("");
|
|
400
572
|
}
|
|
401
573
|
|
|
@@ -432,6 +604,7 @@ Options:
|
|
|
432
604
|
--project NAME Vercel project name
|
|
433
605
|
--scope SLUG Optional Vercel team/user scope slug
|
|
434
606
|
--from IMAGE Source image for /app/bin/wsterm
|
|
607
|
+
--auth-mode MODE basic, github, both, or none
|
|
435
608
|
--auth-user VALUE Username/password auth user
|
|
436
609
|
--auth-password VAL Username/password auth password
|
|
437
610
|
--client-id VALUE GitHub OAuth client id
|