create-op-node 0.10.6 → 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
- };
818
+ return { fullName: newRepoFull, defaultBranch: "main" };
789
819
  }
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}`));
820
- }
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,
@@ -2041,7 +2084,7 @@ var bootstrapCommand = new Command("bootstrap").description(
2041
2084
  ).addOption(new Option("-y, --yes", "Skip confirmation prompts").default(false)).action(async (opts) => {
2042
2085
  p3.intro(pc2.bgCyan(pc2.black(" create-op-node bootstrap ")));
2043
2086
  const nodeType = await selectNodeType(opts);
2044
- const region = await resolveRegion(opts);
2087
+ const region = await resolveRegion2(opts);
2045
2088
  const owner = opts.owner ?? "OpusPopuli";
2046
2089
  const repoName = `opuspopuli-node-${region}`;
2047
2090
  const composeFile = opts.composeFile ?? defaultComposeFiles(opts);
@@ -2095,7 +2138,7 @@ async function selectNodeType(opts) {
2095
2138
  }
2096
2139
  return nodeType;
2097
2140
  }
2098
- async function resolveRegion(opts) {
2141
+ async function resolveRegion2(opts) {
2099
2142
  return opts.region ? opts.region : unwrap(
2100
2143
  await p3.text({
2101
2144
  message: "Region label (the slug used during init \u2014 e.g. us-ca)?",
@@ -4577,7 +4620,7 @@ async function looksLikeRegionsRepo(dir) {
4577
4620
  }
4578
4621
 
4579
4622
  // src/cli.ts
4580
- var VERSION = "0.10.6";
4623
+ var VERSION = "0.10.7";
4581
4624
  var program = new Command();
4582
4625
  program.name("create-op-node").description(
4583
4626
  "Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."