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 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
- } else {
3435
- const line = `subject=${tls2.subject}, issuer=${tls2.issuer}, ${formatExpiry(tls2.daysToExpiry)}`;
3436
- const warn = tls2.daysToExpiry < input.certWarnDays;
3437
- if (warn) {
3438
- push({
3439
- name: "TLS handshake",
3440
- status: "warn",
3441
- detail: tls2.daysToExpiry < 0 ? line : `${line} (< warn threshold ${input.certWarnDays}d)`
3442
- });
3443
- } else {
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
- const healthUrl = `https://${input.apiHost}/health`;
3448
- const health = await deps.http({ url: healthUrl });
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
- const gqlUrl = `https://${input.apiHost}/api`;
3455
- const gql = await deps.graphql({ url: gqlUrl });
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
- } else if (cfSet.length < cfFields.length) {
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
- const tun = await deps.tunnel({
3482
- token: input.cf.token,
3483
- accountId: input.cf.accountId,
3484
- tunnelId: input.cf.tunnelId
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
- } else {
3509
- for (const image of input.images) {
3510
- const cos = await deps.cosign({ image });
3511
- if (cos.ok) {
3512
- push({ name: `cosign verify ${image}`, status: "ok", detail: "signature valid" });
3513
- } else if (cos.skipped) {
3514
- push({ name: `cosign verify ${image}`, status: "skipped", detail: cos.reason });
3515
- } else {
3516
- push({ name: `cosign verify ${image}`, status: "fail", detail: cos.reason });
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
- let cfToken = opts.cfToken;
3567
- if (!cfToken && opts.cfTokenFile) {
3568
- try {
3569
- cfToken = readTokenFile(opts.cfTokenFile);
3570
- } catch (err) {
3571
- p3.cancel(err.message);
3572
- process.exit(2);
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
- const apiHost = opts.apiHost ?? `api.${domain}`;
3576
- const images = opts.image ?? [];
3577
- const totalPhases = 4 + (images.length === 0 ? 1 : images.length);
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
- function phaseIcon2(ph) {
3629
- switch (ph.status) {
3630
- case "ok":
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.8";
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."