create-op-node 0.10.7 → 0.10.8

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
+ };
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" };
2927
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
+ };
2952
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
+ }
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";
@@ -4620,7 +4674,7 @@ async function looksLikeRegionsRepo(dir) {
4620
4674
  }
4621
4675
 
4622
4676
  // src/cli.ts
4623
- var VERSION = "0.10.7";
4677
+ var VERSION = "0.10.8";
4624
4678
  var program = new Command();
4625
4679
  program.name("create-op-node").description(
4626
4680
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."