create-op-node 0.10.7 → 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
@@ -2890,87 +2890,89 @@ async function runReset(input, deps = DEFAULT_DEPS) {
2890
2890
  phases.push(ph);
2891
2891
  deps.onPhase?.(ph);
2892
2892
  };
2893
+ push(await resetStopStackPhase(input, deps));
2894
+ push(await resetLaunchAgentPhase(input, deps));
2895
+ push(await resetDockerLogoutPhase(input, deps));
2896
+ return { phases };
2897
+ }
2898
+ async function resetStopStackPhase(input, deps) {
2893
2899
  if (!input.stack) {
2894
- push({
2900
+ return {
2895
2901
  name: RESET_PHASES.STOP_STACK,
2896
2902
  status: "skipped",
2897
2903
  detail: "--skip-stack or no repo path resolved"
2898
- });
2899
- } else if (input.dryRun) {
2900
- push({
2904
+ };
2905
+ }
2906
+ if (input.dryRun) {
2907
+ return {
2901
2908
  name: RESET_PHASES.STOP_STACK,
2902
2909
  status: "dry-run",
2903
2910
  detail: `would run: docker compose -f ${input.stack.composeFiles.join(" -f ")} down${input.stack.wipeVolumes ? " -v" : ""}${input.stack.removeOrphans ? " --remove-orphans" : ""}${input.stack.wipeImages ? " --rmi all" : ""}`
2904
- });
2905
- } else {
2906
- const result2 = await deps.composeDown({
2907
- files: input.stack.composeFiles,
2908
- cwd: input.stack.repoPath,
2909
- ...input.stack.envFile ? { envFile: input.stack.envFile } : {},
2910
- wipeVolumes: input.stack.wipeVolumes,
2911
- removeOrphans: input.stack.removeOrphans,
2912
- ...input.stack.wipeImages ? { removeImages: "all" } : {}
2913
- });
2914
- if (result2.ok) {
2915
- const bits = [
2916
- input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved",
2917
- input.stack.wipeImages ? "images removed (next bootstrap will re-pull)" : null
2918
- ].filter(Boolean);
2919
- push({
2920
- name: RESET_PHASES.STOP_STACK,
2921
- status: "ok",
2922
- detail: bits.join("; ")
2923
- });
2924
- } else {
2925
- push({ name: RESET_PHASES.STOP_STACK, status: "fail", detail: result2.reason ?? "unknown failure" });
2926
- }
2911
+ };
2927
2912
  }
2913
+ const result2 = await deps.composeDown({
2914
+ files: input.stack.composeFiles,
2915
+ cwd: input.stack.repoPath,
2916
+ ...input.stack.envFile ? { envFile: input.stack.envFile } : {},
2917
+ wipeVolumes: input.stack.wipeVolumes,
2918
+ removeOrphans: input.stack.removeOrphans,
2919
+ ...input.stack.wipeImages ? { removeImages: "all" } : {}
2920
+ });
2921
+ if (!result2.ok) {
2922
+ return { name: RESET_PHASES.STOP_STACK, status: "fail", detail: result2.reason ?? "unknown failure" };
2923
+ }
2924
+ const bits = [
2925
+ input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved",
2926
+ input.stack.wipeImages ? "images removed (next bootstrap will re-pull)" : null
2927
+ ].filter(Boolean);
2928
+ return { name: RESET_PHASES.STOP_STACK, status: "ok", detail: bits.join("; ") };
2929
+ }
2930
+ async function resetLaunchAgentPhase(input, deps) {
2928
2931
  if (!input.launchAgent) {
2929
- push({ name: RESET_PHASES.LAUNCH_AGENT, status: "skipped", detail: "--skip-launch-agent or no plist found" });
2930
- } else if (input.dryRun) {
2931
- push({
2932
+ return { name: RESET_PHASES.LAUNCH_AGENT, status: "skipped", detail: "--skip-launch-agent or no plist found" };
2933
+ }
2934
+ if (input.dryRun) {
2935
+ return {
2932
2936
  name: RESET_PHASES.LAUNCH_AGENT,
2933
2937
  status: "dry-run",
2934
2938
  detail: `would unload ${input.launchAgent.paths.plistFile}, rm plist${input.launchAgent.keepKeyFile ? "" : " + key file"}`
2935
- });
2936
- } else {
2937
- const result2 = await deps.teardownLaunchAgent(
2938
- input.launchAgent.paths,
2939
- { keepKeyFile: input.launchAgent.keepKeyFile }
2940
- );
2941
- if (result2.ok) {
2942
- const removed = result2.steps.filter((s) => s.step !== "unload").map((s) => s.step).join(", ");
2943
- push({ name: RESET_PHASES.LAUNCH_AGENT, status: "ok", detail: `unloaded + ${removed}` });
2944
- } else {
2945
- const fail = result2.steps.find((s) => !s.ok);
2946
- push({
2947
- name: RESET_PHASES.LAUNCH_AGENT,
2948
- status: "fail",
2949
- detail: `${fail?.step}: ${fail?.reason ?? "unknown"}`
2950
- });
2951
- }
2939
+ };
2940
+ }
2941
+ const result2 = await deps.teardownLaunchAgent(
2942
+ input.launchAgent.paths,
2943
+ { keepKeyFile: input.launchAgent.keepKeyFile }
2944
+ );
2945
+ if (!result2.ok) {
2946
+ const fail = result2.steps.find((s) => !s.ok);
2947
+ return {
2948
+ name: RESET_PHASES.LAUNCH_AGENT,
2949
+ status: "fail",
2950
+ detail: `${fail?.step}: ${fail?.reason ?? "unknown"}`
2951
+ };
2952
2952
  }
2953
+ const removed = result2.steps.filter((s) => s.step !== "unload").map((s) => s.step).join(", ");
2954
+ return { name: RESET_PHASES.LAUNCH_AGENT, status: "ok", detail: `unloaded + ${removed}` };
2955
+ }
2956
+ async function resetDockerLogoutPhase(input, deps) {
2953
2957
  if (!input.dockerLogout) {
2954
- push({ name: RESET_PHASES.DOCKER_LOGOUT, status: "skipped", detail: "--skip-docker-logout" });
2955
- } else if (input.dryRun) {
2956
- push({
2958
+ return { name: RESET_PHASES.DOCKER_LOGOUT, status: "skipped", detail: "--skip-docker-logout" };
2959
+ }
2960
+ if (input.dryRun) {
2961
+ return {
2957
2962
  name: RESET_PHASES.DOCKER_LOGOUT,
2958
2963
  status: "dry-run",
2959
2964
  detail: `would run: docker logout ${input.dockerLogout.registry}`
2960
- });
2961
- } else {
2962
- const result2 = await deps.dockerLogout(input.dockerLogout.registry);
2963
- if (result2.ok) {
2964
- push({
2965
- name: RESET_PHASES.DOCKER_LOGOUT,
2966
- status: "ok",
2967
- detail: `credentials removed from ${input.dockerLogout.registry}`
2968
- });
2969
- } else {
2970
- push({ name: RESET_PHASES.DOCKER_LOGOUT, status: "warn", detail: result2.reason ?? "unknown" });
2971
- }
2965
+ };
2972
2966
  }
2973
- return { phases };
2967
+ const result2 = await deps.dockerLogout(input.dockerLogout.registry);
2968
+ if (result2.ok) {
2969
+ return {
2970
+ name: RESET_PHASES.DOCKER_LOGOUT,
2971
+ status: "ok",
2972
+ detail: `credentials removed from ${input.dockerLogout.registry}`
2973
+ };
2974
+ }
2975
+ return { name: RESET_PHASES.DOCKER_LOGOUT, status: "warn", detail: result2.reason ?? "unknown" };
2974
2976
  }
2975
2977
  async function fileExists2(path) {
2976
2978
  try {
@@ -3026,6 +3028,43 @@ var resetCommand = new Command("reset").description(
3026
3028
  const wipeImages = opts.wipeImages ?? false;
3027
3029
  const wipeData = (opts.wipeData ?? false) || wipeImages;
3028
3030
  const removeOrphans = opts.removeOrphans ?? true;
3031
+ const region = await resolveResetRegion(opts);
3032
+ const owner = opts.owner ?? "OpusPopuli";
3033
+ const repoName = `opuspopuli-node-${region}`;
3034
+ const launchAgentPaths = defaultPaths();
3035
+ const plistExists = await fileExists2(launchAgentPaths.plistFile);
3036
+ const keyFileExists = await fileExists2(launchAgentPaths.keyFile);
3037
+ const { repoPath, stackSkipReason } = await locateResetRepo(opts, owner, repoName);
3038
+ const runningContainers = repoPath ? await composePs({
3039
+ files: resolveComposeFiles(repoPath, opts.composeFile),
3040
+ cwd: repoPath,
3041
+ ...opts.envFile ? { envFile: opts.envFile } : {}
3042
+ }) : null;
3043
+ renderSnapshotNote({
3044
+ region,
3045
+ repoPath,
3046
+ stackSkipReason,
3047
+ runningContainers,
3048
+ plistExists,
3049
+ keyFileExists,
3050
+ launchAgentPaths,
3051
+ opts,
3052
+ wipeData,
3053
+ wipeImages
3054
+ });
3055
+ await confirmReset({ opts, region, wipeData });
3056
+ const input = buildResetInput({
3057
+ opts,
3058
+ repoPath,
3059
+ plistExists,
3060
+ launchAgentPaths,
3061
+ wipeData,
3062
+ wipeImages,
3063
+ removeOrphans
3064
+ });
3065
+ await runResetWithSpinners({ opts, region, wipeData, input });
3066
+ });
3067
+ async function resolveResetRegion(opts) {
3029
3068
  const region = opts.region ? opts.region : unwrap(
3030
3069
  await p3.text({
3031
3070
  message: "Region label (the slug used during init \u2014 e.g. us-ca)?",
@@ -3037,51 +3076,52 @@ var resetCommand = new Command("reset").description(
3037
3076
  p3.cancel(`--region ${JSON.stringify(region)} is not a valid region slug.`);
3038
3077
  process.exit(2);
3039
3078
  }
3040
- const owner = opts.owner ?? "OpusPopuli";
3041
- const repoName = `opuspopuli-node-${region}`;
3042
- const launchAgentPaths = defaultPaths();
3043
- const plistExists = await fileExists2(launchAgentPaths.plistFile);
3044
- const keyFileExists = await fileExists2(launchAgentPaths.keyFile);
3045
- let repoPath;
3046
- let stackSkipReason;
3047
- if (!opts.skipStack) {
3048
- const located = await locateOrCloneRepo({
3049
- owner,
3050
- name: repoName,
3051
- cwd: process.cwd(),
3052
- allowClone: false,
3053
- ...opts.repoDir ? { explicit: opts.repoDir } : {}
3054
- });
3055
- switch (located.kind) {
3056
- case "found":
3057
- repoPath = located.path;
3058
- break;
3059
- case "explicit-not-a-node-repo":
3060
- p3.cancel(
3061
- `--repo-dir ${JSON.stringify(located.path)} doesn't look like a node repo (missing one of: ${NODE_REPO_MARKERS.join(", ")}). Verify the path or drop --repo-dir to let reset search the cwd.`
3062
- );
3063
- process.exit(2);
3064
- break;
3065
- case "clone-disallowed":
3066
- stackSkipReason = `no checkout found at ${process.cwd()}; pass --repo-dir to target one`;
3067
- break;
3068
- case "cloned":
3069
- case "gh-not-installed":
3070
- case "clone-failed":
3071
- stackSkipReason = `unexpected outcome ${located.kind} from locateOrCloneRepo with allowClone=false`;
3072
- break;
3073
- }
3074
- } else {
3075
- stackSkipReason = "--skip-stack";
3076
- }
3077
- let runningContainers = null;
3078
- if (repoPath) {
3079
- runningContainers = await composePs({
3080
- files: resolveComposeFiles(repoPath, opts.composeFile),
3081
- cwd: repoPath,
3082
- ...opts.envFile ? { envFile: opts.envFile } : {}
3083
- });
3079
+ return region;
3080
+ }
3081
+ async function locateResetRepo(opts, owner, repoName) {
3082
+ if (opts.skipStack) {
3083
+ return { stackSkipReason: "--skip-stack" };
3084
3084
  }
3085
+ const located = await locateOrCloneRepo({
3086
+ owner,
3087
+ name: repoName,
3088
+ cwd: process.cwd(),
3089
+ allowClone: false,
3090
+ ...opts.repoDir ? { explicit: opts.repoDir } : {}
3091
+ });
3092
+ switch (located.kind) {
3093
+ case "found":
3094
+ return { repoPath: located.path };
3095
+ case "explicit-not-a-node-repo":
3096
+ p3.cancel(
3097
+ `--repo-dir ${JSON.stringify(located.path)} doesn't look like a node repo (missing one of: ${NODE_REPO_MARKERS.join(", ")}). Verify the path or drop --repo-dir to let reset search the cwd.`
3098
+ );
3099
+ return process.exit(2);
3100
+ case "clone-disallowed":
3101
+ return { stackSkipReason: `no checkout found at ${process.cwd()}; pass --repo-dir to target one` };
3102
+ case "cloned":
3103
+ case "gh-not-installed":
3104
+ case "clone-failed":
3105
+ return {
3106
+ stackSkipReason: `unexpected outcome ${located.kind} from locateOrCloneRepo with allowClone=false`
3107
+ };
3108
+ default:
3109
+ return assertNever();
3110
+ }
3111
+ }
3112
+ function renderSnapshotNote(args) {
3113
+ const {
3114
+ region,
3115
+ repoPath,
3116
+ stackSkipReason,
3117
+ runningContainers,
3118
+ plistExists,
3119
+ keyFileExists,
3120
+ launchAgentPaths,
3121
+ opts,
3122
+ wipeData,
3123
+ wipeImages
3124
+ } = args;
3085
3125
  let runningContainersLabel;
3086
3126
  if (runningContainers === null) {
3087
3127
  runningContainersLabel = pc2.dim("unknown");
@@ -3104,6 +3144,9 @@ var resetCommand = new Command("reset").description(
3104
3144
  ].join("\n"),
3105
3145
  "Snapshot"
3106
3146
  );
3147
+ }
3148
+ async function confirmReset(args) {
3149
+ const { opts, region, wipeData } = args;
3107
3150
  if (wipeData && !opts.dryRun) {
3108
3151
  unwrap(
3109
3152
  await p3.text({
@@ -3111,7 +3154,9 @@ var resetCommand = new Command("reset").description(
3111
3154
  validate: (v) => v === region ? void 0 : `must match the region label exactly`
3112
3155
  })
3113
3156
  );
3114
- } else if (!opts.dryRun && !opts.yes) {
3157
+ return;
3158
+ }
3159
+ if (!opts.dryRun && !opts.yes) {
3115
3160
  const cont = unwrap(
3116
3161
  await p3.confirm({
3117
3162
  message: "Proceed with reset? (volumes will be preserved)",
@@ -3123,6 +3168,9 @@ var resetCommand = new Command("reset").description(
3123
3168
  process.exit(0);
3124
3169
  }
3125
3170
  }
3171
+ }
3172
+ function buildResetInput(args) {
3173
+ const { opts, repoPath, plistExists, launchAgentPaths, wipeData, wipeImages, removeOrphans } = args;
3126
3174
  const stack = repoPath ? {
3127
3175
  repoPath,
3128
3176
  composeFiles: resolveComposeFiles(repoPath, opts.composeFile),
@@ -3136,11 +3184,20 @@ var resetCommand = new Command("reset").description(
3136
3184
  keepKeyFile: opts.keepKeyFile ?? false
3137
3185
  } : void 0;
3138
3186
  const dockerLogoutInput = !opts.skipDockerLogout ? { registry: opts.registry ?? GHCR_REGISTRY } : void 0;
3187
+ return {
3188
+ ...stack ? { stack } : {},
3189
+ ...launchAgent ? { launchAgent } : {},
3190
+ ...dockerLogoutInput ? { dockerLogout: dockerLogoutInput } : {},
3191
+ dryRun: opts.dryRun ?? false
3192
+ };
3193
+ }
3194
+ async function runResetWithSpinners(args) {
3195
+ const { opts, region, wipeData, input } = args;
3139
3196
  const phaseSpins = /* @__PURE__ */ new Map();
3140
3197
  const actingPhases = [];
3141
- if (stack && !opts.dryRun) actingPhases.push(RESET_PHASES.STOP_STACK);
3142
- if (launchAgent && !opts.dryRun) actingPhases.push(RESET_PHASES.LAUNCH_AGENT);
3143
- if (dockerLogoutInput && !opts.dryRun) actingPhases.push(RESET_PHASES.DOCKER_LOGOUT);
3198
+ if (input.stack && !input.dryRun) actingPhases.push(RESET_PHASES.STOP_STACK);
3199
+ if (input.launchAgent && !input.dryRun) actingPhases.push(RESET_PHASES.LAUNCH_AGENT);
3200
+ if (input.dockerLogout && !input.dryRun) actingPhases.push(RESET_PHASES.DOCKER_LOGOUT);
3144
3201
  for (const name of actingPhases) {
3145
3202
  const s = p3.spinner();
3146
3203
  s.start(`${name}\u2026`);
@@ -3156,30 +3213,27 @@ var resetCommand = new Command("reset").description(
3156
3213
  p3.log.info(line);
3157
3214
  }
3158
3215
  };
3159
- const report = await runReset(
3160
- {
3161
- ...stack ? { stack } : {},
3162
- ...launchAgent ? { launchAgent } : {},
3163
- ...dockerLogoutInput ? { dockerLogout: dockerLogoutInput } : {},
3164
- dryRun: opts.dryRun ?? false
3165
- },
3166
- { ...DEFAULT_DEPS, onPhase: renderPhase }
3167
- );
3216
+ const report = await runReset(input, { ...DEFAULT_DEPS, onPhase: renderPhase });
3168
3217
  for (const [, spin] of phaseSpins) spin.stop(pc2.dim("\u2014 skipped"));
3218
+ reportResetOutcome({ report, opts, wipeData, region });
3219
+ }
3220
+ function reportResetOutcome(args) {
3221
+ const { report, opts, wipeData, region } = args;
3169
3222
  const failed = report.phases.filter((ph) => ph.status === "fail").length;
3170
3223
  if (failed > 0) {
3171
3224
  p3.outro(pc2.red(`${failed} step${failed === 1 ? "" : "s"} failed.`));
3172
3225
  process.exit(1);
3173
- } else if (opts.dryRun) {
3226
+ }
3227
+ if (opts.dryRun) {
3174
3228
  p3.outro(pc2.cyan("Dry run complete. Re-run without --dry-run to apply."));
3175
- } else {
3176
- p3.outro(
3177
- pc2.green(
3178
- wipeData ? `Reset complete \u2014 volumes wiped. Re-run \`create-op-node bootstrap --region ${region}\` to start fresh.` : `Reset complete \u2014 volumes preserved. Re-run \`create-op-node bootstrap --region ${region}\` to bring the stack back up.`
3179
- )
3180
- );
3229
+ return;
3181
3230
  }
3182
- });
3231
+ p3.outro(
3232
+ pc2.green(
3233
+ wipeData ? `Reset complete \u2014 volumes wiped. Re-run \`create-op-node bootstrap --region ${region}\` to start fresh.` : `Reset complete \u2014 volumes preserved. Re-run \`create-op-node bootstrap --region ${region}\` to bring the stack back up.`
3234
+ )
3235
+ );
3236
+ }
3183
3237
 
3184
3238
  // src/lib/cosign.ts
3185
3239
  var COSIGN_OIDC_ISSUER = "https://token.actions.githubusercontent.com";
@@ -3374,36 +3428,47 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
3374
3428
  phases.push(ph);
3375
3429
  deps.onPhase?.(ph);
3376
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) {
3377
3439
  const tls2 = await deps.tls({ host: input.apiHost });
3378
3440
  if (!tls2.ok) {
3379
3441
  push({ name: "TLS handshake", status: "fail", detail: tls2.reason });
3380
- } else {
3381
- const line = `subject=${tls2.subject}, issuer=${tls2.issuer}, ${formatExpiry(tls2.daysToExpiry)}`;
3382
- const warn = tls2.daysToExpiry < input.certWarnDays;
3383
- if (warn) {
3384
- push({
3385
- name: "TLS handshake",
3386
- status: "warn",
3387
- detail: tls2.daysToExpiry < 0 ? line : `${line} (< warn threshold ${input.certWarnDays}d)`
3388
- });
3389
- } else {
3390
- push({ name: "TLS handshake", status: "ok", detail: line });
3391
- }
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;
3392
3452
  }
3393
- const healthUrl = `https://${input.apiHost}/health`;
3394
- 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` });
3395
3457
  if (health.ok) {
3396
3458
  push({ name: "GET /health", status: "ok", detail: `HTTP ${health.status}` });
3397
3459
  } else {
3398
3460
  push({ name: "GET /health", status: "fail", detail: health.reason });
3399
3461
  }
3400
- const gqlUrl = `https://${input.apiHost}/api`;
3401
- 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` });
3402
3465
  if (gql.ok) {
3403
3466
  push({ name: "GraphQL { __typename }", status: "ok", detail: `typename=${gql.typename}` });
3404
3467
  } else {
3405
3468
  push({ name: "GraphQL { __typename }", status: "fail", detail: gql.reason });
3406
3469
  }
3470
+ }
3471
+ async function verifyCloudflarePhase(input, deps, push) {
3407
3472
  const cfFields = [
3408
3473
  ["--cf-token", input.cf?.token],
3409
3474
  ["--cf-account-id", input.cf?.accountId],
@@ -3416,54 +3481,57 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
3416
3481
  status: "skipped",
3417
3482
  detail: "pass --cf-token + --cf-account-id + --tunnel-id to enable"
3418
3483
  });
3419
- } else if (cfSet.length < cfFields.length) {
3484
+ return;
3485
+ }
3486
+ if (cfSet.length < cfFields.length) {
3420
3487
  const missing = cfFields.filter(([, v]) => v === void 0 || v === "").map(([name]) => name).join(", ");
3421
3488
  push({
3422
3489
  name: "Cloudflare Tunnel",
3423
3490
  status: "warn",
3424
3491
  detail: `partial CF config \u2014 missing ${missing}; check skipped`
3425
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
+ });
3426
3508
  } else {
3427
- const tun = await deps.tunnel({
3428
- token: input.cf.token,
3429
- accountId: input.cf.accountId,
3430
- tunnelId: input.cf.tunnelId
3509
+ push({
3510
+ name: "Cloudflare Tunnel",
3511
+ status: "ok",
3512
+ detail: `${tun.connections} connections, status=${tun.status}`
3431
3513
  });
3432
- if (!tun.ok) {
3433
- push({ name: "Cloudflare Tunnel", status: "fail", detail: tun.reason });
3434
- } else if (tun.connections === 0) {
3435
- push({
3436
- name: "Cloudflare Tunnel",
3437
- status: "warn",
3438
- detail: `status=${tun.status}, 0 connections \u2014 cloudflared on the Studio appears offline`
3439
- });
3440
- } else {
3441
- push({
3442
- name: "Cloudflare Tunnel",
3443
- status: "ok",
3444
- detail: `${tun.connections} connections, status=${tun.status}`
3445
- });
3446
- }
3447
3514
  }
3515
+ }
3516
+ async function verifyCosignPhase(input, deps, push) {
3448
3517
  if (input.images.length === 0) {
3449
3518
  push({
3450
3519
  name: "cosign verify",
3451
3520
  status: "skipped",
3452
3521
  detail: "pass --image <ref> (repeatable) to enable"
3453
3522
  });
3454
- } else {
3455
- for (const image of input.images) {
3456
- const cos = await deps.cosign({ image });
3457
- if (cos.ok) {
3458
- push({ name: `cosign verify ${image}`, status: "ok", detail: "signature valid" });
3459
- } else if (cos.skipped) {
3460
- push({ name: `cosign verify ${image}`, status: "skipped", detail: cos.reason });
3461
- } else {
3462
- push({ name: `cosign verify ${image}`, status: "fail", detail: cos.reason });
3463
- }
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 });
3464
3533
  }
3465
3534
  }
3466
- return { phases };
3467
3535
  }
3468
3536
  function readTokenFile(path) {
3469
3537
  const raw = readFileSync(path, "utf8").trim();
@@ -3488,6 +3556,53 @@ var verifyCommand = new Command("verify").description(
3488
3556
  new Option("--show-skipped", "Include skipped phases in the summary").default(false)
3489
3557
  ).action(async (opts) => {
3490
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) {
3491
3606
  const domain = opts.domain ? opts.domain : unwrap(
3492
3607
  await p3.text({
3493
3608
  message: "Public domain of the node?",
@@ -3499,28 +3614,33 @@ var verifyCommand = new Command("verify").description(
3499
3614
  p3.cancel(`--domain ${JSON.stringify(domain)} doesn't look like a domain.`);
3500
3615
  process.exit(2);
3501
3616
  }
3617
+ return domain;
3618
+ }
3619
+ function resolveCertWarnDays(opts) {
3502
3620
  const rawDays = opts.certWarnDays ?? "14";
3503
3621
  const certWarnDays = Number.parseInt(rawDays, 10);
3504
3622
  if (!Number.isFinite(certWarnDays) || certWarnDays < 0 || !/^\d+$/.test(rawDays)) {
3505
3623
  p3.cancel(`--cert-warn-days must be a non-negative integer (got ${JSON.stringify(rawDays)}).`);
3506
3624
  process.exit(2);
3507
3625
  }
3626
+ return certWarnDays;
3627
+ }
3628
+ function resolveCfToken(opts) {
3508
3629
  if (opts.cfToken && opts.cfTokenFile) {
3509
3630
  p3.cancel("Pass either --cf-token or --cf-token-file, not both.");
3510
3631
  process.exit(2);
3511
3632
  }
3512
- let cfToken = opts.cfToken;
3513
- if (!cfToken && opts.cfTokenFile) {
3514
- try {
3515
- cfToken = readTokenFile(opts.cfTokenFile);
3516
- } catch (err) {
3517
- p3.cancel(err.message);
3518
- process.exit(2);
3519
- }
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);
3520
3640
  }
3521
- const apiHost = opts.apiHost ?? `api.${domain}`;
3522
- const images = opts.image ?? [];
3523
- const totalPhases = 4 + (images.length === 0 ? 1 : images.length);
3641
+ }
3642
+ async function runVerifyWithSpinner(args) {
3643
+ const { apiHost, certWarnDays, cfToken, opts, images, totalPhases } = args;
3524
3644
  let phaseIndex = 0;
3525
3645
  let activeSpin = null;
3526
3646
  const renderPhase = (ph) => {
@@ -3529,10 +3649,7 @@ var verifyCommand = new Command("verify").description(
3529
3649
  activeSpin?.stop(formatPhaseLine(prefix, ph));
3530
3650
  activeSpin = null;
3531
3651
  };
3532
- const deps = {
3533
- ...DEFAULT_DEPS2,
3534
- onPhase: renderPhase
3535
- };
3652
+ const deps = { ...DEFAULT_DEPS2, onPhase: renderPhase };
3536
3653
  activeSpin = p3.spinner();
3537
3654
  activeSpin.start("Running checks\u2026");
3538
3655
  const report = await runVerify(
@@ -3549,6 +3666,10 @@ var verifyCommand = new Command("verify").description(
3549
3666
  deps
3550
3667
  );
3551
3668
  activeSpin?.stop("Done.");
3669
+ return report;
3670
+ }
3671
+ function renderVerifySummary(report, args) {
3672
+ const { opts, totalPhases } = args;
3552
3673
  const visible = opts.showSkipped ?? false ? report.phases : report.phases.filter((ph) => ph.status !== "skipped");
3553
3674
  const lines = visible.map((ph) => {
3554
3675
  const idx = report.phases.indexOf(ph) + 1;
@@ -3559,47 +3680,19 @@ var verifyCommand = new Command("verify").description(
3559
3680
  lines.push(pc2.dim(`\xB7 ${skippedCount} phase${skippedCount === 1 ? "" : "s"} skipped (run with --show-skipped to see them)`));
3560
3681
  }
3561
3682
  p3.note(lines.join("\n"), "Summary");
3683
+ }
3684
+ function reportVerifyOutcome(report) {
3562
3685
  const summary = summarize(report);
3563
- if (summary.ok) {
3564
- if (summary.warned === 0) {
3565
- p3.outro(pc2.green("All checks passed."));
3566
- } else {
3567
- p3.outro(pc2.yellow(`Passed with ${summary.warned} warning${summary.warned === 1 ? "" : "s"}.`));
3568
- }
3569
- } else {
3686
+ if (!summary.ok) {
3570
3687
  p3.outro(pc2.red(`${summary.failed} check${summary.failed === 1 ? "" : "s"} failed.`));
3571
3688
  process.exit(1);
3572
3689
  }
3573
- });
3574
- function phaseIcon2(ph) {
3575
- switch (ph.status) {
3576
- case "ok":
3577
- return pc2.green("\u2713");
3578
- case "warn":
3579
- return pc2.yellow("\u26A0");
3580
- case "skipped":
3581
- return pc2.dim("\xB7");
3582
- case "fail":
3583
- return pc2.red("\u2717");
3584
- }
3585
- }
3586
- function phaseColor(status) {
3587
- switch (status) {
3588
- case "ok":
3589
- return pc2.green;
3590
- case "warn":
3591
- return pc2.yellow;
3592
- case "skipped":
3593
- return pc2.dim;
3594
- case "fail":
3595
- 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"}.`));
3596
3694
  }
3597
3695
  }
3598
- function formatPhaseLine(prefix, ph) {
3599
- const icon = phaseIcon2(ph);
3600
- const colorize = phaseColor(ph.status);
3601
- return colorize(`${icon} ${prefix}: ${ph.detail}`);
3602
- }
3603
3696
 
3604
3697
  // src/lib/region-schema.json
3605
3698
  var region_schema_default = {
@@ -4620,7 +4713,7 @@ async function looksLikeRegionsRepo(dir) {
4620
4713
  }
4621
4714
 
4622
4715
  // src/cli.ts
4623
- var VERSION = "0.10.7";
4716
+ var VERSION = "0.10.9";
4624
4717
  var program = new Command();
4625
4718
  program.name("create-op-node").description(
4626
4719
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."