@zerodeploy/cli 0.1.1 → 0.1.2
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 +62 -0
- package/dist/cli.js +242 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,6 +214,68 @@ Delete a deploy token.
|
|
|
214
214
|
zerodeploy token delete 019b1234 --org my-company --site my-website
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
+
### Custom Domains
|
|
218
|
+
|
|
219
|
+
Connect your own domain to any ZeroDeploy site with automatic SSL.
|
|
220
|
+
|
|
221
|
+
#### `zerodeploy domain add <domain> --org <org> --site <site>`
|
|
222
|
+
|
|
223
|
+
Add a custom domain to a site. Returns DNS verification instructions.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
zerodeploy domain add www.example.com --org my-company --site my-website
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### `zerodeploy domain verify <domain> --org <org> --site <site>`
|
|
230
|
+
|
|
231
|
+
Verify domain ownership after adding the TXT record to your DNS.
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
zerodeploy domain verify www.example.com --org my-company --site my-website
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `zerodeploy domain list --org <org> --site <site>`
|
|
238
|
+
|
|
239
|
+
List all custom domains for a site.
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
zerodeploy domain list --org my-company --site my-website
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `zerodeploy domain remove <domain> --org <org> --site <site>`
|
|
246
|
+
|
|
247
|
+
Remove a custom domain from a site.
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
zerodeploy domain remove www.example.com --org my-company --site my-website
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### `zerodeploy domain redirect <domain> --org <org> --site <site> --mode <mode>`
|
|
254
|
+
|
|
255
|
+
Set redirect mode for a custom domain. This allows automatic redirects between www and apex (non-www) domains.
|
|
256
|
+
|
|
257
|
+
**Options:**
|
|
258
|
+
- `--mode <mode>` - Redirect mode: `none`, `www_to_apex`, or `apex_to_www`
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Redirect www.example.com to example.com
|
|
262
|
+
zerodeploy domain redirect example.com --org my-company --site my-website --mode www_to_apex
|
|
263
|
+
|
|
264
|
+
# Redirect example.com to www.example.com
|
|
265
|
+
zerodeploy domain redirect www.example.com --org my-company --site my-website --mode apex_to_www
|
|
266
|
+
|
|
267
|
+
# Disable redirects
|
|
268
|
+
zerodeploy domain redirect example.com --org my-company --site my-website --mode none
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Custom Domain Setup:**
|
|
272
|
+
|
|
273
|
+
1. Add the domain: `zerodeploy domain add www.example.com --org my-org --site my-site`
|
|
274
|
+
2. Add the TXT record to your DNS (shown in output)
|
|
275
|
+
3. Verify ownership: `zerodeploy domain verify www.example.com --org my-org --site my-site`
|
|
276
|
+
4. Add the CNAME record to your DNS (shown in output)
|
|
277
|
+
5. Your site is now live at `https://www.example.com`
|
|
278
|
+
|
|
217
279
|
## Configuration File
|
|
218
280
|
|
|
219
281
|
Create a `zerodeploy.json` in your project root:
|
package/dist/cli.js
CHANGED
|
@@ -3453,7 +3453,7 @@ var domainAddCommand = new Command2("add").description("Add a custom domain to a
|
|
|
3453
3453
|
console.log(` Value: ${data.verification.recordValue}`);
|
|
3454
3454
|
console.log();
|
|
3455
3455
|
console.log("After adding the record, run:");
|
|
3456
|
-
console.log(` zerodeploy domain verify ${data.
|
|
3456
|
+
console.log(` zerodeploy domain verify ${data.domain} --org ${options.org} --site ${options.site}`);
|
|
3457
3457
|
console.log();
|
|
3458
3458
|
} catch (err) {
|
|
3459
3459
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -3474,6 +3474,16 @@ function formatStatus(status) {
|
|
|
3474
3474
|
return status;
|
|
3475
3475
|
}
|
|
3476
3476
|
}
|
|
3477
|
+
function formatRedirect(mode) {
|
|
3478
|
+
switch (mode) {
|
|
3479
|
+
case "www_to_apex":
|
|
3480
|
+
return "www→apex";
|
|
3481
|
+
case "apex_to_www":
|
|
3482
|
+
return "apex→www";
|
|
3483
|
+
default:
|
|
3484
|
+
return "";
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3477
3487
|
var domainListCommand = new Command2("list").description("List custom domains for a site").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").action(async (options) => {
|
|
3478
3488
|
const token = loadToken();
|
|
3479
3489
|
if (!token) {
|
|
@@ -3501,7 +3511,9 @@ var domainListCommand = new Command2("list").description("List custom domains fo
|
|
|
3501
3511
|
console.log("Custom Domains:");
|
|
3502
3512
|
console.log();
|
|
3503
3513
|
for (const d of domains) {
|
|
3504
|
-
|
|
3514
|
+
const redirect = formatRedirect(d.redirect_mode);
|
|
3515
|
+
const redirectStr = redirect ? ` [${redirect}]` : "";
|
|
3516
|
+
console.log(` ${d.domain.padEnd(30)} ${formatStatus(d.verification_status).padEnd(15)}${redirectStr}`);
|
|
3505
3517
|
}
|
|
3506
3518
|
console.log();
|
|
3507
3519
|
} catch (err) {
|
|
@@ -3511,14 +3523,33 @@ var domainListCommand = new Command2("list").description("List custom domains fo
|
|
|
3511
3523
|
});
|
|
3512
3524
|
|
|
3513
3525
|
// src/commands/domain/verify.ts
|
|
3514
|
-
var domainVerifyCommand = new Command2("verify").description("Verify ownership of a custom domain").argument("<
|
|
3526
|
+
var domainVerifyCommand = new Command2("verify").description("Verify ownership of a custom domain").argument("<domain>", "Domain to verify (e.g., www.example.com)").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").action(async (domainName, options) => {
|
|
3515
3527
|
const token = loadToken();
|
|
3516
3528
|
if (!token) {
|
|
3517
3529
|
console.log("Not logged in. Run: zerodeploy login");
|
|
3518
3530
|
return;
|
|
3519
3531
|
}
|
|
3520
3532
|
try {
|
|
3521
|
-
const
|
|
3533
|
+
const listRes = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains`, {
|
|
3534
|
+
headers: {
|
|
3535
|
+
Authorization: `Bearer ${token}`
|
|
3536
|
+
}
|
|
3537
|
+
});
|
|
3538
|
+
if (!listRes.ok) {
|
|
3539
|
+
const error = await listRes.json();
|
|
3540
|
+
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
3541
|
+
}
|
|
3542
|
+
const domains = await listRes.json();
|
|
3543
|
+
const domain = domains.find((d) => d.domain === domainName);
|
|
3544
|
+
if (!domain) {
|
|
3545
|
+
console.error(`
|
|
3546
|
+
❌ Domain not found: ${domainName}`);
|
|
3547
|
+
console.log();
|
|
3548
|
+
console.log("Add it first with:");
|
|
3549
|
+
console.log(` zerodeploy domain add ${domainName} --org ${options.org} --site ${options.site}`);
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
const res = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains/${domain.id}/verify`, {
|
|
3522
3553
|
method: "POST",
|
|
3523
3554
|
headers: {
|
|
3524
3555
|
Authorization: `Bearer ${token}`,
|
|
@@ -3528,7 +3559,7 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
3528
3559
|
const data = await res.json();
|
|
3529
3560
|
if (!res.ok) {
|
|
3530
3561
|
console.error(`
|
|
3531
|
-
❌ Verification failed for
|
|
3562
|
+
❌ Verification failed for ${domainName}`);
|
|
3532
3563
|
if (data.message) {
|
|
3533
3564
|
console.log();
|
|
3534
3565
|
console.log(data.message);
|
|
@@ -3536,7 +3567,7 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
3536
3567
|
console.log();
|
|
3537
3568
|
console.log("Tips:");
|
|
3538
3569
|
console.log(" • DNS changes can take up to 48 hours to propagate");
|
|
3539
|
-
console.log(
|
|
3570
|
+
console.log(` • Verify the TXT record is set correctly using: dig TXT _zerodeploy.${domainName}`);
|
|
3540
3571
|
console.log(" • Try again in a few minutes");
|
|
3541
3572
|
return;
|
|
3542
3573
|
}
|
|
@@ -3587,14 +3618,33 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
3587
3618
|
});
|
|
3588
3619
|
|
|
3589
3620
|
// src/commands/domain/remove.ts
|
|
3590
|
-
var domainRemoveCommand = new Command2("remove").description("Remove a custom domain from a site").argument("<
|
|
3621
|
+
var domainRemoveCommand = new Command2("remove").description("Remove a custom domain from a site").argument("<domain>", "Domain to remove (e.g., www.example.com)").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").action(async (domainName, options) => {
|
|
3591
3622
|
const token = loadToken();
|
|
3592
3623
|
if (!token) {
|
|
3593
3624
|
console.log("Not logged in. Run: zerodeploy login");
|
|
3594
3625
|
return;
|
|
3595
3626
|
}
|
|
3596
3627
|
try {
|
|
3597
|
-
const
|
|
3628
|
+
const listRes = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains`, {
|
|
3629
|
+
headers: {
|
|
3630
|
+
Authorization: `Bearer ${token}`
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
if (!listRes.ok) {
|
|
3634
|
+
const error = await listRes.json();
|
|
3635
|
+
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
3636
|
+
}
|
|
3637
|
+
const domains = await listRes.json();
|
|
3638
|
+
const domain = domains.find((d) => d.domain === domainName);
|
|
3639
|
+
if (!domain) {
|
|
3640
|
+
console.error(`
|
|
3641
|
+
❌ Domain not found: ${domainName}`);
|
|
3642
|
+
console.log();
|
|
3643
|
+
console.log("List domains with:");
|
|
3644
|
+
console.log(` zerodeploy domain list --org ${options.org} --site ${options.site}`);
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
const res = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains/${domain.id}`, {
|
|
3598
3648
|
method: "DELETE",
|
|
3599
3649
|
headers: {
|
|
3600
3650
|
Authorization: `Bearer ${token}`
|
|
@@ -3612,12 +3662,77 @@ var domainRemoveCommand = new Command2("remove").description("Remove a custom do
|
|
|
3612
3662
|
}
|
|
3613
3663
|
});
|
|
3614
3664
|
|
|
3665
|
+
// src/commands/domain/redirect.ts
|
|
3666
|
+
function formatRedirectMode(mode) {
|
|
3667
|
+
switch (mode) {
|
|
3668
|
+
case "none":
|
|
3669
|
+
return "No redirect";
|
|
3670
|
+
case "www_to_apex":
|
|
3671
|
+
return "www → apex (e.g., www.example.com → example.com)";
|
|
3672
|
+
case "apex_to_www":
|
|
3673
|
+
return "apex → www (e.g., example.com → www.example.com)";
|
|
3674
|
+
default:
|
|
3675
|
+
return mode;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
var domainRedirectCommand = new Command2("redirect").description("Set redirect mode for a custom domain").argument("<domain>", "Domain name (e.g., example.com)").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").requiredOption("--mode <mode>", "Redirect mode: none, www_to_apex, or apex_to_www").action(async (domain, options) => {
|
|
3679
|
+
const token = loadToken();
|
|
3680
|
+
if (!token) {
|
|
3681
|
+
console.log("Not logged in. Run: zerodeploy login");
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
const validModes = ["none", "www_to_apex", "apex_to_www"];
|
|
3685
|
+
if (!validModes.includes(options.mode)) {
|
|
3686
|
+
console.error(`Invalid mode: ${options.mode}`);
|
|
3687
|
+
console.error("Valid modes: none, www_to_apex, apex_to_www");
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
try {
|
|
3691
|
+
const listRes = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains`, {
|
|
3692
|
+
headers: {
|
|
3693
|
+
Authorization: `Bearer ${token}`
|
|
3694
|
+
}
|
|
3695
|
+
});
|
|
3696
|
+
if (!listRes.ok) {
|
|
3697
|
+
const error = await listRes.json();
|
|
3698
|
+
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
3699
|
+
}
|
|
3700
|
+
const domains = await listRes.json();
|
|
3701
|
+
const targetDomain = domains.find((d) => d.domain === domain.toLowerCase());
|
|
3702
|
+
if (!targetDomain) {
|
|
3703
|
+
console.error(`Domain not found: ${domain}`);
|
|
3704
|
+
console.error(`Run 'zerodeploy domain list --org ${options.org} --site ${options.site}' to see configured domains.`);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const res = await fetch(`${API_URL}/orgs/${options.org}/sites/${options.site}/domains/${targetDomain.id}/redirect`, {
|
|
3708
|
+
method: "PATCH",
|
|
3709
|
+
headers: {
|
|
3710
|
+
Authorization: `Bearer ${token}`,
|
|
3711
|
+
"Content-Type": "application/json"
|
|
3712
|
+
},
|
|
3713
|
+
body: JSON.stringify({ redirectMode: options.mode })
|
|
3714
|
+
});
|
|
3715
|
+
if (!res.ok) {
|
|
3716
|
+
const error = await res.json();
|
|
3717
|
+
throw new Error(error.error || `API Error ${res.status}`);
|
|
3718
|
+
}
|
|
3719
|
+
const data = await res.json();
|
|
3720
|
+
console.log(`
|
|
3721
|
+
✅ Redirect mode updated for ${data.domain}`);
|
|
3722
|
+
console.log(` Mode: ${formatRedirectMode(data.redirect_mode)}`);
|
|
3723
|
+
console.log();
|
|
3724
|
+
} catch (err) {
|
|
3725
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
3726
|
+
console.error("Failed to update redirect mode:", message);
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
|
|
3615
3730
|
// src/commands/domain/index.ts
|
|
3616
|
-
var domainCommand = new Command2("domain").description("Manage custom domains").addCommand(domainAddCommand).addCommand(domainListCommand).addCommand(domainVerifyCommand).addCommand(domainRemoveCommand);
|
|
3731
|
+
var domainCommand = new Command2("domain").description("Manage custom domains").addCommand(domainAddCommand).addCommand(domainListCommand).addCommand(domainVerifyCommand).addCommand(domainRemoveCommand).addCommand(domainRedirectCommand);
|
|
3617
3732
|
|
|
3618
3733
|
// src/commands/deploy/index.ts
|
|
3619
|
-
import { resolve as resolve3 } from "node:path";
|
|
3620
|
-
import { stat as stat2 } from "node:fs/promises";
|
|
3734
|
+
import { resolve as resolve3, basename } from "node:path";
|
|
3735
|
+
import { stat as stat2, writeFile } from "node:fs/promises";
|
|
3621
3736
|
import { spawn } from "node:child_process";
|
|
3622
3737
|
|
|
3623
3738
|
// src/utils/files.ts
|
|
@@ -4335,6 +4450,29 @@ function getConfigPath(cwd = process.cwd()) {
|
|
|
4335
4450
|
return resolve2(cwd, CONFIG_FILENAME);
|
|
4336
4451
|
}
|
|
4337
4452
|
|
|
4453
|
+
// src/utils/prompt.ts
|
|
4454
|
+
import * as readline3 from "node:readline";
|
|
4455
|
+
async function confirm(message, defaultValue = true) {
|
|
4456
|
+
const rl = readline3.createInterface({
|
|
4457
|
+
input: process.stdin,
|
|
4458
|
+
output: process.stdout
|
|
4459
|
+
});
|
|
4460
|
+
const hint = defaultValue ? "[Y/n]" : "[y/N]";
|
|
4461
|
+
return new Promise((resolve3) => {
|
|
4462
|
+
rl.question(`${message} ${hint} `, (answer) => {
|
|
4463
|
+
rl.close();
|
|
4464
|
+
const normalized = answer.trim().toLowerCase();
|
|
4465
|
+
if (normalized === "") {
|
|
4466
|
+
resolve3(defaultValue);
|
|
4467
|
+
} else if (normalized === "y" || normalized === "yes") {
|
|
4468
|
+
resolve3(true);
|
|
4469
|
+
} else {
|
|
4470
|
+
resolve3(false);
|
|
4471
|
+
}
|
|
4472
|
+
});
|
|
4473
|
+
});
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4338
4476
|
// src/commands/deploy/list.ts
|
|
4339
4477
|
var deployListCommand = new Command2("list").description("List deployments for a site").argument("<siteSlug>", "Site slug").requiredOption("--org <orgSlug>", "Organization slug").option("--limit <number>", "Number of deployments to show", "10").action(async (siteSlug, options) => {
|
|
4340
4478
|
const token = loadToken();
|
|
@@ -4414,7 +4552,37 @@ var deployRollbackCommand = new Command2("rollback").description("Rollback to a
|
|
|
4414
4552
|
}
|
|
4415
4553
|
});
|
|
4416
4554
|
|
|
4555
|
+
// src/commands/deploy/promote.ts
|
|
4556
|
+
var deployPromoteCommand = new Command2("promote").description("Promote a preview deployment to production").argument("<deploymentId>", "Deployment ID (or first 8 chars) to promote").action(async (deploymentId) => {
|
|
4557
|
+
const token = loadToken();
|
|
4558
|
+
if (!token) {
|
|
4559
|
+
console.log("Not logged in. Run: zerodeploy login");
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
try {
|
|
4563
|
+
const client = getClient(token);
|
|
4564
|
+
const res = await client.deployments[":id"].rollback.$post({
|
|
4565
|
+
param: { id: deploymentId }
|
|
4566
|
+
});
|
|
4567
|
+
if (!res.ok) {
|
|
4568
|
+
const error = await res.json();
|
|
4569
|
+
console.error(`Error: ${error.error}`);
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
const result = await res.json();
|
|
4573
|
+
console.log("Deployment promoted to production!");
|
|
4574
|
+
console.log(` Deployment: ${result.deployment.id}`);
|
|
4575
|
+
console.log(` URL: ${result.deployment.url}`);
|
|
4576
|
+
} catch (err) {
|
|
4577
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
4578
|
+
console.error("Failed to promote deployment:", message);
|
|
4579
|
+
}
|
|
4580
|
+
});
|
|
4581
|
+
|
|
4417
4582
|
// src/commands/deploy/index.ts
|
|
4583
|
+
function slugify(input) {
|
|
4584
|
+
return input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "");
|
|
4585
|
+
}
|
|
4418
4586
|
async function runCommand(command, cwd) {
|
|
4419
4587
|
return new Promise((promiseResolve) => {
|
|
4420
4588
|
const [cmd, ...args] = command.split(" ");
|
|
@@ -4473,26 +4641,63 @@ async function uploadArchive(token, uploadUrl, archive) {
|
|
|
4473
4641
|
});
|
|
4474
4642
|
return res.ok;
|
|
4475
4643
|
}
|
|
4476
|
-
var deployCommand = new Command2("deploy").description("Deploy a site").argument("[site]", "Site slug").option("--org <org>", "Organization slug").option("--dir <directory>", "Directory to deploy (default: auto-detect)").option("--build", "Run build command before deploying").option("--no-build", "Skip build step").option("--build-command <command>", "Override build command").option("--install", "Run install command before building").option("--pr <number>", "PR number (for GitHub Actions)").option("--pr-title <title>", "PR title").option("--commit <sha>", "Commit SHA").option("--commit-message <message>", "Commit message").option("--branch <branch>", "Branch name").option("--github-output", "Output deployment info in GitHub Actions format").enablePositionalOptions().addCommand(deployListCommand).addCommand(deployRollbackCommand).action(async (siteSlugArg, options) => {
|
|
4644
|
+
var deployCommand = new Command2("deploy").description("Deploy a site").argument("[site]", "Site slug").option("--org <org>", "Organization slug").option("--dir <directory>", "Directory to deploy (default: auto-detect)").option("--build", "Run build command before deploying").option("--no-build", "Skip build step").option("--build-command <command>", "Override build command").option("--install", "Run install command before building").option("--preview", "Deploy without setting as current (preview only)").option("--pr <number>", "PR number (for GitHub Actions)").option("--pr-title <title>", "PR title").option("--commit <sha>", "Commit SHA").option("--commit-message <message>", "Commit message").option("--branch <branch>", "Branch name").option("--github-output", "Output deployment info in GitHub Actions format").enablePositionalOptions().addCommand(deployListCommand).addCommand(deployRollbackCommand).addCommand(deployPromoteCommand).action(async (siteSlugArg, options) => {
|
|
4477
4645
|
const cwd = process.cwd();
|
|
4478
|
-
const config = loadProjectConfig(cwd);
|
|
4479
|
-
const siteSlug = siteSlugArg || config.site;
|
|
4480
|
-
const orgSlug = options.org || config.org;
|
|
4481
|
-
const dirOption = options.dir || config.dir;
|
|
4482
|
-
if (!siteSlug) {
|
|
4483
|
-
console.log("Error: Site is required. Provide as argument or in zerodeploy.json");
|
|
4484
|
-
deployCommand.help();
|
|
4485
|
-
return;
|
|
4486
|
-
}
|
|
4487
|
-
if (!orgSlug) {
|
|
4488
|
-
console.log('Error: --org is required (or set "org" in zerodeploy.json)');
|
|
4489
|
-
return;
|
|
4490
|
-
}
|
|
4491
4646
|
const token = loadToken();
|
|
4492
4647
|
if (!token) {
|
|
4493
4648
|
console.log("Not logged in. Run: zerodeploy login");
|
|
4494
4649
|
return;
|
|
4495
4650
|
}
|
|
4651
|
+
const config = loadProjectConfig(cwd);
|
|
4652
|
+
let siteSlug = siteSlugArg || config.site;
|
|
4653
|
+
let orgSlug = options.org || config.org;
|
|
4654
|
+
const dirOption = options.dir || config.dir;
|
|
4655
|
+
if (!siteSlug || !orgSlug) {
|
|
4656
|
+
const client = getClient(token);
|
|
4657
|
+
const meRes = await client.auth.me.$get();
|
|
4658
|
+
if (!meRes.ok) {
|
|
4659
|
+
console.log("Error: Failed to fetch user info");
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4662
|
+
const userInfo = await meRes.json();
|
|
4663
|
+
if (!orgSlug) {
|
|
4664
|
+
if (!userInfo.personalOrg) {
|
|
4665
|
+
console.log("Error: No personal org found. Please create one with: zerodeploy org create <name>");
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
orgSlug = userInfo.personalOrg.slug;
|
|
4669
|
+
}
|
|
4670
|
+
if (!siteSlug) {
|
|
4671
|
+
const folderName = basename(cwd);
|
|
4672
|
+
const suggestedName = slugify(folderName) || "my-site";
|
|
4673
|
+
console.log("");
|
|
4674
|
+
const shouldCreate = await confirm(`No site configured. Create site "${suggestedName}"?`, true);
|
|
4675
|
+
if (!shouldCreate) {
|
|
4676
|
+
console.log("Deploy cancelled. Create a site first with: zerodeploy site create");
|
|
4677
|
+
return;
|
|
4678
|
+
}
|
|
4679
|
+
console.log("");
|
|
4680
|
+
console.log(`Creating site "${suggestedName}"...`);
|
|
4681
|
+
const createRes = await client.orgs[":orgSlug"].sites.$post({
|
|
4682
|
+
param: { orgSlug },
|
|
4683
|
+
json: { name: suggestedName, subdomain: suggestedName }
|
|
4684
|
+
});
|
|
4685
|
+
if (!createRes.ok) {
|
|
4686
|
+
const error = await createRes.json();
|
|
4687
|
+
console.log(`Error: ${error.error || "Failed to create site"}`);
|
|
4688
|
+
return;
|
|
4689
|
+
}
|
|
4690
|
+
const site = await createRes.json();
|
|
4691
|
+
siteSlug = site.slug;
|
|
4692
|
+
console.log(`Created site: ${site.subdomain}.zerodeploy.app`);
|
|
4693
|
+
const configPath = getConfigPath(cwd);
|
|
4694
|
+
const newConfig = { org: orgSlug, site: siteSlug, dir: dirOption || config.dir };
|
|
4695
|
+
await writeFile(configPath, JSON.stringify(newConfig, null, 2) + `
|
|
4696
|
+
`);
|
|
4697
|
+
console.log(`Saved config to zerodeploy.json`);
|
|
4698
|
+
console.log("");
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4496
4701
|
const framework = await detectFramework(cwd);
|
|
4497
4702
|
if (framework) {
|
|
4498
4703
|
console.log(`Detected: ${framework.name}`);
|
|
@@ -4635,16 +4840,24 @@ Error: Build failed`);
|
|
|
4635
4840
|
"Content-Type": "application/json",
|
|
4636
4841
|
Authorization: `Bearer ${token}`
|
|
4637
4842
|
},
|
|
4638
|
-
body: JSON.stringify({})
|
|
4843
|
+
body: JSON.stringify({ preview: options.preview || false })
|
|
4639
4844
|
});
|
|
4640
4845
|
if (!finalizeRes.ok) {
|
|
4641
4846
|
console.log("Error: Failed to finalize deployment");
|
|
4642
4847
|
return;
|
|
4643
4848
|
}
|
|
4644
4849
|
console.log("");
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4850
|
+
if (options.preview) {
|
|
4851
|
+
console.log("Preview deployment created!");
|
|
4852
|
+
console.log(`Preview: ${deployment.previewUrl}`);
|
|
4853
|
+
console.log("");
|
|
4854
|
+
console.log(`To make this deployment live, run:`);
|
|
4855
|
+
console.log(` zerodeploy deploy promote ${deployment.id.slice(0, 8)}`);
|
|
4856
|
+
} else {
|
|
4857
|
+
console.log("Deployment successful!");
|
|
4858
|
+
console.log(`URL: ${deployment.url}`);
|
|
4859
|
+
console.log(`Preview: ${deployment.previewUrl}`);
|
|
4860
|
+
}
|
|
4648
4861
|
if (options.githubOutput) {
|
|
4649
4862
|
const githubOutputFile = process.env.GITHUB_OUTPUT;
|
|
4650
4863
|
if (githubOutputFile) {
|