@donotdev/cli 0.0.18 → 0.0.19

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.
Files changed (131) hide show
  1. package/dependencies-matrix.json +55 -156
  2. package/dist/bin/commands/coach.js +8177 -0
  3. package/dist/bin/commands/create-app.js +5 -5
  4. package/dist/bin/commands/create-project.js +10 -7
  5. package/dist/bin/commands/deploy.js +81 -59
  6. package/dist/bin/commands/doctor.js +243 -698
  7. package/dist/bin/commands/emu.js +2 -2
  8. package/dist/bin/commands/format.js +4 -1
  9. package/dist/bin/commands/get-demo.js +8351 -0
  10. package/dist/bin/commands/make-admin.js +773 -152
  11. package/dist/bin/commands/setup.js +519 -1711
  12. package/dist/bin/commands/staging.js +17852 -0
  13. package/dist/bin/commands/sync-secrets.js +2 -11
  14. package/dist/bin/commands/type-check.js +7733 -1713
  15. package/dist/bin/dndev.js +913 -185
  16. package/dist/bin/donotdev.js +913 -185
  17. package/dist/index.js +96 -65
  18. package/package.json +1 -1
  19. package/templates/app-demo/index.html.example +147 -10
  20. package/templates/app-demo/src/App.tsx.example +7 -13
  21. package/templates/app-demo/src/config/app.ts.example +12 -48
  22. package/templates/app-demo/src/entities/product.ts.example +38 -0
  23. package/templates/app-demo/src/globals.css.example +5 -1
  24. package/templates/app-demo/src/main.tsx.example +13 -7
  25. package/templates/app-demo/src/pages/ChangelogPage.tsx.example +14 -0
  26. package/templates/app-demo/src/pages/DashboardPage.tsx.example +15 -0
  27. package/templates/app-demo/src/pages/HomePage.tsx.example +3 -77
  28. package/templates/app-demo/src/pages/PricingPage.tsx.example +14 -0
  29. package/templates/app-demo/src/pages/ProductsPage.tsx.example +17 -0
  30. package/templates/app-demo/src/pages/ProfilePage.tsx.example +16 -0
  31. package/templates/app-demo/src/pages/SettingsPage.tsx.example +15 -0
  32. package/templates/app-demo/src/pages/ShowcaseDetailPage.tsx.example +112 -0
  33. package/templates/app-demo/src/pages/ShowcasePage.tsx.example +91 -0
  34. package/templates/app-demo/src/pages/legal/LegalPage.tsx.example +14 -0
  35. package/templates/app-demo/src/pages/legal/PrivacyPage.tsx.example +14 -0
  36. package/templates/app-demo/src/pages/legal/TermsPage.tsx.example +14 -0
  37. package/templates/app-demo/tsconfig.json.example +1 -1
  38. package/templates/app-demo/vite.config.ts.example +23 -48
  39. package/templates/app-expo/README.md.example +1 -1
  40. package/templates/app-expo/app/index.tsx.example +1 -1
  41. package/templates/app-vite/src/pages/HomePage.tsx.example +8 -10
  42. package/templates/overlay-firebase/env.fragment.example +1 -1
  43. package/templates/overlay-firebase/env.fragment.expo.example +1 -1
  44. package/templates/overlay-firebase/env.fragment.nextjs.example +1 -1
  45. package/templates/overlay-supabase/env.fragment.example +1 -1
  46. package/templates/overlay-supabase/env.fragment.expo.example +1 -1
  47. package/templates/overlay-supabase/env.fragment.nextjs.example +1 -1
  48. package/templates/overlay-vercel/env.fragment.example +1 -1
  49. package/templates/overlay-vercel/env.fragment.nextjs.example +1 -1
  50. package/templates/root-consumer/AI.md.example +4 -3
  51. package/templates/root-consumer/guides/dndev/AGENT_START_HERE.md.example +21 -6
  52. package/templates/root-consumer/guides/dndev/COMPONENTS_ADV.md.example +16 -179
  53. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +19 -21
  54. package/templates/root-consumer/guides/dndev/GOTCHAS.md.example +14 -3
  55. package/templates/root-consumer/guides/dndev/INDEX.md.example +2 -2
  56. package/templates/root-consumer/guides/dndev/SETUP_APP_CONFIG.md.example +3 -3
  57. package/templates/root-consumer/guides/dndev/SETUP_BLOG.md.example +19 -2
  58. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +35 -1
  59. package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +17 -12
  60. package/templates/root-consumer/guides/dndev/SETUP_LAYOUTS.md.example +32 -0
  61. package/templates/root-consumer/guides/dndev/SETUP_OAUTH_PROVIDERS.md.example +1 -1
  62. package/templates/root-consumer/guides/dndev/SETUP_PAGES.md.example +19 -15
  63. package/templates/root-consumer/guides/dndev/SETUP_STRIPE.md.example +2 -2
  64. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +17 -12
  65. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +37 -16
  66. package/templates/root-consumer/guides/dndev/USE_ROUTING.md.example +18 -18
  67. package/templates/root-consumer/guides/dndev/essences_reference.css.example +119 -2
  68. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +14 -0
  69. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +6 -0
  70. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +14 -0
  71. package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +4 -5
  72. package/templates/root-consumer/guides/wai-way/page_patterns.md.example +2 -2
  73. package/dist/bin/commands/agent-setup.d.ts +0 -6
  74. package/dist/bin/commands/agent-setup.d.ts.map +0 -1
  75. package/dist/bin/commands/agent-setup.js.map +0 -1
  76. package/dist/bin/commands/build.d.ts +0 -11
  77. package/dist/bin/commands/build.d.ts.map +0 -1
  78. package/dist/bin/commands/build.js.map +0 -1
  79. package/dist/bin/commands/bump.d.ts +0 -11
  80. package/dist/bin/commands/bump.d.ts.map +0 -1
  81. package/dist/bin/commands/bump.js.map +0 -1
  82. package/dist/bin/commands/cacheout.d.ts +0 -11
  83. package/dist/bin/commands/cacheout.d.ts.map +0 -1
  84. package/dist/bin/commands/cacheout.js.map +0 -1
  85. package/dist/bin/commands/create-app.d.ts +0 -11
  86. package/dist/bin/commands/create-app.d.ts.map +0 -1
  87. package/dist/bin/commands/create-app.js.map +0 -1
  88. package/dist/bin/commands/create-project.d.ts +0 -11
  89. package/dist/bin/commands/create-project.d.ts.map +0 -1
  90. package/dist/bin/commands/create-project.js.map +0 -1
  91. package/dist/bin/commands/deploy.d.ts +0 -11
  92. package/dist/bin/commands/deploy.d.ts.map +0 -1
  93. package/dist/bin/commands/deploy.js.map +0 -1
  94. package/dist/bin/commands/dev.d.ts +0 -11
  95. package/dist/bin/commands/dev.d.ts.map +0 -1
  96. package/dist/bin/commands/dev.js.map +0 -1
  97. package/dist/bin/commands/doctor.d.ts +0 -6
  98. package/dist/bin/commands/doctor.d.ts.map +0 -1
  99. package/dist/bin/commands/doctor.js.map +0 -1
  100. package/dist/bin/commands/emu.d.ts +0 -11
  101. package/dist/bin/commands/emu.d.ts.map +0 -1
  102. package/dist/bin/commands/emu.js.map +0 -1
  103. package/dist/bin/commands/format.d.ts +0 -11
  104. package/dist/bin/commands/format.d.ts.map +0 -1
  105. package/dist/bin/commands/format.js.map +0 -1
  106. package/dist/bin/commands/make-admin.d.ts +0 -11
  107. package/dist/bin/commands/make-admin.d.ts.map +0 -1
  108. package/dist/bin/commands/make-admin.js.map +0 -1
  109. package/dist/bin/commands/preview.d.ts +0 -11
  110. package/dist/bin/commands/preview.d.ts.map +0 -1
  111. package/dist/bin/commands/preview.js.map +0 -1
  112. package/dist/bin/commands/setup.d.ts +0 -6
  113. package/dist/bin/commands/setup.d.ts.map +0 -1
  114. package/dist/bin/commands/setup.js.map +0 -1
  115. package/dist/bin/commands/sync-secrets.d.ts +0 -11
  116. package/dist/bin/commands/sync-secrets.d.ts.map +0 -1
  117. package/dist/bin/commands/sync-secrets.js.map +0 -1
  118. package/dist/bin/commands/type-check.d.ts +0 -14
  119. package/dist/bin/commands/type-check.d.ts.map +0 -1
  120. package/dist/bin/commands/type-check.js.map +0 -1
  121. package/dist/bin/commands/wai.d.ts +0 -11
  122. package/dist/bin/commands/wai.d.ts.map +0 -1
  123. package/dist/bin/commands/wai.js.map +0 -1
  124. package/dist/index.d.ts +0 -8
  125. package/dist/index.d.ts.map +0 -1
  126. package/dist/index.js.map +0 -1
  127. package/templates/app-demo/src/components/ThemeToggle.tsx.example +0 -48
  128. package/templates/app-demo/src/pages/DetailPage.tsx.example +0 -103
  129. package/templates/app-demo/src/pages/FullPage.tsx.example +0 -142
  130. package/templates/app-demo/src/pages/components/DemoLayout.tsx.example +0 -266
  131. package/templates/app-demo/src/pages/components/LayoutRoute.tsx.example +0 -20
@@ -281,7 +281,7 @@ function fD({ input: e2 = j, output: u2 = M, overwrite: t = true, hideCursor: F2
281
281
  e2.off("keypress", i), F2 && u2.write(import_sisteransi.cursor.show), e2.isTTY && !AD && e2.setRawMode(false), s.terminal = false, s.close();
282
282
  };
283
283
  }
284
- var import_sisteransi, import_picocolors, uD, W, tD, eD, FD, sD, w, N, I, R, r, iD, CD, ED, d, oD, y, V, nD, G, _, z, K, aD, k, hD, lD, xD, B, AD, S, gD, vD, h, x, dD, A, kD, $D, H, SD, TD, jD, U, MD, OD, PD, J, LD, RD;
284
+ var import_sisteransi, import_picocolors, uD, W, tD, eD, FD, sD, w, N, I, R, r, iD, CD, ED, d, oD, y, V, nD, G, _, z, K, aD, k, hD, lD, xD, B, AD, S, gD, vD, h, x, dD, A, OD, PD, J, LD, RD;
285
285
  var init_dist = __esm({
286
286
  "node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/node_modules/@clack/core/dist/index.mjs"() {
287
287
  init_utils();
@@ -519,63 +519,6 @@ var init_dist = __esm({
519
519
  }
520
520
  };
521
521
  A = /* @__PURE__ */ new WeakMap();
522
- kD = Object.defineProperty;
523
- $D = (e2, u2, t) => u2 in e2 ? kD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
524
- H = (e2, u2, t) => ($D(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
525
- SD = class extends x {
526
- constructor(u2) {
527
- super(u2, false), H(this, "options"), H(this, "cursor", 0), this.options = u2.options, this.value = [...u2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: t }) => t === u2.cursorAt), 0), this.on("key", (t) => {
528
- t === "a" && this.toggleAll();
529
- }), this.on("cursor", (t) => {
530
- switch (t) {
531
- case "left":
532
- case "up":
533
- this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
534
- break;
535
- case "down":
536
- case "right":
537
- this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
538
- break;
539
- case "space":
540
- this.toggleValue();
541
- break;
542
- }
543
- });
544
- }
545
- get _value() {
546
- return this.options[this.cursor].value;
547
- }
548
- toggleAll() {
549
- const u2 = this.value.length === this.options.length;
550
- this.value = u2 ? [] : this.options.map((t) => t.value);
551
- }
552
- toggleValue() {
553
- const u2 = this.value.includes(this._value);
554
- this.value = u2 ? this.value.filter((t) => t !== this._value) : [...this.value, this._value];
555
- }
556
- };
557
- TD = Object.defineProperty;
558
- jD = (e2, u2, t) => u2 in e2 ? TD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
559
- U = (e2, u2, t) => (jD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
560
- MD = class extends x {
561
- constructor({ mask: u2, ...t }) {
562
- super(t), U(this, "valueWithCursor", ""), U(this, "_mask", "\u2022"), this._mask = u2 ?? "\u2022", this.on("finalize", () => {
563
- this.valueWithCursor = this.masked;
564
- }), this.on("value", () => {
565
- if (this.cursor >= this.value.length) this.valueWithCursor = `${this.masked}${import_picocolors.default.inverse(import_picocolors.default.hidden("_"))}`;
566
- else {
567
- const F2 = this.masked.slice(0, this.cursor), s = this.masked.slice(this.cursor);
568
- this.valueWithCursor = `${F2}${import_picocolors.default.inverse(s[0])}${s.slice(1)}`;
569
- }
570
- });
571
- }
572
- get cursor() {
573
- return this._cursor;
574
- }
575
- get masked() {
576
- return this.value.replaceAll(/./g, this._mask);
577
- }
578
- };
579
522
  OD = Object.defineProperty;
580
523
  PD = (e2, u2, t) => u2 in e2 ? OD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
581
524
  J = (e2, u2, t) => (PD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
@@ -627,7 +570,7 @@ import y2 from "node:process";
627
570
  function ce() {
628
571
  return y2.platform !== "win32" ? y2.env.TERM !== "linux" : !!y2.env.CI || !!y2.env.WT_SESSION || !!y2.env.TERMINUS_SUBLIME || y2.env.ConEmuTask === "{cmd::Cmder}" || y2.env.TERM_PROGRAM === "Terminus-Sublime" || y2.env.TERM_PROGRAM === "vscode" || y2.env.TERM === "xterm-256color" || y2.env.TERM === "alacritty" || y2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
629
572
  }
630
- var import_picocolors2, import_sisteransi2, V2, u, le, L2, W2, C, ue, o, d2, k2, P2, A2, T, F, $e, _2, me, de, pe, q, D, U2, K2, b2, G2, he, ge, ye, ve, fe, Me, xe, Ie, Se, M2, J2, Y2;
573
+ var import_picocolors2, import_sisteransi2, V2, u, le, L2, W2, C, ue, o, d2, k2, P2, A2, T, F, $e, _2, me, de, pe, q, D, U, K2, b2, G2, he, ye, ve, Me, xe, Ie, Se, M2, J2, Y2;
631
574
  var init_dist2 = __esm({
632
575
  "node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs"() {
633
576
  init_utils();
@@ -656,7 +599,7 @@ var init_dist2 = __esm({
656
599
  pe = u("\u256F", "+");
657
600
  q = u("\u25CF", "\u2022");
658
601
  D = u("\u25C6", "*");
659
- U2 = u("\u25B2", "!");
602
+ U = u("\u25B2", "!");
660
603
  K2 = u("\u25A0", "x");
661
604
  b2 = (t) => {
662
605
  switch (t) {
@@ -699,27 +642,6 @@ ${import_picocolors2.default.gray(o)}` : ""}`;
699
642
  default:
700
643
  return `${n}${import_picocolors2.default.cyan(o)} ${i}
701
644
  ${import_picocolors2.default.cyan(d2)}
702
- `;
703
- }
704
- } }).prompt();
705
- ge = (t) => new MD({ validate: t.validate, mask: t.mask ?? $e, render() {
706
- const n = `${import_picocolors2.default.gray(o)}
707
- ${b2(this.state)} ${t.message}
708
- `, r2 = this.valueWithCursor, i = this.masked;
709
- switch (this.state) {
710
- case "error":
711
- return `${n.trim()}
712
- ${import_picocolors2.default.yellow(o)} ${i}
713
- ${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(this.error)}
714
- `;
715
- case "submit":
716
- return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(i)}`;
717
- case "cancel":
718
- return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i ?? ""))}${i ? `
719
- ${import_picocolors2.default.gray(o)}` : ""}`;
720
- default:
721
- return `${n}${import_picocolors2.default.cyan(o)} ${r2}
722
- ${import_picocolors2.default.cyan(d2)}
723
645
  `;
724
646
  }
725
647
  } }).prompt();
@@ -770,46 +692,6 @@ ${import_picocolors2.default.gray(o)}`;
770
692
  return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
771
693
  ${import_picocolors2.default.cyan(o)} `)}
772
694
  ${import_picocolors2.default.cyan(d2)}
773
- `;
774
- }
775
- } }).prompt();
776
- };
777
- fe = (t) => {
778
- const n = (r2, i) => {
779
- const s = r2.label ?? String(r2.value);
780
- return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
781
- };
782
- return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? true, cursorAt: t.cursorAt, validate(r2) {
783
- if (this.required && r2.length === 0) return `Please select at least one option.
784
- ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
785
- }, render() {
786
- const r2 = `${import_picocolors2.default.gray(o)}
787
- ${b2(this.state)} ${t.message}
788
- `, i = (s, c) => {
789
- const a = this.value.includes(s.value);
790
- return c && a ? n(s, "active-selected") : a ? n(s, "selected") : n(s, c ? "active" : "inactive");
791
- };
792
- switch (this.state) {
793
- case "submit":
794
- return `${r2}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
795
- case "cancel": {
796
- const s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => n(c, "cancelled")).join(import_picocolors2.default.dim(", "));
797
- return `${r2}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
798
- ${import_picocolors2.default.gray(o)}` : ""}`;
799
- }
800
- case "error": {
801
- const s = this.error.split(`
802
- `).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
803
- `);
804
- return `${r2 + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
805
- ${import_picocolors2.default.yellow(o)} `)}
806
- ${s}
807
- `;
808
- }
809
- default:
810
- return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
811
- ${import_picocolors2.default.cyan(o)} `)}
812
- ${import_picocolors2.default.cyan(d2)}
813
695
  `;
814
696
  }
815
697
  } }).prompt();
@@ -861,7 +743,7 @@ ${import_picocolors2.default.gray(d2)} ${t}
861
743
  }, step: (t) => {
862
744
  M2.message(t, { symbol: import_picocolors2.default.green(C) });
863
745
  }, warn: (t) => {
864
- M2.message(t, { symbol: import_picocolors2.default.yellow(U2) });
746
+ M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
865
747
  }, warning: (t) => {
866
748
  M2.warn(t);
867
749
  }, error: (t) => {
@@ -888,7 +770,7 @@ ${import_picocolors2.default.gray(d2)} ${t}
888
770
  }, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
889
771
  const h2 = (performance.now() - m2) / 1e3, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
890
772
  return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
891
- }, H2 = (m2 = "") => {
773
+ }, H = (m2 = "") => {
892
774
  a = true, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
893
775
  `);
894
776
  let h2 = 0, w2 = 0;
@@ -911,7 +793,7 @@ ${import_picocolors2.default.gray(d2)} ${t}
911
793
  `) : process.stdout.write(`${w2} ${l2}
912
794
  `), E(), s();
913
795
  };
914
- return { start: H2, stop: N2, message: (m2 = "") => {
796
+ return { start: H, stop: N2, message: (m2 = "") => {
915
797
  l2 = R2(m2 ?? l2);
916
798
  } };
917
799
  };
@@ -8361,24 +8243,6 @@ async function askForSelection(message, choices, defaultValue = 0) {
8361
8243
  }
8362
8244
  return result;
8363
8245
  }
8364
- async function askForMultiSelection(message, choices, defaultIndices = []) {
8365
- const options = choices.map((choice) => ({
8366
- value: choice.value,
8367
- label: choice.title,
8368
- hint: choice.hint
8369
- }));
8370
- const initialValues = defaultIndices.map((i) => choices[i]?.value).filter(Boolean);
8371
- const result = await fe({
8372
- message,
8373
- options,
8374
- initialValues: initialValues.length > 0 ? initialValues : void 0
8375
- });
8376
- if (pD(result)) {
8377
- xe("Operation cancelled.");
8378
- process.exit(0);
8379
- }
8380
- return result;
8381
- }
8382
8246
  var init_cli_input = __esm({
8383
8247
  "packages/tooling/src/utils/cli-input.ts"() {
8384
8248
  "use strict";
@@ -8405,14 +8269,27 @@ function readEnvVar(filePath, varName) {
8405
8269
  }
8406
8270
  return null;
8407
8271
  }
8408
- function resolveVercelToken(appDir) {
8409
- if (process.env.VERCEL_TOKEN) return process.env.VERCEL_TOKEN;
8410
- const fromEnv = readEnvVar(joinPath(appDir, ".env"), "VERCEL_TOKEN");
8411
- if (fromEnv) return fromEnv;
8412
- const fromLocal = readEnvVar(joinPath(appDir, ".env.local"), "VERCEL_TOKEN");
8272
+ function resolveVercelVar(appDir, varName) {
8273
+ if (process.env[varName]) return process.env[varName];
8274
+ const fromLocal = readEnvVar(joinPath(appDir, ".env.local"), varName);
8413
8275
  if (fromLocal) return fromLocal;
8276
+ const fromEnv = readEnvVar(joinPath(appDir, ".env"), varName);
8277
+ if (fromEnv) return fromEnv;
8414
8278
  return null;
8415
8279
  }
8280
+ function resolveVercelCredentials(appDir) {
8281
+ const token = resolveVercelVar(appDir, "VERCEL_TOKEN");
8282
+ const orgId = resolveVercelVar(appDir, "VERCEL_ORG_ID");
8283
+ const projectId = resolveVercelVar(appDir, "VERCEL_PROJECT_ID");
8284
+ const missing = [];
8285
+ if (!token) missing.push("VERCEL_TOKEN");
8286
+ if (!orgId) missing.push("VERCEL_ORG_ID");
8287
+ if (!projectId) missing.push("VERCEL_PROJECT_ID");
8288
+ if (missing.length > 0) return { missing };
8289
+ return {
8290
+ credentials: { token, orgId, projectId }
8291
+ };
8292
+ }
8416
8293
  var init_vercel_token = __esm({
8417
8294
  "packages/tooling/src/cli/setup/vercel-token.ts"() {
8418
8295
  "use strict";
@@ -8444,6 +8321,67 @@ var init_error_handling = __esm({
8444
8321
  }
8445
8322
  });
8446
8323
 
8324
+ // packages/tooling/src/utils/cross-app-detection.ts
8325
+ function detectBackendFromProviders(appDir) {
8326
+ for (const candidate of PROVIDERS_CANDIDATES) {
8327
+ const filePath = joinPath(appDir, candidate);
8328
+ if (!pathExists(filePath)) continue;
8329
+ const content = readSync(filePath, { format: "text" });
8330
+ if (typeof content !== "string") continue;
8331
+ if (content.includes("@donotdev/firebase") || content.includes("FirebaseAuth") || content.includes("firebaseAuth")) {
8332
+ return "firebase";
8333
+ }
8334
+ if (content.includes("@donotdev/supabase") || content.includes("SupabaseAuth") || content.includes("supabaseAuth")) {
8335
+ return "supabase";
8336
+ }
8337
+ break;
8338
+ }
8339
+ return null;
8340
+ }
8341
+ function analyzeProjectTopology(projectRoot) {
8342
+ const apps = detectApps(projectRoot);
8343
+ const appBackends = /* @__PURE__ */ new Map();
8344
+ const backendOwners = /* @__PURE__ */ new Map();
8345
+ for (const app of apps) {
8346
+ let backend = app.platform === "firebase" || app.platform === "supabase" ? app.platform : null;
8347
+ if (!backend) {
8348
+ backend = detectBackendFromProviders(app.path);
8349
+ }
8350
+ appBackends.set(app.name, backend);
8351
+ if (pathExists(joinPath(app.path, "supabase", "config.toml"))) {
8352
+ backendOwners.set("supabase", app);
8353
+ }
8354
+ if (pathExists(joinPath(app.path, "firebase.json")) || pathExists(joinPath(app.path, "functions", "firebase.json"))) {
8355
+ backendOwners.set("firebase", app);
8356
+ }
8357
+ }
8358
+ if (!backendOwners.has("firebase") && (pathExists(joinPath(projectRoot, "firebase.json")) || pathExists(joinPath(projectRoot, ".firebaserc")))) {
8359
+ }
8360
+ return { apps, appBackends, backendOwners };
8361
+ }
8362
+ function findBackendApp(topology, appName) {
8363
+ const backend = topology.appBackends.get(appName);
8364
+ if (!backend) return void 0;
8365
+ return topology.backendOwners.get(backend);
8366
+ }
8367
+ var PROVIDERS_CANDIDATES;
8368
+ var init_cross_app_detection = __esm({
8369
+ "packages/tooling/src/utils/cross-app-detection.ts"() {
8370
+ "use strict";
8371
+ init_utils();
8372
+ init_app_detection();
8373
+ init_pathResolver();
8374
+ PROVIDERS_CANDIDATES = [
8375
+ "src/config/providers.ts",
8376
+ "src/config/providers.tsx",
8377
+ "src/providers.ts",
8378
+ "src/providers.tsx",
8379
+ "src/lib/providers.ts",
8380
+ "src/lib/providers.tsx"
8381
+ ];
8382
+ }
8383
+ });
8384
+
8447
8385
  // packages/tooling/src/cli/setup/types.ts
8448
8386
  function computeOverallStatus(steps) {
8449
8387
  const hasFailure = steps.some((s) => s.status === "failed");
@@ -8523,7 +8461,7 @@ function isFirebaseInstalled() {
8523
8461
  async function checkPrerequisites() {
8524
8462
  if (!isFirebaseInstalled()) {
8525
8463
  log.error("Firebase CLI is not installed.");
8526
- log.info("Install it with: npm install -g firebase-tools");
8464
+ log.info("Install it with: bun install -g firebase-tools");
8527
8465
  process.exit(1);
8528
8466
  }
8529
8467
  if (!isFirebaseLoggedIn()) {
@@ -8974,15 +8912,40 @@ var init_firebase = __esm({
8974
8912
  });
8975
8913
 
8976
8914
  // packages/tooling/src/utils/supabase-management.ts
8977
- import { execSync } from "node:child_process";
8978
- function extractProjectRef(supabaseUrl) {
8979
- const match = supabaseUrl.match(/https?:\/\/([a-z0-9]+)\.supabase\.co/);
8980
- if (!match?.[1]) {
8981
- throw new Error(
8982
- `Cannot extract project ref from URL: ${supabaseUrl}. Expected format: https://<ref>.supabase.co`
8983
- );
8915
+ import { execSync, spawnSync as spawnSync2 } from "node:child_process";
8916
+ function stripQuotes(value) {
8917
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8918
+ return value.slice(1, -1);
8919
+ }
8920
+ return value;
8921
+ }
8922
+ function detectDbUrl(appDir) {
8923
+ const envPaths = [
8924
+ joinPath(appDir, "supabase", "functions", ".env"),
8925
+ joinPath(appDir, "functions", ".env"),
8926
+ joinPath(appDir, ".env.local"),
8927
+ joinPath(appDir, ".env")
8928
+ ];
8929
+ for (const envPath of envPaths) {
8930
+ if (!pathExists(envPath)) continue;
8931
+ try {
8932
+ const content = readSync(envPath);
8933
+ if (!content) continue;
8934
+ for (const line of content.split("\n")) {
8935
+ const trimmed = line.trim();
8936
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
8937
+ const eqIdx = trimmed.indexOf("=");
8938
+ const key = trimmed.slice(0, eqIdx).trim();
8939
+ const value = stripQuotes(trimmed.slice(eqIdx + 1).trim());
8940
+ if (key === "SUPABASE_DB_URL" && value) {
8941
+ return value;
8942
+ }
8943
+ }
8944
+ } catch {
8945
+ continue;
8946
+ }
8984
8947
  }
8985
- return match[1];
8948
+ return null;
8986
8949
  }
8987
8950
  async function cleanupOldEntityMigrations(supabaseDir, latestFile) {
8988
8951
  const migrationsDir = joinPath(supabaseDir, "migrations");
@@ -9040,35 +9003,38 @@ async function executeMigration(options) {
9040
9003
  const projectRoot = dirname4(supabaseDir);
9041
9004
  try {
9042
9005
  const cmd = getSupabaseCmd();
9043
- execSync(`${cmd} db push --include-all`, {
9006
+ const parts = cmd.split(" ");
9007
+ const bin = parts[0];
9008
+ const baseArgs = [
9009
+ ...parts.slice(1),
9010
+ "db",
9011
+ "push",
9012
+ "--include-all",
9013
+ "--db-url",
9014
+ options.dbUrl
9015
+ ];
9016
+ const result = spawnSync2(bin, baseArgs, {
9044
9017
  cwd: projectRoot,
9045
9018
  stdio: ["pipe", "pipe", "pipe"],
9046
9019
  input: "Y\n",
9047
9020
  env: { ...process.env }
9048
9021
  });
9022
+ if (result.status !== 0) {
9023
+ const stderr = result.stderr?.toString() || "";
9024
+ throw new Error(stderr || `Process exited with code ${result.status}`);
9025
+ }
9049
9026
  } catch (error2) {
9050
- const message = error2 instanceof Error ? error2.message : String(error2);
9027
+ const raw = error2 instanceof Error ? error2.message : String(error2);
9028
+ const sanitized = raw.replace(
9029
+ /postgresql:\/\/[^\s"']*/g,
9030
+ "postgresql://***"
9031
+ );
9051
9032
  throw new Error(
9052
- `Failed to push migrations: ${message}
9053
- Apply manually: copy the generated .sql content into the Supabase Dashboard SQL Editor.`
9033
+ `Failed to push migrations: ${sanitized}
9034
+ Check your SUPABASE_DB_URL (format: postgresql://postgres:<password>@db.<ref>.supabase.co:5432/postgres)`
9054
9035
  );
9055
9036
  }
9056
9037
  }
9057
- function ensureProjectLinked(appDir, projectRef) {
9058
- const linkMarker = joinPath(appDir, ".supabase");
9059
- if (pathExists(linkMarker)) return;
9060
- const cmd = getSupabaseCmd();
9061
- try {
9062
- execSync(`${cmd} link --project-ref ${projectRef}`, {
9063
- cwd: appDir,
9064
- stdio: "pipe",
9065
- env: { ...process.env }
9066
- });
9067
- } catch (error2) {
9068
- const message = error2 instanceof Error ? error2.message : String(error2);
9069
- throw new Error(`Failed to link Supabase project: ${message}`);
9070
- }
9071
- }
9072
9038
  function getSupabaseCmd() {
9073
9039
  try {
9074
9040
  execSync("supabase --version", { stdio: "pipe" });
@@ -10288,30 +10254,32 @@ async function main3(options = {}) {
10288
10254
  const supabaseUrl = existingConfig.url;
10289
10255
  log.success(`Supabase URL: ${supabaseUrl}`);
10290
10256
  const secretKey = detectSecretKey(appDir);
10291
- if (secretKey) {
10292
- log.success("Secret key detected");
10293
- } else {
10257
+ const dbUrl = detectDbUrl(appDir);
10258
+ if (secretKey) log.success("Secret key detected");
10259
+ else log.error("Missing SUPABASE_SECRET_KEY in supabase/functions/.env");
10260
+ if (dbUrl) log.success("DB URL detected");
10261
+ else log.error("Missing SUPABASE_DB_URL in supabase/functions/.env");
10262
+ if (!secretKey || !dbUrl) {
10294
10263
  const functionsEnvPath = joinPath(supabaseDir, "functions", ".env");
10264
+ const missing = [];
10265
+ if (!secretKey) missing.push(" SUPABASE_SECRET_KEY=your-service-role-key");
10266
+ if (!dbUrl)
10267
+ missing.push(
10268
+ " SUPABASE_DB_URL=postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres"
10269
+ );
10295
10270
  Me(
10296
10271
  [
10297
10272
  `Fill in ${functionsEnvPath}:`,
10298
- " SUPABASE_SECRET_KEY=your-service-role-key",
10273
+ ...missing,
10299
10274
  "",
10300
- "Get this from: https://supabase.com/dashboard > Settings > API > service_role key",
10275
+ "Get these from: https://supabase.com/dashboard > Settings > Database",
10301
10276
  "",
10302
- "Without it, migrations cannot be pushed to the remote database."
10277
+ "Then re-run: dndev setup supabase"
10303
10278
  ].join("\n"),
10304
- "Missing Secret Key"
10305
- );
10306
- }
10307
- try {
10308
- const projectRef = extractProjectRef(supabaseUrl);
10309
- ensureProjectLinked(appDir, projectRef);
10310
- log.success("Supabase CLI linked");
10311
- } catch (error2) {
10312
- log.warn(
10313
- `Could not link Supabase project: ${error2 instanceof Error ? error2.message : String(error2)}`
10279
+ "Missing Credentials"
10314
10280
  );
10281
+ Se("Setup aborted.");
10282
+ return;
10315
10283
  }
10316
10284
  const entitiesDir = joinPath(projectRoot, "entities");
10317
10285
  const hasEntities = pathExists(entitiesDir) && pathExists(joinPath(entitiesDir, "index.ts"));
@@ -10364,25 +10332,21 @@ async function main3(options = {}) {
10364
10332
  }
10365
10333
  }
10366
10334
  let migrationsPushed = false;
10367
- if (!secretKey) {
10368
- log.info("Skipping migration push (no secret key).");
10369
- } else {
10370
- const shouldPush = await askForConfirmation(
10371
- "Push migrations to remote database?",
10372
- true
10373
- );
10374
- if (shouldPush) {
10375
- const s = Y2();
10376
- s.start("Pushing migrations...");
10377
- try {
10378
- await executeMigration({ supabaseDir });
10379
- s.stop("Migrations pushed successfully");
10380
- migrationsPushed = true;
10381
- } catch (error2) {
10382
- s.stop("Failed to push migrations");
10383
- log.error(`${error2 instanceof Error ? error2.message : String(error2)}`);
10384
- log.info("Apply manually via the Supabase Dashboard SQL Editor.");
10385
- }
10335
+ const shouldPush = await askForConfirmation(
10336
+ "Push migrations to remote database?",
10337
+ true
10338
+ );
10339
+ if (shouldPush) {
10340
+ const s = Y2();
10341
+ s.start("Pushing migrations...");
10342
+ try {
10343
+ await executeMigration({ supabaseDir, dbUrl });
10344
+ s.stop("Migrations pushed successfully");
10345
+ migrationsPushed = true;
10346
+ } catch (error2) {
10347
+ s.stop("Failed to push migrations");
10348
+ log.error(`${error2 instanceof Error ? error2.message : String(error2)}`);
10349
+ log.info("Apply manually via the Supabase Dashboard SQL Editor.");
10386
10350
  }
10387
10351
  }
10388
10352
  try {
@@ -10441,96 +10405,121 @@ var init_supabase = __esm({
10441
10405
  async run(ctx) {
10442
10406
  const steps = [];
10443
10407
  const framework = ctx.app?.framework === "nextjs" ? "nextjs" : "vite";
10408
+ const supabaseDir = joinPath(ctx.appDir, "supabase");
10444
10409
  const existingConfig = detectPublicConfig(ctx.appDir, framework);
10445
10410
  if (!existingConfig) {
10446
- const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
10447
- const envPath = joinPath(ctx.appDir, ".env");
10448
- Me(
10449
- [
10450
- `Fill in ${envPath}:`,
10451
- ` ${prefix}SUPABASE_URL=https://your-project.supabase.co`,
10452
- ` ${prefix}SUPABASE_PUBLIC_KEY=sb_publishable_...`,
10453
- "",
10454
- "Get these from: https://supabase.com/dashboard > Settings > API",
10455
- "",
10456
- "Then re-run: dndev setup supabase"
10457
- ].join("\n"),
10458
- "Missing Public Credentials"
10459
- );
10460
10411
  steps.push({
10461
10412
  name: "Credentials",
10462
10413
  status: "failed",
10463
- message: "Missing Supabase credentials in .env",
10464
- coachingTopic: "supabase-credentials"
10414
+ message: "Missing Supabase URL or public key in .env"
10465
10415
  });
10466
10416
  return { provider: "supabase", steps, overallStatus: "failed" };
10467
10417
  }
10468
- const supabaseUrl = existingConfig.url;
10469
- steps.push({
10470
- name: "Public credentials",
10471
- status: "success",
10472
- message: `URL: ${supabaseUrl}`
10473
- });
10474
10418
  const secretKey = detectSecretKey(ctx.appDir);
10475
- if (secretKey) {
10419
+ const dbUrl = detectDbUrl(ctx.appDir);
10420
+ const missingSecrets = [];
10421
+ if (!secretKey) missingSecrets.push("SUPABASE_SECRET_KEY");
10422
+ if (!dbUrl) missingSecrets.push("SUPABASE_DB_URL");
10423
+ if (missingSecrets.length > 0) {
10476
10424
  steps.push({
10477
- name: "Secret key",
10478
- status: "success",
10479
- message: "Detected in functions/.env"
10425
+ name: "Credentials",
10426
+ status: "failed",
10427
+ message: `Missing in functions/.env: ${missingSecrets.join(", ")}`
10480
10428
  });
10429
+ return { provider: "supabase", steps, overallStatus: "failed" };
10430
+ }
10431
+ steps.push({
10432
+ name: "Credentials",
10433
+ status: "success",
10434
+ message: `${existingConfig.url}`
10435
+ });
10436
+ const entitiesDir = joinPath(ctx.projectRoot, "entities");
10437
+ const hasEntities = pathExists(entitiesDir) && pathExists(joinPath(entitiesDir, "index.ts"));
10438
+ let generatedEntityFile = null;
10439
+ if (hasEntities) {
10440
+ try {
10441
+ const { SqlGenerator: SqlGenerator2 } = await Promise.resolve().then(() => (init_sql_generator(), sql_generator_exports));
10442
+ const generator = new SqlGenerator2({
10443
+ entityDir: entitiesDir,
10444
+ outputDir: joinPath(supabaseDir, "migrations"),
10445
+ singleFile: true
10446
+ });
10447
+ const s = Y2();
10448
+ s.start("Generating SQL from entities...");
10449
+ const result = await generator.run();
10450
+ if (result.success) {
10451
+ s.stop("SQL generated");
10452
+ generatedEntityFile = result.generatedFiles[0] ?? null;
10453
+ steps.push({
10454
+ name: "SQL generation",
10455
+ status: "success",
10456
+ message: `${result.filesGenerated} migration file(s)`
10457
+ });
10458
+ } else {
10459
+ s.stop("SQL generation failed");
10460
+ steps.push({
10461
+ name: "SQL generation",
10462
+ status: "failed",
10463
+ message: "Generator returned failure"
10464
+ });
10465
+ }
10466
+ } catch (error2) {
10467
+ steps.push({
10468
+ name: "SQL generation",
10469
+ status: "failed",
10470
+ message: getErrorMessage(error2)
10471
+ });
10472
+ }
10481
10473
  } else {
10482
- const supabaseDir = joinPath(ctx.appDir, "supabase");
10483
- const functionsEnvPath = joinPath(supabaseDir, "functions", ".env");
10484
- Me(
10485
- [
10486
- `Fill in ${functionsEnvPath}:`,
10487
- " SUPABASE_SECRET_KEY=your-service-role-key",
10488
- "",
10489
- "Get this from: https://supabase.com/dashboard > Settings > API > service_role key"
10490
- ].join("\n"),
10491
- "Missing Secret Key"
10492
- );
10493
10474
  steps.push({
10494
- name: "Secret key",
10495
- status: "needs-manual",
10496
- message: "Secret key not found \u2014 needed for migrations",
10497
- coachingTopic: "supabase-secret-key"
10475
+ name: "SQL generation",
10476
+ status: "skipped",
10477
+ message: "No entities/ directory"
10498
10478
  });
10499
10479
  }
10500
- if (!ctx.dryRun) {
10480
+ if (generatedEntityFile) {
10481
+ try {
10482
+ await cleanupOldEntityMigrations(supabaseDir, generatedEntityFile);
10483
+ } catch {
10484
+ }
10485
+ }
10486
+ const migrationsDir = joinPath(supabaseDir, "migrations");
10487
+ if (pathExists(migrationsDir)) {
10488
+ const s = Y2();
10489
+ s.start("Pushing migrations...");
10501
10490
  try {
10502
- const projectRef = extractProjectRef(supabaseUrl);
10503
- ensureProjectLinked(ctx.appDir, projectRef);
10491
+ await executeMigration({ supabaseDir, dbUrl });
10492
+ s.stop("Migrations pushed");
10504
10493
  steps.push({
10505
- name: "CLI link",
10494
+ name: "DB migrations",
10506
10495
  status: "success",
10507
- message: "Supabase CLI linked"
10496
+ message: "All migrations applied"
10508
10497
  });
10509
- } catch (err) {
10498
+ } catch (error2) {
10499
+ s.stop("Migration push failed");
10510
10500
  steps.push({
10511
- name: "CLI link",
10501
+ name: "DB migrations",
10512
10502
  status: "failed",
10513
- message: getErrorMessage(err)
10503
+ message: getErrorMessage(error2)
10504
+ });
10505
+ }
10506
+ }
10507
+ if (hasEntities) {
10508
+ try {
10509
+ await generateCrudFunction(supabaseDir, ctx.projectRoot);
10510
+ steps.push({
10511
+ name: "CRUD function",
10512
+ status: "success",
10513
+ message: "Entity imports patched"
10514
+ });
10515
+ } catch (error2) {
10516
+ steps.push({
10517
+ name: "CRUD function",
10518
+ status: "failed",
10519
+ message: getErrorMessage(error2)
10514
10520
  });
10515
10521
  }
10516
- } else {
10517
- steps.push({ name: "CLI link", status: "skipped", message: "Dry run" });
10518
10522
  }
10519
- Me(
10520
- [
10521
- "If you use custom RLS policies beyond the defaults,",
10522
- "review them in the Supabase Dashboard:",
10523
- "",
10524
- "https://supabase.com/dashboard > Authentication > Policies"
10525
- ].join("\n"),
10526
- "Row Level Security"
10527
- );
10528
- steps.push({
10529
- name: "RLS policies",
10530
- status: "needs-manual",
10531
- message: "Review custom RLS policies in dashboard",
10532
- coachingTopic: "supabase-rls"
10533
- });
10534
10523
  return {
10535
10524
  provider: "supabase",
10536
10525
  steps,
@@ -10546,27 +10535,13 @@ var vercel_exports = {};
10546
10535
  __export(vercel_exports, {
10547
10536
  vercelWizard: () => vercelWizard
10548
10537
  });
10549
- import { spawnSync as spawnSync2 } from "node:child_process";
10550
- function isVercelInstalled() {
10551
- try {
10552
- const result = spawnSync2("vercel", ["--version"], {
10553
- stdio: "pipe",
10554
- encoding: "utf-8"
10555
- });
10556
- return result.status === 0;
10557
- } catch {
10558
- return false;
10559
- }
10560
- }
10561
10538
  var vercelWizard;
10562
10539
  var init_vercel = __esm({
10563
10540
  "packages/tooling/src/cli/setup/vercel.ts"() {
10564
10541
  "use strict";
10565
10542
  init_utils();
10566
10543
  init_cli_output();
10567
- init_cli_output();
10568
10544
  init_pathResolver();
10569
- init_error_handling();
10570
10545
  init_types();
10571
10546
  init_vercel_token();
10572
10547
  vercelWizard = {
@@ -10577,108 +10552,22 @@ var init_vercel = __esm({
10577
10552
  },
10578
10553
  async run(ctx) {
10579
10554
  const steps = [];
10580
- if (!isVercelInstalled()) {
10581
- log.error("Vercel CLI is not installed.");
10582
- log.info("Install it with: npm install -g vercel");
10583
- steps.push({
10584
- name: "Vercel CLI",
10585
- status: "failed",
10586
- message: "Not installed. Run: npm install -g vercel"
10587
- });
10588
- return { provider: "vercel", steps, overallStatus: "failed" };
10589
- }
10590
- steps.push({ name: "Vercel CLI", status: "success", message: "Installed" });
10591
- const token = resolveVercelToken(ctx.appDir);
10592
- if (token) {
10593
- log.success("VERCEL_TOKEN detected \u2014 no login required");
10555
+ const result = resolveVercelCredentials(ctx.appDir);
10556
+ if (result.credentials) {
10557
+ log.success("Vercel credentials detected");
10594
10558
  steps.push({
10595
- name: "Auth",
10559
+ name: "Credentials",
10596
10560
  status: "success",
10597
- message: "Token-based auth (VERCEL_TOKEN)"
10598
- });
10599
- } else {
10600
- Me(
10601
- [
10602
- "No VERCEL_TOKEN found. Add to .env or .env.local:",
10603
- " VERCEL_TOKEN=your_token_here",
10604
- "",
10605
- "Your customer generates it at:",
10606
- " https://vercel.com/account/tokens",
10607
- " \u2192 Scope to their team for isolation",
10608
- "",
10609
- "This way you deploy to THEIR Vercel without logging in."
10610
- ].join("\n"),
10611
- "Vercel Authentication"
10612
- );
10613
- steps.push({
10614
- name: "Auth",
10615
- status: "needs-manual",
10616
- message: "Drop VERCEL_TOKEN in .env (get it from customer)",
10617
- coachingTopic: "vercel-token"
10618
- });
10619
- }
10620
- if (token && !ctx.dryRun) {
10621
- const s = Y2();
10622
- s.start("Linking Vercel project...");
10623
- try {
10624
- const result = spawnSync2(
10625
- "vercel",
10626
- ["link", "--yes", "--token", token],
10627
- {
10628
- cwd: ctx.appDir,
10629
- stdio: ["inherit", "pipe", "pipe"],
10630
- encoding: "utf-8",
10631
- timeout: 3e4
10632
- }
10633
- );
10634
- if (result.status === 0) {
10635
- s.stop("Vercel project linked");
10636
- steps.push({
10637
- name: "Project link",
10638
- status: "success",
10639
- message: "Linked to Vercel project"
10640
- });
10641
- } else {
10642
- s.stop("Vercel link failed");
10643
- const stderr = result.stderr?.trim() || "Unknown error";
10644
- steps.push({
10645
- name: "Project link",
10646
- status: "failed",
10647
- message: stderr
10648
- });
10649
- }
10650
- } catch (err) {
10651
- s.stop("Vercel link failed");
10652
- steps.push({
10653
- name: "Project link",
10654
- status: "failed",
10655
- message: getErrorMessage(err)
10656
- });
10657
- }
10658
- } else if (!token) {
10659
- steps.push({
10660
- name: "Project link",
10661
- status: "skipped",
10662
- message: "No token \u2014 cannot link"
10561
+ message: "VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID found"
10663
10562
  });
10664
10563
  } else {
10564
+ log.error(`Missing in .env.local: ${result.missing.join(", ")}`);
10665
10565
  steps.push({
10666
- name: "Project link",
10667
- status: "skipped",
10668
- message: "Dry run"
10566
+ name: "Credentials",
10567
+ status: "failed",
10568
+ message: `Missing: ${result.missing.join(", ")}`
10669
10569
  });
10670
10570
  }
10671
- Me(
10672
- [
10673
- "Once VERCEL_TOKEN is in .env:",
10674
- " dndev deploy \u2192 picks it up automatically",
10675
- " dndev deploy:web \u2192 deploy specific app",
10676
- "",
10677
- "deploy.ts reads vercel.json + injects the token.",
10678
- 'No separate "vercel deploy" needed.'
10679
- ].join("\n"),
10680
- "Deployment"
10681
- );
10682
10571
  return {
10683
10572
  provider: "vercel",
10684
10573
  steps,
@@ -10689,868 +10578,77 @@ var init_vercel = __esm({
10689
10578
  }
10690
10579
  });
10691
10580
 
10692
- // packages/tooling/src/cli/setup/stripe.ts
10693
- var stripe_exports = {};
10694
- __export(stripe_exports, {
10695
- stripeWizard: () => stripeWizard
10696
- });
10697
- function isValidPublishableKey(key) {
10698
- return key.startsWith("pk_test_") || key.startsWith("pk_live_");
10699
- }
10700
- function isValidSecretKey(key) {
10701
- return key.startsWith("sk_test_") || key.startsWith("sk_live_") || key.startsWith("rk_test_") || key.startsWith("rk_live_");
10702
- }
10703
- function isValidWebhookSecret(key) {
10704
- return key.startsWith("whsec_");
10705
- }
10706
- function readEnvValue(envPath, varName) {
10707
- if (!pathExists(envPath)) return null;
10708
- const content = readSync(envPath, { format: "text" });
10709
- if (typeof content !== "string") return null;
10710
- for (const line of content.split(/\r?\n/)) {
10711
- const trimmed = line.trim();
10712
- if (!trimmed || trimmed.startsWith("#")) continue;
10713
- if (trimmed.startsWith(`${varName}=`)) {
10714
- return trimmed.substring(`${varName}=`.length).trim();
10715
- }
10716
- }
10717
- return null;
10718
- }
10719
- function writeEnvVar(envPath, varName, value) {
10720
- let content = "";
10721
- if (pathExists(envPath)) {
10722
- const raw = readSync(envPath, { format: "text" });
10723
- content = typeof raw === "string" ? raw : "";
10724
- }
10725
- const lines = content.split("\n");
10726
- let replaced = false;
10727
- const updated = lines.map((line) => {
10728
- if (line.trim().startsWith(`${varName}=`) || line.trim().startsWith(`# ${varName}=`)) {
10729
- replaced = true;
10730
- return `${varName}=${value}`;
10731
- }
10732
- return line;
10733
- });
10734
- if (!replaced) {
10735
- updated.push(`${varName}=${value}`);
10736
- }
10737
- write(envPath, updated.join("\n"));
10738
- }
10739
- var stripeWizard;
10740
- var init_stripe = __esm({
10741
- "packages/tooling/src/cli/setup/stripe.ts"() {
10742
- "use strict";
10743
- init_utils();
10744
- init_dist2();
10745
- init_cli_output();
10746
- init_cli_output();
10747
- init_pathResolver();
10748
- init_cli_input();
10749
- init_types();
10750
- stripeWizard = {
10751
- id: "stripe",
10752
- name: "Stripe",
10753
- isRelevant(ctx) {
10754
- const envPath = joinPath(ctx.appDir, ".env");
10755
- if (pathExists(envPath)) {
10756
- const content = readSync(envPath, { format: "text" });
10757
- if (typeof content === "string" && content.includes("STRIPE_"))
10758
- return true;
10759
- }
10760
- const pkgPath = joinPath(ctx.appDir, "package.json");
10761
- if (pathExists(pkgPath)) {
10762
- const pkg = readSync(pkgPath, { format: "json" });
10763
- if (pkg && typeof pkg === "object") {
10764
- const deps = {
10765
- ...pkg.dependencies
10766
- };
10767
- if (deps["@donotdev/billing"]) return true;
10768
- }
10769
- }
10770
- return false;
10771
- },
10772
- async run(ctx) {
10773
- const steps = [];
10774
- const framework = ctx.app?.framework === "nextjs" ? "nextjs" : "vite";
10775
- const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
10776
- const envPath = joinPath(ctx.appDir, ".env");
10777
- const existingPk = readEnvValue(envPath, `${prefix}STRIPE_PUBLISHABLE_KEY`);
10778
- let publishableKey = existingPk;
10779
- if (existingPk && isValidPublishableKey(existingPk)) {
10780
- steps.push({
10781
- name: "Publishable key",
10782
- status: "success",
10783
- message: "Found in .env"
10784
- });
10785
- } else {
10786
- const pk = await askForInput(
10787
- "Stripe publishable key (pk_test_... or pk_live_...)",
10788
- ""
10789
- );
10790
- if (isValidPublishableKey(pk)) {
10791
- if (!ctx.dryRun) {
10792
- writeEnvVar(envPath, `${prefix}STRIPE_PUBLISHABLE_KEY`, pk);
10793
- log.success("Wrote STRIPE_PUBLISHABLE_KEY to .env");
10794
- }
10795
- publishableKey = pk;
10796
- steps.push({
10797
- name: "Publishable key",
10798
- status: "success",
10799
- message: "Written to .env"
10800
- });
10801
- } else {
10802
- steps.push({
10803
- name: "Publishable key",
10804
- status: "failed",
10805
- message: "Invalid format \u2014 must start with pk_test_ or pk_live_"
10806
- });
10807
- }
10808
- }
10809
- const functionsEnvPath = joinPath(
10810
- ctx.appDir,
10811
- "supabase",
10812
- "functions",
10813
- ".env"
10814
- );
10815
- const altFunctionsEnvPath = joinPath(ctx.appDir, "functions", ".env");
10816
- const secretEnvPath = pathExists(functionsEnvPath) ? functionsEnvPath : altFunctionsEnvPath;
10817
- const existingSk = readEnvValue(secretEnvPath, "STRIPE_SECRET_KEY");
10818
- if (existingSk && isValidSecretKey(existingSk)) {
10819
- steps.push({
10820
- name: "Secret key",
10821
- status: "success",
10822
- message: "Found in functions/.env"
10823
- });
10824
- } else {
10825
- const sk = await ge({
10826
- message: "Stripe secret key (sk_test_... or sk_live_...):"
10827
- });
10828
- if (typeof sk === "string" && isValidSecretKey(sk)) {
10829
- if (!ctx.dryRun) {
10830
- writeEnvVar(secretEnvPath, "STRIPE_SECRET_KEY", sk);
10831
- log.success("Wrote STRIPE_SECRET_KEY to functions/.env");
10832
- }
10833
- steps.push({
10834
- name: "Secret key",
10835
- status: "success",
10836
- message: "Written to functions/.env"
10837
- });
10838
- } else {
10839
- steps.push({
10840
- name: "Secret key",
10841
- status: "failed",
10842
- message: "STRIPE_SECRET_KEY has invalid format"
10843
- });
10844
- }
10845
- }
10846
- const existingWh = readEnvValue(secretEnvPath, "STRIPE_WEBHOOK_SECRET");
10847
- if (existingWh && isValidWebhookSecret(existingWh)) {
10848
- steps.push({
10849
- name: "Webhook secret",
10850
- status: "success",
10851
- message: "Found in functions/.env"
10852
- });
10853
- } else {
10854
- Me(
10855
- [
10856
- "Set up your Stripe webhook endpoint:",
10857
- "",
10858
- "1. Go to https://dashboard.stripe.com/webhooks",
10859
- '2. Click "Add endpoint"',
10860
- "3. Set URL to your functions endpoint:",
10861
- " e.g., https://your-project.supabase.co/functions/v1/stripe-webhook",
10862
- "4. Select events: checkout.session.completed, customer.subscription.*",
10863
- "5. Copy the webhook signing secret (whsec_...)",
10864
- "",
10865
- `Then add to ${secretEnvPath}:`,
10866
- " STRIPE_WEBHOOK_SECRET=whsec_..."
10867
- ].join("\n"),
10868
- "Webhook Setup"
10869
- );
10870
- steps.push({
10871
- name: "Webhook secret",
10872
- status: "needs-manual",
10873
- message: "Configure webhook endpoint in Stripe Dashboard",
10874
- coachingTopic: "stripe-webhook"
10875
- });
10876
- }
10877
- return {
10878
- provider: "stripe",
10879
- steps,
10880
- overallStatus: computeOverallStatus(steps)
10881
- };
10882
- }
10883
- };
10884
- }
10885
- });
10581
+ // packages/cli/src/bin/commands/setup.ts
10582
+ init_utils();
10886
10583
 
10887
- // packages/tooling/src/cli/setup/oauth.ts
10888
- var oauth_exports = {};
10889
- __export(oauth_exports, {
10890
- oauthWizard: () => oauthWizard
10891
- });
10892
- function getRedirectUri(ctx, provider) {
10893
- if (provider === "firebase") {
10894
- const rcPath = joinPath(ctx.projectRoot, ".firebaserc");
10895
- if (pathExists(rcPath)) {
10896
- try {
10897
- const raw = readSync(rcPath, { format: "text" });
10898
- const rc = JSON.parse(typeof raw === "string" ? raw : "{}");
10899
- const projectId = rc?.projects?.default;
10900
- if (projectId) {
10901
- return `https://${projectId}.firebaseapp.com/__/auth/handler`;
10902
- }
10903
- } catch {
10904
- }
10905
- }
10906
- return "https://<YOUR_PROJECT_ID>.firebaseapp.com/__/auth/handler";
10907
- }
10908
- const envPath = joinPath(ctx.appDir, ".env");
10909
- if (pathExists(envPath)) {
10910
- const content = readSync(envPath, { format: "text" });
10911
- if (typeof content === "string") {
10912
- const match = content.match(/(?:VITE_|NEXT_PUBLIC_)SUPABASE_URL=(.+)/);
10913
- if (match?.[1]) {
10914
- return `${match[1].trim()}/auth/v1/callback`;
10915
- }
10916
- }
10917
- }
10918
- return "https://<YOUR_PROJECT_REF>.supabase.co/auth/v1/callback";
10919
- }
10920
- var OAUTH_PROVIDERS, oauthWizard;
10921
- var init_oauth = __esm({
10922
- "packages/tooling/src/cli/setup/oauth.ts"() {
10923
- "use strict";
10924
- init_utils();
10925
- init_cli_output();
10926
- init_pathResolver();
10927
- init_cli_input();
10928
- init_types();
10929
- OAUTH_PROVIDERS = [
10930
- {
10931
- id: "google",
10932
- name: "Google",
10933
- consoleUrl: "https://console.cloud.google.com/apis/credentials",
10934
- instructions: [
10935
- "1. Go to Google Cloud Console > APIs & Services > Credentials",
10936
- "2. Create OAuth 2.0 Client ID (Web application)",
10937
- "3. Add authorized redirect URI (see below)",
10938
- "4. Copy Client ID + Client Secret"
10939
- ]
10940
- },
10941
- {
10942
- id: "github",
10943
- name: "GitHub",
10944
- consoleUrl: "https://github.com/settings/developers",
10945
- instructions: [
10946
- "1. Go to GitHub > Settings > Developer settings > OAuth Apps",
10947
- '2. Click "New OAuth App"',
10948
- "3. Set Authorization callback URL (see below)",
10949
- "4. Copy Client ID + Client Secret"
10950
- ]
10951
- },
10952
- {
10953
- id: "apple",
10954
- name: "Apple",
10955
- consoleUrl: "https://developer.apple.com/account/resources/identifiers/list/serviceId",
10956
- instructions: [
10957
- "1. Go to Apple Developer > Certificates, Identifiers & Profiles",
10958
- "2. Create a Services ID",
10959
- '3. Enable "Sign In with Apple"',
10960
- "4. Configure redirect URL (see below)",
10961
- "5. Create a private key for Sign In with Apple"
10962
- ]
10963
- }
10964
- ];
10965
- oauthWizard = {
10966
- id: "oauth",
10967
- name: "OAuth Providers",
10968
- isRelevant(ctx) {
10969
- const pkgPath = joinPath(ctx.appDir, "package.json");
10970
- if (pathExists(pkgPath)) {
10971
- const pkg = readSync(pkgPath, { format: "json" });
10972
- if (pkg && typeof pkg === "object") {
10973
- const deps = pkg.dependencies;
10974
- if (deps?.["@donotdev/auth"] || deps?.["@donotdev/oauth"]) return true;
10975
- }
10976
- }
10977
- return false;
10978
- },
10979
- async run(ctx) {
10980
- const steps = [];
10981
- const hasFirebase = pathExists(joinPath(ctx.projectRoot, ".firebaserc")) || pathExists(joinPath(ctx.appDir, "firebase.json"));
10982
- const hasSupabase = pathExists(joinPath(ctx.appDir, "supabase"));
10983
- const backendProvider = hasFirebase ? "firebase" : "supabase";
10984
- const redirectUri = getRedirectUri(ctx, backendProvider);
10985
- const selected = await askForMultiSelection(
10986
- "Which OAuth providers do you want to configure?",
10987
- OAUTH_PROVIDERS.map((p2) => ({
10988
- title: p2.name,
10989
- value: p2.id
10990
- }))
10991
- );
10992
- if (selected.length === 0) {
10993
- steps.push({
10994
- name: "OAuth",
10995
- status: "skipped",
10996
- message: "No providers selected"
10997
- });
10998
- return { provider: "oauth", steps, overallStatus: "success" };
10999
- }
11000
- for (const providerId of selected) {
11001
- const provider = OAUTH_PROVIDERS.find((p2) => p2.id === providerId);
11002
- if (!provider) continue;
11003
- Me(
11004
- [
11005
- ...provider.instructions,
11006
- "",
11007
- `Redirect URI: ${redirectUri}`,
11008
- "",
11009
- `Console: ${provider.consoleUrl}`,
11010
- "",
11011
- hasSupabase ? `Then enable "${provider.name}" in Supabase Dashboard > Authentication > Providers` : `Then enable "${provider.name}" in Firebase Console > Authentication > Sign-in method`
11012
- ].join("\n"),
11013
- `${provider.name} OAuth Setup`
11014
- );
11015
- steps.push({
11016
- name: `${provider.name} OAuth`,
11017
- status: "needs-manual",
11018
- message: `Register app in ${provider.name} console + enable provider`,
11019
- coachingTopic: `oauth-${provider.id}`
11020
- });
11021
- }
11022
- return {
11023
- provider: "oauth",
11024
- steps,
11025
- overallStatus: computeOverallStatus(steps)
11026
- };
11027
- }
11028
- };
11029
- }
11030
- });
10584
+ // packages/tooling/src/index.ts
10585
+ init_utils();
11031
10586
 
11032
- // packages/tooling/src/cli/setup/auth.ts
11033
- var auth_exports = {};
11034
- __export(auth_exports, {
11035
- authWizard: () => authWizard
11036
- });
11037
- function detectAuthProviders(ctx) {
11038
- const providers = ["email"];
11039
- const srcDir = joinPath(ctx.appDir, "src");
11040
- if (!pathExists(srcDir)) return providers;
11041
- const candidates = [
11042
- joinPath(srcDir, "providers.ts"),
11043
- joinPath(srcDir, "providers.tsx"),
11044
- joinPath(srcDir, "config", "providers.ts"),
11045
- joinPath(srcDir, "lib", "providers.ts")
11046
- ];
11047
- for (const candidate of candidates) {
11048
- if (!pathExists(candidate)) continue;
11049
- const content = readSync(candidate, { format: "text" });
11050
- if (typeof content !== "string") continue;
11051
- if (content.includes("google") || content.includes("Google"))
11052
- providers.push("google");
11053
- if (content.includes("github") || content.includes("GitHub"))
11054
- providers.push("github");
11055
- if (content.includes("apple") || content.includes("Apple"))
11056
- providers.push("apple");
11057
- if (content.includes("facebook") || content.includes("Facebook"))
11058
- providers.push("facebook");
11059
- break;
11060
- }
11061
- return [...new Set(providers)];
11062
- }
11063
- var authWizard;
11064
- var init_auth = __esm({
11065
- "packages/tooling/src/cli/setup/auth.ts"() {
11066
- "use strict";
11067
- init_utils();
11068
- init_cli_output();
11069
- init_pathResolver();
11070
- init_types();
11071
- authWizard = {
11072
- id: "auth",
11073
- name: "Authentication",
11074
- isRelevant(ctx) {
11075
- const pkgPath = joinPath(ctx.appDir, "package.json");
11076
- if (pathExists(pkgPath)) {
11077
- const pkg = readSync(pkgPath, { format: "json" });
11078
- if (pkg && typeof pkg === "object") {
11079
- const deps = pkg.dependencies;
11080
- if (deps?.["@donotdev/auth"]) return true;
11081
- }
11082
- }
11083
- return false;
11084
- },
11085
- async run(ctx) {
11086
- const steps = [];
11087
- const detectedProviders = detectAuthProviders(ctx);
11088
- const hasFirebase = pathExists(joinPath(ctx.projectRoot, ".firebaserc")) || pathExists(joinPath(ctx.appDir, "firebase.json"));
11089
- const hasSupabase = pathExists(joinPath(ctx.appDir, "supabase"));
11090
- steps.push({
11091
- name: "Detected providers",
11092
- status: "success",
11093
- message: `Found: ${detectedProviders.join(", ")}`
11094
- });
11095
- if (hasFirebase) {
11096
- const rcPath = joinPath(ctx.projectRoot, ".firebaserc");
11097
- let projectId = "<YOUR_PROJECT_ID>";
11098
- if (pathExists(rcPath)) {
11099
- try {
11100
- const raw = readSync(rcPath, { format: "text" });
11101
- const rc = JSON.parse(typeof raw === "string" ? raw : "{}");
11102
- if (rc?.projects?.default) projectId = rc.projects.default;
11103
- } catch {
11104
- }
11105
- }
11106
- const providerList = detectedProviders.map((p2) => ` - ${p2.charAt(0).toUpperCase() + p2.slice(1)}`).join("\n");
11107
- Me(
11108
- [
11109
- "Enable these auth providers in Firebase Console:",
11110
- `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
11111
- "",
11112
- providerList,
11113
- "",
11114
- "For each provider:",
11115
- "\u2192 Click the provider \u2192 Enable \u2192 Save",
11116
- "\u2192 For OAuth providers, paste the Client ID + Secret from the provider console"
11117
- ].join("\n"),
11118
- "Firebase Authentication"
11119
- );
11120
- steps.push({
11121
- name: "Firebase Auth",
11122
- status: "needs-manual",
11123
- message: "Enable auth providers in Firebase Console",
11124
- coachingTopic: "auth-firebase"
11125
- });
11126
- }
11127
- if (hasSupabase) {
11128
- const providerList = detectedProviders.map((p2) => ` - ${p2.charAt(0).toUpperCase() + p2.slice(1)}`).join("\n");
11129
- Me(
11130
- [
11131
- "Enable these auth providers in Supabase Dashboard:",
11132
- "https://supabase.com/dashboard > Authentication > Providers",
11133
- "",
11134
- providerList,
11135
- "",
11136
- "For each provider:",
11137
- "\u2192 Toggle the provider on",
11138
- "\u2192 For OAuth providers, paste the Client ID + Secret from the provider console"
11139
- ].join("\n"),
11140
- "Supabase Authentication"
11141
- );
11142
- steps.push({
11143
- name: "Supabase Auth",
11144
- status: "needs-manual",
11145
- message: "Enable auth providers in Supabase Dashboard",
11146
- coachingTopic: "auth-supabase"
11147
- });
11148
- }
11149
- return {
11150
- provider: "auth",
11151
- steps,
11152
- overallStatus: computeOverallStatus(steps)
11153
- };
11154
- }
11155
- };
11156
- }
11157
- });
10587
+ // packages/tooling/src/cli/index.ts
10588
+ init_utils();
11158
10589
 
11159
- // packages/tooling/src/cli/doctor/check-env.ts
11160
- var check_env_exports = {};
11161
- __export(check_env_exports, {
11162
- envCheck: () => envCheck,
11163
- parseEnvFile: () => parseEnvFile
11164
- });
11165
- function parseEnvFile(filePath) {
11166
- const map = /* @__PURE__ */ new Map();
11167
- if (!pathExists(filePath)) return map;
11168
- const content = readSync(filePath, { format: "text" });
11169
- if (typeof content !== "string") return map;
11170
- for (const line of content.split(/\r?\n/)) {
11171
- const trimmed = line.trim();
11172
- if (!trimmed || trimmed.startsWith("#")) continue;
11173
- const eqIdx = trimmed.indexOf("=");
11174
- if (eqIdx < 0) continue;
11175
- const key = trimmed.substring(0, eqIdx).trim();
11176
- const value = trimmed.substring(eqIdx + 1).trim();
11177
- if (key) map.set(key, value);
11178
- }
11179
- return map;
11180
- }
11181
- var envCheck;
11182
- var init_check_env = __esm({
11183
- "packages/tooling/src/cli/doctor/check-env.ts"() {
11184
- "use strict";
11185
- init_utils();
11186
- init_pathResolver();
11187
- envCheck = {
11188
- id: "env",
11189
- name: "Environment Files",
11190
- isRelevant(_ctx) {
11191
- return true;
11192
- },
11193
- async run(ctx) {
11194
- const results = [];
11195
- const envPath = joinPath(ctx.appDir, ".env");
11196
- const examplePath = joinPath(ctx.appDir, ".env.example");
11197
- if (!pathExists(envPath)) {
11198
- results.push({
11199
- name: ".env file",
11200
- status: "fail",
11201
- message: "Not found \u2014 run dndev setup"
11202
- });
11203
- return results;
11204
- }
11205
- results.push({ name: ".env file", status: "pass", message: "Found" });
11206
- if (pathExists(examplePath)) {
11207
- const example = parseEnvFile(examplePath);
11208
- const actual = parseEnvFile(envPath);
11209
- const missing = [];
11210
- const empty = [];
11211
- for (const [key] of example) {
11212
- if (!actual.has(key)) {
11213
- missing.push(key);
11214
- } else if (!actual.get(key)) {
11215
- empty.push(key);
11216
- }
11217
- }
11218
- if (missing.length > 0) {
11219
- results.push({
11220
- name: "Missing vars",
11221
- status: "fail",
11222
- message: `${missing.length} var(s) in .env.example not in .env: ${missing.join(", ")}`
11223
- });
11224
- }
11225
- if (empty.length > 0) {
11226
- results.push({
11227
- name: "Empty vars",
11228
- status: "warn",
11229
- message: `${empty.length} var(s) are empty: ${empty.join(", ")}`
11230
- });
11231
- }
11232
- if (missing.length === 0 && empty.length === 0) {
11233
- results.push({
11234
- name: ".env vs .env.example",
11235
- status: "pass",
11236
- message: `All ${example.size} expected vars present`
11237
- });
11238
- }
11239
- }
11240
- const gitignorePath = joinPath(ctx.projectRoot, ".gitignore");
11241
- if (pathExists(gitignorePath)) {
11242
- const content = readSync(gitignorePath, { format: "text" });
11243
- if (typeof content === "string") {
11244
- const hasEnvIgnore = content.split("\n").some((line) => {
11245
- const trimmed = line.trim();
11246
- if (!trimmed || trimmed.startsWith("#")) return false;
11247
- return /^\.env(\*|\..*)?$/.test(trimmed);
11248
- });
11249
- if (hasEnvIgnore) {
11250
- results.push({
11251
- name: ".gitignore",
11252
- status: "pass",
11253
- message: ".env is gitignored"
11254
- });
11255
- } else {
11256
- results.push({
11257
- name: ".gitignore",
11258
- status: "warn",
11259
- message: ".env might not be gitignored \u2014 verify manually"
11260
- });
11261
- }
11262
- }
11263
- }
11264
- return results;
11265
- }
11266
- };
11267
- }
11268
- });
10590
+ // packages/tooling/src/cli/setup/setup.ts
10591
+ init_utils();
10592
+ init_cli_output();
10593
+ init_pathResolver();
10594
+ init_cross_app_detection();
11269
10595
 
11270
- // packages/tooling/src/cli/doctor/check-firebase.ts
11271
- var check_firebase_exports = {};
11272
- __export(check_firebase_exports, {
11273
- firebaseCheck: () => firebaseCheck
11274
- });
11275
- import { spawnSync as spawnSync3 } from "node:child_process";
11276
- var firebaseCheck;
11277
- var init_check_firebase = __esm({
11278
- "packages/tooling/src/cli/doctor/check-firebase.ts"() {
11279
- "use strict";
11280
- init_utils();
11281
- init_pathResolver();
11282
- firebaseCheck = {
11283
- id: "firebase",
11284
- name: "Firebase",
11285
- isRelevant(ctx) {
11286
- return pathExists(joinPath(ctx.appDir, "firebase.json")) || pathExists(joinPath(ctx.projectRoot, "firebase.json")) || pathExists(joinPath(ctx.projectRoot, ".firebaserc"));
11287
- },
11288
- async run(ctx) {
11289
- const results = [];
11290
- try {
11291
- const result = spawnSync3("firebase", ["--version"], {
11292
- stdio: "pipe",
11293
- encoding: "utf-8",
11294
- timeout: 1e4
11295
- });
11296
- if (result.status === 0) {
11297
- results.push({
11298
- name: "Firebase CLI",
11299
- status: "pass",
11300
- message: result.stdout.trim()
11301
- });
11302
- } else {
11303
- results.push({
11304
- name: "Firebase CLI",
11305
- status: "fail",
11306
- message: "Not installed"
11307
- });
11308
- }
11309
- } catch {
11310
- results.push({
11311
- name: "Firebase CLI",
11312
- status: "fail",
11313
- message: "Not installed"
11314
- });
11315
- }
11316
- const rcPath = joinPath(ctx.projectRoot, ".firebaserc");
11317
- if (pathExists(rcPath)) {
11318
- try {
11319
- const raw = readSync(rcPath, { format: "text" });
11320
- if (typeof raw !== "string") throw new Error("Not a string");
11321
- const rc = JSON.parse(raw);
11322
- const projects = rc?.projects;
11323
- const projectId = projects && typeof projects === "object" ? projects.default : void 0;
11324
- if (projectId && typeof projectId === "string") {
11325
- results.push({
11326
- name: ".firebaserc",
11327
- status: "pass",
11328
- message: `Project: ${projectId}`
11329
- });
11330
- } else {
11331
- results.push({
11332
- name: ".firebaserc",
11333
- status: "warn",
11334
- message: "No default project set"
11335
- });
11336
- }
11337
- } catch {
11338
- results.push({
11339
- name: ".firebaserc",
11340
- status: "warn",
11341
- message: "Invalid JSON"
11342
- });
11343
- }
11344
- } else {
11345
- results.push({
11346
- name: ".firebaserc",
11347
- status: "fail",
11348
- message: "Missing \u2014 run dndev setup firebase"
11349
- });
11350
- }
11351
- const framework = ctx.app?.framework ?? "vite";
11352
- const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
11353
- const envPath = joinPath(ctx.appDir, ".env");
11354
- if (pathExists(envPath)) {
11355
- const content = readSync(envPath, { format: "text" });
11356
- if (typeof content === "string") {
11357
- const requiredKeys = [
11358
- "FIREBASE_API_KEY",
11359
- "FIREBASE_PROJECT_ID",
11360
- "FIREBASE_AUTH_DOMAIN"
11361
- ];
11362
- const missing = requiredKeys.filter(
11363
- (key) => !content.includes(`${prefix}${key}=`)
11364
- );
11365
- if (missing.length === 0) {
11366
- results.push({
11367
- name: "Firebase .env",
11368
- status: "pass",
11369
- message: "All required keys present"
11370
- });
11371
- } else {
11372
- results.push({
11373
- name: "Firebase .env",
11374
- status: "fail",
11375
- message: `Missing: ${missing.join(", ")}`
11376
- });
11377
- }
11378
- }
11379
- } else {
11380
- results.push({
11381
- name: "Firebase .env",
11382
- status: "fail",
11383
- message: ".env file not found"
11384
- });
11385
- }
11386
- const saKeyPaths = [
11387
- joinPath(ctx.appDir, "service-account-key.json"),
11388
- joinPath(ctx.projectRoot, "service-account-key.json")
11389
- ];
11390
- const saFound = saKeyPaths.some((p2) => pathExists(p2));
11391
- if (saFound) {
11392
- results.push({
11393
- name: "Service account key",
11394
- status: "pass",
11395
- message: "Found"
11396
- });
11397
- } else {
11398
- results.push({
11399
- name: "Service account key",
11400
- status: "warn",
11401
- message: "Not found (needed for deploy/emu)"
11402
- });
11403
- }
11404
- return results;
11405
- }
11406
- };
10596
+ // packages/tooling/src/cli/setup/validate-env.ts
10597
+ init_utils();
10598
+ init_pathResolver();
10599
+ init_cli_output();
10600
+ var FIREBASE_VARS = [
10601
+ {
10602
+ provider: "Firebase",
10603
+ varName: "FIREBASE_API_KEY",
10604
+ envFile: ".env",
10605
+ needsPrefix: true
10606
+ },
10607
+ {
10608
+ provider: "Firebase",
10609
+ varName: "FIREBASE_PROJECT_ID",
10610
+ envFile: ".env",
10611
+ needsPrefix: true
10612
+ },
10613
+ {
10614
+ provider: "Firebase",
10615
+ varName: "FIREBASE_AUTH_DOMAIN",
10616
+ envFile: ".env",
10617
+ needsPrefix: true
11407
10618
  }
11408
- });
11409
-
11410
- // packages/tooling/src/cli/doctor/check-supabase.ts
11411
- var check_supabase_exports = {};
11412
- __export(check_supabase_exports, {
11413
- supabaseCheck: () => supabaseCheck
11414
- });
11415
- import { spawnSync as spawnSync4 } from "node:child_process";
11416
- var supabaseCheck;
11417
- var init_check_supabase = __esm({
11418
- "packages/tooling/src/cli/doctor/check-supabase.ts"() {
11419
- "use strict";
11420
- init_utils();
11421
- init_pathResolver();
11422
- init_supabase();
11423
- supabaseCheck = {
11424
- id: "supabase",
11425
- name: "Supabase",
11426
- isRelevant(ctx) {
11427
- return pathExists(joinPath(ctx.appDir, "supabase")) || pathExists(joinPath(ctx.appDir, "supabase", "config.toml"));
11428
- },
11429
- async run(ctx) {
11430
- const results = [];
11431
- const framework = ctx.app?.framework === "nextjs" ? "nextjs" : "vite";
11432
- const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
11433
- try {
11434
- const result = spawnSync4("supabase", ["--version"], {
11435
- stdio: "pipe",
11436
- encoding: "utf-8",
11437
- timeout: 1e4
11438
- });
11439
- if (result.status === 0) {
11440
- results.push({
11441
- name: "Supabase CLI",
11442
- status: "pass",
11443
- message: result.stdout.trim()
11444
- });
11445
- } else {
11446
- results.push({
11447
- name: "Supabase CLI",
11448
- status: "warn",
11449
- message: "Not installed (needed for migrations)"
11450
- });
11451
- }
11452
- } catch {
11453
- results.push({
11454
- name: "Supabase CLI",
11455
- status: "warn",
11456
- message: "Not installed"
11457
- });
11458
- }
11459
- const configPath = joinPath(ctx.appDir, "supabase", "config.toml");
11460
- if (pathExists(configPath)) {
11461
- results.push({ name: "config.toml", status: "pass", message: "Found" });
11462
- } else {
11463
- results.push({
11464
- name: "config.toml",
11465
- status: "warn",
11466
- message: "Missing (expected at supabase/config.toml)"
11467
- });
11468
- }
11469
- const envPath = joinPath(ctx.appDir, ".env");
11470
- if (pathExists(envPath)) {
11471
- const content = readSync(envPath, { format: "text" });
11472
- if (typeof content === "string") {
11473
- const urlMatch = content.match(
11474
- new RegExp(`${prefix}SUPABASE_URL=(.+)`)
11475
- );
11476
- const url = urlMatch?.[1]?.trim();
11477
- if (url && isValidSupabaseUrl(url)) {
11478
- results.push({ name: "Supabase URL", status: "pass", message: url });
11479
- } else if (url) {
11480
- results.push({
11481
- name: "Supabase URL",
11482
- status: "fail",
11483
- message: "Invalid URL format"
11484
- });
11485
- } else {
11486
- results.push({
11487
- name: "Supabase URL",
11488
- status: "fail",
11489
- message: "Missing in .env"
11490
- });
11491
- }
11492
- const keyMatch = content.match(
11493
- new RegExp(`${prefix}SUPABASE_(?:PUBLIC_KEY|ANON_KEY)=(.+)`)
11494
- );
11495
- const key = keyMatch?.[1]?.trim();
11496
- if (key && isValidPublicKey(key)) {
11497
- results.push({
11498
- name: "Supabase public key",
11499
- status: "pass",
11500
- message: "Present"
11501
- });
11502
- } else {
11503
- results.push({
11504
- name: "Supabase public key",
11505
- status: "fail",
11506
- message: "Missing in .env"
11507
- });
11508
- }
11509
- }
11510
- } else {
11511
- results.push({
11512
- name: "Supabase .env",
11513
- status: "fail",
11514
- message: ".env file not found"
11515
- });
11516
- }
11517
- const functionsEnvCandidates = [
11518
- joinPath(ctx.appDir, "supabase", "functions", ".env"),
11519
- joinPath(ctx.appDir, "functions", ".env")
11520
- ];
11521
- let secretFound = false;
11522
- for (const fenvPath of functionsEnvCandidates) {
11523
- if (!pathExists(fenvPath)) continue;
11524
- const content = readSync(fenvPath, { format: "text" });
11525
- if (typeof content === "string" && content.match(/SUPABASE_(?:SECRET|SERVICE_ROLE)_KEY=.+/)) {
11526
- secretFound = true;
11527
- results.push({
11528
- name: "Secret key",
11529
- status: "pass",
11530
- message: `Found in ${fenvPath.split("/").pop()}`
11531
- });
11532
- break;
11533
- }
11534
- }
11535
- if (!secretFound) {
11536
- results.push({
11537
- name: "Secret key",
11538
- status: "warn",
11539
- message: "Not found (needed for migrations + deploy)"
11540
- });
11541
- }
11542
- return results;
11543
- }
11544
- };
10619
+ ];
10620
+ var SUPABASE_VARS = [
10621
+ {
10622
+ provider: "Supabase",
10623
+ varName: "SUPABASE_URL",
10624
+ envFile: ".env",
10625
+ needsPrefix: true
10626
+ },
10627
+ {
10628
+ provider: "Supabase",
10629
+ varName: "SUPABASE_PUBLIC_KEY",
10630
+ envFile: ".env",
10631
+ needsPrefix: true
11545
10632
  }
11546
- });
11547
-
11548
- // packages/tooling/src/cli/doctor/check-stripe.ts
11549
- var check_stripe_exports = {};
11550
- __export(check_stripe_exports, {
11551
- stripeCheck: () => stripeCheck
11552
- });
11553
- function readEnvValue2(envPath, varName) {
10633
+ ];
10634
+ var SUPABASE_SECRET_VARS = [
10635
+ {
10636
+ provider: "Supabase",
10637
+ varName: "SUPABASE_SECRET_KEY",
10638
+ envFile: "supabase/functions/.env"
10639
+ },
10640
+ {
10641
+ provider: "Supabase",
10642
+ varName: "SUPABASE_DB_URL",
10643
+ envFile: "supabase/functions/.env"
10644
+ }
10645
+ ];
10646
+ var VERCEL_VARS = [
10647
+ { provider: "Vercel", varName: "VERCEL_TOKEN", envFile: ".env.local" },
10648
+ { provider: "Vercel", varName: "VERCEL_ORG_ID", envFile: ".env.local" },
10649
+ { provider: "Vercel", varName: "VERCEL_PROJECT_ID", envFile: ".env.local" }
10650
+ ];
10651
+ function readEnvValue(envPath, varName) {
11554
10652
  if (!pathExists(envPath)) return null;
11555
10653
  const content = readSync(envPath, { format: "text" });
11556
10654
  if (typeof content !== "string") return null;
@@ -11558,520 +10656,230 @@ function readEnvValue2(envPath, varName) {
11558
10656
  const trimmed = line.trim();
11559
10657
  if (!trimmed || trimmed.startsWith("#")) continue;
11560
10658
  if (trimmed.startsWith(`${varName}=`)) {
11561
- let val = trimmed.substring(`${varName}=`.length).trim();
11562
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
11563
- val = val.slice(1, -1);
11564
- }
11565
- return val || null;
10659
+ return trimmed.substring(`${varName}=`.length).trim();
11566
10660
  }
11567
10661
  }
11568
10662
  return null;
11569
10663
  }
11570
- function findFunctionsEnv(ctx) {
11571
- const candidates = [
11572
- joinPath(ctx.appDir, "supabase", "functions", ".env"),
11573
- joinPath(ctx.appDir, "functions", ".env")
11574
- ];
11575
- return candidates.find((p2) => pathExists(p2)) ?? null;
10664
+ function isProviderRelevant(ctx, provider) {
10665
+ switch (provider) {
10666
+ case "Firebase":
10667
+ return pathExists(joinPath(ctx.appDir, "firebase.json")) || pathExists(joinPath(ctx.projectRoot, "firebase.json")) || pathExists(joinPath(ctx.projectRoot, ".firebaserc"));
10668
+ case "Supabase":
10669
+ return pathExists(joinPath(ctx.appDir, "supabase")) || pathExists(joinPath(ctx.appDir, "supabase", "config.toml"));
10670
+ case "Vercel":
10671
+ return pathExists(joinPath(ctx.appDir, "vercel.json"));
10672
+ default:
10673
+ return false;
10674
+ }
11576
10675
  }
11577
- var stripeCheck;
11578
- var init_check_stripe = __esm({
11579
- "packages/tooling/src/cli/doctor/check-stripe.ts"() {
11580
- "use strict";
11581
- init_utils();
11582
- init_pathResolver();
11583
- stripeCheck = {
11584
- id: "stripe",
11585
- name: "Stripe",
11586
- isRelevant(ctx) {
11587
- const pkgPath = joinPath(ctx.appDir, "package.json");
11588
- if (pathExists(pkgPath)) {
11589
- const pkg = readSync(pkgPath, { format: "json" });
11590
- if (pkg && typeof pkg === "object") {
11591
- const deps = pkg.dependencies;
11592
- if (deps?.["@donotdev/billing"]) return true;
11593
- }
11594
- }
11595
- const envPath = joinPath(ctx.appDir, ".env");
11596
- if (pathExists(envPath)) {
11597
- const content = readSync(envPath, { format: "text" });
11598
- if (typeof content === "string" && content.includes("STRIPE_PUBLISHABLE_KEY"))
11599
- return true;
11600
- }
11601
- return false;
11602
- },
11603
- async run(ctx) {
11604
- const results = [];
11605
- const framework = ctx.app?.framework ?? "vite";
11606
- const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
11607
- const envPath = joinPath(ctx.appDir, ".env");
11608
- const functionsEnvPath = findFunctionsEnv(ctx);
11609
- const pk = readEnvValue2(envPath, `${prefix}STRIPE_PUBLISHABLE_KEY`);
11610
- let pkMode = null;
11611
- if (pk) {
11612
- if (pk.startsWith("pk_test_") || pk.startsWith("pk_live_")) {
11613
- pkMode = pk.startsWith("pk_test_") ? "test" : "live";
11614
- results.push({
11615
- name: "Publishable key",
11616
- status: "pass",
11617
- message: `Valid (${pkMode} mode)`
11618
- });
11619
- } else {
11620
- results.push({
11621
- name: "Publishable key",
11622
- status: "fail",
11623
- message: "Invalid format \u2014 must start with pk_test_ or pk_live_"
11624
- });
11625
- }
11626
- } else {
11627
- results.push({
11628
- name: "Publishable key",
11629
- status: "fail",
11630
- message: "Missing in .env"
11631
- });
11632
- }
11633
- let skMode = null;
11634
- if (functionsEnvPath) {
11635
- const sk = readEnvValue2(functionsEnvPath, "STRIPE_SECRET_KEY");
11636
- if (sk) {
11637
- if (sk.startsWith("sk_test_") || sk.startsWith("sk_live_") || sk.startsWith("rk_test_") || sk.startsWith("rk_live_")) {
11638
- skMode = sk.includes("_test_") ? "test" : "live";
11639
- results.push({
11640
- name: "Secret key",
11641
- status: "pass",
11642
- message: `Valid (${skMode} mode)`
11643
- });
11644
- } else {
11645
- results.push({
11646
- name: "Secret key",
11647
- status: "fail",
11648
- message: "STRIPE_SECRET_KEY has invalid format"
11649
- });
11650
- }
11651
- } else {
11652
- results.push({
11653
- name: "Secret key",
11654
- status: "warn",
11655
- message: "Not found in functions/.env"
11656
- });
11657
- }
11658
- } else {
11659
- results.push({
11660
- name: "Secret key",
11661
- status: "warn",
11662
- message: "No functions/.env found"
11663
- });
11664
- }
11665
- if (functionsEnvPath) {
11666
- const wh = readEnvValue2(functionsEnvPath, "STRIPE_WEBHOOK_SECRET");
11667
- if (wh) {
11668
- if (wh.startsWith("whsec_")) {
11669
- results.push({
11670
- name: "Webhook secret",
11671
- status: "pass",
11672
- message: "Valid format"
11673
- });
11674
- } else {
11675
- results.push({
11676
- name: "Webhook secret",
11677
- status: "fail",
11678
- message: "STRIPE_WEBHOOK_SECRET has invalid format"
11679
- });
11680
- }
11681
- } else {
11682
- results.push({
11683
- name: "Webhook secret",
11684
- status: "warn",
11685
- message: "Not found \u2014 webhooks won't verify"
11686
- });
11687
- }
11688
- }
11689
- if (pkMode && skMode) {
11690
- if (pkMode !== skMode) {
11691
- results.push({
11692
- name: "Key mode",
11693
- status: "warn",
11694
- message: `Publishable key is ${pkMode} but secret key is ${skMode}`
11695
- });
11696
- } else {
11697
- results.push({
11698
- name: "Key mode",
11699
- status: "pass",
11700
- message: `Both keys are ${pkMode}`
11701
- });
11702
- }
11703
- }
11704
- return results;
11705
- }
11706
- };
10676
+ function validateRequiredEnvVars(ctx) {
10677
+ const missing = [];
10678
+ const framework = ctx.app?.framework === "nextjs" ? "nextjs" : "vite";
10679
+ const prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
10680
+ const requirements = [];
10681
+ if (isProviderRelevant(ctx, "Firebase")) {
10682
+ requirements.push(...FIREBASE_VARS);
10683
+ }
10684
+ if (isProviderRelevant(ctx, "Supabase")) {
10685
+ requirements.push(...SUPABASE_VARS);
10686
+ const sbFunctionsEnv = joinPath(
10687
+ ctx.appDir,
10688
+ "supabase",
10689
+ "functions",
10690
+ ".env"
10691
+ );
10692
+ const altFunctionsEnv = joinPath(ctx.appDir, "functions", ".env");
10693
+ if (pathExists(joinPath(ctx.appDir, "supabase", "functions")) || pathExists(sbFunctionsEnv)) {
10694
+ requirements.push(...SUPABASE_SECRET_VARS);
10695
+ } else if (pathExists(altFunctionsEnv)) {
10696
+ requirements.push({
10697
+ provider: "Supabase",
10698
+ varName: "SUPABASE_SECRET_KEY",
10699
+ envFile: "functions/.env"
10700
+ });
10701
+ }
11707
10702
  }
11708
- });
11709
-
11710
- // packages/tooling/src/cli/doctor/check-auth.ts
11711
- var check_auth_exports = {};
11712
- __export(check_auth_exports, {
11713
- authCheck: () => authCheck
11714
- });
11715
- var authCheck;
11716
- var init_check_auth = __esm({
11717
- "packages/tooling/src/cli/doctor/check-auth.ts"() {
11718
- "use strict";
11719
- init_utils();
11720
- init_pathResolver();
11721
- authCheck = {
11722
- id: "auth",
11723
- name: "Authentication",
11724
- isRelevant(ctx) {
11725
- const pkgPath = joinPath(ctx.appDir, "package.json");
11726
- if (pathExists(pkgPath)) {
11727
- const content = readSync(pkgPath, { format: "text" });
11728
- if (typeof content === "string" && content.includes("@donotdev/auth"))
11729
- return true;
11730
- }
11731
- return false;
11732
- },
11733
- async run(ctx) {
11734
- const results = [];
11735
- results.push({
11736
- name: "@donotdev/auth",
11737
- status: "pass",
11738
- message: "Installed"
10703
+ if (isProviderRelevant(ctx, "Vercel")) {
10704
+ requirements.push(...VERCEL_VARS);
10705
+ }
10706
+ for (const req of requirements) {
10707
+ const actualVarName = req.needsPrefix ? `${prefix}${req.varName}` : req.varName;
10708
+ const envFilePath = joinPath(ctx.appDir, req.envFile);
10709
+ if (req.varName === "SUPABASE_PUBLIC_KEY") {
10710
+ const pkValue = readEnvValue(envFilePath, actualVarName);
10711
+ const anonValue = readEnvValue(envFilePath, `${prefix}SUPABASE_ANON_KEY`);
10712
+ if ((!pkValue || pkValue === "") && (!anonValue || anonValue === "")) {
10713
+ missing.push({
10714
+ provider: req.provider,
10715
+ varName: actualVarName,
10716
+ envFile: req.envFile,
10717
+ status: pathExists(envFilePath) ? "empty" : "missing"
11739
10718
  });
11740
- const candidates = [
11741
- joinPath(ctx.appDir, "src", "providers.ts"),
11742
- joinPath(ctx.appDir, "src", "providers.tsx"),
11743
- joinPath(ctx.appDir, "src", "config", "providers.ts"),
11744
- joinPath(ctx.appDir, "src", "lib", "providers.ts")
11745
- ];
11746
- const providerFile = candidates.find((c) => pathExists(c));
11747
- if (providerFile) {
11748
- results.push({
11749
- name: "Providers config",
11750
- status: "pass",
11751
- message: `Found: ${providerFile.split("/").slice(-2).join("/")}`
11752
- });
11753
- const content = readSync(providerFile, { format: "text" });
11754
- if (typeof content === "string") {
11755
- const providers = [];
11756
- if (content.includes("email") || content.includes("Email"))
11757
- providers.push("email");
11758
- if (content.includes("google") || content.includes("Google"))
11759
- providers.push("google");
11760
- if (content.includes("github") || content.includes("GitHub"))
11761
- providers.push("github");
11762
- if (content.includes("apple") || content.includes("Apple"))
11763
- providers.push("apple");
11764
- if (providers.length > 0) {
11765
- results.push({
11766
- name: "Auth providers",
11767
- status: "pass",
11768
- message: `Configured: ${providers.join(", ")}`
11769
- });
11770
- }
11771
- }
11772
- } else {
11773
- results.push({
11774
- name: "Providers config",
11775
- status: "warn",
11776
- message: "No providers.ts found \u2014 auth may not work"
11777
- });
11778
- }
11779
- const hasFirebase = pathExists(joinPath(ctx.projectRoot, ".firebaserc"));
11780
- const hasSupabase = pathExists(joinPath(ctx.appDir, "supabase"));
11781
- if (hasFirebase) {
11782
- results.push({
11783
- name: "Backend",
11784
- status: "pass",
11785
- message: "Firebase Auth (verify providers are enabled in console)",
11786
- detail: "Run dndev setup auth for setup coaching"
11787
- });
11788
- } else if (hasSupabase) {
11789
- results.push({
11790
- name: "Backend",
11791
- status: "pass",
11792
- message: "Supabase Auth (verify providers are enabled in dashboard)",
11793
- detail: "Run dndev setup auth for setup coaching"
11794
- });
11795
- } else {
11796
- results.push({
11797
- name: "Backend",
11798
- status: "warn",
11799
- message: "No backend detected for auth"
11800
- });
11801
- }
11802
- return results;
11803
- }
11804
- };
11805
- }
11806
- });
11807
-
11808
- // packages/tooling/src/cli/doctor/doctor.ts
11809
- var doctor_exports = {};
11810
- __export(doctor_exports, {
11811
- default: () => doctor_default,
11812
- main: () => main4
11813
- });
11814
- function renderResults(allResults, verbose) {
11815
- const lines = [];
11816
- for (const { checkName, results } of allResults) {
11817
- lines.push(checkName);
11818
- for (const result of results) {
11819
- const icon = STATUS_ICONS[result.status];
11820
- lines.push(` ${icon} ${result.name}: ${result.message}`);
11821
- if (verbose && result.detail) {
11822
- lines.push(` ${result.detail}`);
11823
10719
  }
10720
+ continue;
11824
10721
  }
11825
- lines.push("");
11826
- }
11827
- Me(lines.join("\n"), "Health Check Results");
11828
- }
11829
- async function main4(options = {}) {
11830
- Ie("DoNotDev Doctor");
11831
- const projectRoot = process.cwd();
11832
- let appDir = projectRoot;
11833
- let app = null;
11834
- const appsDir = joinPath(projectRoot, "apps");
11835
- if (pathExists(appsDir)) {
11836
- try {
11837
- app = await selectApp(projectRoot, options.app);
11838
- if (app) {
11839
- appDir = app.path;
11840
- }
11841
- } catch {
10722
+ const value = readEnvValue(envFilePath, actualVarName);
10723
+ if (value === null) {
10724
+ missing.push({
10725
+ provider: req.provider,
10726
+ varName: actualVarName,
10727
+ envFile: req.envFile,
10728
+ status: pathExists(envFilePath) ? "missing" : "missing"
10729
+ });
10730
+ } else if (value === "") {
10731
+ missing.push({
10732
+ provider: req.provider,
10733
+ varName: actualVarName,
10734
+ envFile: req.envFile,
10735
+ status: "empty"
10736
+ });
11842
10737
  }
11843
10738
  }
11844
- const ctx = {
11845
- projectRoot,
11846
- appDir,
11847
- app,
11848
- verbose: options.verbose
11849
- };
11850
- const allResults = [];
11851
- let hasFail = false;
11852
- const registry = options.check ? CHECK_REGISTRY.filter((e2) => e2.id === options.check) : CHECK_REGISTRY;
11853
- if (options.check && registry.length === 0) {
11854
- log.error(
11855
- `Unknown check: ${options.check}. Available: ${CHECK_REGISTRY.map((e2) => e2.id).join(", ")}`
11856
- );
11857
- Se("Doctor aborted.");
11858
- return 1;
10739
+ return missing;
10740
+ }
10741
+ function validateAllApps(apps, _topology, projectRoot) {
10742
+ const results = [];
10743
+ for (const app of apps) {
10744
+ const ctx = {
10745
+ projectRoot,
10746
+ appDir: app.path,
10747
+ app
10748
+ };
10749
+ const missing = validateRequiredEnvVars(ctx);
10750
+ results.push({ app, missing });
11859
10751
  }
11860
- for (const entry of registry) {
11861
- const check = await entry.load();
11862
- if (!check.isRelevant(ctx)) continue;
11863
- const results = await check.run(ctx);
11864
- allResults.push({ checkName: check.name, results });
11865
- if (results.some((r2) => r2.status === "fail")) {
11866
- hasFail = true;
10752
+ return results;
10753
+ }
10754
+ function printAllValidationResults(results, multiApp) {
10755
+ const allMissing = [];
10756
+ for (const { app, missing } of results) {
10757
+ for (const m2 of missing) {
10758
+ allMissing.push({ appName: app.name, m: m2 });
11867
10759
  }
11868
10760
  }
11869
- if (allResults.length === 0) {
11870
- log.warn("No checks were relevant for this project.");
11871
- Se("Nothing to check.");
11872
- return 0;
10761
+ if (allMissing.length === 0) {
10762
+ log.success("Credentials OK");
10763
+ return true;
11873
10764
  }
11874
- renderResults(allResults, options.verbose ?? false);
11875
- let passes = 0;
11876
- let warnings = 0;
11877
- let fails = 0;
11878
- for (const { results } of allResults) {
11879
- for (const r2 of results) {
11880
- if (r2.status === "pass") passes++;
11881
- else if (r2.status === "warn") warnings++;
11882
- else if (r2.status === "fail") fails++;
11883
- }
11884
- }
11885
- const totalChecks = passes + warnings + fails;
11886
- const summaryParts = [`${passes}/${totalChecks} passed`];
11887
- if (warnings > 0) summaryParts.push(`${warnings} warning(s)`);
11888
- if (fails > 0) summaryParts.push(`${fails} failure(s)`);
11889
- const exitCode = hasFail ? 1 : 0;
11890
- Se(
11891
- hasFail ? `Health check failed: ${summaryParts.join(", ")}` : `All clear: ${summaryParts.join(", ")}`
11892
- );
11893
- return exitCode;
11894
- }
11895
- var CHECK_REGISTRY, STATUS_ICONS, doctor_default;
11896
- var init_doctor = __esm({
11897
- "packages/tooling/src/cli/doctor/doctor.ts"() {
11898
- "use strict";
11899
- init_utils();
11900
- init_cli_output();
11901
- init_app_selector();
11902
- init_pathResolver();
11903
- CHECK_REGISTRY = [
11904
- { id: "env", load: async () => (await Promise.resolve().then(() => (init_check_env(), check_env_exports))).envCheck },
11905
- {
11906
- id: "firebase",
11907
- load: async () => (await Promise.resolve().then(() => (init_check_firebase(), check_firebase_exports))).firebaseCheck
11908
- },
11909
- {
11910
- id: "supabase",
11911
- load: async () => (await Promise.resolve().then(() => (init_check_supabase(), check_supabase_exports))).supabaseCheck
11912
- },
11913
- {
11914
- id: "stripe",
11915
- load: async () => (await Promise.resolve().then(() => (init_check_stripe(), check_stripe_exports))).stripeCheck
11916
- },
11917
- { id: "auth", load: async () => (await Promise.resolve().then(() => (init_check_auth(), check_auth_exports))).authCheck }
11918
- ];
11919
- STATUS_ICONS = {
11920
- pass: "\u2705",
11921
- // green check
11922
- warn: "\u26A0\uFE0F",
11923
- // warning
11924
- fail: "\u274C"
11925
- // red x
11926
- };
11927
- doctor_default = main4;
10765
+ for (const { appName, m: m2 } of allMissing) {
10766
+ const prefix = multiApp ? `${appName}: ` : "";
10767
+ const detail = m2.status === "empty" ? `empty in ${m2.envFile}` : `missing \u2014 ${m2.envFile}`;
10768
+ log.error(` ${prefix}${m2.varName} (${detail})`);
11928
10769
  }
11929
- });
11930
-
11931
- // packages/cli/src/bin/commands/setup.ts
11932
- init_utils();
11933
-
11934
- // packages/tooling/src/index.ts
11935
- init_utils();
11936
-
11937
- // packages/tooling/src/cli/index.ts
11938
- init_utils();
10770
+ log.warn(`Run 'dndev coach' to see where to get them.`);
10771
+ return false;
10772
+ }
11939
10773
 
11940
10774
  // packages/tooling/src/cli/setup/setup.ts
11941
- init_utils();
11942
- init_cli_output();
11943
- init_cli_input();
11944
- init_app_selector();
11945
- init_pathResolver();
11946
10775
  var WIZARD_REGISTRY = {
11947
10776
  firebase: async () => (await Promise.resolve().then(() => (init_firebase(), firebase_exports))).firebaseWizard,
11948
10777
  supabase: async () => (await Promise.resolve().then(() => (init_supabase(), supabase_exports))).supabaseWizard,
11949
- vercel: async () => (await Promise.resolve().then(() => (init_vercel(), vercel_exports))).vercelWizard,
11950
- stripe: async () => (await Promise.resolve().then(() => (init_stripe(), stripe_exports))).stripeWizard,
11951
- oauth: async () => (await Promise.resolve().then(() => (init_oauth(), oauth_exports))).oauthWizard,
11952
- auth: async () => (await Promise.resolve().then(() => (init_auth(), auth_exports))).authWizard
10778
+ vercel: async () => (await Promise.resolve().then(() => (init_vercel(), vercel_exports))).vercelWizard
11953
10779
  };
11954
- var WIZARD_ORDER = [
11955
- "firebase",
11956
- "supabase",
11957
- "vercel",
11958
- "stripe",
11959
- "auth",
11960
- "oauth"
11961
- ];
11962
- var STATUS_ICONS2 = {
10780
+ var WIZARD_ORDER = ["firebase", "supabase", "vercel"];
10781
+ var STATUS_ICONS = {
11963
10782
  success: "\u2705",
11964
- // green check
11965
10783
  skipped: "\u23ED\uFE0F",
11966
- // skip
11967
10784
  failed: "\u274C",
11968
- // red x
11969
10785
  "needs-manual": "\u{1F449}"
11970
- // pointing right
11971
10786
  };
11972
- function renderSummary(results) {
11973
- const lines = [];
11974
- for (const result of results) {
11975
- lines.push(`${result.provider.toUpperCase()} (${result.overallStatus})`);
10787
+ function renderResults(results, multiApp) {
10788
+ for (const { appName, result } of results) {
10789
+ if (multiApp) {
10790
+ log.info(`
10791
+ ${appName} \u2014 ${result.provider}:`);
10792
+ }
11976
10793
  for (const step of result.steps) {
11977
- const icon = STATUS_ICONS2[step.status];
11978
- lines.push(` ${icon} ${step.name}: ${step.message}`);
10794
+ const icon = STATUS_ICONS[step.status];
10795
+ log.info(` ${icon} ${step.name}: ${step.message}`);
11979
10796
  }
11980
- lines.push("");
11981
10797
  }
11982
- Me(lines.join("\n"), "Setup Summary");
11983
10798
  }
11984
- async function main5(options = {}) {
11985
- Ie("DoNotDev Setup");
11986
- const projectRoot = process.cwd();
11987
- let appDir = projectRoot;
11988
- let app = null;
11989
- const appsDir = joinPath(projectRoot, "apps");
11990
- if (pathExists(appsDir)) {
11991
- try {
11992
- app = await selectApp(projectRoot, options.app);
11993
- if (app) {
11994
- appDir = app.path;
11995
- }
11996
- } catch {
11997
- }
11998
- }
11999
- const ctx = {
10799
+ function buildSetupContext(app, projectRoot, topology, options) {
10800
+ const backendApp = findBackendApp(topology, app.name);
10801
+ return {
12000
10802
  projectRoot,
12001
- appDir,
10803
+ appDir: app.path,
12002
10804
  app,
12003
10805
  verbose: options.verbose,
12004
- dryRun: options.dryRun
10806
+ dryRun: options.dryRun,
10807
+ allApps: topology.apps,
10808
+ backendApp
12005
10809
  };
12006
- let wizardIds;
12007
- if (options.provider) {
12008
- const id = options.provider.toLowerCase();
12009
- if (!WIZARD_REGISTRY[id]) {
12010
- log.error(`Unknown provider: ${options.provider}`);
12011
- log.info(`Available: ${Object.keys(WIZARD_REGISTRY).join(", ")}`);
12012
- Se("Setup aborted.");
12013
- return 1;
12014
- }
12015
- wizardIds = [id];
12016
- } else {
12017
- const relevant = [];
12018
- for (const id of WIZARD_ORDER) {
10810
+ }
10811
+ async function main4(options = {}) {
10812
+ const projectRoot = process.cwd();
10813
+ Ie("DoNotDev Setup");
10814
+ const topology = analyzeProjectTopology(projectRoot);
10815
+ let apps = topology.apps;
10816
+ if (apps.length === 0) {
10817
+ apps = [
10818
+ {
10819
+ name: "root",
10820
+ packageName: "root",
10821
+ path: projectRoot,
10822
+ packageJsonPath: joinPath(projectRoot, "package.json"),
10823
+ framework: "vite",
10824
+ hasFunctions: false
10825
+ }
10826
+ ];
10827
+ }
10828
+ const multiApp = apps.length > 1;
10829
+ const validationResults = validateAllApps(apps, topology, projectRoot);
10830
+ const allValid = printAllValidationResults(validationResults, multiApp);
10831
+ if (!allValid) {
10832
+ Se("Fix missing values, then re-run.");
10833
+ return 1;
10834
+ }
10835
+ const allResults = [];
10836
+ for (const app of apps) {
10837
+ const ctx = buildSetupContext(app, projectRoot, topology, options);
10838
+ let wizardIds;
10839
+ if (options.provider) {
10840
+ const id = options.provider.toLowerCase();
10841
+ if (!WIZARD_REGISTRY[id]) {
10842
+ log.error(`Unknown provider: ${options.provider}`);
10843
+ Se("Setup aborted.");
10844
+ return 1;
10845
+ }
10846
+ const wizard = await WIZARD_REGISTRY[id]();
10847
+ wizardIds = wizard.isRelevant(ctx) ? [id] : [];
10848
+ } else {
10849
+ wizardIds = [];
10850
+ for (const id of WIZARD_ORDER) {
10851
+ const loader = WIZARD_REGISTRY[id];
10852
+ if (!loader) continue;
10853
+ const wizard = await loader();
10854
+ if (wizard.isRelevant(ctx)) {
10855
+ wizardIds.push(id);
10856
+ }
10857
+ }
10858
+ }
10859
+ for (const id of wizardIds) {
12019
10860
  const loader = WIZARD_REGISTRY[id];
12020
10861
  if (!loader) continue;
12021
10862
  const wizard = await loader();
12022
- if (wizard.isRelevant(ctx)) {
12023
- relevant.push({ id, wizard });
12024
- }
12025
- }
12026
- if (relevant.length === 0) {
12027
- log.warn("No providers detected in this project.");
12028
- log.info(
12029
- `Available providers: ${Object.keys(WIZARD_REGISTRY).join(", ")}`
12030
- );
12031
- log.info("Run: dndev setup <provider>");
12032
- Se("Nothing to set up.");
12033
- return 0;
12034
- }
12035
- const selected = await askForMultiSelection(
12036
- "Which providers do you want to set up?",
12037
- relevant.map((r2) => ({
12038
- title: r2.wizard.name,
12039
- value: r2.id,
12040
- hint: "detected"
12041
- }))
12042
- );
12043
- if (selected.length === 0) {
12044
- Se("No providers selected.");
12045
- return 0;
10863
+ const result = await wizard.run(ctx);
10864
+ allResults.push({ appName: app.name, result });
12046
10865
  }
12047
- wizardIds = selected;
12048
10866
  }
12049
- const results = [];
12050
- for (const id of wizardIds) {
12051
- const loader = WIZARD_REGISTRY[id];
12052
- if (!loader) continue;
12053
- log.info(`
12054
- Setting up ${id}...`);
12055
- const wizard = await loader();
12056
- const result = await wizard.run(ctx);
12057
- results.push(result);
12058
- }
12059
- renderSummary(results);
12060
- if (!options.skipDoctor) {
12061
- log.info("\nRunning health checks...");
12062
- try {
12063
- const { main: doctor } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
12064
- await doctor({ app: options.app, verbose: options.verbose });
12065
- } catch {
12066
- log.warn("Doctor check skipped (module not available).");
12067
- }
10867
+ if (allResults.length === 0) {
10868
+ log.warn("No providers detected. Run: dndev setup <provider>");
10869
+ Se("Nothing to set up.");
10870
+ return 0;
12068
10871
  }
12069
- const hasFailures = results.some((r2) => r2.overallStatus === "failed");
12070
- Se(hasFailures ? "Setup completed with errors." : "Setup complete.");
10872
+ renderResults(allResults, multiApp);
10873
+ const hasFailures = allResults.some(
10874
+ (r2) => r2.result.overallStatus === "failed"
10875
+ );
10876
+ Se(
10877
+ hasFailures ? "Setup incomplete \u2014 see errors above." : "Setup complete."
10878
+ );
12071
10879
  return hasFailures ? 1 : 0;
12072
10880
  }
12073
10881
  export {
12074
- main5 as main
10882
+ main4 as main
12075
10883
  };
12076
10884
  /*! Bundled license information:
12077
10885