create-op-node 0.10.8 → 0.10.9
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 +137 -98
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3428,36 +3428,47 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
|
|
|
3428
3428
|
phases.push(ph);
|
|
3429
3429
|
deps.onPhase?.(ph);
|
|
3430
3430
|
};
|
|
3431
|
+
await verifyTlsPhase(input, deps, push);
|
|
3432
|
+
await verifyHealthPhase(input, deps, push);
|
|
3433
|
+
await verifyGraphqlPhase(input, deps, push);
|
|
3434
|
+
await verifyCloudflarePhase(input, deps, push);
|
|
3435
|
+
await verifyCosignPhase(input, deps, push);
|
|
3436
|
+
return { phases };
|
|
3437
|
+
}
|
|
3438
|
+
async function verifyTlsPhase(input, deps, push) {
|
|
3431
3439
|
const tls2 = await deps.tls({ host: input.apiHost });
|
|
3432
3440
|
if (!tls2.ok) {
|
|
3433
3441
|
push({ name: "TLS handshake", status: "fail", detail: tls2.reason });
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
push({ name: "TLS handshake", status: "ok", detail: line });
|
|
3445
|
-
}
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
const line = `subject=${tls2.subject}, issuer=${tls2.issuer}, ${formatExpiry(tls2.daysToExpiry)}`;
|
|
3445
|
+
if (tls2.daysToExpiry < input.certWarnDays) {
|
|
3446
|
+
push({
|
|
3447
|
+
name: "TLS handshake",
|
|
3448
|
+
status: "warn",
|
|
3449
|
+
detail: tls2.daysToExpiry < 0 ? line : `${line} (< warn threshold ${input.certWarnDays}d)`
|
|
3450
|
+
});
|
|
3451
|
+
return;
|
|
3446
3452
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3453
|
+
push({ name: "TLS handshake", status: "ok", detail: line });
|
|
3454
|
+
}
|
|
3455
|
+
async function verifyHealthPhase(input, deps, push) {
|
|
3456
|
+
const health = await deps.http({ url: `https://${input.apiHost}/health` });
|
|
3449
3457
|
if (health.ok) {
|
|
3450
3458
|
push({ name: "GET /health", status: "ok", detail: `HTTP ${health.status}` });
|
|
3451
3459
|
} else {
|
|
3452
3460
|
push({ name: "GET /health", status: "fail", detail: health.reason });
|
|
3453
3461
|
}
|
|
3454
|
-
|
|
3455
|
-
|
|
3462
|
+
}
|
|
3463
|
+
async function verifyGraphqlPhase(input, deps, push) {
|
|
3464
|
+
const gql = await deps.graphql({ url: `https://${input.apiHost}/api` });
|
|
3456
3465
|
if (gql.ok) {
|
|
3457
3466
|
push({ name: "GraphQL { __typename }", status: "ok", detail: `typename=${gql.typename}` });
|
|
3458
3467
|
} else {
|
|
3459
3468
|
push({ name: "GraphQL { __typename }", status: "fail", detail: gql.reason });
|
|
3460
3469
|
}
|
|
3470
|
+
}
|
|
3471
|
+
async function verifyCloudflarePhase(input, deps, push) {
|
|
3461
3472
|
const cfFields = [
|
|
3462
3473
|
["--cf-token", input.cf?.token],
|
|
3463
3474
|
["--cf-account-id", input.cf?.accountId],
|
|
@@ -3470,54 +3481,57 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
|
|
|
3470
3481
|
status: "skipped",
|
|
3471
3482
|
detail: "pass --cf-token + --cf-account-id + --tunnel-id to enable"
|
|
3472
3483
|
});
|
|
3473
|
-
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
if (cfSet.length < cfFields.length) {
|
|
3474
3487
|
const missing = cfFields.filter(([, v]) => v === void 0 || v === "").map(([name]) => name).join(", ");
|
|
3475
3488
|
push({
|
|
3476
3489
|
name: "Cloudflare Tunnel",
|
|
3477
3490
|
status: "warn",
|
|
3478
3491
|
detail: `partial CF config \u2014 missing ${missing}; check skipped`
|
|
3479
3492
|
});
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
const tun = await deps.tunnel({
|
|
3496
|
+
token: input.cf.token,
|
|
3497
|
+
accountId: input.cf.accountId,
|
|
3498
|
+
tunnelId: input.cf.tunnelId
|
|
3499
|
+
});
|
|
3500
|
+
if (!tun.ok) {
|
|
3501
|
+
push({ name: "Cloudflare Tunnel", status: "fail", detail: tun.reason });
|
|
3502
|
+
} else if (tun.connections === 0) {
|
|
3503
|
+
push({
|
|
3504
|
+
name: "Cloudflare Tunnel",
|
|
3505
|
+
status: "warn",
|
|
3506
|
+
detail: `status=${tun.status}, 0 connections \u2014 cloudflared on the Studio appears offline`
|
|
3507
|
+
});
|
|
3480
3508
|
} else {
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3509
|
+
push({
|
|
3510
|
+
name: "Cloudflare Tunnel",
|
|
3511
|
+
status: "ok",
|
|
3512
|
+
detail: `${tun.connections} connections, status=${tun.status}`
|
|
3485
3513
|
});
|
|
3486
|
-
if (!tun.ok) {
|
|
3487
|
-
push({ name: "Cloudflare Tunnel", status: "fail", detail: tun.reason });
|
|
3488
|
-
} else if (tun.connections === 0) {
|
|
3489
|
-
push({
|
|
3490
|
-
name: "Cloudflare Tunnel",
|
|
3491
|
-
status: "warn",
|
|
3492
|
-
detail: `status=${tun.status}, 0 connections \u2014 cloudflared on the Studio appears offline`
|
|
3493
|
-
});
|
|
3494
|
-
} else {
|
|
3495
|
-
push({
|
|
3496
|
-
name: "Cloudflare Tunnel",
|
|
3497
|
-
status: "ok",
|
|
3498
|
-
detail: `${tun.connections} connections, status=${tun.status}`
|
|
3499
|
-
});
|
|
3500
|
-
}
|
|
3501
3514
|
}
|
|
3515
|
+
}
|
|
3516
|
+
async function verifyCosignPhase(input, deps, push) {
|
|
3502
3517
|
if (input.images.length === 0) {
|
|
3503
3518
|
push({
|
|
3504
3519
|
name: "cosign verify",
|
|
3505
3520
|
status: "skipped",
|
|
3506
3521
|
detail: "pass --image <ref> (repeatable) to enable"
|
|
3507
3522
|
});
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
|
-
}
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
for (const image of input.images) {
|
|
3526
|
+
const cos = await deps.cosign({ image });
|
|
3527
|
+
if (cos.ok) {
|
|
3528
|
+
push({ name: `cosign verify ${image}`, status: "ok", detail: "signature valid" });
|
|
3529
|
+
} else if (cos.skipped) {
|
|
3530
|
+
push({ name: `cosign verify ${image}`, status: "skipped", detail: cos.reason });
|
|
3531
|
+
} else {
|
|
3532
|
+
push({ name: `cosign verify ${image}`, status: "fail", detail: cos.reason });
|
|
3518
3533
|
}
|
|
3519
3534
|
}
|
|
3520
|
-
return { phases };
|
|
3521
3535
|
}
|
|
3522
3536
|
function readTokenFile(path) {
|
|
3523
3537
|
const raw = readFileSync(path, "utf8").trim();
|
|
@@ -3542,6 +3556,53 @@ var verifyCommand = new Command("verify").description(
|
|
|
3542
3556
|
new Option("--show-skipped", "Include skipped phases in the summary").default(false)
|
|
3543
3557
|
).action(async (opts) => {
|
|
3544
3558
|
p3.intro(pc2.bgCyan(pc2.black(" create-op-node verify ")));
|
|
3559
|
+
const domain = await resolveDomain(opts);
|
|
3560
|
+
const certWarnDays = resolveCertWarnDays(opts);
|
|
3561
|
+
const cfToken = resolveCfToken(opts);
|
|
3562
|
+
const apiHost = opts.apiHost ?? `api.${domain}`;
|
|
3563
|
+
const images = opts.image ?? [];
|
|
3564
|
+
const totalPhases = 4 + (images.length === 0 ? 1 : images.length);
|
|
3565
|
+
const report = await runVerifyWithSpinner({
|
|
3566
|
+
apiHost,
|
|
3567
|
+
certWarnDays,
|
|
3568
|
+
cfToken,
|
|
3569
|
+
opts,
|
|
3570
|
+
images,
|
|
3571
|
+
totalPhases
|
|
3572
|
+
});
|
|
3573
|
+
renderVerifySummary(report, { opts, totalPhases });
|
|
3574
|
+
reportVerifyOutcome(report);
|
|
3575
|
+
});
|
|
3576
|
+
function phaseIcon2(ph) {
|
|
3577
|
+
switch (ph.status) {
|
|
3578
|
+
case "ok":
|
|
3579
|
+
return pc2.green("\u2713");
|
|
3580
|
+
case "warn":
|
|
3581
|
+
return pc2.yellow("\u26A0");
|
|
3582
|
+
case "skipped":
|
|
3583
|
+
return pc2.dim("\xB7");
|
|
3584
|
+
case "fail":
|
|
3585
|
+
return pc2.red("\u2717");
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
function phaseColor(status) {
|
|
3589
|
+
switch (status) {
|
|
3590
|
+
case "ok":
|
|
3591
|
+
return pc2.green;
|
|
3592
|
+
case "warn":
|
|
3593
|
+
return pc2.yellow;
|
|
3594
|
+
case "skipped":
|
|
3595
|
+
return pc2.dim;
|
|
3596
|
+
case "fail":
|
|
3597
|
+
return pc2.red;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function formatPhaseLine(prefix, ph) {
|
|
3601
|
+
const icon = phaseIcon2(ph);
|
|
3602
|
+
const colorize = phaseColor(ph.status);
|
|
3603
|
+
return colorize(`${icon} ${prefix}: ${ph.detail}`);
|
|
3604
|
+
}
|
|
3605
|
+
async function resolveDomain(opts) {
|
|
3545
3606
|
const domain = opts.domain ? opts.domain : unwrap(
|
|
3546
3607
|
await p3.text({
|
|
3547
3608
|
message: "Public domain of the node?",
|
|
@@ -3553,28 +3614,33 @@ var verifyCommand = new Command("verify").description(
|
|
|
3553
3614
|
p3.cancel(`--domain ${JSON.stringify(domain)} doesn't look like a domain.`);
|
|
3554
3615
|
process.exit(2);
|
|
3555
3616
|
}
|
|
3617
|
+
return domain;
|
|
3618
|
+
}
|
|
3619
|
+
function resolveCertWarnDays(opts) {
|
|
3556
3620
|
const rawDays = opts.certWarnDays ?? "14";
|
|
3557
3621
|
const certWarnDays = Number.parseInt(rawDays, 10);
|
|
3558
3622
|
if (!Number.isFinite(certWarnDays) || certWarnDays < 0 || !/^\d+$/.test(rawDays)) {
|
|
3559
3623
|
p3.cancel(`--cert-warn-days must be a non-negative integer (got ${JSON.stringify(rawDays)}).`);
|
|
3560
3624
|
process.exit(2);
|
|
3561
3625
|
}
|
|
3626
|
+
return certWarnDays;
|
|
3627
|
+
}
|
|
3628
|
+
function resolveCfToken(opts) {
|
|
3562
3629
|
if (opts.cfToken && opts.cfTokenFile) {
|
|
3563
3630
|
p3.cancel("Pass either --cf-token or --cf-token-file, not both.");
|
|
3564
3631
|
process.exit(2);
|
|
3565
3632
|
}
|
|
3566
|
-
|
|
3567
|
-
if (!
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
}
|
|
3633
|
+
if (opts.cfToken) return opts.cfToken;
|
|
3634
|
+
if (!opts.cfTokenFile) return void 0;
|
|
3635
|
+
try {
|
|
3636
|
+
return readTokenFile(opts.cfTokenFile);
|
|
3637
|
+
} catch (err) {
|
|
3638
|
+
p3.cancel(err.message);
|
|
3639
|
+
process.exit(2);
|
|
3574
3640
|
}
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
const
|
|
3641
|
+
}
|
|
3642
|
+
async function runVerifyWithSpinner(args) {
|
|
3643
|
+
const { apiHost, certWarnDays, cfToken, opts, images, totalPhases } = args;
|
|
3578
3644
|
let phaseIndex = 0;
|
|
3579
3645
|
let activeSpin = null;
|
|
3580
3646
|
const renderPhase = (ph) => {
|
|
@@ -3583,10 +3649,7 @@ var verifyCommand = new Command("verify").description(
|
|
|
3583
3649
|
activeSpin?.stop(formatPhaseLine(prefix, ph));
|
|
3584
3650
|
activeSpin = null;
|
|
3585
3651
|
};
|
|
3586
|
-
const deps = {
|
|
3587
|
-
...DEFAULT_DEPS2,
|
|
3588
|
-
onPhase: renderPhase
|
|
3589
|
-
};
|
|
3652
|
+
const deps = { ...DEFAULT_DEPS2, onPhase: renderPhase };
|
|
3590
3653
|
activeSpin = p3.spinner();
|
|
3591
3654
|
activeSpin.start("Running checks\u2026");
|
|
3592
3655
|
const report = await runVerify(
|
|
@@ -3603,6 +3666,10 @@ var verifyCommand = new Command("verify").description(
|
|
|
3603
3666
|
deps
|
|
3604
3667
|
);
|
|
3605
3668
|
activeSpin?.stop("Done.");
|
|
3669
|
+
return report;
|
|
3670
|
+
}
|
|
3671
|
+
function renderVerifySummary(report, args) {
|
|
3672
|
+
const { opts, totalPhases } = args;
|
|
3606
3673
|
const visible = opts.showSkipped ?? false ? report.phases : report.phases.filter((ph) => ph.status !== "skipped");
|
|
3607
3674
|
const lines = visible.map((ph) => {
|
|
3608
3675
|
const idx = report.phases.indexOf(ph) + 1;
|
|
@@ -3613,47 +3680,19 @@ var verifyCommand = new Command("verify").description(
|
|
|
3613
3680
|
lines.push(pc2.dim(`\xB7 ${skippedCount} phase${skippedCount === 1 ? "" : "s"} skipped (run with --show-skipped to see them)`));
|
|
3614
3681
|
}
|
|
3615
3682
|
p3.note(lines.join("\n"), "Summary");
|
|
3683
|
+
}
|
|
3684
|
+
function reportVerifyOutcome(report) {
|
|
3616
3685
|
const summary = summarize(report);
|
|
3617
|
-
if (summary.ok) {
|
|
3618
|
-
if (summary.warned === 0) {
|
|
3619
|
-
p3.outro(pc2.green("All checks passed."));
|
|
3620
|
-
} else {
|
|
3621
|
-
p3.outro(pc2.yellow(`Passed with ${summary.warned} warning${summary.warned === 1 ? "" : "s"}.`));
|
|
3622
|
-
}
|
|
3623
|
-
} else {
|
|
3686
|
+
if (!summary.ok) {
|
|
3624
3687
|
p3.outro(pc2.red(`${summary.failed} check${summary.failed === 1 ? "" : "s"} failed.`));
|
|
3625
3688
|
process.exit(1);
|
|
3626
3689
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
return pc2.green("\u2713");
|
|
3632
|
-
case "warn":
|
|
3633
|
-
return pc2.yellow("\u26A0");
|
|
3634
|
-
case "skipped":
|
|
3635
|
-
return pc2.dim("\xB7");
|
|
3636
|
-
case "fail":
|
|
3637
|
-
return pc2.red("\u2717");
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
function phaseColor(status) {
|
|
3641
|
-
switch (status) {
|
|
3642
|
-
case "ok":
|
|
3643
|
-
return pc2.green;
|
|
3644
|
-
case "warn":
|
|
3645
|
-
return pc2.yellow;
|
|
3646
|
-
case "skipped":
|
|
3647
|
-
return pc2.dim;
|
|
3648
|
-
case "fail":
|
|
3649
|
-
return pc2.red;
|
|
3690
|
+
if (summary.warned === 0) {
|
|
3691
|
+
p3.outro(pc2.green("All checks passed."));
|
|
3692
|
+
} else {
|
|
3693
|
+
p3.outro(pc2.yellow(`Passed with ${summary.warned} warning${summary.warned === 1 ? "" : "s"}.`));
|
|
3650
3694
|
}
|
|
3651
3695
|
}
|
|
3652
|
-
function formatPhaseLine(prefix, ph) {
|
|
3653
|
-
const icon = phaseIcon2(ph);
|
|
3654
|
-
const colorize = phaseColor(ph.status);
|
|
3655
|
-
return colorize(`${icon} ${prefix}: ${ph.detail}`);
|
|
3656
|
-
}
|
|
3657
3696
|
|
|
3658
3697
|
// src/lib/region-schema.json
|
|
3659
3698
|
var region_schema_default = {
|
|
@@ -4674,7 +4713,7 @@ async function looksLikeRegionsRepo(dir) {
|
|
|
4674
4713
|
}
|
|
4675
4714
|
|
|
4676
4715
|
// src/cli.ts
|
|
4677
|
-
var VERSION = "0.10.
|
|
4716
|
+
var VERSION = "0.10.9";
|
|
4678
4717
|
var program = new Command();
|
|
4679
4718
|
program.name("create-op-node").description(
|
|
4680
4719
|
"Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."
|