gzero-cli 0.1.0-preview.2 → 0.2.0
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 +42 -21
- package/dist/index.js +464 -30
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
**Deploy UIs and APIs, manage env vars, domains, and billing, straight from your terminal or an AI agent.**
|
|
13
13
|
|
|
14
|
-
[](https://www.npmjs.com/package/gzero-cli)
|
|
15
15
|
[](https://nodejs.org)
|
|
16
16
|
[](#install)
|
|
17
17
|
[](#connect-an-ai-agent-mcp)
|
|
@@ -37,33 +37,45 @@ Works on Linux, macOS, and Windows.
|
|
|
37
37
|
|
|
38
38
|
## Quick start
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
One command gets you live. Run it in your project directory:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
gzero login # device-code sign in via your browser
|
|
44
|
-
|
|
43
|
+
gzero login # device-code sign in via your browser (one time)
|
|
44
|
+
cd my-app
|
|
45
|
+
gzero deploy # publish to GitHub if needed, scan, create, and ship
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
`gzero
|
|
48
|
-
|
|
49
|
-
framework, build command, and output folder for you. At the end it offers to
|
|
50
|
-
deploy.
|
|
48
|
+
`gzero deploy` (short alias: `g0 deploy`) is context-aware and does the right
|
|
49
|
+
thing for where you are:
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
- **Linked directory** (a `.gzero/project.json` file exists): redeploys that project.
|
|
52
|
+
- **Git repo with a GitHub remote:** scans, creates the project, and deploys.
|
|
53
|
+
- **Local repo, not on GitHub yet:** offers to create a GitHub repo on your
|
|
54
|
+
account and push your code there first, then continues.
|
|
55
|
+
- **Not a git repo:** falls back to picking one of your GitHub repositories.
|
|
56
|
+
|
|
57
|
+
`init` vs `deploy`, in one line: **init sets up and links, deploy ships.** Use
|
|
58
|
+
`gzero init` to create and link a project without deploying yet.
|
|
53
59
|
|
|
54
60
|
```bash
|
|
55
61
|
gzero deploy # deploy a project and watch it go live
|
|
56
|
-
gzero status # check readiness, URL, and env vars
|
|
62
|
+
gzero status # check hosting mode, readiness, URL, and env vars
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
`gzero
|
|
60
|
-
`init` flow, then deploys. Run `gzero` with no arguments (or `gzero --help`) to
|
|
61
|
-
see the full command list.
|
|
65
|
+
Run `gzero` with no arguments (or `gzero --help`) to see the full command list.
|
|
62
66
|
|
|
63
67
|
> **Note**
|
|
64
|
-
> GroundZero builds
|
|
65
|
-
>
|
|
66
|
-
>
|
|
68
|
+
> GroundZero builds from GitHub, so your code has to live in a GitHub repo. The
|
|
69
|
+
> CLI can create that repo and push for you (reusing your GitHub sign-in, no
|
|
70
|
+
> extra tokens), so a purely local project can go live without leaving the
|
|
71
|
+
> terminal.
|
|
72
|
+
|
|
73
|
+
### Static vs server-rendered (SSR)
|
|
74
|
+
|
|
75
|
+
When a scan detects a server-rendered app (Next, Nuxt, SvelteKit, ...), the CLI
|
|
76
|
+
asks how to host it: a **static site** (free preview, cheap to go live) or a
|
|
77
|
+
**server-rendered app** running a Node server (the all-in $9/mo plan: server,
|
|
78
|
+
custom domain, SSL). Switch anytime with `gzero ssr enable` / `gzero ssr disable`.
|
|
67
79
|
|
|
68
80
|
## Sign in
|
|
69
81
|
|
|
@@ -98,14 +110,16 @@ in the browser itself.
|
|
|
98
110
|
| `gzero login` | Device-code sign in; saves an API token |
|
|
99
111
|
| `gzero logout` | Revoke the current token and clear local config |
|
|
100
112
|
| `gzero whoami` | Show the signed-in account |
|
|
101
|
-
| `gzero
|
|
113
|
+
| `gzero deploy [project]` | Get the current project live: publish to GitHub if needed, scan, create, and deploy (or redeploy the linked/named project) |
|
|
114
|
+
| `gzero init [repo]` | Set up and link a project (from local code or a GitHub repo) without deploying |
|
|
102
115
|
| `gzero projects` *(alias `ls`)* | List your projects |
|
|
103
|
-
| `gzero
|
|
104
|
-
| `gzero status [project]` | Active deploy, readiness, and env status |
|
|
116
|
+
| `gzero status [project]` | Active deploy, hosting mode, readiness, and env status |
|
|
105
117
|
| `gzero logs [project]` | Print the latest deploy's build log |
|
|
106
118
|
| `gzero open [project]` | Open the project's live URL in your browser |
|
|
107
119
|
| `gzero rollback [project]` | Roll back to a previous successful deploy |
|
|
108
|
-
| `gzero promote [project]` | Promote a deploy to production |
|
|
120
|
+
| `gzero promote [project]` | Promote a live deploy to production |
|
|
121
|
+
| `gzero ssr enable [project]` | Switch a web project to server-rendered hosting (paid) |
|
|
122
|
+
| `gzero ssr disable [project]` | Switch a server-rendered project back to static |
|
|
109
123
|
| `gzero env list [project]` | List env var keys |
|
|
110
124
|
| `gzero env set <project> <key> <value>` | Set an env var |
|
|
111
125
|
| `gzero env rm <project> <key>` | Delete an env var |
|
|
@@ -167,9 +181,16 @@ for details.
|
|
|
167
181
|
## Examples
|
|
168
182
|
|
|
169
183
|
```bash
|
|
170
|
-
#
|
|
184
|
+
# Take a purely local project live: creates a GitHub repo, pushes, then deploys
|
|
185
|
+
cd my-app && gzero deploy
|
|
186
|
+
|
|
187
|
+
# Create and link a project from a specific repo without deploying
|
|
171
188
|
gzero init acme/my-app
|
|
172
189
|
|
|
190
|
+
# Host a server-rendered app on the Node runtime, then deploy it
|
|
191
|
+
gzero ssr enable my-app
|
|
192
|
+
gzero deploy my-app
|
|
193
|
+
|
|
173
194
|
# Deploy a specific branch to production and wait for it to go live
|
|
174
195
|
gzero deploy my-app --branch main --env production
|
|
175
196
|
|
package/dist/index.js
CHANGED
|
@@ -31,9 +31,29 @@ var require_deploy_readiness = __commonJS({
|
|
|
31
31
|
"use strict";
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
33
|
exports.evaluateDeployReadiness = evaluateDeployReadiness;
|
|
34
|
+
function staticSwitchHint(framework) {
|
|
35
|
+
switch (framework) {
|
|
36
|
+
case "next-static":
|
|
37
|
+
return "add `output: 'export'` to next.config (no SSR, ISR, or API routes)";
|
|
38
|
+
case "nuxt-static":
|
|
39
|
+
return "build with `nuxt generate` (or set `nitro: { preset: 'static' }`)";
|
|
40
|
+
case "astro-static":
|
|
41
|
+
return "set `output: 'static'` and remove the SSR adapter";
|
|
42
|
+
case "sveltekit-static":
|
|
43
|
+
return "use `@sveltejs/adapter-static` (no SSR or server endpoints)";
|
|
44
|
+
case "vite-react":
|
|
45
|
+
case "vite-vue":
|
|
46
|
+
case "vite-svelte":
|
|
47
|
+
return "ship a client-only or prerendered build (no Nitro server routes)";
|
|
48
|
+
default:
|
|
49
|
+
return "configure a static build for your framework";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
function evaluateDeployReadiness(input) {
|
|
35
53
|
const blockers = [];
|
|
36
54
|
const warnings = [];
|
|
55
|
+
const detectedRenderMode = input.checks?.some((c) => c.id === "render-mode") ? "ssr" : "static";
|
|
56
|
+
const renderMode = input.renderModeOverride ?? detectedRenderMode;
|
|
37
57
|
if (!input.framework || input.framework === "unknown") {
|
|
38
58
|
blockers.push({
|
|
39
59
|
id: "framework",
|
|
@@ -50,7 +70,7 @@ var require_deploy_readiness = __commonJS({
|
|
|
50
70
|
recommendation: 'Add a "build" script to package.json or run scan again.'
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
|
-
if (!input.outputDir && input.framework !== "plain-html") {
|
|
73
|
+
if (!input.outputDir && input.framework !== "plain-html" && renderMode !== "ssr") {
|
|
54
74
|
blockers.push({
|
|
55
75
|
id: "output",
|
|
56
76
|
level: "error",
|
|
@@ -58,6 +78,14 @@ var require_deploy_readiness = __commonJS({
|
|
|
58
78
|
recommendation: "Run scan again to detect the build output folder."
|
|
59
79
|
});
|
|
60
80
|
}
|
|
81
|
+
if (renderMode === "ssr" && input.serviceType !== void 0 && input.serviceType !== "ssr_web") {
|
|
82
|
+
blockers.push({
|
|
83
|
+
id: "ssr-hosting",
|
|
84
|
+
level: "error",
|
|
85
|
+
message: "This app renders on a server (SSR). Choose Server-rendered (SSR) hosting to deploy it.",
|
|
86
|
+
recommendation: `Pick Server-rendered (SSR) in Hosting mode to continue ($9/mo all-in: server, domain, and SSL). To host as a static site instead, ${staticSwitchHint(input.framework)}, then re-scan.`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
61
89
|
if (input.secretsFound) {
|
|
62
90
|
warnings.push({
|
|
63
91
|
id: "secrets",
|
|
@@ -81,6 +109,8 @@ var require_deploy_readiness = __commonJS({
|
|
|
81
109
|
if (check.id.startsWith("env-"))
|
|
82
110
|
continue;
|
|
83
111
|
const isFrameworkConfigError = check.id === "framework-config" && check.level === "error";
|
|
112
|
+
if (isFrameworkConfigError && renderMode === "ssr")
|
|
113
|
+
continue;
|
|
84
114
|
if (check.level !== "warning" && !isFrameworkConfigError)
|
|
85
115
|
continue;
|
|
86
116
|
warnings.push({
|
|
@@ -95,7 +125,8 @@ var require_deploy_readiness = __commonJS({
|
|
|
95
125
|
canDeploy: blockers.length === 0,
|
|
96
126
|
blockers,
|
|
97
127
|
warnings,
|
|
98
|
-
technicalNotes: input.technicalNotes ?? []
|
|
128
|
+
technicalNotes: input.technicalNotes ?? [],
|
|
129
|
+
renderMode
|
|
99
130
|
};
|
|
100
131
|
}
|
|
101
132
|
}
|
|
@@ -283,6 +314,15 @@ var require_dist2 = __commonJS({
|
|
|
283
314
|
me: () => request("/auth/me"),
|
|
284
315
|
// --- Repos ---
|
|
285
316
|
listRepos: (opts) => request(`/github/repos${opts?.refresh ? "?refresh=true" : ""}`),
|
|
317
|
+
/**
|
|
318
|
+
* Create a new GitHub repository on the signed-in user's account (using their
|
|
319
|
+
* linked OAuth token, server-side). Returns a short-lived tokenized `pushUrl`
|
|
320
|
+
* so a client can push local commits once; callers must not persist it.
|
|
321
|
+
*/
|
|
322
|
+
createRepo: (input) => request("/github/repos", {
|
|
323
|
+
method: "POST",
|
|
324
|
+
body: JSON.stringify(input)
|
|
325
|
+
}),
|
|
286
326
|
// --- Scanner ---
|
|
287
327
|
scanRepo: (input) => request("/scanner/scan", {
|
|
288
328
|
method: "POST",
|
|
@@ -327,6 +367,22 @@ var require_dist2 = __commonJS({
|
|
|
327
367
|
method: "POST",
|
|
328
368
|
body: JSON.stringify({ deployId })
|
|
329
369
|
}),
|
|
370
|
+
/** Switch a web project to server-rendered mode (consumes SSR capacity). */
|
|
371
|
+
enableSsr: (projectId, startCommand) => request(`/projects/${projectId}/ssr`, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
body: JSON.stringify({ startCommand: startCommand ?? null })
|
|
374
|
+
}),
|
|
375
|
+
/** Switch a server-rendered project back to static hosting. */
|
|
376
|
+
disableSsr: (projectId) => request(`/projects/${projectId}/ssr`, { method: "DELETE" }),
|
|
377
|
+
/**
|
|
378
|
+
* Re-point a project to a different GitHub repo. Clears artifacts, cache, and
|
|
379
|
+
* deploy history from the old repo and resets the build config; a rescan and
|
|
380
|
+
* redeploy are required afterwards.
|
|
381
|
+
*/
|
|
382
|
+
changeRepo: (projectId, repoFullName, defaultBranch) => request(`/projects/${projectId}/repo`, {
|
|
383
|
+
method: "POST",
|
|
384
|
+
body: JSON.stringify({ repoFullName, defaultBranch: defaultBranch ?? null })
|
|
385
|
+
}),
|
|
330
386
|
// --- Env vars ---
|
|
331
387
|
listEnvVars: (projectId) => request(`/projects/${projectId}/env-vars`),
|
|
332
388
|
upsertEnvVar: (projectId, key, value) => request(`/projects/${projectId}/env-vars`, {
|
|
@@ -353,9 +409,9 @@ var require_dist2 = __commonJS({
|
|
|
353
409
|
// --- Billing ---
|
|
354
410
|
getBillingStatus: () => request("/billing/status"),
|
|
355
411
|
createCheckout: () => request("/billing/checkout", { method: "POST" }),
|
|
356
|
-
createAddonCheckout: (addonId, productId) => request("/billing/addons/checkout", {
|
|
412
|
+
createAddonCheckout: (addonId, productId, returnPath) => request("/billing/addons/checkout", {
|
|
357
413
|
method: "POST",
|
|
358
|
-
body: JSON.stringify({ addonId, productId })
|
|
414
|
+
body: JSON.stringify({ addonId, productId, returnPath })
|
|
359
415
|
}),
|
|
360
416
|
createBillingPortal: () => request("/billing/portal", { method: "POST" }),
|
|
361
417
|
listInvoices: () => request("/billing/invoices")
|
|
@@ -409,6 +465,23 @@ async function clearConfig() {
|
|
|
409
465
|
} catch {
|
|
410
466
|
}
|
|
411
467
|
}
|
|
468
|
+
function projectLinkPath(dir) {
|
|
469
|
+
return join(dir, ".gzero", "project.json");
|
|
470
|
+
}
|
|
471
|
+
async function loadProjectLink(dir) {
|
|
472
|
+
try {
|
|
473
|
+
const raw = await readFile(projectLinkPath(dir), "utf8");
|
|
474
|
+
const parsed = JSON.parse(raw);
|
|
475
|
+
return parsed.projectId ? parsed : null;
|
|
476
|
+
} catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async function saveProjectLink(dir, link) {
|
|
481
|
+
await mkdir(join(dir, ".gzero"), { recursive: true });
|
|
482
|
+
await writeFile(projectLinkPath(dir), `${JSON.stringify(link, null, 2)}
|
|
483
|
+
`);
|
|
484
|
+
}
|
|
412
485
|
async function resolveSettings(flags2) {
|
|
413
486
|
const file = await loadConfig();
|
|
414
487
|
return {
|
|
@@ -558,6 +631,84 @@ async function resolveProject(c, ref) {
|
|
|
558
631
|
var import_sdk2 = __toESM(require_dist2(), 1);
|
|
559
632
|
import * as p2 from "@clack/prompts";
|
|
560
633
|
import pc3 from "picocolors";
|
|
634
|
+
|
|
635
|
+
// src/git.ts
|
|
636
|
+
import { spawn as spawn2 } from "child_process";
|
|
637
|
+
import { basename } from "path";
|
|
638
|
+
function runGit(args, cwd) {
|
|
639
|
+
return new Promise((resolve) => {
|
|
640
|
+
let stdout = "";
|
|
641
|
+
let stderr = "";
|
|
642
|
+
let child;
|
|
643
|
+
try {
|
|
644
|
+
child = spawn2("git", args, { cwd });
|
|
645
|
+
} catch {
|
|
646
|
+
resolve({ code: 127, stdout: "", stderr: "git not found" });
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
650
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
651
|
+
child.on("error", () => resolve({ code: 127, stdout, stderr: "git not found" }));
|
|
652
|
+
child.on("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
async function gitAvailable() {
|
|
656
|
+
const res = await runGit(["--version"], process.cwd());
|
|
657
|
+
return res.code === 0;
|
|
658
|
+
}
|
|
659
|
+
function parseGithubRemote(url) {
|
|
660
|
+
if (!url) return null;
|
|
661
|
+
const trimmed = url.trim();
|
|
662
|
+
const ssh = /^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/.exec(trimmed);
|
|
663
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
664
|
+
const https = /^https?:\/\/(?:[^@]+@)?github\.com\/([^/]+)\/(.+?)(?:\.git)?\/?$/.exec(trimmed);
|
|
665
|
+
if (https) return `${https[1]}/${https[2]}`;
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
async function inspectGit(cwd) {
|
|
669
|
+
const inside = await runGit(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
670
|
+
if (inside.code !== 0 || inside.stdout.trim() !== "true") {
|
|
671
|
+
return {
|
|
672
|
+
isRepo: false,
|
|
673
|
+
root: null,
|
|
674
|
+
branch: null,
|
|
675
|
+
hasCommits: false,
|
|
676
|
+
originUrl: null,
|
|
677
|
+
githubRepo: null,
|
|
678
|
+
dirty: false
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const [root, branch, head, origin, status] = await Promise.all([
|
|
682
|
+
runGit(["rev-parse", "--show-toplevel"], cwd),
|
|
683
|
+
runGit(["symbolic-ref", "--short", "HEAD"], cwd),
|
|
684
|
+
runGit(["rev-parse", "HEAD"], cwd),
|
|
685
|
+
runGit(["remote", "get-url", "origin"], cwd),
|
|
686
|
+
runGit(["status", "--porcelain"], cwd)
|
|
687
|
+
]);
|
|
688
|
+
const originUrl = origin.code === 0 ? origin.stdout.trim() || null : null;
|
|
689
|
+
return {
|
|
690
|
+
isRepo: true,
|
|
691
|
+
root: root.code === 0 ? root.stdout.trim() : null,
|
|
692
|
+
branch: branch.code === 0 ? branch.stdout.trim() || null : null,
|
|
693
|
+
hasCommits: head.code === 0,
|
|
694
|
+
originUrl,
|
|
695
|
+
githubRepo: parseGithubRemote(originUrl),
|
|
696
|
+
dirty: status.code === 0 && status.stdout.trim().length > 0
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function suggestRepoName(cwd, root) {
|
|
700
|
+
const dir = basename(root ?? cwd) || "app";
|
|
701
|
+
return dir.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
|
|
702
|
+
}
|
|
703
|
+
async function hasGitIdentity(cwd) {
|
|
704
|
+
const [name, email] = await Promise.all([
|
|
705
|
+
runGit(["config", "user.name"], cwd),
|
|
706
|
+
runGit(["config", "user.email"], cwd)
|
|
707
|
+
]);
|
|
708
|
+
return name.code === 0 && name.stdout.trim() !== "" && email.code === 0 && email.stdout.trim() !== "";
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/create.ts
|
|
561
712
|
var MANUAL = "__manual__";
|
|
562
713
|
var CREATE_PRODUCT = "__create__";
|
|
563
714
|
var REPO_ROOT = ".";
|
|
@@ -565,6 +716,21 @@ function guard(value) {
|
|
|
565
716
|
if (p2.isCancel(value)) die("Cancelled.");
|
|
566
717
|
return value;
|
|
567
718
|
}
|
|
719
|
+
function redactSecret(text2, pushUrl) {
|
|
720
|
+
let out = text2;
|
|
721
|
+
if (pushUrl) out = out.split(pushUrl).join("https://github.com");
|
|
722
|
+
const token = /x-access-token:([^@/\s]+)@/.exec(pushUrl)?.[1];
|
|
723
|
+
if (token) out = out.split(token).join("***");
|
|
724
|
+
return out.replace(/(https?:\/\/)[^@/\s]+:[^@/\s]+@/g, "$1");
|
|
725
|
+
}
|
|
726
|
+
async function lookupDefaultBranch(c, repoFullName) {
|
|
727
|
+
try {
|
|
728
|
+
const { repos } = await c.listRepos();
|
|
729
|
+
return repos.find((r) => r.fullName === repoFullName)?.defaultBranch ?? null;
|
|
730
|
+
} catch {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
568
734
|
async function pickRepo(c) {
|
|
569
735
|
const s = p2.spinner();
|
|
570
736
|
s.start("Loading your GitHub repositories");
|
|
@@ -680,19 +846,36 @@ function scanSummary(scan, repoFullName, branch) {
|
|
|
680
846
|
`${pc3.dim("branch")} ${branch}`,
|
|
681
847
|
`${pc3.dim("framework")} ${scan.framework}`
|
|
682
848
|
];
|
|
849
|
+
if (scan.renderMode) lines.push(`${pc3.dim("render")} ${scan.renderMode === "ssr" ? "server-rendered (SSR)" : "static"}`);
|
|
683
850
|
if (scan.scanPath && scan.scanPath !== REPO_ROOT) lines.push(`${pc3.dim("path")} ${scan.scanPath}`);
|
|
684
851
|
if (scan.buildCommand) lines.push(`${pc3.dim("build")} ${scan.buildCommand}`);
|
|
685
852
|
if (scan.outputDir) lines.push(`${pc3.dim("output")} ${scan.outputDir}`);
|
|
853
|
+
if (scan.renderMode === "ssr" && scan.startCommand)
|
|
854
|
+
lines.push(`${pc3.dim("start")} ${scan.startCommand}`);
|
|
686
855
|
if (scan.requiredEnvVars.length > 0)
|
|
687
856
|
lines.push(`${pc3.dim("env vars")} ${scan.requiredEnvVars.join(", ")}`);
|
|
688
857
|
return lines.join("\n");
|
|
689
858
|
}
|
|
859
|
+
function staticExportHint(framework) {
|
|
860
|
+
switch (framework) {
|
|
861
|
+
case "next-static":
|
|
862
|
+
return "Add `output: 'export'` to next.config.js (static export), then re-scan.";
|
|
863
|
+
case "nuxt-static":
|
|
864
|
+
return "Use `nuxt generate` (or nitro preset 'static') to prerender, then re-scan.";
|
|
865
|
+
case "astro-static":
|
|
866
|
+
return "Set `output: 'static'` in astro.config, then re-scan.";
|
|
867
|
+
case "sveltekit-static":
|
|
868
|
+
return "Switch to @sveltejs/adapter-static, then re-scan.";
|
|
869
|
+
default:
|
|
870
|
+
return "Configure a static export for this framework, then re-scan.";
|
|
871
|
+
}
|
|
872
|
+
}
|
|
690
873
|
async function interactiveCreate(c, opts = {}) {
|
|
691
874
|
let repoFullName;
|
|
692
875
|
let defaultBranch;
|
|
693
876
|
if (opts.repo) {
|
|
694
877
|
repoFullName = opts.repo;
|
|
695
|
-
defaultBranch = "main";
|
|
878
|
+
defaultBranch = opts.defaultBranch ?? await lookupDefaultBranch(c, opts.repo) ?? "main";
|
|
696
879
|
} else {
|
|
697
880
|
({ repoFullName, defaultBranch } = await pickRepo(c));
|
|
698
881
|
}
|
|
@@ -700,7 +883,7 @@ async function interactiveCreate(c, opts = {}) {
|
|
|
700
883
|
await p2.select({
|
|
701
884
|
message: "What are you deploying?",
|
|
702
885
|
options: [
|
|
703
|
-
{ value: "web", label: "Web app", hint: "
|
|
886
|
+
{ value: "web", label: "Web app", hint: "React, Vue, Astro, Svelte, Next, Nuxt, ..." },
|
|
704
887
|
{ value: "api", label: "API", hint: "ExpressoTS backend" }
|
|
705
888
|
]
|
|
706
889
|
})
|
|
@@ -721,6 +904,37 @@ async function interactiveCreate(c, opts = {}) {
|
|
|
721
904
|
}
|
|
722
905
|
}
|
|
723
906
|
p2.note(scanSummary(scan, repoFullName, defaultBranch), "Detected configuration");
|
|
907
|
+
let hostingMode = null;
|
|
908
|
+
let buildCommand = scan.buildCommand;
|
|
909
|
+
let outputDir = scan.outputDir;
|
|
910
|
+
let configOverrides = {};
|
|
911
|
+
if (kind === "web" && scan.renderMode === "ssr") {
|
|
912
|
+
hostingMode = guard(
|
|
913
|
+
await p2.select({
|
|
914
|
+
message: "This app renders on a server. How do you want to host it?",
|
|
915
|
+
options: [
|
|
916
|
+
{ value: "ssr", label: "Server-rendered (SSR)", hint: "$9/mo all-in: server, domain, SSL" },
|
|
917
|
+
{
|
|
918
|
+
value: "static",
|
|
919
|
+
label: "Static site",
|
|
920
|
+
hint: scan.staticAlternative ? "build a static export" : "needs a static export config"
|
|
921
|
+
}
|
|
922
|
+
],
|
|
923
|
+
initialValue: "ssr"
|
|
924
|
+
})
|
|
925
|
+
);
|
|
926
|
+
if (hostingMode === "static") {
|
|
927
|
+
configOverrides = { renderMode: "static" };
|
|
928
|
+
if (scan.staticAlternative) {
|
|
929
|
+
buildCommand = scan.staticAlternative.buildCommand;
|
|
930
|
+
outputDir = scan.staticAlternative.outputDir;
|
|
931
|
+
} else {
|
|
932
|
+
p2.note(staticExportHint(scan.framework), "Static export needed");
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
configOverrides = { renderMode: "ssr" };
|
|
936
|
+
}
|
|
937
|
+
}
|
|
724
938
|
const s = p2.spinner();
|
|
725
939
|
s.start(kind === "api" ? "Creating API service" : "Creating project");
|
|
726
940
|
let project;
|
|
@@ -735,17 +949,31 @@ async function interactiveCreate(c, opts = {}) {
|
|
|
735
949
|
defaultBranch
|
|
736
950
|
});
|
|
737
951
|
} else {
|
|
952
|
+
const detectedConfig = {
|
|
953
|
+
framework: scan.framework,
|
|
954
|
+
buildCommand: scan.buildCommand,
|
|
955
|
+
outputDir: scan.outputDir,
|
|
956
|
+
scanPath: scan.scanPath ?? REPO_ROOT,
|
|
957
|
+
startCommand: scan.startCommand ?? null,
|
|
958
|
+
installCommand: scan.installCommand ?? "npm install",
|
|
959
|
+
renderMode: scan.renderMode,
|
|
960
|
+
staticAlternative: scan.staticAlternative ?? null
|
|
961
|
+
};
|
|
738
962
|
project = await c.createProject({
|
|
739
963
|
repoFullName,
|
|
740
964
|
productId,
|
|
741
965
|
defaultBranch,
|
|
742
966
|
framework: scan.framework,
|
|
743
967
|
scanPath: scan.scanPath ?? REPO_ROOT,
|
|
744
|
-
buildCommand
|
|
745
|
-
outputDir
|
|
968
|
+
buildCommand,
|
|
969
|
+
outputDir,
|
|
746
970
|
spaFallback: scan.spaFallback,
|
|
747
971
|
requiredEnvVars: scan.requiredEnvVars,
|
|
748
|
-
secretsFound: scan.secretsFound
|
|
972
|
+
secretsFound: scan.secretsFound,
|
|
973
|
+
lastScanChecks: scan.checks,
|
|
974
|
+
startCommand: scan.startCommand ?? null,
|
|
975
|
+
detectedConfig,
|
|
976
|
+
configOverrides
|
|
749
977
|
});
|
|
750
978
|
}
|
|
751
979
|
} catch (err) {
|
|
@@ -753,7 +981,143 @@ async function interactiveCreate(c, opts = {}) {
|
|
|
753
981
|
fail(err);
|
|
754
982
|
}
|
|
755
983
|
s.stop(`${symbol.ok} Created ${brand.violet(project.name)} ${pc3.dim(`(${project.slug})`)}`);
|
|
756
|
-
|
|
984
|
+
let ssrPending = false;
|
|
985
|
+
if (hostingMode === "ssr") {
|
|
986
|
+
const outcome = await upgradeToSsr(c, project, scan.startCommand ?? null);
|
|
987
|
+
ssrPending = outcome === "checkout";
|
|
988
|
+
}
|
|
989
|
+
return { project, scan, ssrPending };
|
|
990
|
+
}
|
|
991
|
+
async function upgradeToSsr(c, project, startCommand) {
|
|
992
|
+
let billing2;
|
|
993
|
+
try {
|
|
994
|
+
billing2 = await c.getBillingStatus();
|
|
995
|
+
} catch (err) {
|
|
996
|
+
fail(err);
|
|
997
|
+
}
|
|
998
|
+
const hasSpareCapacity = billing2.ssrRuntime.capacity > billing2.ssrRuntime.used;
|
|
999
|
+
if (hasSpareCapacity) {
|
|
1000
|
+
const s = p2.spinner();
|
|
1001
|
+
s.start("Enabling server-rendered hosting");
|
|
1002
|
+
try {
|
|
1003
|
+
await c.enableSsr(project.id, startCommand);
|
|
1004
|
+
s.stop(`${symbol.ok} Server-rendered hosting enabled.`);
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
s.stop("Could not enable server rendering");
|
|
1007
|
+
fail(err);
|
|
1008
|
+
}
|
|
1009
|
+
return "enabled";
|
|
1010
|
+
}
|
|
1011
|
+
const addonId = billing2.availableAddons.find((a) => a.type === "ssr")?.id;
|
|
1012
|
+
if (!addonId) {
|
|
1013
|
+
p2.note("Server rendering isn't available on your account yet.", "Server rendering");
|
|
1014
|
+
return "unavailable";
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
const { url } = await c.createAddonCheckout(addonId);
|
|
1018
|
+
p2.note(
|
|
1019
|
+
`${pc3.underline(url)}
|
|
1020
|
+
|
|
1021
|
+
${pc3.dim("This is the all-in $9/mo plan (server, domain, SSL).")}`,
|
|
1022
|
+
"Complete payment to enable server rendering"
|
|
1023
|
+
);
|
|
1024
|
+
openBrowser(url);
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
fail(err);
|
|
1027
|
+
}
|
|
1028
|
+
return "checkout";
|
|
1029
|
+
}
|
|
1030
|
+
async function createRepoAndPush(c, cwd, git) {
|
|
1031
|
+
if (!await gitAvailable()) die("git is required to create and push a repository.");
|
|
1032
|
+
const suggested = suggestRepoName(cwd, git.root);
|
|
1033
|
+
p2.note(
|
|
1034
|
+
`This project isn't on GitHub yet. GroundZero deploys from GitHub, so we'll
|
|
1035
|
+
create a repo on your account and push your code there.`,
|
|
1036
|
+
"Publish to GitHub"
|
|
1037
|
+
);
|
|
1038
|
+
const name = guard(
|
|
1039
|
+
await p2.text({
|
|
1040
|
+
message: "New GitHub repository name",
|
|
1041
|
+
defaultValue: suggested,
|
|
1042
|
+
placeholder: suggested,
|
|
1043
|
+
validate: (v) => /^[A-Za-z0-9._-]+$/.test(v.trim()) ? void 0 : "Use letters, numbers, '.', '-', '_' only."
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1046
|
+
const repoName = name.trim() || suggested;
|
|
1047
|
+
const isPrivate = guard(
|
|
1048
|
+
await p2.confirm({ message: "Make the repository private?", initialValue: true })
|
|
1049
|
+
);
|
|
1050
|
+
const create = p2.spinner();
|
|
1051
|
+
create.start(`Creating ${repoName} on GitHub`);
|
|
1052
|
+
let repo;
|
|
1053
|
+
try {
|
|
1054
|
+
repo = await c.createRepo({ name: repoName, private: isPrivate });
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
create.stop("Could not create repository");
|
|
1057
|
+
fail(err);
|
|
1058
|
+
}
|
|
1059
|
+
create.stop(`${symbol.ok} Created ${brand.violet(repo.fullName)}`);
|
|
1060
|
+
const root = git.root ?? cwd;
|
|
1061
|
+
const branch = git.branch ?? "main";
|
|
1062
|
+
if (!git.hasCommits) {
|
|
1063
|
+
await runGit(["add", "-A"], root);
|
|
1064
|
+
const identityArgs = await hasGitIdentity(root) ? [] : ["-c", "user.name=GroundZero", "-c", "user.email=cli@gzer0.app"];
|
|
1065
|
+
const commit = await runGit([...identityArgs, "commit", "-m", "Initial commit"], root);
|
|
1066
|
+
if (commit.code !== 0) {
|
|
1067
|
+
die(`Could not create an initial commit: ${commit.stderr.trim() || "commit failed"}`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const push = p2.spinner();
|
|
1071
|
+
push.start(`Pushing ${branch} to ${repo.fullName}`);
|
|
1072
|
+
const cleanUrl = `https://github.com/${repo.fullName}.git`;
|
|
1073
|
+
const remoteName = git.originUrl ? "gzero" : "origin";
|
|
1074
|
+
await runGit(["remote", "remove", remoteName], root);
|
|
1075
|
+
await runGit(["remote", "add", remoteName, cleanUrl], root);
|
|
1076
|
+
const pushed = await runGit(["push", repo.pushUrl, `HEAD:refs/heads/${branch}`], root);
|
|
1077
|
+
if (pushed.code !== 0) {
|
|
1078
|
+
push.stop("Push failed");
|
|
1079
|
+
const detail = redactSecret(pushed.stderr, repo.pushUrl).trim();
|
|
1080
|
+
die(`git push failed: ${detail || "unknown error"}`);
|
|
1081
|
+
}
|
|
1082
|
+
push.stop(
|
|
1083
|
+
`${symbol.ok} Pushed ${branch} to ${brand.violet(repo.fullName)}${remoteName === "gzero" ? pc3.dim(" (remote 'gzero')") : ""}`
|
|
1084
|
+
);
|
|
1085
|
+
return { repoFullName: repo.fullName, branch };
|
|
1086
|
+
}
|
|
1087
|
+
async function onboardProject(c, opts) {
|
|
1088
|
+
if (opts.repo) {
|
|
1089
|
+
return interactiveCreate(c, { repo: opts.repo });
|
|
1090
|
+
}
|
|
1091
|
+
const git = await inspectGit(opts.cwd);
|
|
1092
|
+
if (!git.isRepo) {
|
|
1093
|
+
let projects;
|
|
1094
|
+
try {
|
|
1095
|
+
projects = await c.listProjects();
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
fail(err);
|
|
1098
|
+
}
|
|
1099
|
+
if (projects.length > 0) {
|
|
1100
|
+
const project = await resolveProject(c);
|
|
1101
|
+
return { project, existing: true };
|
|
1102
|
+
}
|
|
1103
|
+
return interactiveCreate(c);
|
|
1104
|
+
}
|
|
1105
|
+
if (git.githubRepo) {
|
|
1106
|
+
let projects;
|
|
1107
|
+
try {
|
|
1108
|
+
projects = await c.listProjects();
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
fail(err);
|
|
1111
|
+
}
|
|
1112
|
+
const match = projects.find((proj) => proj.repoFullName === git.githubRepo);
|
|
1113
|
+
if (match) {
|
|
1114
|
+
p2.note(`Using the existing project for ${git.githubRepo}.`, "Linked");
|
|
1115
|
+
return { project: match, existing: true };
|
|
1116
|
+
}
|
|
1117
|
+
return interactiveCreate(c, { repo: git.githubRepo, defaultBranch: git.branch ?? void 0 });
|
|
1118
|
+
}
|
|
1119
|
+
const pushed = await createRepoAndPush(c, opts.cwd, git);
|
|
1120
|
+
return interactiveCreate(c, { repo: pushed.repoFullName, defaultBranch: pushed.branch });
|
|
757
1121
|
}
|
|
758
1122
|
async function deployProject(c, project, opts = {}) {
|
|
759
1123
|
const s = p2.spinner();
|
|
@@ -798,7 +1162,7 @@ ${pc3.dim("Re-run with --yes to acknowledge warnings.")}`);
|
|
|
798
1162
|
s.stop(`${symbol.warn} Deploy status: ${current.status}`);
|
|
799
1163
|
}
|
|
800
1164
|
}
|
|
801
|
-
async function
|
|
1165
|
+
async function reportReadiness(c, project) {
|
|
802
1166
|
let hardBlockers = [];
|
|
803
1167
|
try {
|
|
804
1168
|
const readiness = await c.getReadiness(project.id);
|
|
@@ -816,6 +1180,10 @@ ${pc3.dim("Set them with")} gzero env set ${project.slug} KEY value`,
|
|
|
816
1180
|
}
|
|
817
1181
|
} catch {
|
|
818
1182
|
}
|
|
1183
|
+
return { hardBlockers };
|
|
1184
|
+
}
|
|
1185
|
+
async function offerDeploy(c, project, opts) {
|
|
1186
|
+
const { hardBlockers } = await reportReadiness(c, project);
|
|
819
1187
|
if (hardBlockers.length > 0) {
|
|
820
1188
|
p2.note(hardBlockers.map((b) => `${symbol.err} ${b.message}`).join("\n"), "Fix before deploying");
|
|
821
1189
|
p2.outro(`Resolve the blockers above, then run ${brand.violet(`gzero deploy ${project.slug}`)}.`);
|
|
@@ -829,12 +1197,25 @@ ${pc3.dim("Set them with")} gzero env set ${project.slug} KEY value`,
|
|
|
829
1197
|
p2.outro(`Run ${brand.violet(`gzero deploy ${project.slug}`)} when you're ready.`);
|
|
830
1198
|
return;
|
|
831
1199
|
}
|
|
832
|
-
await deployProject(c, project, { yes: true });
|
|
1200
|
+
await deployProject(c, project, { yes: true, ...opts.deploy });
|
|
833
1201
|
p2.outro("Done.");
|
|
834
1202
|
}
|
|
1203
|
+
async function finishSetup(c, project, opts = {}) {
|
|
1204
|
+
if (opts.ssrPending) {
|
|
1205
|
+
p2.outro(
|
|
1206
|
+
`Finish the server-rendering upgrade in your browser, then run ${brand.violet(`gzero deploy ${project.slug}`)}.`
|
|
1207
|
+
);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const { hardBlockers } = await reportReadiness(c, project);
|
|
1211
|
+
if (hardBlockers.length > 0) {
|
|
1212
|
+
p2.note(hardBlockers.map((b) => `${symbol.err} ${b.message}`).join("\n"), "Fix before deploying");
|
|
1213
|
+
}
|
|
1214
|
+
p2.outro(`Setup complete. Run ${brand.violet(`gzero deploy ${project.slug}`)} to ship.`);
|
|
1215
|
+
}
|
|
835
1216
|
|
|
836
1217
|
// src/index.ts
|
|
837
|
-
var VERSION = "0.
|
|
1218
|
+
var VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
838
1219
|
var program = new Command();
|
|
839
1220
|
program.name("gzero").description("GroundZero: deploy UIs and APIs from the terminal or an AI agent.").version(VERSION, "-v, --version", "Print the CLI version.").option("--api-url <url>", "API base URL (default: config or GZERO_API_URL)").option("--token <token>", "API token (default: config or GZERO_TOKEN)").configureHelp({
|
|
840
1221
|
styleTitle: (s) => pc4.bold(s),
|
|
@@ -955,36 +1336,52 @@ program.command("projects").alias("ls").description("List your projects.").actio
|
|
|
955
1336
|
fail(err);
|
|
956
1337
|
}
|
|
957
1338
|
});
|
|
958
|
-
program.command("init").argument("[repo]", "GitHub repository (owner/repo) to skip
|
|
1339
|
+
program.command("init").argument("[repo]", "GitHub repository (owner/repo) to skip detection").description("Set up a project (from local code or a GitHub repo) and link this directory. Does not deploy.").action(async (repo, _opts, command) => {
|
|
959
1340
|
const c = await client(flags(command));
|
|
960
1341
|
p3.intro(lockup("init"));
|
|
961
|
-
const
|
|
962
|
-
await
|
|
1342
|
+
const cwd = process.cwd();
|
|
1343
|
+
const { project, ssrPending } = await onboardProject(c, { cwd, repo });
|
|
1344
|
+
await saveProjectLink(cwd, { projectId: project.id, repoFullName: project.repoFullName });
|
|
1345
|
+
await finishSetup(c, project, { ssrPending });
|
|
963
1346
|
});
|
|
964
|
-
program.command("deploy").argument("[project]", "project id, slug, or name").option("-b, --branch <branch>", "branch to deploy").option("-e, --env <environment>", "environment (production or preview)").option("-y, --yes", "acknowledge readiness warnings and deploy anyway").option("--no-follow", "return immediately instead of streaming status").description("
|
|
1347
|
+
program.command("deploy").argument("[project]", "project id, slug, or name").option("-b, --branch <branch>", "branch to deploy").option("-e, --env <environment>", "environment (production or preview)").option("-y, --yes", "acknowledge readiness warnings and deploy anyway").option("--no-follow", "return immediately instead of streaming status").description("Ship the current project to a live URL (sets it up first if needed).").action(async (projectRef, opts, command) => {
|
|
965
1348
|
const c = await client(flags(command));
|
|
966
|
-
|
|
1349
|
+
const cwd = process.cwd();
|
|
1350
|
+
const deployOpts = {
|
|
1351
|
+
yes: Boolean(opts.yes),
|
|
1352
|
+
branch: opts.branch,
|
|
1353
|
+
env: opts.env,
|
|
1354
|
+
follow: opts.follow
|
|
1355
|
+
};
|
|
1356
|
+
if (projectRef) {
|
|
1357
|
+
const project2 = await resolveProject(c, projectRef);
|
|
1358
|
+
await deployProject(c, project2, deployOpts);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const link = await loadProjectLink(cwd);
|
|
1362
|
+
if (link) {
|
|
967
1363
|
let projects;
|
|
968
1364
|
try {
|
|
969
1365
|
projects = await c.listProjects();
|
|
970
1366
|
} catch (err) {
|
|
971
1367
|
fail(err);
|
|
972
1368
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const { project: project2 } = await interactiveCreate(c);
|
|
977
|
-
await offerDeploy(c, project2, { prompt: false });
|
|
1369
|
+
const linked = projects.find((proj) => proj.id === link.projectId);
|
|
1370
|
+
if (linked) {
|
|
1371
|
+
await deployProject(c, linked, deployOpts);
|
|
978
1372
|
return;
|
|
979
1373
|
}
|
|
980
1374
|
}
|
|
981
|
-
|
|
982
|
-
await
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1375
|
+
p3.intro(lockup("deploy"));
|
|
1376
|
+
const { project, ssrPending } = await onboardProject(c, { cwd });
|
|
1377
|
+
await saveProjectLink(cwd, { projectId: project.id, repoFullName: project.repoFullName });
|
|
1378
|
+
if (ssrPending) {
|
|
1379
|
+
p3.outro(
|
|
1380
|
+
`Finish the server-rendering upgrade in your browser, then run ${brand.violet(`gzero deploy ${project.slug}`)}.`
|
|
1381
|
+
);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
await offerDeploy(c, project, { prompt: false, deploy: deployOpts });
|
|
988
1385
|
});
|
|
989
1386
|
program.command("status").argument("[project]", "project id, slug, or name").description("Show the active deploy and readiness for a project.").action(async (projectRef, _opts, command) => {
|
|
990
1387
|
const c = await client(flags(command));
|
|
@@ -992,11 +1389,21 @@ program.command("status").argument("[project]", "project id, slug, or name").des
|
|
|
992
1389
|
try {
|
|
993
1390
|
const status = await c.getProjectStatus(project.id);
|
|
994
1391
|
const active = status.activeDeploy;
|
|
1392
|
+
const hosting = project.serviceType === "ssr_web" ? "server-rendered" : "static";
|
|
995
1393
|
console.log(`${symbol.bullet} ${pc4.bold(project.name)}${pc4.dim(` (${project.slug})`)}`);
|
|
996
1394
|
console.log(` status: ${active ? statusColor(active.status) : pc4.dim("no deploys")}`);
|
|
997
1395
|
if (active?.url) console.log(` url: ${pc4.cyan(active.url)}`);
|
|
1396
|
+
console.log(` hosting: ${hosting}`);
|
|
998
1397
|
console.log(` env vars: ${status.envConfigured ? pc4.green("configured") : pc4.yellow("missing")}`);
|
|
999
1398
|
console.log(` ready: ${status.readiness.canDeploy ? pc4.green("yes") : pc4.red("no")}`);
|
|
1399
|
+
const ssrBlocker = status.readiness.blockers.find((b) => b.id === "ssr-hosting");
|
|
1400
|
+
if (ssrBlocker) {
|
|
1401
|
+
console.log(
|
|
1402
|
+
`
|
|
1403
|
+
${symbol.warn} ${ssrBlocker.message}
|
|
1404
|
+
${pc4.dim(`Run`)} gzero ssr enable ${project.slug} ${pc4.dim("to host it as a server-rendered app.")}`
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1000
1407
|
} catch (err) {
|
|
1001
1408
|
fail(err);
|
|
1002
1409
|
}
|
|
@@ -1077,6 +1484,33 @@ program.command("promote").argument("[project]", "project id, slug, or name").de
|
|
|
1077
1484
|
fail(err);
|
|
1078
1485
|
}
|
|
1079
1486
|
});
|
|
1487
|
+
var ssr = program.command("ssr").description("Manage server-rendered (SSR) hosting.");
|
|
1488
|
+
ssr.command("enable").argument("[project]", "project id, slug, or name").option("--start-command <cmd>", 'Node start command for the server (e.g. "next start")').description("Switch a web project to server-rendered hosting (paid).").action(async (projectRef, opts, command) => {
|
|
1489
|
+
const c = await client(flags(command));
|
|
1490
|
+
const project = await resolveProject(c, projectRef);
|
|
1491
|
+
p3.intro(lockup("ssr"));
|
|
1492
|
+
const outcome = await upgradeToSsr(c, project, opts.startCommand ?? null);
|
|
1493
|
+
if (outcome === "enabled") {
|
|
1494
|
+
p3.outro(`Server rendering is on. Run ${brand.violet(`gzero deploy ${project.slug}`)} to build it.`);
|
|
1495
|
+
} else if (outcome === "checkout") {
|
|
1496
|
+
p3.outro(`Complete payment in your browser, then run ${brand.violet(`gzero deploy ${project.slug}`)}.`);
|
|
1497
|
+
} else {
|
|
1498
|
+
p3.outro("Server rendering is not available on your account.");
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
ssr.command("disable").argument("[project]", "project id, slug, or name").description("Switch a server-rendered project back to static hosting.").action(async (projectRef, _opts, command) => {
|
|
1502
|
+
const c = await client(flags(command));
|
|
1503
|
+
const project = await resolveProject(c, projectRef);
|
|
1504
|
+
const s = p3.spinner();
|
|
1505
|
+
s.start("Switching to static hosting");
|
|
1506
|
+
try {
|
|
1507
|
+
await c.disableSsr(project.id);
|
|
1508
|
+
s.stop(`${symbol.ok} ${brand.violet(project.name)} is now a static site.`);
|
|
1509
|
+
} catch (err) {
|
|
1510
|
+
s.stop("Could not disable server rendering");
|
|
1511
|
+
fail(err);
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1080
1514
|
var env = program.command("env").description("Manage project environment variables.");
|
|
1081
1515
|
env.command("list").argument("[project]", "project id, slug, or name").description("List env var keys for a project.").action(async (projectRef, _opts, command) => {
|
|
1082
1516
|
const c = await client(flags(command));
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gzero-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "GroundZero command-line interface: deploy UIs and APIs, manage env, domains, and billing.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "LICENSE.md",
|
|
6
6
|
"homepage": "https://gzer0.app/docs",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
-
"gzero": "dist/index.js"
|
|
9
|
+
"gzero": "dist/index.js",
|
|
10
|
+
"g0": "dist/index.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|
|
13
14
|
"README.md",
|
|
14
|
-
"LICENSE"
|
|
15
|
+
"LICENSE.md"
|
|
15
16
|
],
|
|
16
17
|
"publishConfig": {
|
|
17
|
-
"tag": "
|
|
18
|
+
"tag": "latest"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"build": "tsup && chmod +x dist/index.js",
|