create-op-node 0.10.5 → 0.10.7

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
@@ -645,6 +645,37 @@ var initCommand = new Command("init").description(
645
645
  ).default(false)
646
646
  ).addOption(new Option("-y, --yes", "Skip the final confirmation").default(false)).action(async (opts) => {
647
647
  p3.intro(pc2.bgCyan(pc2.black(" create-op-node init ")));
648
+ printWelcomeNote(opts);
649
+ const region = await resolveRegion(opts);
650
+ const owner = opts.owner ?? "OpusPopuli";
651
+ const newRepoName = `opuspopuli-node-${region}`;
652
+ const newRepoFull = `${owner}/${newRepoName}`;
653
+ warnIgnoredLocalOnlyFlags(opts);
654
+ const publicConfig = opts.localOnly ? null : await collectPublicConfig(opts);
655
+ const ghToken = await resolveGhToken(opts);
656
+ const keychain = await detectKeychain();
657
+ printSecretStoreNote(keychain);
658
+ await confirmPlan({ opts, region, owner, newRepoFull, publicConfig });
659
+ const created = await createRegionRepo({ opts, ghToken, owner, newRepoName, newRepoFull, region });
660
+ if (publicConfig) {
661
+ await seedRepoSecrets({ publicConfig, newRepoFull });
662
+ }
663
+ const pr = publicConfig ? await openInfraPr({ opts, ghToken, region, newRepoFull, publicConfig, created }) : null;
664
+ await persistPgsodiumKey({ keychain, opts, region });
665
+ if (!publicConfig) {
666
+ printLocalOnlyOutro({ region, newRepoFull });
667
+ return;
668
+ }
669
+ if (!pr) {
670
+ throw new Error("init: PR creation step did not run despite publicConfig present");
671
+ }
672
+ const proceed = await pauseForMerge({ pr, opts });
673
+ if (!proceed) return;
674
+ const tunnelToken = await fetchTunnelToken({ publicConfig });
675
+ await persistTunnelToken({ keychain, region, tunnelToken });
676
+ printDoneOutro(region);
677
+ });
678
+ function printWelcomeNote(opts) {
648
679
  p3.note(
649
680
  opts.localOnly ? [
650
681
  "Local-only setup \u2014 creates the region repo from template + a pgsodium key.",
@@ -663,32 +694,30 @@ var initCommand = new Command("init").description(
663
694
  ].join("\n"),
664
695
  "Welcome"
665
696
  );
666
- const region = opts.region ? opts.region : unwrap(
697
+ }
698
+ async function resolveRegion(opts) {
699
+ return opts.region ? opts.region : unwrap(
667
700
  await p3.text({
668
701
  message: "Short region label (used as the Keychain service suffix + R2 prefix)?",
669
702
  placeholder: "us-ca",
670
703
  validate: (v) => /^[a-z0-9-]{2,32}$/.test(v ?? "") ? void 0 : "lowercase letters, digits, hyphens; 2\u201332 chars"
671
704
  })
672
705
  );
673
- const owner = opts.owner ?? "OpusPopuli";
674
- const newRepoName = `opuspopuli-node-${region}`;
675
- const newRepoFull = `${owner}/${newRepoName}`;
676
- if (opts.localOnly) {
677
- const ignored = listIgnoredLocalOnlyFlags(opts);
678
- if (ignored.length > 0) {
679
- p3.note(
680
- pc2.yellow(
681
- `\u26A0 The following ${ignored.length === 1 ? "flag is" : "flags are"} ignored with --local-only:
706
+ }
707
+ function warnIgnoredLocalOnlyFlags(opts) {
708
+ if (!opts.localOnly) return;
709
+ const ignored = listIgnoredLocalOnlyFlags(opts);
710
+ if (ignored.length === 0) return;
711
+ p3.note(
712
+ pc2.yellow(
713
+ `\u26A0 The following ${ignored.length === 1 ? "flag is" : "flags are"} ignored with --local-only:
682
714
  ${ignored.join(", ")}
683
- ` + pc2.dim(
684
- "These configure Cloudflare / TFC / PR-merge flow, which --local-only skips."
685
- )
686
- ),
687
- "Notice"
688
- );
689
- }
690
- }
691
- const publicConfig = opts.localOnly ? null : await collectPublicConfig(opts);
715
+ ` + pc2.dim("These configure Cloudflare / TFC / PR-merge flow, which --local-only skips.")
716
+ ),
717
+ "Notice"
718
+ );
719
+ }
720
+ async function resolveGhToken(opts) {
692
721
  let ghToken = opts.ghToken;
693
722
  if (!ghToken) {
694
723
  const fromCli = await ghTokenFromCli();
@@ -709,10 +738,15 @@ var initCommand = new Command("init").description(
709
738
  "github"
710
739
  );
711
740
  }
712
- const keychain = await detectKeychain();
741
+ return ghToken;
742
+ }
743
+ function printSecretStoreNote(keychain) {
713
744
  const kcNote = keychain.available ? `\u2713 macOS Keychain available \u2014 pgsodium key + Tunnel token will be saved to your login keychain.
714
745
  ` + pc2.dim("Note: items don't sync to iCloud Keychain via the security CLI.\n") + pc2.dim("On the Studio, bootstrap will read locally and prompt you to paste if missing.") : `\u26A0 ${keychain.reason ?? "Keychain unavailable"}. Secrets will be printed for manual stash.`;
715
746
  p3.note(kcNote, "Secret store");
747
+ }
748
+ async function confirmPlan(args) {
749
+ const { opts, region, owner, newRepoFull, publicConfig } = args;
716
750
  const orgWriteAccess = pc2.dim(`required for ${owner}`);
717
751
  p3.note(
718
752
  opts.localOnly ? [
@@ -739,20 +773,19 @@ var initCommand = new Command("init").description(
739
773
  ].join("\n"),
740
774
  "Plan"
741
775
  );
742
- if (!opts.yes) {
743
- const go = unwrap(
744
- await p3.confirm({ message: "Proceed?", initialValue: true })
745
- );
746
- if (!go) {
747
- p3.cancel("Cancelled.");
748
- process.exit(0);
749
- }
776
+ if (opts.yes) return;
777
+ const go = unwrap(await p3.confirm({ message: "Proceed?", initialValue: true }));
778
+ if (!go) {
779
+ p3.cancel("Cancelled.");
780
+ process.exit(0);
750
781
  }
782
+ }
783
+ async function createRegionRepo(args) {
784
+ const { opts, ghToken, owner, newRepoName, newRepoFull, region } = args;
751
785
  const repoSpin = p3.spinner();
752
786
  repoSpin.start(`Creating ${newRepoFull} from ${opts.template}\u2026`);
753
- let created;
754
787
  try {
755
- created = await createRepoFromTemplate({
788
+ const created = await createRepoFromTemplate({
756
789
  token: ghToken,
757
790
  template: opts.template ?? "OpusPopuli/opuspopuli-node",
758
791
  owner,
@@ -760,6 +793,7 @@ var initCommand = new Command("init").description(
760
793
  description: `Opus Populi node deployment for ${region}`
761
794
  });
762
795
  repoSpin.stop(pc2.green(`\u2713 Created ${created.fullName}`));
796
+ return { fullName: created.fullName, defaultBranch: created.defaultBranch };
763
797
  } catch (err) {
764
798
  const status = typeof err === "object" && err !== null && "status" in err ? err.status : 0;
765
799
  const message = err.message ?? "unknown error";
@@ -781,154 +815,155 @@ var initCommand = new Command("init").description(
781
815
  );
782
816
  process.exit(0);
783
817
  }
784
- created = {
785
- fullName: newRepoFull,
786
- htmlUrl: `https://github.com/${newRepoFull}`,
787
- defaultBranch: "main"
788
- };
789
- }
790
- if (publicConfig) {
791
- const secrets = [
792
- { name: "CLOUDFLARE_API_TOKEN", value: publicConfig.cfToken },
793
- { name: "CLOUDFLARE_ACCOUNT_ID", value: publicConfig.cfAccount },
794
- { name: "CLOUDFLARE_ZONE_ID", value: publicConfig.cfZone },
795
- { name: "TF_API_TOKEN", value: publicConfig.tfToken },
796
- { name: "TF_CLOUD_ORGANIZATION", value: publicConfig.tfOrg }
797
- ];
798
- const secSpin = p3.spinner();
799
- secSpin.start(`Seeding ${secrets.length} repo secrets\u2026`);
800
- const seeded = [];
801
- for (const s of secrets) {
802
- const r = await setRepoSecret({ repo: newRepoFull, name: s.name, value: s.value });
803
- if (!r.written) {
804
- secSpin.stop(pc2.red(`\u2717 Failed to set ${s.name}: ${r.reason ?? "unknown"}`));
805
- const remaining = secrets.slice(secrets.findIndex((x) => x.name === s.name)).map((x) => x.name);
806
- p3.cancel(
807
- [
808
- seeded.length > 0 ? `Already seeded on ${newRepoFull}: ${seeded.join(", ")}.` : `Nothing seeded yet on ${newRepoFull}.`,
809
- `Still pending: ${remaining.join(", ")}.`,
810
- "",
811
- `Make sure \`gh\` is installed and signed in (\`gh auth login\`).`,
812
- `Re-run with --use-existing-repo to retry (GitHub will overwrite the already-set secrets idempotently).`
813
- ].join("\n")
814
- );
815
- process.exit(1);
816
- }
817
- seeded.push(s.name);
818
- }
819
- secSpin.stop(pc2.green(`\u2713 Seeded ${secrets.length} secrets on ${newRepoFull}`));
818
+ return { fullName: newRepoFull, defaultBranch: "main" };
820
819
  }
821
- let pr = null;
822
- if (publicConfig) {
823
- const branch = `init/region-${region}-${isoStampUtc(/* @__PURE__ */ new Date())}`;
824
- const setupSpin = p3.spinner();
825
- setupSpin.start(`Writing prod.tfvars on branch ${branch}\u2026`);
826
- try {
827
- await createBranch({
828
- token: ghToken,
829
- repo: newRepoFull,
830
- branch,
831
- fromBranch: created.defaultBranch
832
- });
833
- const tfvars = renderProdTfvars({
834
- project: opts.project ?? "opuspopuli",
835
- domain: publicConfig.domain
836
- });
837
- await commitFile({
838
- token: ghToken,
839
- repo: newRepoFull,
840
- branch,
841
- path: "infra/cloudflare/environments/prod.tfvars",
842
- content: tfvars,
843
- message: `init: prod.tfvars for ${region}`
844
- });
845
- } catch (err) {
846
- setupSpin.stop(pc2.red(`\u2717 Couldn't write tfvars: ${err.message}`));
847
- p3.cancel("The repo exists but the branch / commit failed. Open it on GitHub and inspect.");
848
- process.exit(1);
849
- }
850
- setupSpin.stop(pc2.green("\u2713 Wrote prod.tfvars"));
851
- const prSpin = p3.spinner();
852
- prSpin.start("Opening pull request\u2026");
853
- try {
854
- pr = await openPullRequest({
855
- token: ghToken,
856
- repo: newRepoFull,
857
- head: branch,
858
- base: created.defaultBranch,
859
- title: `init: bring up region ${region}`,
860
- body: [
861
- `Initial region deployment for **${region}** (${publicConfig.domain}).`,
862
- "",
863
- "Adds `infra/cloudflare/environments/prod.tfvars`. Merging this PR triggers",
864
- "`cloudflare-infra.yml` which runs `terraform apply` against the",
865
- `\`${publicConfig.tfOrg}\` Terraform Cloud organization, provisioning the Cloudflare`,
866
- "Tunnel, DNS records, R2 buckets, and Pages project.",
820
+ }
821
+ async function seedRepoSecrets(args) {
822
+ const { publicConfig, newRepoFull } = args;
823
+ const secrets = [
824
+ { name: "CLOUDFLARE_API_TOKEN", value: publicConfig.cfToken },
825
+ { name: "CLOUDFLARE_ACCOUNT_ID", value: publicConfig.cfAccount },
826
+ { name: "CLOUDFLARE_ZONE_ID", value: publicConfig.cfZone },
827
+ { name: "TF_API_TOKEN", value: publicConfig.tfToken },
828
+ { name: "TF_CLOUD_ORGANIZATION", value: publicConfig.tfOrg }
829
+ ];
830
+ const secSpin = p3.spinner();
831
+ secSpin.start(`Seeding ${secrets.length} repo secrets\u2026`);
832
+ const seeded = [];
833
+ for (const s of secrets) {
834
+ const r = await setRepoSecret({ repo: newRepoFull, name: s.name, value: s.value });
835
+ if (!r.written) {
836
+ secSpin.stop(pc2.red(`\u2717 Failed to set ${s.name}: ${r.reason ?? "unknown"}`));
837
+ const remaining = secrets.slice(secrets.findIndex((x) => x.name === s.name)).map((x) => x.name);
838
+ p3.cancel(
839
+ [
840
+ seeded.length > 0 ? `Already seeded on ${newRepoFull}: ${seeded.join(", ")}.` : `Nothing seeded yet on ${newRepoFull}.`,
841
+ `Still pending: ${remaining.join(", ")}.`,
867
842
  "",
868
- `Generated by \`create-op-node init\`.`
843
+ `Make sure \`gh\` is installed and signed in (\`gh auth login\`).`,
844
+ `Re-run with --use-existing-repo to retry (GitHub will overwrite the already-set secrets idempotently).`
869
845
  ].join("\n")
870
- });
871
- } catch (err) {
872
- prSpin.stop(pc2.red(`\u2717 Couldn't open PR: ${err.message}`));
873
- p3.cancel("Branch + tfvars are committed; open the PR by hand on GitHub.");
846
+ );
874
847
  process.exit(1);
875
848
  }
849
+ seeded.push(s.name);
850
+ }
851
+ secSpin.stop(pc2.green(`\u2713 Seeded ${secrets.length} secrets on ${newRepoFull}`));
852
+ }
853
+ async function openInfraPr(args) {
854
+ const { opts, ghToken, region, newRepoFull, publicConfig, created } = args;
855
+ const branch = `init/region-${region}-${isoStampUtc(/* @__PURE__ */ new Date())}`;
856
+ const setupSpin = p3.spinner();
857
+ setupSpin.start(`Writing prod.tfvars on branch ${branch}\u2026`);
858
+ try {
859
+ await createBranch({
860
+ token: ghToken,
861
+ repo: newRepoFull,
862
+ branch,
863
+ fromBranch: created.defaultBranch
864
+ });
865
+ const tfvars = renderProdTfvars({
866
+ project: opts.project ?? "opuspopuli",
867
+ domain: publicConfig.domain
868
+ });
869
+ await commitFile({
870
+ token: ghToken,
871
+ repo: newRepoFull,
872
+ branch,
873
+ path: "infra/cloudflare/environments/prod.tfvars",
874
+ content: tfvars,
875
+ message: `init: prod.tfvars for ${region}`
876
+ });
877
+ } catch (err) {
878
+ setupSpin.stop(pc2.red(`\u2717 Couldn't write tfvars: ${err.message}`));
879
+ p3.cancel("The repo exists but the branch / commit failed. Open it on GitHub and inspect.");
880
+ process.exit(1);
881
+ }
882
+ setupSpin.stop(pc2.green("\u2713 Wrote prod.tfvars"));
883
+ const prSpin = p3.spinner();
884
+ prSpin.start("Opening pull request\u2026");
885
+ try {
886
+ const pr = await openPullRequest({
887
+ token: ghToken,
888
+ repo: newRepoFull,
889
+ head: branch,
890
+ base: created.defaultBranch,
891
+ title: `init: bring up region ${region}`,
892
+ body: [
893
+ `Initial region deployment for **${region}** (${publicConfig.domain}).`,
894
+ "",
895
+ "Adds `infra/cloudflare/environments/prod.tfvars`. Merging this PR triggers",
896
+ "`cloudflare-infra.yml` which runs `terraform apply` against the",
897
+ `\`${publicConfig.tfOrg}\` Terraform Cloud organization, provisioning the Cloudflare`,
898
+ "Tunnel, DNS records, R2 buckets, and Pages project.",
899
+ "",
900
+ `Generated by \`create-op-node init\`.`
901
+ ].join("\n")
902
+ });
876
903
  prSpin.stop(pc2.green(`\u2713 Opened PR #${pr.number}`));
904
+ return pr;
905
+ } catch (err) {
906
+ prSpin.stop(pc2.red(`\u2717 Couldn't open PR: ${err.message}`));
907
+ p3.cancel("Branch + tfvars are committed; open the PR by hand on GitHub.");
908
+ process.exit(1);
877
909
  }
910
+ }
911
+ async function persistPgsodiumKey(args) {
912
+ const { keychain, opts, region } = args;
878
913
  const pgsodiumCoords = { region, account: "pgsodium-root-key" };
879
914
  const keyLabel = `org.opuspopuli.${region}/pgsodium-root-key`;
880
- if (keychain.available) {
881
- const existing = opts.overwrite ? null : await readSecret(pgsodiumCoords);
882
- if (existing && /^[a-f0-9]{64}$/.test(existing)) {
883
- p3.note(
884
- `${pc2.green("\u2713")} Re-using existing pgsodium master key from Keychain (${pc2.cyan(keyLabel)}). Pass --overwrite to rotate.`,
885
- "Secret"
886
- );
887
- } else {
888
- const fresh = generatePgsodiumRootKey();
889
- const r = await saveSecret(pgsodiumCoords, fresh);
890
- if (r.written) {
891
- p3.note(
892
- `${pc2.green("\u2713")} pgsodium master key${r.updated ? " rotated" : " saved"} to Keychain as ${pc2.cyan(keyLabel)}.`,
893
- "Secret"
894
- );
895
- } else {
896
- await printKeyForManualSave(fresh, keyLabel, r.reason);
897
- }
898
- }
899
- } else {
900
- const fresh = generatePgsodiumRootKey();
901
- await printKeyForManualSave(fresh, keyLabel);
915
+ if (!keychain.available) {
916
+ const fresh2 = generatePgsodiumRootKey();
917
+ await printKeyForManualSave(fresh2, keyLabel);
918
+ return;
902
919
  }
903
- if (!publicConfig) {
904
- p3.outro(
905
- pc2.cyan(
906
- [
907
- `Region ${region} provisioned for local-only mode.`,
908
- `Repo: https://github.com/${newRepoFull}`,
909
- ``,
910
- `Next: on the Studio (fresh or otherwise \u2014 bootstrap installs`,
911
- `brew packages it needs), run:`,
912
- ` npx create-op-node bootstrap --region ${region} --local-only`,
913
- ``,
914
- `When you're ready to expose publicly, re-run BOTH \`init\` and`,
915
- `\`bootstrap\` without --local-only. Same region repo + pgsodium key`,
916
- `promote cleanly to the production-shaped deploy.`
917
- ].join("\n")
918
- )
920
+ const existing = opts.overwrite ? null : await readSecret(pgsodiumCoords);
921
+ if (existing && /^[a-f0-9]{64}$/.test(existing)) {
922
+ p3.note(
923
+ `${pc2.green("\u2713")} Re-using existing pgsodium master key from Keychain (${pc2.cyan(keyLabel)}). Pass --overwrite to rotate.`,
924
+ "Secret"
919
925
  );
920
926
  return;
921
927
  }
922
- if (!pr) {
923
- throw new Error("init: PR creation step did not run despite publicConfig present");
928
+ const fresh = generatePgsodiumRootKey();
929
+ const r = await saveSecret(pgsodiumCoords, fresh);
930
+ if (r.written) {
931
+ p3.note(
932
+ `${pc2.green("\u2713")} pgsodium master key${r.updated ? " rotated" : " saved"} to Keychain as ${pc2.cyan(keyLabel)}.`,
933
+ "Secret"
934
+ );
935
+ } else {
936
+ await printKeyForManualSave(fresh, keyLabel, r.reason);
924
937
  }
938
+ }
939
+ function printLocalOnlyOutro(args) {
940
+ const { region, newRepoFull } = args;
941
+ p3.outro(
942
+ pc2.cyan(
943
+ [
944
+ `Region ${region} provisioned for local-only mode.`,
945
+ `Repo: https://github.com/${newRepoFull}`,
946
+ ``,
947
+ `Next: on the Studio (fresh or otherwise \u2014 bootstrap installs`,
948
+ `brew packages it needs), run:`,
949
+ ` npx create-op-node bootstrap --region ${region} --local-only`,
950
+ ``,
951
+ `When you're ready to expose publicly, re-run BOTH \`init\` and`,
952
+ `\`bootstrap\` without --local-only. Same region repo + pgsodium key`,
953
+ `promote cleanly to the production-shaped deploy.`
954
+ ].join("\n")
955
+ )
956
+ );
957
+ }
958
+ async function pauseForMerge(args) {
959
+ const { pr, opts } = args;
925
960
  if (opts.skipWait) {
926
961
  p3.outro(
927
962
  pc2.cyan(
928
963
  `Review + merge ${pr.htmlUrl} when ready. Re-run with no --skip-wait or run \`npx create-op-node verify\` afterwards.`
929
964
  )
930
965
  );
931
- return;
966
+ return false;
932
967
  }
933
968
  p3.note(
934
969
  [
@@ -939,15 +974,17 @@ var initCommand = new Command("init").description(
939
974
  ].join("\n"),
940
975
  "Next step: review + merge"
941
976
  );
942
- const merged = unwrap(
943
- await p3.confirm({ message: "PR merged?", initialValue: false })
944
- );
977
+ const merged = unwrap(await p3.confirm({ message: "PR merged?", initialValue: false }));
945
978
  if (!merged) {
946
979
  p3.outro(
947
980
  pc2.cyan(`No worries \u2014 re-run \`create-op-node init --skip-wait\` later to skip this step, or finish manually.`)
948
981
  );
949
- return;
982
+ return false;
950
983
  }
984
+ return true;
985
+ }
986
+ async function fetchTunnelToken(args) {
987
+ const { publicConfig } = args;
951
988
  const ws = await findWorkspace({
952
989
  token: publicConfig.tfToken,
953
990
  organization: publicConfig.tfOrg,
@@ -971,27 +1008,33 @@ var initCommand = new Command("init").description(
971
1008
  );
972
1009
  process.exit(1);
973
1010
  }
1011
+ return tunnelToken;
1012
+ }
1013
+ async function persistTunnelToken(args) {
1014
+ const { keychain, region, tunnelToken } = args;
974
1015
  const tunnelCoords = { region, account: "tunnel-token" };
975
1016
  const tunnelLabel = `org.opuspopuli.${region}/tunnel-token`;
976
- if (keychain.available) {
977
- const r = await saveSecret(tunnelCoords, tunnelToken);
978
- if (r.written) {
979
- p3.note(
980
- `${pc2.green("\u2713")} Tunnel token saved to Keychain as ${pc2.cyan(tunnelLabel)}.`,
981
- "Secret"
982
- );
983
- } else {
984
- await printKeyForManualSave(tunnelToken, tunnelLabel, r.reason);
985
- }
986
- } else {
1017
+ if (!keychain.available) {
987
1018
  await printKeyForManualSave(tunnelToken, tunnelLabel);
1019
+ return;
988
1020
  }
1021
+ const r = await saveSecret(tunnelCoords, tunnelToken);
1022
+ if (r.written) {
1023
+ p3.note(
1024
+ `${pc2.green("\u2713")} Tunnel token saved to Keychain as ${pc2.cyan(tunnelLabel)}.`,
1025
+ "Secret"
1026
+ );
1027
+ } else {
1028
+ await printKeyForManualSave(tunnelToken, tunnelLabel, r.reason);
1029
+ }
1030
+ }
1031
+ function printDoneOutro(region) {
989
1032
  p3.outro(
990
1033
  pc2.cyan(
991
1034
  `Region ${region} is provisioned. Next: \`npx create-op-node bootstrap\` on the Mac Studio.`
992
1035
  )
993
1036
  );
994
- });
1037
+ }
995
1038
  var MIN_TOKEN_LENGTH = {
996
1039
  cloudflare: 40,
997
1040
  tfc: 40,
@@ -1989,6 +2032,10 @@ async function locateOrCloneRepo(input) {
1989
2032
  return { kind: "cloned", path: target };
1990
2033
  }
1991
2034
 
2035
+ // src/lib/assert.ts
2036
+ function assertNever(_value) {
2037
+ }
2038
+
1992
2039
  // src/commands/bootstrap.ts
1993
2040
  var PUBLIC_PROFILES = ["public"];
1994
2041
  var LOCAL_PROFILES = [];
@@ -2036,6 +2083,33 @@ var bootstrapCommand = new Command("bootstrap").description(
2036
2083
  ).default(false)
2037
2084
  ).addOption(new Option("-y, --yes", "Skip confirmation prompts").default(false)).action(async (opts) => {
2038
2085
  p3.intro(pc2.bgCyan(pc2.black(" create-op-node bootstrap ")));
2086
+ const nodeType = await selectNodeType(opts);
2087
+ const region = await resolveRegion2(opts);
2088
+ const owner = opts.owner ?? "OpusPopuli";
2089
+ const repoName = `opuspopuli-node-${region}`;
2090
+ const composeFile = opts.composeFile ?? defaultComposeFiles(opts);
2091
+ const llmModelChoice = opts.llmModel ?? await selectLlmModel(opts);
2092
+ const [embeddingModel, llmModel] = resolveModels({
2093
+ llmModel: llmModelChoice,
2094
+ ...opts.embeddingModel !== void 0 ? { embeddingModel: opts.embeddingModel } : {}
2095
+ });
2096
+ await runSystemChecksPhase();
2097
+ await runBrewPhase(opts);
2098
+ await ensureGhAuth();
2099
+ await promptTailscaleSignin();
2100
+ const repoPath = await locateRegionRepoPhase(opts, owner, repoName);
2101
+ await unlockKeychainPhase();
2102
+ const secrets = await collectSecretsPhase({ region, nodeType, opts });
2103
+ await runLaunchAgentPhase({ opts, secrets, llmModel, embeddingModel });
2104
+ await installWrapperPhase({ repoPath, region, promptServiceUrl: secrets.promptServiceUrl });
2105
+ await loginGhcrPhase();
2106
+ await runOllamaPhase({ opts, embeddingModel, llmModel });
2107
+ await runStackPhase({ opts, repoPath, region, composeFile, secrets, llmModel, embeddingModel });
2108
+ });
2109
+ function defaultComposeFiles(opts) {
2110
+ return opts.localOnly ? ["docker-compose-prod.yml"] : ["docker-compose-prod.yml", "docker-compose-backup.yml"];
2111
+ }
2112
+ async function selectNodeType(opts) {
2039
2113
  const nodeType = opts.nodeType ? opts.nodeType : unwrap(
2040
2114
  await p3.select({
2041
2115
  message: "What kind of node are you provisioning?",
@@ -2062,22 +2136,18 @@ var bootstrapCommand = new Command("bootstrap").description(
2062
2136
  );
2063
2137
  process.exit(1);
2064
2138
  }
2065
- const region = opts.region ? opts.region : unwrap(
2139
+ return nodeType;
2140
+ }
2141
+ async function resolveRegion2(opts) {
2142
+ return opts.region ? opts.region : unwrap(
2066
2143
  await p3.text({
2067
2144
  message: "Region label (the slug used during init \u2014 e.g. us-ca)?",
2068
2145
  placeholder: "us-ca",
2069
2146
  validate: (v) => /^[a-z0-9-]{2,32}$/.test(v ?? "") ? void 0 : "lowercase letters, digits, hyphens; 2\u201332 chars"
2070
2147
  })
2071
2148
  );
2072
- const owner = opts.owner ?? "OpusPopuli";
2073
- const repoName = `opuspopuli-node-${region}`;
2074
- const composeFileDefault = opts.localOnly ? ["docker-compose-prod.yml"] : ["docker-compose-prod.yml", "docker-compose-backup.yml"];
2075
- const composeFile = opts.composeFile ?? composeFileDefault;
2076
- const llmModelChoice = opts.llmModel ?? await selectLlmModel(opts);
2077
- const [embeddingModel, llmModel] = resolveModels({
2078
- llmModel: llmModelChoice,
2079
- ...opts.embeddingModel !== void 0 ? { embeddingModel: opts.embeddingModel } : {}
2080
- });
2149
+ }
2150
+ async function runSystemChecksPhase() {
2081
2151
  const sysSpin = p3.spinner();
2082
2152
  sysSpin.start("Inspecting macOS\u2026");
2083
2153
  const snap = await inspectSystem();
@@ -2122,54 +2192,55 @@ var bootstrapCommand = new Command("bootstrap").description(
2122
2192
  if (!r.ok) p3.note(`${pc2.red("\u2717")} ${r.reason}`, "pmset failed");
2123
2193
  }
2124
2194
  }
2125
- if (!opts.skipBrew) {
2126
- const brewInfo = await detectBrew();
2127
- if (!brewInfo.installed) {
2128
- p3.note(
2129
- [
2130
- `Homebrew is not installed. Open another shell and run:`,
2131
- "",
2132
- pc2.cyan(HOMEBREW_INSTALL_COMMAND),
2133
- "",
2134
- pc2.dim("Then come back and press Enter.")
2135
- ].join("\n"),
2136
- "Manual step"
2137
- );
2138
- unwrap(await p3.confirm({ message: "Homebrew installed?", initialValue: true }));
2139
- }
2140
- const brewSpin = p3.spinner();
2141
- brewSpin.start("Installing Studio packages\u2026 (~5\u201315 min first run)");
2142
- const report = await installPackages(STUDIO_PACKAGES, (pkg, status) => {
2143
- brewSpin.message(`${status}: ${pkg.name}`);
2144
- });
2145
- brewSpin.stop(
2146
- report.failed.length === 0 ? pc2.green(
2147
- `\u2713 ${report.installed.length} installed, ${report.alreadyPresent.length} already present`
2148
- ) : pc2.yellow(
2149
- `\u26A0 ${report.installed.length} installed, ${report.alreadyPresent.length} present, ${report.failed.length} failed`
2150
- )
2195
+ }
2196
+ async function runBrewPhase(opts) {
2197
+ if (opts.skipBrew) return;
2198
+ const brewInfo = await detectBrew();
2199
+ if (!brewInfo.installed) {
2200
+ p3.note(
2201
+ [
2202
+ `Homebrew is not installed. Open another shell and run:`,
2203
+ "",
2204
+ pc2.cyan(HOMEBREW_INSTALL_COMMAND),
2205
+ "",
2206
+ pc2.dim("Then come back and press Enter.")
2207
+ ].join("\n"),
2208
+ "Manual step"
2151
2209
  );
2152
- if (report.failed.length > 0) {
2153
- p3.note(
2154
- [
2155
- report.failed.map((f) => `${pc2.red("\u2022")} ${f.pkg.name}: ${f.reason}`).join("\n"),
2156
- "",
2157
- pc2.dim("A partial install usually fails downstream (Docker / Ollama / compose)."),
2158
- pc2.dim("Install the failed packages manually and re-run with --skip-brew.")
2159
- ].join("\n"),
2160
- "Brew failures"
2161
- );
2162
- const cont = unwrap(
2163
- await p3.confirm({
2164
- message: "Continue anyway? (Default no \u2014 recommended to fix and re-run.)",
2165
- initialValue: false
2166
- })
2167
- );
2168
- if (!cont) process.exit(1);
2169
- }
2210
+ unwrap(await p3.confirm({ message: "Homebrew installed?", initialValue: true }));
2170
2211
  }
2171
- await ensureGhAuth();
2172
- await promptTailscaleSignin();
2212
+ const brewSpin = p3.spinner();
2213
+ brewSpin.start("Installing Studio packages\u2026 (~5\u201315 min first run)");
2214
+ const report = await installPackages(STUDIO_PACKAGES, (pkg, status) => {
2215
+ brewSpin.message(`${status}: ${pkg.name}`);
2216
+ });
2217
+ brewSpin.stop(
2218
+ report.failed.length === 0 ? pc2.green(
2219
+ `\u2713 ${report.installed.length} installed, ${report.alreadyPresent.length} already present`
2220
+ ) : pc2.yellow(
2221
+ `\u26A0 ${report.installed.length} installed, ${report.alreadyPresent.length} present, ${report.failed.length} failed`
2222
+ )
2223
+ );
2224
+ if (report.failed.length > 0) {
2225
+ p3.note(
2226
+ [
2227
+ report.failed.map((f) => `${pc2.red("\u2022")} ${f.pkg.name}: ${f.reason}`).join("\n"),
2228
+ "",
2229
+ pc2.dim("A partial install usually fails downstream (Docker / Ollama / compose)."),
2230
+ pc2.dim("Install the failed packages manually and re-run with --skip-brew.")
2231
+ ].join("\n"),
2232
+ "Brew failures"
2233
+ );
2234
+ const cont = unwrap(
2235
+ await p3.confirm({
2236
+ message: "Continue anyway? (Default no \u2014 recommended to fix and re-run.)",
2237
+ initialValue: false
2238
+ })
2239
+ );
2240
+ if (!cont) process.exit(1);
2241
+ }
2242
+ }
2243
+ async function locateRegionRepoPhase(opts, owner, repoName) {
2173
2244
  const repoSpin = p3.spinner();
2174
2245
  repoSpin.start("Locating your region node repo\u2026");
2175
2246
  const located = await locateOrCloneRepo({
@@ -2187,6 +2258,9 @@ var bootstrapCommand = new Command("bootstrap").description(
2187
2258
  repoSpin.stop(
2188
2259
  located.kind === "cloned" ? pc2.green(`\u2713 Cloned ${owner}/${repoName} to ${repoPath}`) : pc2.green(`\u2713 Found region repo at ${repoPath}`)
2189
2260
  );
2261
+ return repoPath;
2262
+ }
2263
+ async function unlockKeychainPhase() {
2190
2264
  const keychain = await detectKeychain();
2191
2265
  if (!keychain.available) {
2192
2266
  p3.cancel(
@@ -2219,6 +2293,9 @@ var bootstrapCommand = new Command("bootstrap").description(
2219
2293
  }
2220
2294
  p3.note(`${pc2.green("\u2713")} Keychain unlocked for this session.`, "Keychain");
2221
2295
  }
2296
+ }
2297
+ async function collectCoreSecrets(args) {
2298
+ const { region, opts } = args;
2222
2299
  const pgsodiumKey = await loadSecret({
2223
2300
  region,
2224
2301
  account: "pgsodium-root-key",
@@ -2272,6 +2349,18 @@ var bootstrapCommand = new Command("bootstrap").description(
2272
2349
  validate: (v) => URL_SAFE_PASSWORD_RE.test(v) ? void 0 : "must be base64url chars only (no + / =)",
2273
2350
  generate: generateDashboardPassword
2274
2351
  });
2352
+ return {
2353
+ pgsodiumKey,
2354
+ tunnelToken,
2355
+ postgresPassword,
2356
+ jwtSecret,
2357
+ supabaseAnonKey,
2358
+ supabaseServiceRoleKey,
2359
+ dashboardPassword
2360
+ };
2361
+ }
2362
+ async function collectPromptServiceSecrets(args) {
2363
+ const { region, nodeType, opts } = args;
2275
2364
  if (nodeType === "region-with-prompts") {
2276
2365
  await loadSecret({
2277
2366
  region,
@@ -2315,6 +2404,9 @@ var bootstrapCommand = new Command("bootstrap").description(
2315
2404
  );
2316
2405
  process.exit(1);
2317
2406
  }
2407
+ return promptServiceUrl;
2408
+ }
2409
+ async function resolveSupabaseUrl(opts) {
2318
2410
  let supabaseUrl;
2319
2411
  if (opts.supabaseUrl) {
2320
2412
  supabaseUrl = opts.supabaseUrl;
@@ -2338,32 +2430,44 @@ var bootstrapCommand = new Command("bootstrap").description(
2338
2430
  );
2339
2431
  process.exit(1);
2340
2432
  }
2341
- if (!opts.skipLaunchAgent) {
2342
- const laSpin = p3.spinner();
2343
- laSpin.start("Writing pgsodium key file + LaunchAgent plist\u2026");
2344
- const la = await setupLaunchAgent({
2345
- pgsodiumKey,
2346
- ...tunnelToken !== void 0 ? { tunnelToken } : {},
2347
- llmModel,
2348
- embeddingModel,
2349
- postgresPassword,
2350
- jwtSecret,
2351
- supabaseAnonKey,
2352
- supabaseServiceRoleKey,
2353
- dashboardPassword,
2354
- supabaseUrl
2355
- });
2356
- if (!la.ok) {
2357
- laSpin.stop(pc2.red(`\u2717 LaunchAgent step ${la.step} failed.`));
2358
- p3.cancel(la.reason ?? "LaunchAgent setup failed.");
2359
- process.exit(1);
2360
- }
2361
- laSpin.stop(
2362
- pc2.green(
2363
- `\u2713 LaunchAgent loaded (${la.paths.plistFile})${opts.localOnly ? " \u2014 local-only mode, no TUNNEL_TOKEN set" : ""}.`
2364
- )
2365
- );
2433
+ return supabaseUrl;
2434
+ }
2435
+ async function collectSecretsPhase(args) {
2436
+ const core = await collectCoreSecrets({ region: args.region, opts: args.opts });
2437
+ const promptServiceUrl = await collectPromptServiceSecrets(args);
2438
+ const supabaseUrl = await resolveSupabaseUrl(args.opts);
2439
+ return { ...core, promptServiceUrl, supabaseUrl };
2440
+ }
2441
+ async function runLaunchAgentPhase(args) {
2442
+ const { opts, secrets, llmModel, embeddingModel } = args;
2443
+ if (opts.skipLaunchAgent) return;
2444
+ const laSpin = p3.spinner();
2445
+ laSpin.start("Writing pgsodium key file + LaunchAgent plist\u2026");
2446
+ const la = await setupLaunchAgent({
2447
+ pgsodiumKey: secrets.pgsodiumKey,
2448
+ ...secrets.tunnelToken !== void 0 ? { tunnelToken: secrets.tunnelToken } : {},
2449
+ llmModel,
2450
+ embeddingModel,
2451
+ postgresPassword: secrets.postgresPassword,
2452
+ jwtSecret: secrets.jwtSecret,
2453
+ supabaseAnonKey: secrets.supabaseAnonKey,
2454
+ supabaseServiceRoleKey: secrets.supabaseServiceRoleKey,
2455
+ dashboardPassword: secrets.dashboardPassword,
2456
+ supabaseUrl: secrets.supabaseUrl
2457
+ });
2458
+ if (!la.ok) {
2459
+ laSpin.stop(pc2.red(`\u2717 LaunchAgent step ${la.step} failed.`));
2460
+ p3.cancel(la.reason ?? "LaunchAgent setup failed.");
2461
+ process.exit(1);
2366
2462
  }
2463
+ laSpin.stop(
2464
+ pc2.green(
2465
+ `\u2713 LaunchAgent loaded (${la.paths.plistFile})${opts.localOnly ? " \u2014 local-only mode, no TUNNEL_TOKEN set" : ""}.`
2466
+ )
2467
+ );
2468
+ }
2469
+ async function installWrapperPhase(args) {
2470
+ const { repoPath, region, promptServiceUrl } = args;
2367
2471
  const wrapperSpin = p3.spinner();
2368
2472
  wrapperSpin.start("Installing bin/op-compose wrapper\u2026");
2369
2473
  const wrapper = await installOpComposeWrapper({ repoDir: repoPath, region, promptServiceUrl });
@@ -2373,6 +2477,8 @@ var bootstrapCommand = new Command("bootstrap").description(
2373
2477
  process.exit(1);
2374
2478
  }
2375
2479
  wrapperSpin.stop(pc2.green(`\u2713 Installed ${wrapper.path} (mode 0755).`));
2480
+ }
2481
+ async function loginGhcrPhase() {
2376
2482
  const ghcrSpin = p3.spinner();
2377
2483
  ghcrSpin.start("Authenticating Docker to ghcr.io\u2026");
2378
2484
  const ghcr = await loginToGhcr();
@@ -2382,45 +2488,66 @@ var bootstrapCommand = new Command("bootstrap").description(
2382
2488
  process.exit(1);
2383
2489
  }
2384
2490
  ghcrSpin.stop(pc2.green("\u2713 Logged in to ghcr.io."));
2385
- if (!opts.skipOllama) {
2386
- const olSpin = p3.spinner();
2387
- olSpin.start(`Pulling + warming Ollama models\u2026 (${estimatedPullTime(llmModel)})`);
2388
- let olHealth = await checkOllamaHealth();
2389
- if (!olHealth.reachable) {
2390
- olSpin.message("Ollama not reachable \u2014 running `brew services start ollama`\u2026");
2391
- const start = await startOllamaService();
2392
- if (!start.ok) {
2393
- olSpin.stop(pc2.red("\u2717 Couldn't start Ollama service."));
2394
- p3.cancel(
2395
- `${start.reason ?? "unknown"} \u2014 start it manually with \`brew services start ollama\` and re-run with --skip-brew --skip-launch-agent.`
2396
- );
2397
- process.exit(1);
2398
- }
2399
- await new Promise((res) => setTimeout(res, 3e3));
2400
- olHealth = await checkOllamaHealth();
2401
- if (!olHealth.reachable) {
2402
- olSpin.stop(
2403
- pc2.yellow("\u26A0 Ollama service started but daemon not yet answering on :11434.")
2404
- );
2405
- p3.cancel(
2406
- "Give it another few seconds, then re-run with --skip-brew --skip-launch-agent."
2407
- );
2408
- process.exit(1);
2409
- }
2491
+ }
2492
+ async function runOllamaPhase(args) {
2493
+ const { opts, embeddingModel, llmModel } = args;
2494
+ if (opts.skipOllama) return;
2495
+ const olSpin = p3.spinner();
2496
+ olSpin.start(`Pulling + warming Ollama models\u2026 (${estimatedPullTime(llmModel)})`);
2497
+ let olHealth = await checkOllamaHealth();
2498
+ if (!olHealth.reachable) {
2499
+ olSpin.message("Ollama not reachable \u2014 running `brew services start ollama`\u2026");
2500
+ const start = await startOllamaService();
2501
+ if (!start.ok) {
2502
+ olSpin.stop(pc2.red("\u2717 Couldn't start Ollama service."));
2503
+ p3.cancel(
2504
+ `${start.reason ?? "unknown"} \u2014 start it manually with \`brew services start ollama\` and re-run with --skip-brew --skip-launch-agent.`
2505
+ );
2506
+ process.exit(1);
2410
2507
  }
2411
- const modelReport = await setupModels([embeddingModel, llmModel], (model, status) => {
2412
- olSpin.message(`${status}: ${model}`);
2413
- });
2414
- olSpin.stop(
2415
- modelReport.failed.length === 0 ? pc2.green(
2416
- `\u2713 ${modelReport.pulled.length} pulled, ${modelReport.alreadyPresent.length} present, ${modelReport.warmed.length} warmed`
2417
- ) : pc2.yellow(`\u26A0 ${modelReport.failed.length} model pull(s) failed`)
2418
- );
2419
- const probe = await probeHostDockerInternal();
2420
- if (!probe.ok) {
2421
- p3.note(`${pc2.yellow("\u26A0")} ${probe.reason}`, "Docker host networking");
2508
+ await new Promise((res) => setTimeout(res, 3e3));
2509
+ olHealth = await checkOllamaHealth();
2510
+ if (!olHealth.reachable) {
2511
+ olSpin.stop(
2512
+ pc2.yellow("\u26A0 Ollama service started but daemon not yet answering on :11434.")
2513
+ );
2514
+ p3.cancel(
2515
+ "Give it another few seconds, then re-run with --skip-brew --skip-launch-agent."
2516
+ );
2517
+ process.exit(1);
2422
2518
  }
2423
2519
  }
2520
+ const modelReport = await setupModels([embeddingModel, llmModel], (model, status) => {
2521
+ olSpin.message(`${status}: ${model}`);
2522
+ });
2523
+ olSpin.stop(
2524
+ modelReport.failed.length === 0 ? pc2.green(
2525
+ `\u2713 ${modelReport.pulled.length} pulled, ${modelReport.alreadyPresent.length} present, ${modelReport.warmed.length} warmed`
2526
+ ) : pc2.yellow(`\u26A0 ${modelReport.failed.length} model pull(s) failed`)
2527
+ );
2528
+ const probe = await probeHostDockerInternal();
2529
+ if (!probe.ok) {
2530
+ p3.note(`${pc2.yellow("\u26A0")} ${probe.reason}`, "Docker host networking");
2531
+ }
2532
+ }
2533
+ function buildComposeEnv(args) {
2534
+ const { secrets, llmModel, embeddingModel } = args;
2535
+ return {
2536
+ PGSODIUM_ROOT_KEY: secrets.pgsodiumKey,
2537
+ POSTGRES_PASSWORD: secrets.postgresPassword,
2538
+ JWT_SECRET: secrets.jwtSecret,
2539
+ SUPABASE_ANON_KEY: secrets.supabaseAnonKey,
2540
+ SUPABASE_SERVICE_ROLE_KEY: secrets.supabaseServiceRoleKey,
2541
+ DASHBOARD_PASSWORD: secrets.dashboardPassword,
2542
+ SUPABASE_URL: secrets.supabaseUrl,
2543
+ AUTH_JWT_SECRET: process.env["AUTH_JWT_SECRET"] ?? secrets.jwtSecret,
2544
+ ...secrets.tunnelToken !== void 0 ? { TUNNEL_TOKEN: secrets.tunnelToken } : {},
2545
+ ...llmModel ? { LLM_MODEL: llmModel } : {},
2546
+ ...embeddingModel ? { EMBEDDINGS_MODEL: embeddingModel } : {}
2547
+ };
2548
+ }
2549
+ async function runStackPhase(args) {
2550
+ const { opts, repoPath, region, composeFile, secrets, llmModel, embeddingModel } = args;
2424
2551
  if (opts.skipStack) {
2425
2552
  const profileFlag = opts.localOnly ? "" : "--profile public ";
2426
2553
  p3.outro(
@@ -2431,19 +2558,7 @@ var bootstrapCommand = new Command("bootstrap").description(
2431
2558
  return;
2432
2559
  }
2433
2560
  const composeFiles = resolveComposeFiles(repoPath, composeFile);
2434
- const composeEnv = {
2435
- PGSODIUM_ROOT_KEY: pgsodiumKey,
2436
- POSTGRES_PASSWORD: postgresPassword,
2437
- JWT_SECRET: jwtSecret,
2438
- SUPABASE_ANON_KEY: supabaseAnonKey,
2439
- SUPABASE_SERVICE_ROLE_KEY: supabaseServiceRoleKey,
2440
- DASHBOARD_PASSWORD: dashboardPassword,
2441
- SUPABASE_URL: supabaseUrl,
2442
- AUTH_JWT_SECRET: process.env["AUTH_JWT_SECRET"] ?? jwtSecret,
2443
- ...tunnelToken !== void 0 ? { TUNNEL_TOKEN: tunnelToken } : {},
2444
- ...llmModel ? { LLM_MODEL: llmModel } : {},
2445
- ...embeddingModel ? { EMBEDDINGS_MODEL: embeddingModel } : {}
2446
- };
2561
+ const composeEnv = buildComposeEnv({ secrets, llmModel, embeddingModel });
2447
2562
  const composeOpts = {
2448
2563
  files: composeFiles,
2449
2564
  cwd: repoPath,
@@ -2489,9 +2604,25 @@ var bootstrapCommand = new Command("bootstrap").description(
2489
2604
  healthSpin.message(`${healthy}/${running} healthy`);
2490
2605
  }
2491
2606
  });
2607
+ healthSpin.stop(healthOutcomeHeadline(outcome));
2608
+ finishHealthOutcome(outcome, { region, opts, composeFiles });
2609
+ }
2610
+ function healthOutcomeHeadline(outcome) {
2611
+ switch (outcome.kind) {
2612
+ case "healthy":
2613
+ return pc2.green(`\u2713 All ${outcome.snapshots.length} containers healthy.`);
2614
+ case "unhealthy":
2615
+ return pc2.red(`\u2717 ${outcome.problem}`);
2616
+ case "timeout":
2617
+ return pc2.yellow("\u26A0 Timed out waiting for all containers to report healthy.");
2618
+ default:
2619
+ return assertNever();
2620
+ }
2621
+ }
2622
+ function finishHealthOutcome(outcome, args) {
2623
+ const { region, opts, composeFiles } = args;
2492
2624
  switch (outcome.kind) {
2493
2625
  case "healthy":
2494
- healthSpin.stop(pc2.green(`\u2713 All ${outcome.snapshots.length} containers healthy.`));
2495
2626
  if (opts.localOnly) {
2496
2627
  p3.outro(
2497
2628
  pc2.cyan(
@@ -2511,7 +2642,6 @@ var bootstrapCommand = new Command("bootstrap").description(
2511
2642
  }
2512
2643
  return;
2513
2644
  case "unhealthy":
2514
- healthSpin.stop(pc2.red(`\u2717 ${outcome.problem}`));
2515
2645
  p3.note(
2516
2646
  outcome.snapshots.map((s) => ` ${kvHealth(s.health)} ${s.name} (${s.state})`).join("\n"),
2517
2647
  "Container state"
@@ -2522,7 +2652,6 @@ var bootstrapCommand = new Command("bootstrap").description(
2522
2652
  process.exit(1);
2523
2653
  // eslint-disable-next-line no-fallthrough
2524
2654
  case "timeout":
2525
- healthSpin.stop(pc2.yellow("\u26A0 Timed out waiting for all containers to report healthy."));
2526
2655
  p3.note(
2527
2656
  outcome.snapshots.map((s) => ` ${kvHealth(s.health)} ${s.name} (${s.state})`).join("\n"),
2528
2657
  "Container state at timeout"
@@ -2532,11 +2661,10 @@ var bootstrapCommand = new Command("bootstrap").description(
2532
2661
  );
2533
2662
  process.exit(1);
2534
2663
  // eslint-disable-next-line no-fallthrough
2535
- default: {
2664
+ default:
2536
2665
  return;
2537
- }
2538
2666
  }
2539
- });
2667
+ }
2540
2668
  function resolveComposeFiles(repoPath, composeFile) {
2541
2669
  const inputs = composeFile ?? ["docker-compose-prod.yml"];
2542
2670
  return inputs.map((f) => f.startsWith("/") ? f : join(repoPath, f));
@@ -4492,7 +4620,7 @@ async function looksLikeRegionsRepo(dir) {
4492
4620
  }
4493
4621
 
4494
4622
  // src/cli.ts
4495
- var VERSION = "0.10.5";
4623
+ var VERSION = "0.10.7";
4496
4624
  var program = new Command();
4497
4625
  program.name("create-op-node").description(
4498
4626
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."