@zerodeploy/cli 0.1.3 → 0.1.4
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/dist/cli.js +1018 -357
- package/dist/index.js +6915 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2745,6 +2745,48 @@ import os2 from "os";
|
|
|
2745
2745
|
import path2 from "path";
|
|
2746
2746
|
|
|
2747
2747
|
// src/utils/errors.ts
|
|
2748
|
+
var ExitCode = {
|
|
2749
|
+
SUCCESS: 0,
|
|
2750
|
+
AUTH_ERROR: 1,
|
|
2751
|
+
NOT_FOUND: 2,
|
|
2752
|
+
VALIDATION_ERROR: 3,
|
|
2753
|
+
RATE_LIMIT: 4,
|
|
2754
|
+
SERVER_ERROR: 5,
|
|
2755
|
+
NETWORK_ERROR: 6,
|
|
2756
|
+
BILLING_ERROR: 7
|
|
2757
|
+
};
|
|
2758
|
+
function getExitCode(status, errorCode) {
|
|
2759
|
+
if (errorCode) {
|
|
2760
|
+
if (errorCode === "billing_required" || errorCode.includes("billing")) {
|
|
2761
|
+
return ExitCode.BILLING_ERROR;
|
|
2762
|
+
}
|
|
2763
|
+
if (errorCode === "validation_error" || errorCode.includes("invalid")) {
|
|
2764
|
+
return ExitCode.VALIDATION_ERROR;
|
|
2765
|
+
}
|
|
2766
|
+
if (errorCode === "rate_limit_exceeded" || errorCode.includes("rate_limit")) {
|
|
2767
|
+
return ExitCode.RATE_LIMIT;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (status === 402) {
|
|
2771
|
+
return ExitCode.BILLING_ERROR;
|
|
2772
|
+
}
|
|
2773
|
+
if (status === 401 || status === 403) {
|
|
2774
|
+
return ExitCode.AUTH_ERROR;
|
|
2775
|
+
}
|
|
2776
|
+
if (status === 404) {
|
|
2777
|
+
return ExitCode.NOT_FOUND;
|
|
2778
|
+
}
|
|
2779
|
+
if (status === 422 || status === 400) {
|
|
2780
|
+
return ExitCode.VALIDATION_ERROR;
|
|
2781
|
+
}
|
|
2782
|
+
if (status === 429) {
|
|
2783
|
+
return ExitCode.RATE_LIMIT;
|
|
2784
|
+
}
|
|
2785
|
+
if (status >= 500) {
|
|
2786
|
+
return ExitCode.SERVER_ERROR;
|
|
2787
|
+
}
|
|
2788
|
+
return ExitCode.AUTH_ERROR;
|
|
2789
|
+
}
|
|
2748
2790
|
function parseApiError(body) {
|
|
2749
2791
|
if (!body || typeof body !== "object") {
|
|
2750
2792
|
return {
|
|
@@ -2809,6 +2851,18 @@ function displayError(error) {
|
|
|
2809
2851
|
}
|
|
2810
2852
|
console.error();
|
|
2811
2853
|
}
|
|
2854
|
+
async function handleApiError(response) {
|
|
2855
|
+
let body;
|
|
2856
|
+
try {
|
|
2857
|
+
body = await response.json();
|
|
2858
|
+
} catch {
|
|
2859
|
+
body = null;
|
|
2860
|
+
}
|
|
2861
|
+
const error = parseApiError(body);
|
|
2862
|
+
displayError(error);
|
|
2863
|
+
const exitCode = getExitCode(response.status, error.code);
|
|
2864
|
+
process.exit(exitCode);
|
|
2865
|
+
}
|
|
2812
2866
|
function displayNetworkError(error) {
|
|
2813
2867
|
if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
|
|
2814
2868
|
displayError({
|
|
@@ -2838,15 +2892,19 @@ function displayAuthError() {
|
|
|
2838
2892
|
docs: "https://zerodeploy.dev/docs/cli/auth"
|
|
2839
2893
|
});
|
|
2840
2894
|
}
|
|
2895
|
+
function handleAuthError() {
|
|
2896
|
+
displayAuthError();
|
|
2897
|
+
process.exit(ExitCode.AUTH_ERROR);
|
|
2898
|
+
}
|
|
2841
2899
|
|
|
2842
2900
|
// src/auth/token.ts
|
|
2843
2901
|
var DIR = path2.join(os2.homedir(), ".zerodeploy");
|
|
2844
2902
|
var TOKEN_PATH = path2.join(DIR, "token");
|
|
2845
2903
|
function saveToken(token) {
|
|
2846
2904
|
if (!fs6.existsSync(DIR)) {
|
|
2847
|
-
fs6.mkdirSync(DIR);
|
|
2905
|
+
fs6.mkdirSync(DIR, { mode: 448 });
|
|
2848
2906
|
}
|
|
2849
|
-
fs6.writeFileSync(TOKEN_PATH, token, "utf8");
|
|
2907
|
+
fs6.writeFileSync(TOKEN_PATH, token, { encoding: "utf8", mode: 384 });
|
|
2850
2908
|
}
|
|
2851
2909
|
function loadToken() {
|
|
2852
2910
|
const envToken = process.env.ZERODEPLOY_TOKEN;
|
|
@@ -2881,8 +2939,8 @@ var loginCommand = new Command2("login").description("Login via GitHub OAuth wit
|
|
|
2881
2939
|
if (!token) {
|
|
2882
2940
|
res.writeHead(400);
|
|
2883
2941
|
res.end("Login failed: missing token");
|
|
2884
|
-
|
|
2885
|
-
process.exit(
|
|
2942
|
+
displayError({ code: "auth_error", message: "Login failed: no token received" });
|
|
2943
|
+
process.exit(ExitCode.AUTH_ERROR);
|
|
2886
2944
|
}
|
|
2887
2945
|
saveToken(token);
|
|
2888
2946
|
if (pending) {
|
|
@@ -3204,7 +3262,8 @@ var hc = (baseUrl, options) => createProxy(function proxyCallback(opts) {
|
|
|
3204
3262
|
var DEFAULT_OPTIONS = {
|
|
3205
3263
|
maxRetries: 3,
|
|
3206
3264
|
baseDelayMs: 1000,
|
|
3207
|
-
maxDelayMs: 1e4
|
|
3265
|
+
maxDelayMs: 1e4,
|
|
3266
|
+
timeoutMs: 30000
|
|
3208
3267
|
};
|
|
3209
3268
|
var RETRYABLE_STATUS_CODES = new Set([
|
|
3210
3269
|
408,
|
|
@@ -3277,8 +3336,17 @@ async function fetchWithRetry(input, init, options) {
|
|
|
3277
3336
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
3278
3337
|
let lastError;
|
|
3279
3338
|
for (let attempt = 0;attempt <= opts.maxRetries; attempt++) {
|
|
3339
|
+
let controller;
|
|
3340
|
+
let timeoutId;
|
|
3341
|
+
if (opts.timeoutMs > 0) {
|
|
3342
|
+
controller = new AbortController;
|
|
3343
|
+
timeoutId = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
3344
|
+
}
|
|
3280
3345
|
try {
|
|
3281
|
-
const
|
|
3346
|
+
const fetchInit = controller ? { ...init, signal: controller.signal } : init ?? {};
|
|
3347
|
+
const response = await fetch(input, fetchInit);
|
|
3348
|
+
if (timeoutId)
|
|
3349
|
+
clearTimeout(timeoutId);
|
|
3282
3350
|
if (isRetryableStatus(response.status) && attempt < opts.maxRetries) {
|
|
3283
3351
|
const headerDelay = response.status === 429 ? getDelayFromHeaders(response) : null;
|
|
3284
3352
|
const delayMs = headerDelay ?? calculateDelay(attempt, opts.baseDelayMs, opts.maxDelayMs);
|
|
@@ -3289,6 +3357,8 @@ async function fetchWithRetry(input, init, options) {
|
|
|
3289
3357
|
}
|
|
3290
3358
|
return response;
|
|
3291
3359
|
} catch (error) {
|
|
3360
|
+
if (timeoutId)
|
|
3361
|
+
clearTimeout(timeoutId);
|
|
3292
3362
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3293
3363
|
if (!isRetryableError(error) || attempt >= opts.maxRetries) {
|
|
3294
3364
|
throw lastError;
|
|
@@ -3311,6 +3381,35 @@ function formatRetryMessage(attempt, maxRetries, error) {
|
|
|
3311
3381
|
return `Retry ${attempt}/${maxRetries} (${errorDesc})`;
|
|
3312
3382
|
}
|
|
3313
3383
|
|
|
3384
|
+
// ../../packages/api-client/src/interceptors.ts
|
|
3385
|
+
async function applyRequestInterceptors(request, interceptors) {
|
|
3386
|
+
let req = request;
|
|
3387
|
+
for (const interceptor of interceptors) {
|
|
3388
|
+
req = await interceptor(req);
|
|
3389
|
+
}
|
|
3390
|
+
return req;
|
|
3391
|
+
}
|
|
3392
|
+
async function applyResponseInterceptors(response, interceptors) {
|
|
3393
|
+
let res = response;
|
|
3394
|
+
for (const interceptor of interceptors) {
|
|
3395
|
+
res = await interceptor(res);
|
|
3396
|
+
}
|
|
3397
|
+
return res;
|
|
3398
|
+
}
|
|
3399
|
+
function createInterceptedFetch(baseFetch, interceptors) {
|
|
3400
|
+
return async (input, init) => {
|
|
3401
|
+
let request = new Request(input, init);
|
|
3402
|
+
if (interceptors.request?.length) {
|
|
3403
|
+
request = await applyRequestInterceptors(request, interceptors.request);
|
|
3404
|
+
}
|
|
3405
|
+
let response = await baseFetch(request);
|
|
3406
|
+
if (interceptors.response?.length) {
|
|
3407
|
+
response = await applyResponseInterceptors(response, interceptors.response);
|
|
3408
|
+
}
|
|
3409
|
+
return response;
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3314
3413
|
// ../../packages/api-client/src/index.ts
|
|
3315
3414
|
var DEFAULT_RETRY_OPTIONS = {
|
|
3316
3415
|
maxRetries: 3,
|
|
@@ -3329,6 +3428,10 @@ function createClient(baseUrl, token, options) {
|
|
|
3329
3428
|
} else if (options?.useCredentials) {
|
|
3330
3429
|
fetchFn = (input, init) => fetch(input, { ...init, credentials: "include" });
|
|
3331
3430
|
}
|
|
3431
|
+
if (options?.interceptors && (options.interceptors.request?.length || options.interceptors.response?.length)) {
|
|
3432
|
+
const baseFetch = fetchFn || fetch;
|
|
3433
|
+
fetchFn = createInterceptedFetch(baseFetch, options.interceptors);
|
|
3434
|
+
}
|
|
3332
3435
|
return hc(baseUrl, {
|
|
3333
3436
|
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
3334
3437
|
fetch: fetchFn
|
|
@@ -3353,18 +3456,22 @@ function getClient(token, retryOptions) {
|
|
|
3353
3456
|
}
|
|
3354
3457
|
|
|
3355
3458
|
// src/commands/whoami.ts
|
|
3356
|
-
var whoamiCommand = new Command2("whoami").description("Show the currently logged-in user").action(async () => {
|
|
3459
|
+
var whoamiCommand = new Command2("whoami").description("Show the currently logged-in user").option("--json", "Output as JSON").action(async (options) => {
|
|
3357
3460
|
const token = loadToken();
|
|
3358
3461
|
if (!token) {
|
|
3359
|
-
|
|
3360
|
-
process.exit(1);
|
|
3462
|
+
handleAuthError();
|
|
3361
3463
|
}
|
|
3362
3464
|
try {
|
|
3363
3465
|
const client = getClient(token);
|
|
3364
3466
|
const res = await client.auth.me.$get();
|
|
3365
|
-
if (!res.ok)
|
|
3366
|
-
|
|
3467
|
+
if (!res.ok) {
|
|
3468
|
+
await handleApiError(res);
|
|
3469
|
+
}
|
|
3367
3470
|
const data = await res.json();
|
|
3471
|
+
if (options.json) {
|
|
3472
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3368
3475
|
console.log("Logged in as:");
|
|
3369
3476
|
console.log(` Username: ${data.username}`);
|
|
3370
3477
|
console.log(` Email: ${data.email || "(not set)"}`);
|
|
@@ -3376,8 +3483,9 @@ var whoamiCommand = new Command2("whoami").description("Show the currently logge
|
|
|
3376
3483
|
console.log(" We'll email you when your account is approved.");
|
|
3377
3484
|
}
|
|
3378
3485
|
} catch (err) {
|
|
3379
|
-
|
|
3380
|
-
|
|
3486
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3487
|
+
displayError({ code: "network_error", message: `Failed to fetch user info: ${message}` });
|
|
3488
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3381
3489
|
}
|
|
3382
3490
|
});
|
|
3383
3491
|
|
|
@@ -3396,19 +3504,16 @@ function formatUsageLine(label, current, max, suffix = "") {
|
|
|
3396
3504
|
const percentage = Math.round(current / max * 100);
|
|
3397
3505
|
return ` ${label.padEnd(24)} ${bar} ${current}/${max}${suffix} (${percentage}%)`;
|
|
3398
3506
|
}
|
|
3399
|
-
var usageCommand = new Command2("usage").description("Show current usage and
|
|
3507
|
+
var usageCommand = new Command2("usage").description("Show current usage and limits").option("--json", "Output as JSON").action(async (options) => {
|
|
3400
3508
|
const token = loadToken();
|
|
3401
3509
|
if (!token) {
|
|
3402
|
-
|
|
3403
|
-
process.exit(1);
|
|
3510
|
+
handleAuthError();
|
|
3404
3511
|
}
|
|
3405
3512
|
try {
|
|
3406
3513
|
const client = getClient(token);
|
|
3407
3514
|
const res = await client.auth.me.usage.$get();
|
|
3408
3515
|
if (!res.ok) {
|
|
3409
|
-
|
|
3410
|
-
displayError(parseApiError(body));
|
|
3411
|
-
process.exit(1);
|
|
3516
|
+
await handleApiError(res);
|
|
3412
3517
|
}
|
|
3413
3518
|
const data = await res.json();
|
|
3414
3519
|
if (options.json) {
|
|
@@ -3416,14 +3521,12 @@ var usageCommand = new Command2("usage").description("Show current usage and pla
|
|
|
3416
3521
|
return;
|
|
3417
3522
|
}
|
|
3418
3523
|
console.log();
|
|
3419
|
-
console.log(`Plan: ${data.plan.toUpperCase()}`);
|
|
3420
|
-
console.log();
|
|
3421
3524
|
console.log("Account Usage:");
|
|
3422
3525
|
console.log(formatUsageLine("Organizations", data.usage.orgs, data.limits.max_orgs));
|
|
3423
3526
|
console.log(formatUsageLine("Total Sites", data.usage.sites, data.limits.max_orgs * data.limits.max_sites_per_org));
|
|
3424
3527
|
console.log(formatUsageLine("Deployments (month)", data.usage.deployments_this_month, data.limits.max_deployments_per_month));
|
|
3425
3528
|
console.log();
|
|
3426
|
-
console.log("
|
|
3529
|
+
console.log("Limits:");
|
|
3427
3530
|
console.log(` Sites per org: ${data.limits.max_sites_per_org}`);
|
|
3428
3531
|
console.log(` Deployments per day: ${data.limits.max_deployments_per_day}`);
|
|
3429
3532
|
console.log(` Deployments per month: ${data.limits.max_deployments_per_month}`);
|
|
@@ -3448,66 +3551,71 @@ var usageCommand = new Command2("usage").description("Show current usage and pla
|
|
|
3448
3551
|
} catch (err) {
|
|
3449
3552
|
const message = err instanceof Error ? err.message : String(err);
|
|
3450
3553
|
displayError({
|
|
3451
|
-
code: "
|
|
3554
|
+
code: "network_error",
|
|
3452
3555
|
message: `Failed to fetch usage info: ${message}`
|
|
3453
3556
|
});
|
|
3454
|
-
process.exit(
|
|
3557
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3455
3558
|
}
|
|
3456
3559
|
});
|
|
3457
3560
|
|
|
3458
3561
|
// src/commands/org/list.ts
|
|
3459
|
-
var orgListCommand = new Command2("list").description("List your organizations").action(async () => {
|
|
3562
|
+
var orgListCommand = new Command2("list").description("List your organizations").option("--json", "Output as JSON").action(async (options) => {
|
|
3460
3563
|
const token = loadToken();
|
|
3461
3564
|
if (!token) {
|
|
3462
|
-
|
|
3463
|
-
return;
|
|
3565
|
+
handleAuthError();
|
|
3464
3566
|
}
|
|
3465
3567
|
try {
|
|
3466
3568
|
const client = getClient(token);
|
|
3467
3569
|
const res = await client.orgs.$get();
|
|
3468
|
-
if (!res.ok)
|
|
3469
|
-
|
|
3570
|
+
if (!res.ok) {
|
|
3571
|
+
await handleApiError(res);
|
|
3572
|
+
}
|
|
3470
3573
|
const { data: orgs } = await res.json();
|
|
3574
|
+
if (options.json) {
|
|
3575
|
+
console.log(JSON.stringify(orgs, null, 2));
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3471
3578
|
if (orgs.length === 0) {
|
|
3472
3579
|
console.log("No organizations found.");
|
|
3473
3580
|
} else {
|
|
3474
3581
|
console.log("Organizations:");
|
|
3475
3582
|
for (const o of orgs) {
|
|
3476
|
-
console.log(` ${o.slug.padEnd(20)} ${o.name}
|
|
3583
|
+
console.log(` ${o.slug.padEnd(20)} ${o.name}`);
|
|
3477
3584
|
}
|
|
3478
3585
|
}
|
|
3479
3586
|
} catch (err) {
|
|
3480
|
-
|
|
3587
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3588
|
+
displayError({ code: "network_error", message: `Failed to fetch orgs: ${message}` });
|
|
3589
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3481
3590
|
}
|
|
3482
3591
|
});
|
|
3483
3592
|
|
|
3484
3593
|
// src/commands/org/create.ts
|
|
3485
|
-
var orgCreateCommand = new Command2("create").description("Create a new organization").argument("<name>", "Organization name").action(async (name) => {
|
|
3594
|
+
var orgCreateCommand = new Command2("create").description("Create a new organization").argument("<name>", "Organization name").option("--json", "Output as JSON").action(async (name, options) => {
|
|
3486
3595
|
const token = loadToken();
|
|
3487
3596
|
if (!token) {
|
|
3488
|
-
|
|
3489
|
-
return;
|
|
3597
|
+
handleAuthError();
|
|
3490
3598
|
}
|
|
3491
3599
|
try {
|
|
3492
3600
|
const client = getClient(token);
|
|
3493
3601
|
const res = await client.orgs.$post({ json: { name } });
|
|
3494
3602
|
if (!res.ok) {
|
|
3495
|
-
|
|
3496
|
-
|
|
3603
|
+
await handleApiError(res);
|
|
3604
|
+
}
|
|
3605
|
+
const { data: org } = await res.json();
|
|
3606
|
+
if (options.json) {
|
|
3607
|
+
console.log(JSON.stringify(org, null, 2));
|
|
3497
3608
|
return;
|
|
3498
3609
|
}
|
|
3499
|
-
const org = await res.json();
|
|
3500
3610
|
console.log(`
|
|
3501
|
-
|
|
3611
|
+
Created org: ${org.name}`);
|
|
3502
3612
|
console.log(` Slug: ${org.slug}`);
|
|
3503
3613
|
console.log(` ID: ${org.id}
|
|
3504
3614
|
`);
|
|
3505
3615
|
} catch (err) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
displayError({ code: "unknown_error", message: "Failed to create org" });
|
|
3510
|
-
}
|
|
3616
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3617
|
+
displayError({ code: "network_error", message: `Failed to create org: ${message}` });
|
|
3618
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3511
3619
|
}
|
|
3512
3620
|
});
|
|
3513
3621
|
|
|
@@ -3528,8 +3636,7 @@ function prompt(question) {
|
|
|
3528
3636
|
var orgDeleteCommand = new Command2("delete").description("Delete an organization (must have no sites)").argument("<orgSlug>", "Organization slug").option("--force", "Skip confirmation prompt").action(async (orgSlug, options) => {
|
|
3529
3637
|
const token = loadToken();
|
|
3530
3638
|
if (!token) {
|
|
3531
|
-
|
|
3532
|
-
return;
|
|
3639
|
+
handleAuthError();
|
|
3533
3640
|
}
|
|
3534
3641
|
if (!options.force) {
|
|
3535
3642
|
const answer = await prompt(`Are you sure you want to delete organization "${orgSlug}"? This cannot be undone. (y/N) `);
|
|
@@ -3544,20 +3651,16 @@ var orgDeleteCommand = new Command2("delete").description("Delete an organizatio
|
|
|
3544
3651
|
param: { orgSlug }
|
|
3545
3652
|
});
|
|
3546
3653
|
if (!res.ok) {
|
|
3547
|
-
|
|
3548
|
-
displayError(parseApiError(body));
|
|
3549
|
-
return;
|
|
3654
|
+
await handleApiError(res);
|
|
3550
3655
|
}
|
|
3551
3656
|
const result = await res.json();
|
|
3552
3657
|
console.log(`
|
|
3553
|
-
|
|
3658
|
+
${result.message}
|
|
3554
3659
|
`);
|
|
3555
3660
|
} catch (err) {
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
displayError({ code: "unknown_error", message: "Failed to delete organization" });
|
|
3560
|
-
}
|
|
3661
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3662
|
+
displayError({ code: "network_error", message: `Failed to delete organization: ${message}` });
|
|
3663
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3561
3664
|
}
|
|
3562
3665
|
});
|
|
3563
3666
|
|
|
@@ -3565,18 +3668,22 @@ var orgDeleteCommand = new Command2("delete").description("Delete an organizatio
|
|
|
3565
3668
|
var orgCommand = new Command2("org").description("Manage organizations").addCommand(orgListCommand).addCommand(orgCreateCommand).addCommand(orgDeleteCommand);
|
|
3566
3669
|
|
|
3567
3670
|
// src/commands/site/list.ts
|
|
3568
|
-
var siteListCommand = new Command2("list").description("List sites in an organization").argument("<orgSlug>", "Organization slug").action(async (orgSlug) => {
|
|
3671
|
+
var siteListCommand = new Command2("list").description("List sites in an organization").argument("<orgSlug>", "Organization slug").option("--json", "Output as JSON").action(async (orgSlug, options) => {
|
|
3569
3672
|
const token = loadToken();
|
|
3570
3673
|
if (!token) {
|
|
3571
|
-
|
|
3572
|
-
return;
|
|
3674
|
+
handleAuthError();
|
|
3573
3675
|
}
|
|
3574
3676
|
try {
|
|
3575
3677
|
const client = getClient(token);
|
|
3576
3678
|
const res = await client.orgs[":orgSlug"].sites.$get({ param: { orgSlug } });
|
|
3577
|
-
if (!res.ok)
|
|
3578
|
-
|
|
3679
|
+
if (!res.ok) {
|
|
3680
|
+
await handleApiError(res);
|
|
3681
|
+
}
|
|
3579
3682
|
const { data: sites } = await res.json();
|
|
3683
|
+
if (options.json) {
|
|
3684
|
+
console.log(JSON.stringify(sites, null, 2));
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3580
3687
|
if (sites.length === 0) {
|
|
3581
3688
|
console.log("No sites found.");
|
|
3582
3689
|
return;
|
|
@@ -3587,17 +3694,17 @@ var siteListCommand = new Command2("list").description("List sites in an organiz
|
|
|
3587
3694
|
console.log(` ${s.slug.padEnd(20)} ${s.name}${repo}`);
|
|
3588
3695
|
}
|
|
3589
3696
|
} catch (err) {
|
|
3590
|
-
const message = err instanceof Error ? err.message :
|
|
3591
|
-
|
|
3697
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3698
|
+
displayError({ code: "network_error", message: `Failed to list sites: ${message}` });
|
|
3699
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3592
3700
|
}
|
|
3593
3701
|
});
|
|
3594
3702
|
|
|
3595
3703
|
// src/commands/site/create.ts
|
|
3596
|
-
var siteCreateCommand = new Command2("create").description("Create a site in an organization").argument("<orgSlug>", "Organization slug").argument("<name>", "Site name").
|
|
3704
|
+
var siteCreateCommand = new Command2("create").description("Create a site in an organization").argument("<orgSlug>", "Organization slug").argument("<name>", "Site name").option("--subdomain <subdomain>", "Subdomain for the site (defaults to slugified name)").option("--repo <owner/repo>", 'Link to GitHub repository (e.g., "vercel/next.js")').option("--json", "Output as JSON").action(async (orgSlug, name, options) => {
|
|
3597
3705
|
const token = loadToken();
|
|
3598
3706
|
if (!token) {
|
|
3599
|
-
|
|
3600
|
-
return;
|
|
3707
|
+
handleAuthError();
|
|
3601
3708
|
}
|
|
3602
3709
|
try {
|
|
3603
3710
|
const client = getClient(token);
|
|
@@ -3606,11 +3713,14 @@ var siteCreateCommand = new Command2("create").description("Create a site in an
|
|
|
3606
3713
|
json: { name, subdomain: options.subdomain, githubRepo: options.repo }
|
|
3607
3714
|
});
|
|
3608
3715
|
if (!res.ok) {
|
|
3609
|
-
|
|
3610
|
-
|
|
3716
|
+
await handleApiError(res);
|
|
3717
|
+
}
|
|
3718
|
+
const { data: site } = await res.json();
|
|
3719
|
+
if (options.json) {
|
|
3720
|
+
console.log(JSON.stringify(site, null, 2));
|
|
3721
|
+
return;
|
|
3611
3722
|
}
|
|
3612
|
-
|
|
3613
|
-
console.log(`✅ Created site: ${site.name}`);
|
|
3723
|
+
console.log(`Created site: ${site.name}`);
|
|
3614
3724
|
console.log(` Subdomain: ${site.subdomain}.zerodeploy.app`);
|
|
3615
3725
|
console.log(` Slug: ${site.slug}`);
|
|
3616
3726
|
console.log(` ID: ${site.id}`);
|
|
@@ -3618,8 +3728,9 @@ var siteCreateCommand = new Command2("create").description("Create a site in an
|
|
|
3618
3728
|
console.log(` Repo: ${site.github_repo}`);
|
|
3619
3729
|
}
|
|
3620
3730
|
} catch (err) {
|
|
3621
|
-
const message = err instanceof Error ? err.message :
|
|
3622
|
-
|
|
3731
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3732
|
+
displayError({ code: "network_error", message: `Failed to create site: ${message}` });
|
|
3733
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3623
3734
|
}
|
|
3624
3735
|
});
|
|
3625
3736
|
|
|
@@ -3640,8 +3751,7 @@ function prompt2(question) {
|
|
|
3640
3751
|
var siteDeleteCommand = new Command2("delete").description("Delete a site and all its deployments").argument("<siteSlug>", "Site slug").requiredOption("--org <orgSlug>", "Organization slug").option("--force", "Skip confirmation prompt").action(async (siteSlug, options) => {
|
|
3641
3752
|
const token = loadToken();
|
|
3642
3753
|
if (!token) {
|
|
3643
|
-
|
|
3644
|
-
return;
|
|
3754
|
+
handleAuthError();
|
|
3645
3755
|
}
|
|
3646
3756
|
if (!options.force) {
|
|
3647
3757
|
const answer = await prompt2(`Are you sure you want to delete site "${siteSlug}" and all its deployments? This cannot be undone. (y/N) `);
|
|
@@ -3656,23 +3766,22 @@ var siteDeleteCommand = new Command2("delete").description("Delete a site and al
|
|
|
3656
3766
|
param: { orgSlug: options.org, siteSlug }
|
|
3657
3767
|
});
|
|
3658
3768
|
if (!res.ok) {
|
|
3659
|
-
|
|
3660
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
3769
|
+
await handleApiError(res);
|
|
3661
3770
|
}
|
|
3662
3771
|
const result = await res.json();
|
|
3663
|
-
console.log(
|
|
3772
|
+
console.log(result.message);
|
|
3664
3773
|
} catch (err) {
|
|
3665
|
-
const message = err instanceof Error ? err.message :
|
|
3666
|
-
|
|
3774
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3775
|
+
displayError({ code: "network_error", message: `Failed to delete site: ${message}` });
|
|
3776
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3667
3777
|
}
|
|
3668
3778
|
});
|
|
3669
3779
|
|
|
3670
3780
|
// src/commands/site/link.ts
|
|
3671
|
-
var siteLinkCommand = new Command2("link").description("Link a site to a GitHub repository").argument("<orgSlug>", "Organization slug").argument("<siteSlug>", "Site slug").argument("<repo>", 'GitHub repository (e.g., "owner/repo")').action(async (orgSlug, siteSlug, repo) => {
|
|
3781
|
+
var siteLinkCommand = new Command2("link").description("Link a site to a GitHub repository").argument("<orgSlug>", "Organization slug").argument("<siteSlug>", "Site slug").argument("<repo>", 'GitHub repository (e.g., "owner/repo")').option("--json", "Output as JSON").action(async (orgSlug, siteSlug, repo, options) => {
|
|
3672
3782
|
const token = loadToken();
|
|
3673
3783
|
if (!token) {
|
|
3674
|
-
|
|
3675
|
-
return;
|
|
3784
|
+
handleAuthError();
|
|
3676
3785
|
}
|
|
3677
3786
|
try {
|
|
3678
3787
|
const client = getClient(token);
|
|
@@ -3681,22 +3790,24 @@ var siteLinkCommand = new Command2("link").description("Link a site to a GitHub
|
|
|
3681
3790
|
json: { githubRepo: repo }
|
|
3682
3791
|
});
|
|
3683
3792
|
if (!res.ok) {
|
|
3684
|
-
|
|
3685
|
-
|
|
3793
|
+
await handleApiError(res);
|
|
3794
|
+
}
|
|
3795
|
+
const { data: site } = await res.json();
|
|
3796
|
+
if (options.json) {
|
|
3797
|
+
console.log(JSON.stringify(site, null, 2));
|
|
3686
3798
|
return;
|
|
3687
3799
|
}
|
|
3688
|
-
const site = await res.json();
|
|
3689
3800
|
console.log(`Site "${site.name}" linked to ${site.github_repo}`);
|
|
3690
3801
|
} catch (err) {
|
|
3691
|
-
const message = err instanceof Error ? err.message :
|
|
3692
|
-
|
|
3802
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3803
|
+
displayError({ code: "network_error", message: `Failed to link repository: ${message}` });
|
|
3804
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3693
3805
|
}
|
|
3694
3806
|
});
|
|
3695
3807
|
var siteUnlinkCommand = new Command2("unlink").description("Unlink a site from its GitHub repository").argument("<orgSlug>", "Organization slug").argument("<siteSlug>", "Site slug").action(async (orgSlug, siteSlug) => {
|
|
3696
3808
|
const token = loadToken();
|
|
3697
3809
|
if (!token) {
|
|
3698
|
-
|
|
3699
|
-
return;
|
|
3810
|
+
handleAuthError();
|
|
3700
3811
|
}
|
|
3701
3812
|
try {
|
|
3702
3813
|
const client = getClient(token);
|
|
@@ -3705,15 +3816,14 @@ var siteUnlinkCommand = new Command2("unlink").description("Unlink a site from i
|
|
|
3705
3816
|
json: { githubRepo: null }
|
|
3706
3817
|
});
|
|
3707
3818
|
if (!res.ok) {
|
|
3708
|
-
|
|
3709
|
-
console.log(`Error: ${error.error || "Failed to unlink repository"}`);
|
|
3710
|
-
return;
|
|
3819
|
+
await handleApiError(res);
|
|
3711
3820
|
}
|
|
3712
|
-
const site = await res.json();
|
|
3821
|
+
const { data: site } = await res.json();
|
|
3713
3822
|
console.log(`Site "${site.name}" unlinked from GitHub repository`);
|
|
3714
3823
|
} catch (err) {
|
|
3715
|
-
const message = err instanceof Error ? err.message :
|
|
3716
|
-
|
|
3824
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3825
|
+
displayError({ code: "network_error", message: `Failed to unlink repository: ${message}` });
|
|
3826
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3717
3827
|
}
|
|
3718
3828
|
});
|
|
3719
3829
|
|
|
@@ -3721,8 +3831,7 @@ var siteUnlinkCommand = new Command2("unlink").description("Unlink a site from i
|
|
|
3721
3831
|
var siteSubdomainCommand = new Command2("subdomain").description("Update the subdomain for a site").argument("<orgSlug>", "Organization slug").argument("<siteSlug>", "Site slug").argument("<subdomain>", "New subdomain").action(async (orgSlug, siteSlug, subdomain) => {
|
|
3722
3832
|
const token = loadToken();
|
|
3723
3833
|
if (!token) {
|
|
3724
|
-
|
|
3725
|
-
return;
|
|
3834
|
+
handleAuthError();
|
|
3726
3835
|
}
|
|
3727
3836
|
try {
|
|
3728
3837
|
const client = getClient(token);
|
|
@@ -3731,15 +3840,15 @@ var siteSubdomainCommand = new Command2("subdomain").description("Update the sub
|
|
|
3731
3840
|
json: { subdomain }
|
|
3732
3841
|
});
|
|
3733
3842
|
if (!res.ok) {
|
|
3734
|
-
|
|
3735
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
3843
|
+
await handleApiError(res);
|
|
3736
3844
|
}
|
|
3737
|
-
const site = await res.json();
|
|
3738
|
-
console.log(
|
|
3845
|
+
const { data: site } = await res.json();
|
|
3846
|
+
console.log(`Updated subdomain for ${site.name}`);
|
|
3739
3847
|
console.log(` New URL: ${site.subdomain}.zerodeploy.app`);
|
|
3740
3848
|
} catch (err) {
|
|
3741
|
-
const message = err instanceof Error ? err.message :
|
|
3742
|
-
|
|
3849
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3850
|
+
displayError({ code: "network_error", message: `Failed to update subdomain: ${message}` });
|
|
3851
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3743
3852
|
}
|
|
3744
3853
|
});
|
|
3745
3854
|
|
|
@@ -3747,8 +3856,7 @@ var siteSubdomainCommand = new Command2("subdomain").description("Update the sub
|
|
|
3747
3856
|
var siteRenameCommand = new Command2("rename").description("Rename a site").argument("<siteSlug>", "Site slug").argument("<newName>", "New name for the site").requiredOption("--org <orgSlug>", "Organization slug").action(async (siteSlug, newName, options) => {
|
|
3748
3857
|
const token = loadToken();
|
|
3749
3858
|
if (!token) {
|
|
3750
|
-
|
|
3751
|
-
return;
|
|
3859
|
+
handleAuthError();
|
|
3752
3860
|
}
|
|
3753
3861
|
try {
|
|
3754
3862
|
const client = getClient(token);
|
|
@@ -3757,14 +3865,14 @@ var siteRenameCommand = new Command2("rename").description("Rename a site").argu
|
|
|
3757
3865
|
json: { name: newName }
|
|
3758
3866
|
});
|
|
3759
3867
|
if (!res.ok) {
|
|
3760
|
-
|
|
3761
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
3868
|
+
await handleApiError(res);
|
|
3762
3869
|
}
|
|
3763
|
-
const site = await res.json();
|
|
3764
|
-
console.log(
|
|
3870
|
+
const { data: site } = await res.json();
|
|
3871
|
+
console.log(`Renamed site to: ${site.name}`);
|
|
3765
3872
|
} catch (err) {
|
|
3766
|
-
const message = err instanceof Error ? err.message :
|
|
3767
|
-
|
|
3873
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3874
|
+
displayError({ code: "network_error", message: `Failed to rename site: ${message}` });
|
|
3875
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3768
3876
|
}
|
|
3769
3877
|
});
|
|
3770
3878
|
|
|
@@ -3784,16 +3892,19 @@ function formatNumber(n) {
|
|
|
3784
3892
|
return (n / 1000).toFixed(1) + "K";
|
|
3785
3893
|
return n.toString();
|
|
3786
3894
|
}
|
|
3787
|
-
var siteStatsCommand = new Command2("stats").description("View site traffic analytics").argument("<site>", "Site slug").requiredOption("--org <orgSlug>", "Organization slug").option("--period <period>", "Time period: 24h, 7d, or 30d", "7d").action(async (site, options) => {
|
|
3895
|
+
var siteStatsCommand = new Command2("stats").description("View site traffic analytics").argument("<site>", "Site slug").requiredOption("--org <orgSlug>", "Organization slug").option("--period <period>", "Time period: 24h, 7d, or 30d", "7d").option("--json", "Output as JSON").action(async (site, options) => {
|
|
3788
3896
|
const token = loadToken();
|
|
3789
3897
|
if (!token) {
|
|
3790
|
-
|
|
3791
|
-
return;
|
|
3898
|
+
handleAuthError();
|
|
3792
3899
|
}
|
|
3793
3900
|
const validPeriods = ["24h", "7d", "30d"];
|
|
3794
3901
|
if (!validPeriods.includes(options.period)) {
|
|
3795
|
-
|
|
3796
|
-
|
|
3902
|
+
displayError({
|
|
3903
|
+
code: "validation_error",
|
|
3904
|
+
message: `Invalid period: ${options.period}`,
|
|
3905
|
+
hint: `Valid periods: ${validPeriods.join(", ")}`
|
|
3906
|
+
});
|
|
3907
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
3797
3908
|
}
|
|
3798
3909
|
try {
|
|
3799
3910
|
const client = getClient(token);
|
|
@@ -3802,10 +3913,13 @@ var siteStatsCommand = new Command2("stats").description("View site traffic anal
|
|
|
3802
3913
|
query: { period: options.period }
|
|
3803
3914
|
});
|
|
3804
3915
|
if (!res.ok) {
|
|
3805
|
-
|
|
3806
|
-
|
|
3916
|
+
await handleApiError(res);
|
|
3917
|
+
}
|
|
3918
|
+
const { data } = await res.json();
|
|
3919
|
+
if (options.json) {
|
|
3920
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3921
|
+
return;
|
|
3807
3922
|
}
|
|
3808
|
-
const data = await res.json();
|
|
3809
3923
|
if (!data.configured) {
|
|
3810
3924
|
console.log("Analytics not configured for this site.");
|
|
3811
3925
|
return;
|
|
@@ -3873,8 +3987,9 @@ var siteStatsCommand = new Command2("stats").description("View site traffic anal
|
|
|
3873
3987
|
}
|
|
3874
3988
|
console.log("View detailed analytics in the dashboard.");
|
|
3875
3989
|
} catch (err) {
|
|
3876
|
-
const message = err instanceof Error ? err.message :
|
|
3877
|
-
|
|
3990
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3991
|
+
displayError({ code: "network_error", message: `Failed to get analytics: ${message}` });
|
|
3992
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3878
3993
|
}
|
|
3879
3994
|
});
|
|
3880
3995
|
|
|
@@ -3885,8 +4000,7 @@ var siteCommand = new Command2("site").description("Manage sites").addCommand(si
|
|
|
3885
4000
|
var domainAddCommand = new Command2("add").description("Add a custom domain to a site").argument("<domain>", "Domain to add (e.g., www.example.com)").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").action(async (domain, options) => {
|
|
3886
4001
|
const token = loadToken();
|
|
3887
4002
|
if (!token) {
|
|
3888
|
-
|
|
3889
|
-
return;
|
|
4003
|
+
handleAuthError();
|
|
3890
4004
|
}
|
|
3891
4005
|
try {
|
|
3892
4006
|
const client = getClient(token);
|
|
@@ -3895,17 +4009,16 @@ var domainAddCommand = new Command2("add").description("Add a custom domain to a
|
|
|
3895
4009
|
json: { domain }
|
|
3896
4010
|
});
|
|
3897
4011
|
if (!res.ok) {
|
|
3898
|
-
|
|
3899
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4012
|
+
await handleApiError(res);
|
|
3900
4013
|
}
|
|
3901
|
-
const data = await res.json();
|
|
4014
|
+
const { data } = await res.json();
|
|
3902
4015
|
console.log(`
|
|
3903
|
-
|
|
4016
|
+
Domain added: ${data.domain}`);
|
|
3904
4017
|
console.log(` ID: ${data.id}`);
|
|
3905
4018
|
console.log(` Status: ${data.verification_status}`);
|
|
3906
4019
|
console.log();
|
|
3907
|
-
console.log("
|
|
3908
|
-
console.log("
|
|
4020
|
+
console.log("DNS Verification Required");
|
|
4021
|
+
console.log("-------------------------");
|
|
3909
4022
|
console.log();
|
|
3910
4023
|
console.log("Add the following TXT record to your DNS:");
|
|
3911
4024
|
console.log();
|
|
@@ -3917,8 +4030,9 @@ var domainAddCommand = new Command2("add").description("Add a custom domain to a
|
|
|
3917
4030
|
console.log(` zerodeploy domain verify ${data.domain} --org ${options.org} --site ${options.site}`);
|
|
3918
4031
|
console.log();
|
|
3919
4032
|
} catch (err) {
|
|
3920
|
-
const message = err instanceof Error ? err.message :
|
|
3921
|
-
|
|
4033
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4034
|
+
displayError({ code: "network_error", message: `Failed to add domain: ${message}` });
|
|
4035
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3922
4036
|
}
|
|
3923
4037
|
});
|
|
3924
4038
|
|
|
@@ -3926,11 +4040,11 @@ var domainAddCommand = new Command2("add").description("Add a custom domain to a
|
|
|
3926
4040
|
function formatStatus(status) {
|
|
3927
4041
|
switch (status) {
|
|
3928
4042
|
case "verified":
|
|
3929
|
-
return "
|
|
4043
|
+
return "verified";
|
|
3930
4044
|
case "pending":
|
|
3931
|
-
return "
|
|
4045
|
+
return "pending";
|
|
3932
4046
|
case "failed":
|
|
3933
|
-
return "
|
|
4047
|
+
return "failed";
|
|
3934
4048
|
default:
|
|
3935
4049
|
return status;
|
|
3936
4050
|
}
|
|
@@ -3938,18 +4052,17 @@ function formatStatus(status) {
|
|
|
3938
4052
|
function formatRedirect(mode) {
|
|
3939
4053
|
switch (mode) {
|
|
3940
4054
|
case "www_to_apex":
|
|
3941
|
-
return "www
|
|
4055
|
+
return "www->apex";
|
|
3942
4056
|
case "apex_to_www":
|
|
3943
|
-
return "apex
|
|
4057
|
+
return "apex->www";
|
|
3944
4058
|
default:
|
|
3945
4059
|
return "";
|
|
3946
4060
|
}
|
|
3947
4061
|
}
|
|
3948
|
-
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) => {
|
|
4062
|
+
var domainListCommand = new Command2("list").description("List custom domains for a site").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--json", "Output as JSON").action(async (options) => {
|
|
3949
4063
|
const token = loadToken();
|
|
3950
4064
|
if (!token) {
|
|
3951
|
-
|
|
3952
|
-
return;
|
|
4065
|
+
handleAuthError();
|
|
3953
4066
|
}
|
|
3954
4067
|
try {
|
|
3955
4068
|
const client = getClient(token);
|
|
@@ -3957,10 +4070,13 @@ var domainListCommand = new Command2("list").description("List custom domains fo
|
|
|
3957
4070
|
param: { orgSlug: options.org, siteSlug: options.site }
|
|
3958
4071
|
});
|
|
3959
4072
|
if (!res.ok) {
|
|
3960
|
-
|
|
3961
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4073
|
+
await handleApiError(res);
|
|
3962
4074
|
}
|
|
3963
4075
|
const { data: domains } = await res.json();
|
|
4076
|
+
if (options.json) {
|
|
4077
|
+
console.log(JSON.stringify(domains, null, 2));
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
3964
4080
|
if (domains.length === 0) {
|
|
3965
4081
|
console.log("No custom domains configured.");
|
|
3966
4082
|
console.log();
|
|
@@ -3977,17 +4093,17 @@ var domainListCommand = new Command2("list").description("List custom domains fo
|
|
|
3977
4093
|
}
|
|
3978
4094
|
console.log();
|
|
3979
4095
|
} catch (err) {
|
|
3980
|
-
const message = err instanceof Error ? err.message :
|
|
3981
|
-
|
|
4096
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4097
|
+
displayError({ code: "network_error", message: `Failed to list domains: ${message}` });
|
|
4098
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
3982
4099
|
}
|
|
3983
4100
|
});
|
|
3984
4101
|
|
|
3985
4102
|
// src/commands/domain/verify.ts
|
|
3986
|
-
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) => {
|
|
4103
|
+
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").option("--json", "Output as JSON").action(async (domainName, options) => {
|
|
3987
4104
|
const token = loadToken();
|
|
3988
4105
|
if (!token) {
|
|
3989
|
-
|
|
3990
|
-
return;
|
|
4106
|
+
handleAuthError();
|
|
3991
4107
|
}
|
|
3992
4108
|
try {
|
|
3993
4109
|
const client = getClient(token);
|
|
@@ -3995,39 +4111,40 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
3995
4111
|
param: { orgSlug: options.org, siteSlug: options.site }
|
|
3996
4112
|
});
|
|
3997
4113
|
if (!listRes.ok) {
|
|
3998
|
-
|
|
3999
|
-
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
4114
|
+
await handleApiError(listRes);
|
|
4000
4115
|
}
|
|
4001
4116
|
const { data: domains } = await listRes.json();
|
|
4002
4117
|
const domain = domains.find((d) => d.domain === domainName);
|
|
4003
4118
|
if (!domain) {
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4119
|
+
displayError({
|
|
4120
|
+
code: "not_found",
|
|
4121
|
+
message: `Domain not found: ${domainName}`,
|
|
4122
|
+
hint: `Add it first with:
|
|
4123
|
+
zerodeploy domain add ${domainName} --org ${options.org} --site ${options.site}`
|
|
4124
|
+
});
|
|
4125
|
+
process.exit(ExitCode.NOT_FOUND);
|
|
4010
4126
|
}
|
|
4011
4127
|
const res = await client.orgs[":orgSlug"].sites[":siteSlug"].domains[":domainId"].verify.$post({
|
|
4012
4128
|
param: { orgSlug: options.org, siteSlug: options.site, domainId: domain.id }
|
|
4013
4129
|
});
|
|
4014
|
-
const data = await res.json();
|
|
4130
|
+
const { data } = await res.json();
|
|
4015
4131
|
if (!res.ok) {
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4132
|
+
displayError({
|
|
4133
|
+
code: "verification_failed",
|
|
4134
|
+
message: `Verification failed for ${domainName}`,
|
|
4135
|
+
hint: `Tips:
|
|
4136
|
+
- DNS changes can take up to 48 hours to propagate
|
|
4137
|
+
- Verify the TXT record is set correctly using: dig TXT _zerodeploy.${domainName}
|
|
4138
|
+
- Try again in a few minutes`
|
|
4139
|
+
});
|
|
4140
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
4141
|
+
}
|
|
4142
|
+
if (options.json) {
|
|
4143
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4027
4144
|
return;
|
|
4028
4145
|
}
|
|
4029
4146
|
console.log(`
|
|
4030
|
-
|
|
4147
|
+
Domain verified: ${data.domain}`);
|
|
4031
4148
|
console.log();
|
|
4032
4149
|
if (data.cloudflare) {
|
|
4033
4150
|
console.log(` Cloudflare SSL: ${data.cloudflare.status}`);
|
|
@@ -4036,8 +4153,8 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
4036
4153
|
const domainParts = data.domain.split(".");
|
|
4037
4154
|
const isApexDomain = domainParts.length === 2;
|
|
4038
4155
|
const target = `${data.siteSubdomain || "your-site"}.zerodeploy.app`;
|
|
4039
|
-
console.log("
|
|
4040
|
-
console.log("
|
|
4156
|
+
console.log("Final DNS Setup");
|
|
4157
|
+
console.log("---------------");
|
|
4041
4158
|
console.log();
|
|
4042
4159
|
if (isApexDomain) {
|
|
4043
4160
|
console.log("For apex domains, DNS setup depends on your provider:");
|
|
@@ -4067,8 +4184,9 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
4067
4184
|
console.log(` https://${data.domain}`);
|
|
4068
4185
|
console.log();
|
|
4069
4186
|
} catch (err) {
|
|
4070
|
-
const message = err instanceof Error ? err.message :
|
|
4071
|
-
|
|
4187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4188
|
+
displayError({ code: "network_error", message: `Failed to verify domain: ${message}` });
|
|
4189
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4072
4190
|
}
|
|
4073
4191
|
});
|
|
4074
4192
|
|
|
@@ -4076,8 +4194,7 @@ var domainVerifyCommand = new Command2("verify").description("Verify ownership o
|
|
|
4076
4194
|
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) => {
|
|
4077
4195
|
const token = loadToken();
|
|
4078
4196
|
if (!token) {
|
|
4079
|
-
|
|
4080
|
-
return;
|
|
4197
|
+
handleAuthError();
|
|
4081
4198
|
}
|
|
4082
4199
|
try {
|
|
4083
4200
|
const client = getClient(token);
|
|
@@ -4085,31 +4202,31 @@ var domainRemoveCommand = new Command2("remove").description("Remove a custom do
|
|
|
4085
4202
|
param: { orgSlug: options.org, siteSlug: options.site }
|
|
4086
4203
|
});
|
|
4087
4204
|
if (!listRes.ok) {
|
|
4088
|
-
|
|
4089
|
-
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
4205
|
+
await handleApiError(listRes);
|
|
4090
4206
|
}
|
|
4091
4207
|
const { data: domains } = await listRes.json();
|
|
4092
4208
|
const domain = domains.find((d) => d.domain === domainName);
|
|
4093
4209
|
if (!domain) {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4210
|
+
displayError({
|
|
4211
|
+
code: "not_found",
|
|
4212
|
+
message: `Domain not found: ${domainName}`,
|
|
4213
|
+
hint: `List domains with:
|
|
4214
|
+
zerodeploy domain list --org ${options.org} --site ${options.site}`
|
|
4215
|
+
});
|
|
4216
|
+
process.exit(ExitCode.NOT_FOUND);
|
|
4100
4217
|
}
|
|
4101
4218
|
const res = await client.orgs[":orgSlug"].sites[":siteSlug"].domains[":domainId"].$delete({
|
|
4102
4219
|
param: { orgSlug: options.org, siteSlug: options.site, domainId: domain.id }
|
|
4103
4220
|
});
|
|
4104
4221
|
if (!res.ok) {
|
|
4105
|
-
|
|
4106
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4222
|
+
await handleApiError(res);
|
|
4107
4223
|
}
|
|
4108
4224
|
const data = await res.json();
|
|
4109
|
-
console.log(
|
|
4225
|
+
console.log(data.message);
|
|
4110
4226
|
} catch (err) {
|
|
4111
|
-
const message = err instanceof Error ? err.message :
|
|
4112
|
-
|
|
4227
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4228
|
+
displayError({ code: "network_error", message: `Failed to remove domain: ${message}` });
|
|
4229
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4113
4230
|
}
|
|
4114
4231
|
});
|
|
4115
4232
|
|
|
@@ -4119,24 +4236,26 @@ function formatRedirectMode(mode) {
|
|
|
4119
4236
|
case "none":
|
|
4120
4237
|
return "No redirect";
|
|
4121
4238
|
case "www_to_apex":
|
|
4122
|
-
return "www
|
|
4239
|
+
return "www -> apex (e.g., www.example.com -> example.com)";
|
|
4123
4240
|
case "apex_to_www":
|
|
4124
|
-
return "apex
|
|
4241
|
+
return "apex -> www (e.g., example.com -> www.example.com)";
|
|
4125
4242
|
default:
|
|
4126
4243
|
return mode;
|
|
4127
4244
|
}
|
|
4128
4245
|
}
|
|
4129
|
-
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) => {
|
|
4246
|
+
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").option("--json", "Output as JSON").action(async (domain, options) => {
|
|
4130
4247
|
const token = loadToken();
|
|
4131
4248
|
if (!token) {
|
|
4132
|
-
|
|
4133
|
-
return;
|
|
4249
|
+
handleAuthError();
|
|
4134
4250
|
}
|
|
4135
4251
|
const validModes = ["none", "www_to_apex", "apex_to_www"];
|
|
4136
4252
|
if (!validModes.includes(options.mode)) {
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4253
|
+
displayError({
|
|
4254
|
+
code: "validation_error",
|
|
4255
|
+
message: `Invalid mode: ${options.mode}`,
|
|
4256
|
+
hint: "Valid modes: none, www_to_apex, apex_to_www"
|
|
4257
|
+
});
|
|
4258
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
4140
4259
|
}
|
|
4141
4260
|
try {
|
|
4142
4261
|
const client = getClient(token);
|
|
@@ -4144,32 +4263,38 @@ var domainRedirectCommand = new Command2("redirect").description("Set redirect m
|
|
|
4144
4263
|
param: { orgSlug: options.org, siteSlug: options.site }
|
|
4145
4264
|
});
|
|
4146
4265
|
if (!listRes.ok) {
|
|
4147
|
-
|
|
4148
|
-
throw new Error(error.error || `API Error ${listRes.status}`);
|
|
4266
|
+
await handleApiError(listRes);
|
|
4149
4267
|
}
|
|
4150
4268
|
const { data: domains } = await listRes.json();
|
|
4151
4269
|
const targetDomain = domains.find((d) => d.domain === domain.toLowerCase());
|
|
4152
4270
|
if (!targetDomain) {
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4271
|
+
displayError({
|
|
4272
|
+
code: "not_found",
|
|
4273
|
+
message: `Domain not found: ${domain}`,
|
|
4274
|
+
hint: `Run 'zerodeploy domain list --org ${options.org} --site ${options.site}' to see configured domains.`
|
|
4275
|
+
});
|
|
4276
|
+
process.exit(ExitCode.NOT_FOUND);
|
|
4156
4277
|
}
|
|
4157
4278
|
const res = await client.orgs[":orgSlug"].sites[":siteSlug"].domains[":domainId"].redirect.$patch({
|
|
4158
4279
|
param: { orgSlug: options.org, siteSlug: options.site, domainId: targetDomain.id },
|
|
4159
4280
|
json: { redirectMode: options.mode }
|
|
4160
4281
|
});
|
|
4161
4282
|
if (!res.ok) {
|
|
4162
|
-
|
|
4163
|
-
|
|
4283
|
+
await handleApiError(res);
|
|
4284
|
+
}
|
|
4285
|
+
const { data } = await res.json();
|
|
4286
|
+
if (options.json) {
|
|
4287
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4288
|
+
return;
|
|
4164
4289
|
}
|
|
4165
|
-
const data = await res.json();
|
|
4166
4290
|
console.log(`
|
|
4167
|
-
|
|
4291
|
+
Redirect mode updated for ${data.domain}`);
|
|
4168
4292
|
console.log(` Mode: ${formatRedirectMode(data.redirect_mode)}`);
|
|
4169
4293
|
console.log();
|
|
4170
4294
|
} catch (err) {
|
|
4171
|
-
const message = err instanceof Error ? err.message :
|
|
4172
|
-
|
|
4295
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4296
|
+
displayError({ code: "network_error", message: `Failed to update redirect mode: ${message}` });
|
|
4297
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4173
4298
|
}
|
|
4174
4299
|
});
|
|
4175
4300
|
|
|
@@ -4177,11 +4302,10 @@ var domainRedirectCommand = new Command2("redirect").description("Set redirect m
|
|
|
4177
4302
|
var domainCommand = new Command2("domain").description("Manage custom domains").addCommand(domainAddCommand).addCommand(domainListCommand).addCommand(domainVerifyCommand).addCommand(domainRemoveCommand).addCommand(domainRedirectCommand);
|
|
4178
4303
|
|
|
4179
4304
|
// src/commands/form/list.ts
|
|
4180
|
-
var formListCommand = new Command2("list").description("List forms for a site").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").action(async (options) => {
|
|
4305
|
+
var formListCommand = new Command2("list").description("List forms for a site").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--json", "Output as JSON").action(async (options) => {
|
|
4181
4306
|
const token = loadToken();
|
|
4182
4307
|
if (!token) {
|
|
4183
|
-
|
|
4184
|
-
return;
|
|
4308
|
+
handleAuthError();
|
|
4185
4309
|
}
|
|
4186
4310
|
try {
|
|
4187
4311
|
const client = getClient(token);
|
|
@@ -4189,10 +4313,13 @@ var formListCommand = new Command2("list").description("List forms for a site").
|
|
|
4189
4313
|
param: { orgSlug: options.org, siteSlug: options.site }
|
|
4190
4314
|
});
|
|
4191
4315
|
if (!res.ok) {
|
|
4192
|
-
|
|
4193
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4316
|
+
await handleApiError(res);
|
|
4194
4317
|
}
|
|
4195
4318
|
const { data: forms } = await res.json();
|
|
4319
|
+
if (options.json) {
|
|
4320
|
+
console.log(JSON.stringify(forms, null, 2));
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4196
4323
|
if (forms.length === 0) {
|
|
4197
4324
|
console.log("No forms found.");
|
|
4198
4325
|
console.log();
|
|
@@ -4211,17 +4338,17 @@ var formListCommand = new Command2("list").description("List forms for a site").
|
|
|
4211
4338
|
console.log("Export submissions with:");
|
|
4212
4339
|
console.log(` zerodeploy form export <name> --org ${options.org} --site ${options.site}`);
|
|
4213
4340
|
} catch (err) {
|
|
4214
|
-
const message = err instanceof Error ? err.message :
|
|
4215
|
-
|
|
4341
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4342
|
+
displayError({ code: "network_error", message: `Failed to list forms: ${message}` });
|
|
4343
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4216
4344
|
}
|
|
4217
4345
|
});
|
|
4218
4346
|
|
|
4219
4347
|
// src/commands/form/submissions.ts
|
|
4220
|
-
var formSubmissionsCommand = new Command2("submissions").description("View recent form submissions").argument("<name>", "Form name").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--limit <n>", "Number of submissions to show", "10").option("--offset <n>", "Offset for pagination", "0").action(async (name, options) => {
|
|
4348
|
+
var formSubmissionsCommand = new Command2("submissions").description("View recent form submissions").argument("<name>", "Form name").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--limit <n>", "Number of submissions to show", "10").option("--offset <n>", "Offset for pagination", "0").option("--json", "Output as JSON").action(async (name, options) => {
|
|
4221
4349
|
const token = loadToken();
|
|
4222
4350
|
if (!token) {
|
|
4223
|
-
|
|
4224
|
-
return;
|
|
4351
|
+
handleAuthError();
|
|
4225
4352
|
}
|
|
4226
4353
|
try {
|
|
4227
4354
|
const client = getClient(token);
|
|
@@ -4230,11 +4357,14 @@ var formSubmissionsCommand = new Command2("submissions").description("View recen
|
|
|
4230
4357
|
query: { limit: options.limit, offset: options.offset }
|
|
4231
4358
|
});
|
|
4232
4359
|
if (!res.ok) {
|
|
4233
|
-
|
|
4234
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4360
|
+
await handleApiError(res);
|
|
4235
4361
|
}
|
|
4236
4362
|
const data = await res.json();
|
|
4237
4363
|
const { form, submissions, total, limit, offset } = data;
|
|
4364
|
+
if (options.json) {
|
|
4365
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4366
|
+
return;
|
|
4367
|
+
}
|
|
4238
4368
|
if (submissions.length === 0) {
|
|
4239
4369
|
console.log(`No submissions found for form "${name}".`);
|
|
4240
4370
|
return;
|
|
@@ -4278,8 +4408,9 @@ var formSubmissionsCommand = new Command2("submissions").description("View recen
|
|
|
4278
4408
|
console.log("Export all to CSV with:");
|
|
4279
4409
|
console.log(` zerodeploy form export ${name} --org ${options.org} --site ${options.site}`);
|
|
4280
4410
|
} catch (err) {
|
|
4281
|
-
const message = err instanceof Error ? err.message :
|
|
4282
|
-
|
|
4411
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4412
|
+
displayError({ code: "network_error", message: `Failed to get submissions: ${message}` });
|
|
4413
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4283
4414
|
}
|
|
4284
4415
|
});
|
|
4285
4416
|
|
|
@@ -4289,8 +4420,7 @@ import { resolve } from "path";
|
|
|
4289
4420
|
var formExportCommand = new Command2("export").description("Export form submissions as CSV").argument("<name>", "Form name").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("-o, --output <file>", "Output file path (default: <name>-submissions.csv)").action(async (name, options) => {
|
|
4290
4421
|
const token = loadToken();
|
|
4291
4422
|
if (!token) {
|
|
4292
|
-
|
|
4293
|
-
return;
|
|
4423
|
+
handleAuthError();
|
|
4294
4424
|
}
|
|
4295
4425
|
try {
|
|
4296
4426
|
const client = getClient(token);
|
|
@@ -4302,8 +4432,7 @@ var formExportCommand = new Command2("export").description("Export form submissi
|
|
|
4302
4432
|
return;
|
|
4303
4433
|
}
|
|
4304
4434
|
if (!res.ok) {
|
|
4305
|
-
|
|
4306
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4435
|
+
await handleApiError(res);
|
|
4307
4436
|
}
|
|
4308
4437
|
const csv = await res.text();
|
|
4309
4438
|
const outputPath = options.output || `${name}-submissions.csv`;
|
|
@@ -4313,8 +4442,9 @@ var formExportCommand = new Command2("export").description("Export form submissi
|
|
|
4313
4442
|
`).length - 1;
|
|
4314
4443
|
console.log(`Exported ${lineCount} submissions to ${outputPath}`);
|
|
4315
4444
|
} catch (err) {
|
|
4316
|
-
const message = err instanceof Error ? err.message :
|
|
4317
|
-
|
|
4445
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4446
|
+
displayError({ code: "network_error", message: `Failed to export form: ${message}` });
|
|
4447
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4318
4448
|
}
|
|
4319
4449
|
});
|
|
4320
4450
|
|
|
@@ -4335,8 +4465,7 @@ function prompt3(question) {
|
|
|
4335
4465
|
var formDeleteCommand = new Command2("delete").description("Delete a form and all its submissions").argument("<name>", "Form name").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--force", "Skip confirmation prompt").action(async (name, options) => {
|
|
4336
4466
|
const token = loadToken();
|
|
4337
4467
|
if (!token) {
|
|
4338
|
-
|
|
4339
|
-
return;
|
|
4468
|
+
handleAuthError();
|
|
4340
4469
|
}
|
|
4341
4470
|
if (!options.force) {
|
|
4342
4471
|
const answer = await prompt3(`Are you sure you want to delete form "${name}" and all its submissions? This cannot be undone. (y/N) `);
|
|
@@ -4351,14 +4480,14 @@ var formDeleteCommand = new Command2("delete").description("Delete a form and al
|
|
|
4351
4480
|
param: { orgSlug: options.org, siteSlug: options.site, formName: name }
|
|
4352
4481
|
});
|
|
4353
4482
|
if (!res.ok) {
|
|
4354
|
-
|
|
4355
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4483
|
+
await handleApiError(res);
|
|
4356
4484
|
}
|
|
4357
4485
|
const result = await res.json();
|
|
4358
|
-
console.log(
|
|
4486
|
+
console.log(`${result.message}`);
|
|
4359
4487
|
} catch (err) {
|
|
4360
|
-
const message = err instanceof Error ? err.message :
|
|
4361
|
-
|
|
4488
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4489
|
+
displayError({ code: "network_error", message: `Failed to delete form: ${message}` });
|
|
4490
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4362
4491
|
}
|
|
4363
4492
|
});
|
|
4364
4493
|
|
|
@@ -4366,20 +4495,21 @@ var formDeleteCommand = new Command2("delete").description("Delete a form and al
|
|
|
4366
4495
|
var formNotifyCommand = new Command2("notify").description("Configure email notifications for form submissions").argument("<name>", "Form name").requiredOption("--org <orgSlug>", "Organization slug").requiredOption("--site <siteSlug>", "Site slug").option("--email <email>", "Email address to receive notifications").option("--disable", "Disable email notifications").action(async (name, options) => {
|
|
4367
4496
|
const token = loadToken();
|
|
4368
4497
|
if (!token) {
|
|
4369
|
-
|
|
4370
|
-
return;
|
|
4498
|
+
handleAuthError();
|
|
4371
4499
|
}
|
|
4372
4500
|
if (options.email && options.disable) {
|
|
4373
|
-
|
|
4374
|
-
|
|
4501
|
+
displayError({ code: "validation_error", message: "Cannot use both --email and --disable options" });
|
|
4502
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
4375
4503
|
}
|
|
4376
4504
|
if (!options.email && !options.disable) {
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4505
|
+
displayError({
|
|
4506
|
+
code: "validation_error",
|
|
4507
|
+
message: "Please specify --email <email> or --disable",
|
|
4508
|
+
hint: `Examples:
|
|
4509
|
+
zerodeploy form notify ${name} --org ${options.org} --site ${options.site} --email alerts@example.com
|
|
4510
|
+
zerodeploy form notify ${name} --org ${options.org} --site ${options.site} --disable`
|
|
4511
|
+
});
|
|
4512
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
4383
4513
|
}
|
|
4384
4514
|
try {
|
|
4385
4515
|
const client = getClient(token);
|
|
@@ -4389,8 +4519,7 @@ var formNotifyCommand = new Command2("notify").description("Configure email noti
|
|
|
4389
4519
|
json: { notification_email: notificationEmail }
|
|
4390
4520
|
});
|
|
4391
4521
|
if (!res.ok) {
|
|
4392
|
-
|
|
4393
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
4522
|
+
await handleApiError(res);
|
|
4394
4523
|
}
|
|
4395
4524
|
const { form } = await res.json();
|
|
4396
4525
|
if (form.notification_email) {
|
|
@@ -4400,8 +4529,9 @@ var formNotifyCommand = new Command2("notify").description("Configure email noti
|
|
|
4400
4529
|
console.log(`Email notifications disabled for form "${name}"`);
|
|
4401
4530
|
}
|
|
4402
4531
|
} catch (err) {
|
|
4403
|
-
const message = err instanceof Error ? err.message :
|
|
4404
|
-
|
|
4532
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4533
|
+
displayError({ code: "network_error", message: `Failed to update form notifications: ${message}` });
|
|
4534
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
4405
4535
|
}
|
|
4406
4536
|
});
|
|
4407
4537
|
|
|
@@ -5261,6 +5391,109 @@ function createProgressBar(options) {
|
|
|
5261
5391
|
};
|
|
5262
5392
|
}
|
|
5263
5393
|
|
|
5394
|
+
// src/utils/ci.ts
|
|
5395
|
+
function detectCI() {
|
|
5396
|
+
if (process.env.GITHUB_ACTIONS === "true") {
|
|
5397
|
+
return {
|
|
5398
|
+
name: "github-actions",
|
|
5399
|
+
prNumber: extractGitHubPRNumber(),
|
|
5400
|
+
prTitle: null,
|
|
5401
|
+
commitSha: process.env.GITHUB_SHA || null,
|
|
5402
|
+
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || null,
|
|
5403
|
+
commitMessage: null
|
|
5404
|
+
};
|
|
5405
|
+
}
|
|
5406
|
+
if (process.env.CIRCLECI === "true") {
|
|
5407
|
+
return {
|
|
5408
|
+
name: "circleci",
|
|
5409
|
+
prNumber: extractCircleCIPRNumber(),
|
|
5410
|
+
prTitle: null,
|
|
5411
|
+
commitSha: process.env.CIRCLE_SHA1 || null,
|
|
5412
|
+
branch: process.env.CIRCLE_BRANCH || null,
|
|
5413
|
+
commitMessage: null
|
|
5414
|
+
};
|
|
5415
|
+
}
|
|
5416
|
+
if (process.env.GITLAB_CI === "true") {
|
|
5417
|
+
return {
|
|
5418
|
+
name: "gitlab-ci",
|
|
5419
|
+
prNumber: process.env.CI_MERGE_REQUEST_IID ? parseInt(process.env.CI_MERGE_REQUEST_IID, 10) : null,
|
|
5420
|
+
prTitle: process.env.CI_MERGE_REQUEST_TITLE || null,
|
|
5421
|
+
commitSha: process.env.CI_COMMIT_SHA || null,
|
|
5422
|
+
branch: process.env.CI_COMMIT_REF_NAME || null,
|
|
5423
|
+
commitMessage: process.env.CI_COMMIT_MESSAGE || null
|
|
5424
|
+
};
|
|
5425
|
+
}
|
|
5426
|
+
if (process.env.BITBUCKET_BUILD_NUMBER) {
|
|
5427
|
+
return {
|
|
5428
|
+
name: "bitbucket-pipelines",
|
|
5429
|
+
prNumber: process.env.BITBUCKET_PR_ID ? parseInt(process.env.BITBUCKET_PR_ID, 10) : null,
|
|
5430
|
+
prTitle: null,
|
|
5431
|
+
commitSha: process.env.BITBUCKET_COMMIT || null,
|
|
5432
|
+
branch: process.env.BITBUCKET_BRANCH || null,
|
|
5433
|
+
commitMessage: process.env.BITBUCKET_COMMIT_MESSAGE || null
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5436
|
+
if (process.env.TF_BUILD === "True") {
|
|
5437
|
+
return {
|
|
5438
|
+
name: "azure-pipelines",
|
|
5439
|
+
prNumber: process.env.SYSTEM_PULLREQUEST_PULLREQUESTID ? parseInt(process.env.SYSTEM_PULLREQUEST_PULLREQUESTID, 10) : null,
|
|
5440
|
+
prTitle: null,
|
|
5441
|
+
commitSha: process.env.BUILD_SOURCEVERSION || null,
|
|
5442
|
+
branch: process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH || process.env.BUILD_SOURCEBRANCHNAME || null,
|
|
5443
|
+
commitMessage: process.env.BUILD_SOURCEVERSIONMESSAGE || null
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
5446
|
+
if (process.env.TRAVIS === "true") {
|
|
5447
|
+
const travisPR = process.env.TRAVIS_PULL_REQUEST;
|
|
5448
|
+
return {
|
|
5449
|
+
name: "travis-ci",
|
|
5450
|
+
prNumber: travisPR && travisPR !== "false" ? parseInt(travisPR, 10) : null,
|
|
5451
|
+
prTitle: null,
|
|
5452
|
+
commitSha: process.env.TRAVIS_COMMIT || null,
|
|
5453
|
+
branch: process.env.TRAVIS_PULL_REQUEST_BRANCH || process.env.TRAVIS_BRANCH || null,
|
|
5454
|
+
commitMessage: process.env.TRAVIS_COMMIT_MESSAGE || null
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
if (process.env.JENKINS_URL) {
|
|
5458
|
+
return {
|
|
5459
|
+
name: "jenkins",
|
|
5460
|
+
prNumber: extractJenkinsPRNumber(),
|
|
5461
|
+
prTitle: null,
|
|
5462
|
+
commitSha: process.env.GIT_COMMIT || null,
|
|
5463
|
+
branch: process.env.CHANGE_BRANCH || process.env.GIT_BRANCH || null,
|
|
5464
|
+
commitMessage: null
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
return null;
|
|
5468
|
+
}
|
|
5469
|
+
function extractGitHubPRNumber() {
|
|
5470
|
+
const ref = process.env.GITHUB_REF;
|
|
5471
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request" && ref) {
|
|
5472
|
+
const match = ref.match(/refs\/pull\/(\d+)/);
|
|
5473
|
+
if (match?.[1])
|
|
5474
|
+
return parseInt(match[1], 10);
|
|
5475
|
+
}
|
|
5476
|
+
return null;
|
|
5477
|
+
}
|
|
5478
|
+
function extractCircleCIPRNumber() {
|
|
5479
|
+
const url = process.env.CIRCLE_PULL_REQUEST;
|
|
5480
|
+
if (url) {
|
|
5481
|
+
const match = url.match(/\/pull\/(\d+)/);
|
|
5482
|
+
if (match?.[1])
|
|
5483
|
+
return parseInt(match[1], 10);
|
|
5484
|
+
}
|
|
5485
|
+
return null;
|
|
5486
|
+
}
|
|
5487
|
+
function extractJenkinsPRNumber() {
|
|
5488
|
+
const changeId = process.env.CHANGE_ID;
|
|
5489
|
+
if (changeId) {
|
|
5490
|
+
const num = parseInt(changeId, 10);
|
|
5491
|
+
if (!isNaN(num))
|
|
5492
|
+
return num;
|
|
5493
|
+
}
|
|
5494
|
+
return null;
|
|
5495
|
+
}
|
|
5496
|
+
|
|
5264
5497
|
// src/commands/deploy/list.ts
|
|
5265
5498
|
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) => {
|
|
5266
5499
|
const token = loadToken();
|
|
@@ -5333,8 +5566,8 @@ var deployRollbackCommand = new Command2("rollback").description("Rollback to a
|
|
|
5333
5566
|
}
|
|
5334
5567
|
const result = await res.json();
|
|
5335
5568
|
console.log(`✅ ${result.message}`);
|
|
5336
|
-
console.log(` Deployment: ${result.deployment.id}`);
|
|
5337
|
-
console.log(` URL: ${result.deployment.url}`);
|
|
5569
|
+
console.log(` Deployment: ${result.data.deployment.id}`);
|
|
5570
|
+
console.log(` URL: ${result.data.deployment.url}`);
|
|
5338
5571
|
} catch (err) {
|
|
5339
5572
|
console.error("Failed to rollback:", err.message);
|
|
5340
5573
|
}
|
|
@@ -5359,8 +5592,8 @@ var deployPromoteCommand = new Command2("promote").description("Promote a previe
|
|
|
5359
5592
|
}
|
|
5360
5593
|
const result = await res.json();
|
|
5361
5594
|
console.log("Deployment promoted to production!");
|
|
5362
|
-
console.log(` Deployment: ${result.deployment.id}`);
|
|
5363
|
-
console.log(` URL: ${result.deployment.url}`);
|
|
5595
|
+
console.log(` Deployment: ${result.data.deployment.id}`);
|
|
5596
|
+
console.log(` URL: ${result.data.deployment.url}`);
|
|
5364
5597
|
} catch (err) {
|
|
5365
5598
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
5366
5599
|
console.error("Failed to promote deployment:", message);
|
|
@@ -5493,7 +5726,7 @@ async function uploadArchive(token, uploadUrl, archive, onRetry) {
|
|
|
5493
5726
|
});
|
|
5494
5727
|
return res.ok;
|
|
5495
5728
|
}
|
|
5496
|
-
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("--no-verify", "Skip deployment verification").option("--no-auto-rollback", "Disable automatic rollback on verification failure").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) => {
|
|
5729
|
+
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("--prod", "Force production deploy (override auto-preview for PRs)").option("--no-verify", "Skip deployment verification").option("--no-auto-rollback", "Disable automatic rollback on verification failure").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) => {
|
|
5497
5730
|
const cwd = process.cwd();
|
|
5498
5731
|
const token = loadToken();
|
|
5499
5732
|
if (!token) {
|
|
@@ -5539,7 +5772,7 @@ var deployCommand = new Command2("deploy").description("Deploy a site").argument
|
|
|
5539
5772
|
console.log(`Error: ${error.error || "Failed to create site"}`);
|
|
5540
5773
|
return;
|
|
5541
5774
|
}
|
|
5542
|
-
const site = await createRes.json();
|
|
5775
|
+
const { data: site } = await createRes.json();
|
|
5543
5776
|
siteSlug = site.slug;
|
|
5544
5777
|
console.log(`Created site: ${site.subdomain}.zerodeploy.app`);
|
|
5545
5778
|
const configPath = getConfigPath(cwd);
|
|
@@ -5669,21 +5902,43 @@ Error: Build failed`);
|
|
|
5669
5902
|
console.log(`Found ${files.length} files (${formatBytes2(totalSize)})`);
|
|
5670
5903
|
try {
|
|
5671
5904
|
const client = getClient(token);
|
|
5905
|
+
const ci = detectCI();
|
|
5906
|
+
if (ci) {
|
|
5907
|
+
console.log(`Detected CI: ${ci.name}`);
|
|
5908
|
+
if (ci.prNumber)
|
|
5909
|
+
console.log(` PR: #${ci.prNumber}`);
|
|
5910
|
+
if (ci.commitSha)
|
|
5911
|
+
console.log(` Commit: ${ci.commitSha.slice(0, 7)}`);
|
|
5912
|
+
if (ci.branch)
|
|
5913
|
+
console.log(` Branch: ${ci.branch}`);
|
|
5914
|
+
console.log("");
|
|
5915
|
+
}
|
|
5916
|
+
const isPreview = options.preview === true || !options.prod && ci?.prNumber != null;
|
|
5917
|
+
if (!options.preview && !options.prod && ci?.prNumber != null) {
|
|
5918
|
+
console.log("Auto-preview enabled for PR deployment");
|
|
5919
|
+
console.log(" Use --prod to deploy to production instead");
|
|
5920
|
+
console.log("");
|
|
5921
|
+
}
|
|
5672
5922
|
const deployPayload = { siteSlug, orgSlug };
|
|
5673
|
-
|
|
5674
|
-
|
|
5923
|
+
const prNumber = options.pr ? parseInt(options.pr, 10) : ci?.prNumber;
|
|
5924
|
+
if (prNumber) {
|
|
5925
|
+
deployPayload.prNumber = prNumber;
|
|
5675
5926
|
}
|
|
5676
|
-
|
|
5677
|
-
|
|
5927
|
+
const prTitle = options.prTitle ?? ci?.prTitle;
|
|
5928
|
+
if (prTitle) {
|
|
5929
|
+
deployPayload.prTitle = prTitle;
|
|
5678
5930
|
}
|
|
5679
|
-
|
|
5680
|
-
|
|
5931
|
+
const commitSha = options.commit ?? ci?.commitSha;
|
|
5932
|
+
if (commitSha) {
|
|
5933
|
+
deployPayload.commitSha = commitSha;
|
|
5681
5934
|
}
|
|
5682
|
-
|
|
5683
|
-
|
|
5935
|
+
const commitMessage = options.commitMessage ?? ci?.commitMessage;
|
|
5936
|
+
if (commitMessage) {
|
|
5937
|
+
deployPayload.commitMessage = commitMessage;
|
|
5684
5938
|
}
|
|
5685
|
-
|
|
5686
|
-
|
|
5939
|
+
const branch = options.branch ?? ci?.branch;
|
|
5940
|
+
if (branch) {
|
|
5941
|
+
deployPayload.branch = branch;
|
|
5687
5942
|
}
|
|
5688
5943
|
const createRes = await client.deployments.$post({
|
|
5689
5944
|
json: deployPayload
|
|
@@ -5693,7 +5948,7 @@ Error: Build failed`);
|
|
|
5693
5948
|
displayError(parseApiError(body));
|
|
5694
5949
|
return;
|
|
5695
5950
|
}
|
|
5696
|
-
const deployment = await createRes.json();
|
|
5951
|
+
const { data: deployment } = await createRes.json();
|
|
5697
5952
|
console.log(`Created deployment: ${deployment.id}`);
|
|
5698
5953
|
const progressBar2 = createProgressBar({ total: files.length, label: "Archiving" });
|
|
5699
5954
|
const archive = await createTarGz(files, (current, total) => {
|
|
@@ -5720,7 +5975,7 @@ Error: Build failed`);
|
|
|
5720
5975
|
"Content-Type": "application/json",
|
|
5721
5976
|
Authorization: `Bearer ${token}`
|
|
5722
5977
|
},
|
|
5723
|
-
body: JSON.stringify({ preview:
|
|
5978
|
+
body: JSON.stringify({ preview: isPreview })
|
|
5724
5979
|
}, {
|
|
5725
5980
|
maxRetries: 3,
|
|
5726
5981
|
onRetry: (attempt, error, delayMs) => {
|
|
@@ -5732,7 +5987,7 @@ Error: Build failed`);
|
|
|
5732
5987
|
return;
|
|
5733
5988
|
}
|
|
5734
5989
|
finalizeSpinner.stop();
|
|
5735
|
-
const verifyUrl =
|
|
5990
|
+
const verifyUrl = isPreview ? deployment.previewUrl : deployment.url;
|
|
5736
5991
|
let verified = false;
|
|
5737
5992
|
if (options.verify !== false) {
|
|
5738
5993
|
const verifySpinner = createSpinner("Verifying...");
|
|
@@ -5748,7 +6003,7 @@ Error: Build failed`);
|
|
|
5748
6003
|
} else if (verification.error) {
|
|
5749
6004
|
console.log(` ${verification.error}`);
|
|
5750
6005
|
}
|
|
5751
|
-
if (options.autoRollback !== false && !
|
|
6006
|
+
if (options.autoRollback !== false && !isPreview) {
|
|
5752
6007
|
console.log("");
|
|
5753
6008
|
console.log("Auto-rolling back to previous deployment...");
|
|
5754
6009
|
const rollback = await autoRollback(token, orgSlug, siteSlug, deployment.id);
|
|
@@ -5771,7 +6026,7 @@ Error: Build failed`);
|
|
|
5771
6026
|
}
|
|
5772
6027
|
}
|
|
5773
6028
|
console.log("");
|
|
5774
|
-
if (
|
|
6029
|
+
if (isPreview) {
|
|
5775
6030
|
console.log("Preview deployment created!");
|
|
5776
6031
|
if (verified) {
|
|
5777
6032
|
console.log(`Preview: ${deployment.previewUrl} (verified)`);
|
|
@@ -5799,12 +6054,15 @@ Error: Build failed`);
|
|
|
5799
6054
|
appendFileSync(githubOutputFile, `deployment_url=${deployment.url}
|
|
5800
6055
|
`);
|
|
5801
6056
|
appendFileSync(githubOutputFile, `preview_url=${deployment.previewUrl}
|
|
6057
|
+
`);
|
|
6058
|
+
appendFileSync(githubOutputFile, `is_preview=${isPreview}
|
|
5802
6059
|
`);
|
|
5803
6060
|
} else {
|
|
5804
6061
|
console.log("");
|
|
5805
6062
|
console.log("::set-output name=deployment_id::" + deployment.id);
|
|
5806
6063
|
console.log("::set-output name=deployment_url::" + deployment.url);
|
|
5807
6064
|
console.log("::set-output name=preview_url::" + deployment.previewUrl);
|
|
6065
|
+
console.log("::set-output name=is_preview::" + isPreview);
|
|
5808
6066
|
}
|
|
5809
6067
|
}
|
|
5810
6068
|
} catch (err) {
|
|
@@ -5820,20 +6078,24 @@ Error: Build failed`);
|
|
|
5820
6078
|
});
|
|
5821
6079
|
|
|
5822
6080
|
// src/commands/deployments/list.ts
|
|
5823
|
-
var deploymentsListCommand = new Command2("list").description("List deployments for a site").argument("<site>", "Site slug").requiredOption("--org <org>", "Organization slug").action(async (site, options) => {
|
|
6081
|
+
var deploymentsListCommand = new Command2("list").description("List deployments for a site").argument("<site>", "Site slug").requiredOption("--org <org>", "Organization slug").option("--json", "Output as JSON").action(async (site, options) => {
|
|
5824
6082
|
const token = loadToken();
|
|
5825
6083
|
if (!token) {
|
|
5826
|
-
|
|
5827
|
-
return;
|
|
6084
|
+
handleAuthError();
|
|
5828
6085
|
}
|
|
5829
6086
|
try {
|
|
5830
6087
|
const client = getClient(token);
|
|
5831
6088
|
const res = await client.orgs[":orgSlug"].sites[":siteSlug"].deployments.$get({
|
|
5832
6089
|
param: { orgSlug: options.org, siteSlug: site }
|
|
5833
6090
|
});
|
|
5834
|
-
if (!res.ok)
|
|
5835
|
-
|
|
6091
|
+
if (!res.ok) {
|
|
6092
|
+
await handleApiError(res);
|
|
6093
|
+
}
|
|
5836
6094
|
const { data: deployments } = await res.json();
|
|
6095
|
+
if (options.json) {
|
|
6096
|
+
console.log(JSON.stringify(deployments, null, 2));
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
5837
6099
|
if (deployments.length === 0) {
|
|
5838
6100
|
console.log("No deployments found.");
|
|
5839
6101
|
return;
|
|
@@ -5856,8 +6118,9 @@ var deploymentsListCommand = new Command2("list").description("List deployments
|
|
|
5856
6118
|
console.log();
|
|
5857
6119
|
}
|
|
5858
6120
|
} catch (err) {
|
|
5859
|
-
const message = err instanceof Error ? err.message :
|
|
5860
|
-
|
|
6121
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6122
|
+
displayError({ code: "network_error", message: `Failed to list deployments: ${message}` });
|
|
6123
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
5861
6124
|
}
|
|
5862
6125
|
});
|
|
5863
6126
|
|
|
@@ -5873,24 +6136,23 @@ function formatBytes3(bytes) {
|
|
|
5873
6136
|
function formatStatus3(status) {
|
|
5874
6137
|
switch (status) {
|
|
5875
6138
|
case "pending":
|
|
5876
|
-
return "
|
|
6139
|
+
return "Pending";
|
|
5877
6140
|
case "uploading":
|
|
5878
|
-
return "
|
|
6141
|
+
return "Uploading";
|
|
5879
6142
|
case "processing":
|
|
5880
|
-
return "
|
|
6143
|
+
return "Processing";
|
|
5881
6144
|
case "ready":
|
|
5882
|
-
return "
|
|
6145
|
+
return "Ready";
|
|
5883
6146
|
case "failed":
|
|
5884
|
-
return "
|
|
6147
|
+
return "Failed";
|
|
5885
6148
|
default:
|
|
5886
6149
|
return status;
|
|
5887
6150
|
}
|
|
5888
6151
|
}
|
|
5889
|
-
var deploymentsShowCommand = new Command2("show").description("View deployment details").argument("<id>", "Deployment ID (full or short)").action(async (id) => {
|
|
6152
|
+
var deploymentsShowCommand = new Command2("show").description("View deployment details").argument("<id>", "Deployment ID (full or short)").option("--json", "Output as JSON").action(async (id, options) => {
|
|
5890
6153
|
const token = loadToken();
|
|
5891
6154
|
if (!token) {
|
|
5892
|
-
|
|
5893
|
-
return;
|
|
6155
|
+
handleAuthError();
|
|
5894
6156
|
}
|
|
5895
6157
|
try {
|
|
5896
6158
|
const client = getClient(token);
|
|
@@ -5899,15 +6161,20 @@ var deploymentsShowCommand = new Command2("show").description("View deployment d
|
|
|
5899
6161
|
});
|
|
5900
6162
|
if (!res.ok) {
|
|
5901
6163
|
if (res.status === 404) {
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
6164
|
+
displayError({
|
|
6165
|
+
code: "not_found",
|
|
6166
|
+
message: `Deployment not found: ${id}`,
|
|
6167
|
+
hint: "Use the full deployment ID or at least 8 characters."
|
|
6168
|
+
});
|
|
6169
|
+
process.exit(ExitCode.NOT_FOUND);
|
|
5906
6170
|
}
|
|
5907
|
-
|
|
5908
|
-
|
|
6171
|
+
await handleApiError(res);
|
|
6172
|
+
}
|
|
6173
|
+
const { data: d } = await res.json();
|
|
6174
|
+
if (options.json) {
|
|
6175
|
+
console.log(JSON.stringify(d, null, 2));
|
|
6176
|
+
return;
|
|
5909
6177
|
}
|
|
5910
|
-
const d = await res.json();
|
|
5911
6178
|
console.log("Deployment Details");
|
|
5912
6179
|
console.log("=".repeat(50));
|
|
5913
6180
|
console.log();
|
|
@@ -5957,8 +6224,9 @@ var deploymentsShowCommand = new Command2("show").description("View deployment d
|
|
|
5957
6224
|
console.log(` zerodeploy rollback <site> --org <org> --to ${d.id.slice(0, 8)}`);
|
|
5958
6225
|
}
|
|
5959
6226
|
} catch (err) {
|
|
5960
|
-
const message = err instanceof Error ? err.message :
|
|
5961
|
-
|
|
6227
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6228
|
+
displayError({ code: "network_error", message: `Failed to get deployment: ${message}` });
|
|
6229
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
5962
6230
|
}
|
|
5963
6231
|
});
|
|
5964
6232
|
|
|
@@ -5969,8 +6237,7 @@ var deploymentsCommand = new Command2("deployments").description("Manage deploym
|
|
|
5969
6237
|
var rollbackCommand = new Command2("rollback").description("Rollback a site to a previous deployment").argument("<site>", "Site slug").requiredOption("--org <org>", "Organization slug").option("--to <deploymentId>", "Deployment ID to rollback to (defaults to previous deployment)").action(async (site, options) => {
|
|
5970
6238
|
const token = loadToken();
|
|
5971
6239
|
if (!token) {
|
|
5972
|
-
|
|
5973
|
-
return;
|
|
6240
|
+
handleAuthError();
|
|
5974
6241
|
}
|
|
5975
6242
|
try {
|
|
5976
6243
|
const client = getClient(token);
|
|
@@ -5979,13 +6246,14 @@ var rollbackCommand = new Command2("rollback").description("Rollback a site to a
|
|
|
5979
6246
|
const listRes = await client.orgs[":orgSlug"].sites[":siteSlug"].deployments.$get({
|
|
5980
6247
|
param: { orgSlug: options.org, siteSlug: site }
|
|
5981
6248
|
});
|
|
5982
|
-
if (!listRes.ok)
|
|
5983
|
-
|
|
6249
|
+
if (!listRes.ok) {
|
|
6250
|
+
await handleApiError(listRes);
|
|
6251
|
+
}
|
|
5984
6252
|
const { data: deployments } = await listRes.json();
|
|
5985
6253
|
const readyDeployments = deployments.filter((d) => d.status === "ready");
|
|
5986
6254
|
if (readyDeployments.length < 2) {
|
|
5987
|
-
|
|
5988
|
-
|
|
6255
|
+
displayError({ code: "validation_error", message: "No previous deployment to rollback to." });
|
|
6256
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
5989
6257
|
}
|
|
5990
6258
|
const currentIndex = readyDeployments.findIndex((d) => d.is_current);
|
|
5991
6259
|
if (currentIndex === -1 || currentIndex >= readyDeployments.length - 1) {
|
|
@@ -5998,138 +6266,402 @@ var rollbackCommand = new Command2("rollback").description("Rollback a site to a
|
|
|
5998
6266
|
param: { id: deploymentId }
|
|
5999
6267
|
});
|
|
6000
6268
|
if (!res.ok) {
|
|
6001
|
-
|
|
6002
|
-
throw new Error(error.error || `API Error ${res.status}`);
|
|
6269
|
+
await handleApiError(res);
|
|
6003
6270
|
}
|
|
6004
6271
|
const result = await res.json();
|
|
6005
|
-
const shortId = result.deployment.id.slice(0, 8);
|
|
6006
|
-
const commit = result.deployment.commit_sha ? ` (${result.deployment.commit_sha.slice(0, 7)})` : "";
|
|
6272
|
+
const shortId = result.data.deployment.id.slice(0, 8);
|
|
6273
|
+
const commit = result.data.deployment.commit_sha ? ` (${result.data.deployment.commit_sha.slice(0, 7)})` : "";
|
|
6007
6274
|
console.log(`Rolled back to deployment ${shortId}${commit}`);
|
|
6008
|
-
console.log(`URL: ${result.deployment.url}`);
|
|
6275
|
+
console.log(`URL: ${result.data.deployment.url}`);
|
|
6009
6276
|
} catch (err) {
|
|
6010
|
-
const message = err instanceof Error ? err.message :
|
|
6011
|
-
|
|
6277
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6278
|
+
displayError({ code: "network_error", message: `Failed to rollback: ${message}` });
|
|
6279
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6012
6280
|
}
|
|
6013
6281
|
});
|
|
6014
6282
|
|
|
6015
6283
|
// src/commands/token/create.ts
|
|
6016
|
-
var tokenCreateCommand = new Command2("create").description("Create
|
|
6284
|
+
var tokenCreateCommand = new Command2("create").description("Create an API token").argument("<name>", 'Token name (e.g., "GitHub Actions", "CI Pipeline")').option("--site <siteId>", "Create a site-scoped deploy token (instead of a PAT)").option("--org <orgSlug>", "Create an org-scoped deploy token (instead of a PAT)").option("--expires <days>", "Token expiration in days (default: never)", parseInt).option("--json", "Output as JSON").action(async (name, options) => {
|
|
6017
6285
|
const token = loadToken();
|
|
6018
6286
|
if (!token) {
|
|
6019
|
-
|
|
6020
|
-
|
|
6287
|
+
handleAuthError();
|
|
6288
|
+
}
|
|
6289
|
+
if (options.site && options.org) {
|
|
6290
|
+
displayError({ code: "validation_error", message: "--site and --org cannot be used together" });
|
|
6291
|
+
process.exit(ExitCode.VALIDATION_ERROR);
|
|
6021
6292
|
}
|
|
6022
6293
|
try {
|
|
6023
6294
|
const client = getClient(token);
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6295
|
+
let scopeType = "user";
|
|
6296
|
+
let permissions = ["*"];
|
|
6297
|
+
if (options.site) {
|
|
6298
|
+
scopeType = "site";
|
|
6299
|
+
permissions = ["deploy"];
|
|
6300
|
+
} else if (options.org) {
|
|
6301
|
+
scopeType = "org";
|
|
6302
|
+
permissions = ["deploy"];
|
|
6303
|
+
}
|
|
6304
|
+
const body = {
|
|
6305
|
+
name,
|
|
6306
|
+
scope_type: scopeType,
|
|
6307
|
+
permissions
|
|
6308
|
+
};
|
|
6309
|
+
if (options.site) {
|
|
6310
|
+
body.site_id = options.site;
|
|
6311
|
+
}
|
|
6312
|
+
if (options.org) {
|
|
6313
|
+
body.org_slug = options.org;
|
|
6314
|
+
}
|
|
6315
|
+
if (options.expires) {
|
|
6316
|
+
body.expires_in_days = options.expires;
|
|
6317
|
+
}
|
|
6318
|
+
const res = await client.tokens.$post({ json: body });
|
|
6028
6319
|
if (!res.ok) {
|
|
6029
|
-
|
|
6030
|
-
|
|
6320
|
+
await handleApiError(res);
|
|
6321
|
+
}
|
|
6322
|
+
const { data: result } = await res.json();
|
|
6323
|
+
if (options.json) {
|
|
6324
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6031
6325
|
return;
|
|
6032
6326
|
}
|
|
6033
|
-
|
|
6327
|
+
let typeLabel = "Personal Access Token (PAT)";
|
|
6328
|
+
if (result.scope_type === "site") {
|
|
6329
|
+
typeLabel = "Site Deploy Token";
|
|
6330
|
+
} else if (result.scope_type === "org") {
|
|
6331
|
+
typeLabel = "Org Deploy Token";
|
|
6332
|
+
}
|
|
6034
6333
|
console.log("");
|
|
6035
|
-
console.log(
|
|
6334
|
+
console.log(`${typeLabel} created successfully!`);
|
|
6036
6335
|
console.log("");
|
|
6037
|
-
console.log(`Name:
|
|
6038
|
-
console.log(`ID:
|
|
6336
|
+
console.log(`Name: ${result.name}`);
|
|
6337
|
+
console.log(`ID: ${result.id}`);
|
|
6338
|
+
console.log(`Prefix: ${result.token_prefix}`);
|
|
6339
|
+
console.log(`Permissions: ${result.permissions.join(", ")}`);
|
|
6340
|
+
if (result.org_slug) {
|
|
6341
|
+
console.log(`Org: ${result.org_slug}`);
|
|
6342
|
+
}
|
|
6343
|
+
if (result.expires_at) {
|
|
6344
|
+
console.log(`Expires: ${new Date(result.expires_at).toLocaleDateString()}`);
|
|
6345
|
+
}
|
|
6039
6346
|
console.log("");
|
|
6040
6347
|
console.log("Token (save this - it will not be shown again):");
|
|
6041
6348
|
console.log("");
|
|
6042
6349
|
console.log(` ${result.token}`);
|
|
6043
6350
|
console.log("");
|
|
6044
|
-
|
|
6045
|
-
|
|
6351
|
+
if (result.scope_type === "site" || result.scope_type === "org") {
|
|
6352
|
+
console.log("Usage in GitHub Actions:");
|
|
6353
|
+
console.log(" Add this token as a repository secret named ZERODEPLOY_TOKEN");
|
|
6354
|
+
if (result.scope_type === "org") {
|
|
6355
|
+
console.log(" This token can deploy to any site in the organization.");
|
|
6356
|
+
}
|
|
6357
|
+
} else {
|
|
6358
|
+
console.log("Usage:");
|
|
6359
|
+
console.log(" Use this token in the Authorization header:");
|
|
6360
|
+
console.log(" Authorization: Bearer <token>");
|
|
6361
|
+
}
|
|
6046
6362
|
console.log("");
|
|
6047
6363
|
} catch (err) {
|
|
6048
|
-
const message = err instanceof Error ? err.message :
|
|
6049
|
-
|
|
6364
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6365
|
+
displayError({ code: "network_error", message: `Failed to create token: ${message}` });
|
|
6366
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6050
6367
|
}
|
|
6051
6368
|
});
|
|
6052
6369
|
|
|
6053
6370
|
// src/commands/token/list.ts
|
|
6054
|
-
var tokenListCommand = new Command2("list").description("List
|
|
6371
|
+
var tokenListCommand = new Command2("list").description("List API tokens").option("--site <siteId>", "Filter by site ID (for site-scoped deploy tokens)").option("--org <orgId>", "Filter by org ID (for org-scoped deploy tokens)").option("--type <type>", "Filter by type: user (PAT), site, or org (deploy tokens)", "all").option("--json", "Output as JSON").action(async (options) => {
|
|
6055
6372
|
const token = loadToken();
|
|
6056
6373
|
if (!token) {
|
|
6057
|
-
|
|
6058
|
-
return;
|
|
6374
|
+
handleAuthError();
|
|
6059
6375
|
}
|
|
6060
6376
|
try {
|
|
6061
6377
|
const client = getClient(token);
|
|
6062
|
-
const
|
|
6063
|
-
|
|
6064
|
-
|
|
6378
|
+
const query = {};
|
|
6379
|
+
if (options.type === "user") {
|
|
6380
|
+
query.scope_type = "user";
|
|
6381
|
+
} else if (options.type === "site") {
|
|
6382
|
+
query.scope_type = "site";
|
|
6383
|
+
} else if (options.type === "org") {
|
|
6384
|
+
query.scope_type = "org";
|
|
6385
|
+
}
|
|
6386
|
+
if (options.site) {
|
|
6387
|
+
query.site_id = options.site;
|
|
6388
|
+
}
|
|
6389
|
+
if (options.org) {
|
|
6390
|
+
query.org_id = options.org;
|
|
6391
|
+
}
|
|
6392
|
+
const res = await client.tokens.$get({ query });
|
|
6065
6393
|
if (!res.ok) {
|
|
6066
|
-
|
|
6067
|
-
console.log(`Error: ${error.error || "Failed to list tokens"}`);
|
|
6068
|
-
return;
|
|
6394
|
+
await handleApiError(res);
|
|
6069
6395
|
}
|
|
6070
6396
|
const { data: tokens } = await res.json();
|
|
6397
|
+
if (options.json) {
|
|
6398
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
6399
|
+
return;
|
|
6400
|
+
}
|
|
6071
6401
|
if (tokens.length === 0) {
|
|
6072
|
-
console.log(
|
|
6402
|
+
console.log("No API tokens found");
|
|
6403
|
+
if (options.type !== "all") {
|
|
6404
|
+
console.log(`(filtered by type: ${options.type})`);
|
|
6405
|
+
}
|
|
6073
6406
|
return;
|
|
6074
6407
|
}
|
|
6075
|
-
console.log(
|
|
6408
|
+
console.log("API Tokens:");
|
|
6076
6409
|
console.log("");
|
|
6077
6410
|
for (const t of tokens) {
|
|
6078
6411
|
const created = new Date(t.created_at).toLocaleDateString();
|
|
6079
6412
|
const lastUsed = t.last_used_at ? new Date(t.last_used_at).toLocaleDateString() : "Never";
|
|
6080
|
-
|
|
6413
|
+
const expires = t.expires_at ? new Date(t.expires_at).toLocaleDateString() : "Never";
|
|
6414
|
+
let typeLabel = "PAT";
|
|
6415
|
+
if (t.scope_type === "site") {
|
|
6416
|
+
typeLabel = "Site";
|
|
6417
|
+
} else if (t.scope_type === "org") {
|
|
6418
|
+
typeLabel = "Org";
|
|
6419
|
+
}
|
|
6420
|
+
const permissions = t.permissions.join(", ");
|
|
6421
|
+
console.log(` ${t.token_prefix.padEnd(14)} ${t.name.padEnd(20)} [${typeLabel}]`);
|
|
6422
|
+
console.log(` Created: ${created} Last used: ${lastUsed} Expires: ${expires}`);
|
|
6423
|
+
console.log(` Permissions: ${permissions}`);
|
|
6424
|
+
if (t.site_id) {
|
|
6425
|
+
console.log(` Site ID: ${t.site_id}`);
|
|
6426
|
+
}
|
|
6427
|
+
if (t.org_slug) {
|
|
6428
|
+
console.log(` Org: ${t.org_slug}`);
|
|
6429
|
+
}
|
|
6430
|
+
console.log("");
|
|
6081
6431
|
}
|
|
6082
|
-
console.log("");
|
|
6083
6432
|
} catch (err) {
|
|
6084
|
-
const message = err instanceof Error ? err.message :
|
|
6085
|
-
|
|
6433
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6434
|
+
displayError({ code: "network_error", message: `Failed to list tokens: ${message}` });
|
|
6435
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6086
6436
|
}
|
|
6087
6437
|
});
|
|
6088
6438
|
|
|
6089
6439
|
// src/commands/token/delete.ts
|
|
6090
|
-
var tokenDeleteCommand = new Command2("delete").description("Delete
|
|
6440
|
+
var tokenDeleteCommand = new Command2("delete").description("Delete an API token").argument("<tokenId>", 'Token ID or token prefix (e.g., "zd_abc12345")').action(async (tokenIdOrPrefix) => {
|
|
6091
6441
|
const token = loadToken();
|
|
6092
6442
|
if (!token) {
|
|
6093
|
-
|
|
6094
|
-
return;
|
|
6443
|
+
handleAuthError();
|
|
6095
6444
|
}
|
|
6096
6445
|
try {
|
|
6097
6446
|
const client = getClient(token);
|
|
6098
|
-
let fullTokenId =
|
|
6099
|
-
if (
|
|
6100
|
-
const listRes = await client.
|
|
6101
|
-
param: { orgSlug: options.org, siteSlug: options.site }
|
|
6102
|
-
});
|
|
6447
|
+
let fullTokenId = tokenIdOrPrefix;
|
|
6448
|
+
if (tokenIdOrPrefix.length < 36) {
|
|
6449
|
+
const listRes = await client.tokens.$get({ query: {} });
|
|
6103
6450
|
if (!listRes.ok) {
|
|
6104
|
-
|
|
6105
|
-
console.log(`Error: ${error.error || "Failed to find token"}`);
|
|
6106
|
-
return;
|
|
6451
|
+
await handleApiError(listRes);
|
|
6107
6452
|
}
|
|
6108
6453
|
const { data: tokens } = await listRes.json();
|
|
6109
|
-
const match = tokens.find((t) => t.id.startsWith(
|
|
6454
|
+
const match = tokens.find((t) => t.token_prefix === tokenIdOrPrefix || t.token_prefix.startsWith(tokenIdOrPrefix) || t.id.startsWith(tokenIdOrPrefix));
|
|
6110
6455
|
if (!match) {
|
|
6111
|
-
|
|
6112
|
-
|
|
6456
|
+
displayError({ code: "not_found", message: `No token found matching "${tokenIdOrPrefix}"`, hint: "Use `zerodeploy token list` to see available tokens" });
|
|
6457
|
+
process.exit(ExitCode.NOT_FOUND);
|
|
6113
6458
|
}
|
|
6114
6459
|
fullTokenId = match.id;
|
|
6460
|
+
console.log(`Found token: ${match.name} (${match.token_prefix})`);
|
|
6115
6461
|
}
|
|
6116
|
-
const res = await client.
|
|
6117
|
-
param: {
|
|
6462
|
+
const res = await client.tokens[":tokenId"].$delete({
|
|
6463
|
+
param: { tokenId: fullTokenId }
|
|
6118
6464
|
});
|
|
6119
6465
|
if (!res.ok) {
|
|
6120
|
-
|
|
6121
|
-
console.log(`Error: ${error.error || "Failed to delete token"}`);
|
|
6122
|
-
return;
|
|
6466
|
+
await handleApiError(res);
|
|
6123
6467
|
}
|
|
6124
6468
|
console.log("Token deleted successfully");
|
|
6125
6469
|
} catch (err) {
|
|
6126
|
-
const message = err instanceof Error ? err.message :
|
|
6127
|
-
|
|
6470
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6471
|
+
displayError({ code: "network_error", message: `Failed to delete token: ${message}` });
|
|
6472
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6128
6473
|
}
|
|
6129
6474
|
});
|
|
6130
6475
|
|
|
6131
6476
|
// src/commands/token/index.ts
|
|
6132
|
-
var tokenCommand = new Command2("token").description("Manage
|
|
6477
|
+
var tokenCommand = new Command2("token").description("Manage API tokens (PATs and deploy tokens)").addCommand(tokenCreateCommand).addCommand(tokenListCommand).addCommand(tokenDeleteCommand);
|
|
6478
|
+
|
|
6479
|
+
// src/commands/billing/index.ts
|
|
6480
|
+
function formatCents(cents) {
|
|
6481
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
6482
|
+
}
|
|
6483
|
+
function formatDate(dateStr) {
|
|
6484
|
+
return new Date(dateStr).toLocaleDateString("en-US", {
|
|
6485
|
+
year: "numeric",
|
|
6486
|
+
month: "short",
|
|
6487
|
+
day: "numeric"
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6490
|
+
function formatMonth(monthStr) {
|
|
6491
|
+
const [year, month] = monthStr.split("-");
|
|
6492
|
+
return new Date(Number(year), Number(month) - 1).toLocaleDateString("en-US", {
|
|
6493
|
+
year: "numeric",
|
|
6494
|
+
month: "long"
|
|
6495
|
+
});
|
|
6496
|
+
}
|
|
6497
|
+
function getReasonLabel(reason) {
|
|
6498
|
+
const labels = {
|
|
6499
|
+
beta_credit: "Beta Credit",
|
|
6500
|
+
promo_credit: "Promotional Credit",
|
|
6501
|
+
support_credit: "Support Credit",
|
|
6502
|
+
refund: "Refund"
|
|
6503
|
+
};
|
|
6504
|
+
return labels[reason] || reason;
|
|
6505
|
+
}
|
|
6506
|
+
var billingUsageCommand = new Command2("usage").description("Show account balance and current period usage").option("--json", "Output as JSON").action(async (options) => {
|
|
6507
|
+
const token = loadToken();
|
|
6508
|
+
if (!token) {
|
|
6509
|
+
handleAuthError();
|
|
6510
|
+
}
|
|
6511
|
+
try {
|
|
6512
|
+
const client = getClient(token);
|
|
6513
|
+
const res = await client.billing.usage.$get();
|
|
6514
|
+
if (!res.ok) {
|
|
6515
|
+
await handleApiError(res);
|
|
6516
|
+
}
|
|
6517
|
+
const { data } = await res.json();
|
|
6518
|
+
if (options.json) {
|
|
6519
|
+
console.log(JSON.stringify(data, null, 2));
|
|
6520
|
+
return;
|
|
6521
|
+
}
|
|
6522
|
+
console.log();
|
|
6523
|
+
console.log("Account Balance");
|
|
6524
|
+
console.log("───────────────");
|
|
6525
|
+
const balanceColor = data.balance_cents > 0 ? "\x1B[32m" : "\x1B[33m";
|
|
6526
|
+
const reset = "\x1B[0m";
|
|
6527
|
+
console.log(` Balance: ${balanceColor}${data.balance_formatted}${reset}`);
|
|
6528
|
+
console.log(` Status: ${data.billing_status}`);
|
|
6529
|
+
console.log(` Can Deploy: ${data.can_deploy ? "\x1B[32mYes\x1B[0m" : "\x1B[31mNo\x1B[0m"}`);
|
|
6530
|
+
if (!data.can_deploy) {
|
|
6531
|
+
console.log();
|
|
6532
|
+
console.log("\x1B[33m ⚠ Add balance to enable deployments\x1B[0m");
|
|
6533
|
+
}
|
|
6534
|
+
console.log();
|
|
6535
|
+
console.log("Current Period Usage");
|
|
6536
|
+
console.log("────────────────────");
|
|
6537
|
+
console.log(` Period: ${formatDate(data.current_period.start)} - ${formatDate(data.current_period.end)}`);
|
|
6538
|
+
console.log();
|
|
6539
|
+
const usage = data.current_period.usage;
|
|
6540
|
+
const included = data.current_period.included;
|
|
6541
|
+
const cost = data.current_period.estimated_cost;
|
|
6542
|
+
console.log(" Resource Used Included Overage");
|
|
6543
|
+
console.log(" ─────────────────────────────────────────────────────");
|
|
6544
|
+
console.log(` Storage ${usage.storage_gb.toFixed(2)} GB ${included.storage_gb} GB ${cost.storage_cents > 0 ? formatCents(cost.storage_cents) : "-"}`);
|
|
6545
|
+
console.log(` Requests ${usage.requests_millions.toFixed(2)}M ${included.requests_millions}M ${cost.requests_cents > 0 ? formatCents(cost.requests_cents) : "-"}`);
|
|
6546
|
+
console.log(` Form Submissions ${usage.form_submissions} ${included.form_submissions} ${cost.forms_cents > 0 ? formatCents(cost.forms_cents) : "-"}`);
|
|
6547
|
+
console.log(` Custom Domains ${usage.domains_count} ${included.domains_count} ${cost.domains_cents > 0 ? formatCents(cost.domains_cents) : "-"}`);
|
|
6548
|
+
console.log();
|
|
6549
|
+
console.log(` Base: ${formatCents(cost.base_cents)}/month`);
|
|
6550
|
+
if (cost.usage_total_cents > 0) {
|
|
6551
|
+
console.log(` Usage: +${formatCents(cost.usage_total_cents)}`);
|
|
6552
|
+
}
|
|
6553
|
+
console.log(` ─────────────────────────────────────────────────────`);
|
|
6554
|
+
console.log(` Estimated Total: ${formatCents(cost.total_cents)}`);
|
|
6555
|
+
console.log();
|
|
6556
|
+
} catch (err) {
|
|
6557
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6558
|
+
displayError({
|
|
6559
|
+
code: "network_error",
|
|
6560
|
+
message: `Failed to fetch billing info: ${message}`
|
|
6561
|
+
});
|
|
6562
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6565
|
+
var billingBillsCommand = new Command2("bills").description("Show billing history").option("--json", "Output as JSON").option("--limit <n>", "Number of bills to show", "10").action(async (options) => {
|
|
6566
|
+
const token = loadToken();
|
|
6567
|
+
if (!token) {
|
|
6568
|
+
handleAuthError();
|
|
6569
|
+
}
|
|
6570
|
+
try {
|
|
6571
|
+
const client = getClient(token);
|
|
6572
|
+
const res = await client.billing.bills.$get({
|
|
6573
|
+
query: { limit: options.limit }
|
|
6574
|
+
});
|
|
6575
|
+
if (!res.ok) {
|
|
6576
|
+
await handleApiError(res);
|
|
6577
|
+
}
|
|
6578
|
+
const { data: bills, pagination } = await res.json();
|
|
6579
|
+
if (options.json) {
|
|
6580
|
+
console.log(JSON.stringify({ bills, pagination }, null, 2));
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6583
|
+
console.log();
|
|
6584
|
+
console.log("Billing History");
|
|
6585
|
+
console.log("───────────────");
|
|
6586
|
+
if (bills.length === 0) {
|
|
6587
|
+
console.log(" No billing history yet");
|
|
6588
|
+
console.log();
|
|
6589
|
+
return;
|
|
6590
|
+
}
|
|
6591
|
+
console.log();
|
|
6592
|
+
console.log(" Period Status Total");
|
|
6593
|
+
console.log(" ─────────────────────────────────────");
|
|
6594
|
+
for (const bill of bills) {
|
|
6595
|
+
const statusColor = bill.status === "paid" ? "\x1B[32m" : bill.status === "finalized" ? "\x1B[33m" : "\x1B[90m";
|
|
6596
|
+
const reset = "\x1B[0m";
|
|
6597
|
+
console.log(` ${formatMonth(bill.billing_month).padEnd(18)} ${statusColor}${bill.status.padEnd(10)}${reset} ${formatCents(bill.final_amount_cents)}`);
|
|
6598
|
+
}
|
|
6599
|
+
console.log();
|
|
6600
|
+
if (pagination.total > bills.length) {
|
|
6601
|
+
console.log(` Showing ${bills.length} of ${pagination.total} bills`);
|
|
6602
|
+
console.log();
|
|
6603
|
+
}
|
|
6604
|
+
} catch (err) {
|
|
6605
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6606
|
+
displayError({
|
|
6607
|
+
code: "network_error",
|
|
6608
|
+
message: `Failed to fetch bills: ${message}`
|
|
6609
|
+
});
|
|
6610
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6611
|
+
}
|
|
6612
|
+
});
|
|
6613
|
+
var billingAdjustmentsCommand = new Command2("adjustments").description("Show balance adjustment history (credits)").option("--json", "Output as JSON").option("--limit <n>", "Number of adjustments to show", "10").action(async (options) => {
|
|
6614
|
+
const token = loadToken();
|
|
6615
|
+
if (!token) {
|
|
6616
|
+
handleAuthError();
|
|
6617
|
+
}
|
|
6618
|
+
try {
|
|
6619
|
+
const client = getClient(token);
|
|
6620
|
+
const res = await client.billing.adjustments.$get({
|
|
6621
|
+
query: { limit: options.limit }
|
|
6622
|
+
});
|
|
6623
|
+
if (!res.ok) {
|
|
6624
|
+
await handleApiError(res);
|
|
6625
|
+
}
|
|
6626
|
+
const { data: adjustments, pagination } = await res.json();
|
|
6627
|
+
if (options.json) {
|
|
6628
|
+
console.log(JSON.stringify({ adjustments, pagination }, null, 2));
|
|
6629
|
+
return;
|
|
6630
|
+
}
|
|
6631
|
+
console.log();
|
|
6632
|
+
console.log("Balance Adjustments");
|
|
6633
|
+
console.log("───────────────────");
|
|
6634
|
+
if (adjustments.length === 0) {
|
|
6635
|
+
console.log(" No balance adjustments yet");
|
|
6636
|
+
console.log();
|
|
6637
|
+
return;
|
|
6638
|
+
}
|
|
6639
|
+
console.log();
|
|
6640
|
+
console.log(" Date Reason Amount");
|
|
6641
|
+
console.log(" ────────────────────────────────────────");
|
|
6642
|
+
for (const adj of adjustments) {
|
|
6643
|
+
console.log(` ${formatDate(adj.created_at).padEnd(12)} ${getReasonLabel(adj.reason).padEnd(18)} \x1B[32m+${formatCents(adj.amount_cents)}\x1B[0m`);
|
|
6644
|
+
if (adj.description) {
|
|
6645
|
+
console.log(` ${adj.description}`);
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
console.log();
|
|
6649
|
+
if (pagination.total > adjustments.length) {
|
|
6650
|
+
console.log(` Showing ${adjustments.length} of ${pagination.total} adjustments`);
|
|
6651
|
+
console.log();
|
|
6652
|
+
}
|
|
6653
|
+
} catch (err) {
|
|
6654
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6655
|
+
displayError({
|
|
6656
|
+
code: "network_error",
|
|
6657
|
+
message: `Failed to fetch adjustments: ${message}`
|
|
6658
|
+
});
|
|
6659
|
+
process.exit(ExitCode.NETWORK_ERROR);
|
|
6660
|
+
}
|
|
6661
|
+
});
|
|
6662
|
+
var billingCommand = new Command2("billing").description("View account balance and billing information").addCommand(billingUsageCommand).addCommand(billingBillsCommand).addCommand(billingAdjustmentsCommand).action(async () => {
|
|
6663
|
+
await billingUsageCommand.parseAsync(["usage"], { from: "user" });
|
|
6664
|
+
});
|
|
6133
6665
|
|
|
6134
6666
|
// src/commands/init.ts
|
|
6135
6667
|
import { writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
@@ -6282,9 +6814,136 @@ var accountCommand = new Command2("account").description("Manage your ZeroDeploy
|
|
|
6282
6814
|
}
|
|
6283
6815
|
}));
|
|
6284
6816
|
|
|
6817
|
+
// src/commands/inspect.ts
|
|
6818
|
+
var VERSION = "0.1.3";
|
|
6819
|
+
function getCommandByPath(rootCommand, path3) {
|
|
6820
|
+
let current = rootCommand;
|
|
6821
|
+
for (const name of path3) {
|
|
6822
|
+
const subcommand = current.commands.find((cmd) => cmd.name() === name);
|
|
6823
|
+
if (!subcommand) {
|
|
6824
|
+
return null;
|
|
6825
|
+
}
|
|
6826
|
+
current = subcommand;
|
|
6827
|
+
}
|
|
6828
|
+
return current;
|
|
6829
|
+
}
|
|
6830
|
+
function extractArguments(cmd) {
|
|
6831
|
+
const args = cmd.registeredArguments || [];
|
|
6832
|
+
return args.map((arg) => {
|
|
6833
|
+
const metadata = {
|
|
6834
|
+
name: arg.name(),
|
|
6835
|
+
description: arg.description || "",
|
|
6836
|
+
required: arg.required
|
|
6837
|
+
};
|
|
6838
|
+
if (arg.defaultValue !== undefined) {
|
|
6839
|
+
metadata.default = String(arg.defaultValue);
|
|
6840
|
+
}
|
|
6841
|
+
return metadata;
|
|
6842
|
+
});
|
|
6843
|
+
}
|
|
6844
|
+
function extractOptions(cmd) {
|
|
6845
|
+
const options = cmd.options || [];
|
|
6846
|
+
return options.filter((opt) => {
|
|
6847
|
+
const longName = opt.long?.replace(/^--/, "") || "";
|
|
6848
|
+
return longName !== "help" && longName !== "version";
|
|
6849
|
+
}).map((opt) => {
|
|
6850
|
+
const longName = opt.long?.replace(/^--/, "") || "";
|
|
6851
|
+
const shortName = opt.short?.replace(/^-/, "");
|
|
6852
|
+
const metadata = {
|
|
6853
|
+
name: longName,
|
|
6854
|
+
description: opt.description || "",
|
|
6855
|
+
required: opt.mandatory || false
|
|
6856
|
+
};
|
|
6857
|
+
if (shortName) {
|
|
6858
|
+
metadata.short = shortName;
|
|
6859
|
+
}
|
|
6860
|
+
if (opt.flags.includes("<") || opt.flags.includes("[")) {
|
|
6861
|
+
metadata.type = "string";
|
|
6862
|
+
} else {
|
|
6863
|
+
metadata.type = "boolean";
|
|
6864
|
+
}
|
|
6865
|
+
if (opt.defaultValue !== undefined && opt.defaultValue !== false) {
|
|
6866
|
+
metadata.default = String(opt.defaultValue);
|
|
6867
|
+
}
|
|
6868
|
+
return metadata;
|
|
6869
|
+
});
|
|
6870
|
+
}
|
|
6871
|
+
function generateExamples(cmd, fullPath) {
|
|
6872
|
+
const examples = [];
|
|
6873
|
+
const cmdPath = ["zerodeploy", ...fullPath].join(" ");
|
|
6874
|
+
const args = cmd.registeredArguments || [];
|
|
6875
|
+
let basicExample = cmdPath;
|
|
6876
|
+
for (const arg of args) {
|
|
6877
|
+
if (arg.required) {
|
|
6878
|
+
basicExample += ` <${arg.name()}>`;
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
if (basicExample !== cmdPath || args.length === 0) {
|
|
6882
|
+
examples.push(basicExample);
|
|
6883
|
+
}
|
|
6884
|
+
const cmdName = fullPath[fullPath.length - 1];
|
|
6885
|
+
const parentName = fullPath[fullPath.length - 2];
|
|
6886
|
+
if (cmdName === "list" && parentName === "org") {
|
|
6887
|
+
examples.push("zerodeploy org list");
|
|
6888
|
+
} else if (cmdName === "create" && parentName === "org") {
|
|
6889
|
+
examples.push("zerodeploy org create my-company");
|
|
6890
|
+
} else if (cmdName === "list" && parentName === "site") {
|
|
6891
|
+
examples.push("zerodeploy site list acme");
|
|
6892
|
+
} else if (cmdName === "create" && parentName === "site") {
|
|
6893
|
+
examples.push('zerodeploy site create acme "My Site"');
|
|
6894
|
+
} else if (cmdName === "deploy") {
|
|
6895
|
+
examples.push("zerodeploy deploy");
|
|
6896
|
+
examples.push("zerodeploy deploy ./dist --org acme --site web");
|
|
6897
|
+
}
|
|
6898
|
+
return examples;
|
|
6899
|
+
}
|
|
6900
|
+
function getCommandMetadata(rootCommand, path3) {
|
|
6901
|
+
if (path3.length === 0) {
|
|
6902
|
+
const commands = rootCommand.commands.map((cmd2) => cmd2.name()).filter((name) => name !== "help").sort();
|
|
6903
|
+
return {
|
|
6904
|
+
name: "zerodeploy",
|
|
6905
|
+
version: VERSION,
|
|
6906
|
+
commands
|
|
6907
|
+
};
|
|
6908
|
+
}
|
|
6909
|
+
const cmd = getCommandByPath(rootCommand, path3);
|
|
6910
|
+
if (!cmd) {
|
|
6911
|
+
return { error: `Command not found: ${path3.join(" ")}` };
|
|
6912
|
+
}
|
|
6913
|
+
const metadata = {
|
|
6914
|
+
name: cmd.name(),
|
|
6915
|
+
description: cmd.description() || ""
|
|
6916
|
+
};
|
|
6917
|
+
const subcommands = cmd.commands.map((sub) => sub.name()).filter((name) => name !== "help");
|
|
6918
|
+
if (subcommands.length > 0) {
|
|
6919
|
+
metadata.subcommands = subcommands.sort();
|
|
6920
|
+
return metadata;
|
|
6921
|
+
}
|
|
6922
|
+
metadata.usage = `zerodeploy ${path3.join(" ")}` + (cmd.registeredArguments || []).map((arg) => arg.required ? ` <${arg.name()}>` : ` [${arg.name()}]`).join("");
|
|
6923
|
+
const args = extractArguments(cmd);
|
|
6924
|
+
if (args.length > 0) {
|
|
6925
|
+
metadata.arguments = args;
|
|
6926
|
+
}
|
|
6927
|
+
const options = extractOptions(cmd);
|
|
6928
|
+
if (options.length > 0) {
|
|
6929
|
+
metadata.options = options;
|
|
6930
|
+
}
|
|
6931
|
+
const examples = generateExamples(cmd, path3);
|
|
6932
|
+
if (examples.length > 0) {
|
|
6933
|
+
metadata.examples = [...new Set(examples)];
|
|
6934
|
+
}
|
|
6935
|
+
return metadata;
|
|
6936
|
+
}
|
|
6937
|
+
function createInspectCommand(rootProgram) {
|
|
6938
|
+
return new Command2("inspect").description("Output command metadata as JSON (for LLM/automation use)").argument("[command...]", 'Command path to inspect (e.g., "org create")').action((commandPath) => {
|
|
6939
|
+
const metadata = getCommandMetadata(rootProgram, commandPath || []);
|
|
6940
|
+
console.log(JSON.stringify(metadata, null, 2));
|
|
6941
|
+
});
|
|
6942
|
+
}
|
|
6943
|
+
|
|
6285
6944
|
// src/index.ts
|
|
6286
6945
|
var program3 = new Command;
|
|
6287
|
-
program3.name("zerodeploy").description("ZeroDeploy CLI").version("0.1.
|
|
6946
|
+
program3.name("zerodeploy").description("ZeroDeploy CLI").version("0.1.3").enablePositionalOptions();
|
|
6288
6947
|
program3.addCommand(loginCommand);
|
|
6289
6948
|
program3.addCommand(logoutCommand);
|
|
6290
6949
|
program3.addCommand(whoamiCommand);
|
|
@@ -6297,6 +6956,8 @@ program3.addCommand(deployCommand);
|
|
|
6297
6956
|
program3.addCommand(deploymentsCommand);
|
|
6298
6957
|
program3.addCommand(rollbackCommand);
|
|
6299
6958
|
program3.addCommand(tokenCommand);
|
|
6959
|
+
program3.addCommand(billingCommand);
|
|
6300
6960
|
program3.addCommand(initCommand);
|
|
6301
6961
|
program3.addCommand(accountCommand);
|
|
6962
|
+
program3.addCommand(createInspectCommand(program3));
|
|
6302
6963
|
program3.parse(process.argv);
|