@donotdev/cli 0.0.16 → 0.0.18

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 (87) hide show
  1. package/dependencies-matrix.json +38 -26
  2. package/dist/bin/commands/bump.js +9 -2
  3. package/dist/bin/commands/create-app.js +185 -81
  4. package/dist/bin/commands/create-project.js +186 -85
  5. package/dist/bin/commands/deploy.js +51 -20
  6. package/dist/bin/commands/doctor.js +249 -56
  7. package/dist/bin/commands/emu.js +18 -20
  8. package/dist/bin/commands/make-admin.js +30 -10
  9. package/dist/bin/commands/setup.js +512 -122
  10. package/dist/bin/commands/type-check.d.ts.map +1 -1
  11. package/dist/bin/commands/type-check.js +7 -3
  12. package/dist/bin/commands/type-check.js.map +1 -1
  13. package/dist/bin/dndev.js +9 -6
  14. package/dist/bin/donotdev.js +35 -20
  15. package/dist/index.js +262 -129
  16. package/package.json +1 -1
  17. package/templates/root-consumer/.claude/commands/brainstorm.md.example +15 -1
  18. package/templates/root-consumer/.claude/commands/build.md.example +24 -2
  19. package/templates/root-consumer/.claude/commands/design.md.example +17 -0
  20. package/templates/root-consumer/.claude/commands/polish.md.example +17 -0
  21. package/templates/root-consumer/AI.md.example +50 -18
  22. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +6 -6
  23. package/templates/root-consumer/guides/dndev/INDEX.md.example +2 -2
  24. package/templates/root-consumer/guides/dndev/SETUP_AUTH.md.example +13 -6
  25. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +149 -1086
  26. package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +68 -16
  27. package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +6 -111
  28. package/templates/root-consumer/guides/dndev/SETUP_PAGES.md.example +64 -0
  29. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +123 -32
  30. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +108 -91
  31. package/templates/root-consumer/guides/dndev/advanced/EMULATORS.md.example +2 -2
  32. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +1 -1
  33. package/dist/bin/commands/firebase-setup.d.ts +0 -6
  34. package/dist/bin/commands/firebase-setup.d.ts.map +0 -1
  35. package/dist/bin/commands/firebase-setup.js +0 -7
  36. package/dist/bin/commands/firebase-setup.js.map +0 -1
  37. package/dist/bin/commands/supabase-setup.d.ts +0 -6
  38. package/dist/bin/commands/supabase-setup.d.ts.map +0 -1
  39. package/dist/bin/commands/supabase-setup.js +0 -7
  40. package/dist/bin/commands/supabase-setup.js.map +0 -1
  41. package/templates/functions-firebase/functions-firebase/README.md.example +0 -123
  42. package/templates/functions-firebase/functions-firebase/build.mjs.example +0 -5
  43. package/templates/functions-firebase/functions-firebase/src/auth/getCustomClaims.ts.example +0 -19
  44. package/templates/functions-firebase/functions-firebase/src/auth/getUserAuthStatus.ts.example +0 -21
  45. package/templates/functions-firebase/functions-firebase/src/auth/index.ts.example +0 -11
  46. package/templates/functions-firebase/functions-firebase/src/auth/removeCustomClaims.ts.example +0 -21
  47. package/templates/functions-firebase/functions-firebase/src/auth/setCustomClaims.ts.example +0 -21
  48. package/templates/functions-firebase/functions-firebase/src/billing/handleStripeWebhook.ts.example +0 -24
  49. package/templates/functions-firebase/functions-firebase/src/billing/index.ts.example +0 -10
  50. package/templates/functions-firebase/functions-firebase/src/billing/processPaymentSuccess.ts.example +0 -14
  51. package/templates/functions-firebase/functions-firebase/src/billing/refreshSubscriptionStatus.ts.example +0 -14
  52. package/templates/functions-firebase/functions-firebase/src/index.ts.example +0 -39
  53. package/templates/functions-firebase/functions-firebase/src/oauth/checkGitHubAccess.ts.example +0 -14
  54. package/templates/functions-firebase/functions-firebase/src/oauth/disconnect.ts.example +0 -14
  55. package/templates/functions-firebase/functions-firebase/src/oauth/exchangeToken.ts.example +0 -14
  56. package/templates/functions-firebase/functions-firebase/src/oauth/getConnections.ts.example +0 -14
  57. package/templates/functions-firebase/functions-firebase/src/oauth/grantGitHubAccess.ts.example +0 -14
  58. package/templates/functions-firebase/functions-firebase/src/oauth/index.ts.example +0 -17
  59. package/templates/functions-firebase/functions-firebase/src/oauth/refreshToken.ts.example +0 -14
  60. package/templates/functions-firebase/functions-firebase/src/oauth/revokeGitHubAccess.ts.example +0 -14
  61. package/templates/functions-firebase/functions-firebase/tsconfig.json.example +0 -21
  62. package/templates/functions-vercel/functions-vercel/README.md.example +0 -116
  63. package/templates/functions-vercel/functions-vercel/build.mjs.example +0 -52
  64. package/templates/functions-vercel/functions-vercel/src/api/auth/getCustomClaims.ts.example +0 -20
  65. package/templates/functions-vercel/functions-vercel/src/api/auth/getUserAuthStatus.ts.example +0 -20
  66. package/templates/functions-vercel/functions-vercel/src/api/auth/removeCustomClaims.ts.example +0 -20
  67. package/templates/functions-vercel/functions-vercel/src/api/auth/setCustomClaims.ts.example +0 -20
  68. package/templates/functions-vercel/functions-vercel/src/api/billing/handleStripeWebhook.ts.example +0 -20
  69. package/templates/functions-vercel/functions-vercel/src/api/billing/processPaymentSuccess.ts.example +0 -20
  70. package/templates/functions-vercel/functions-vercel/src/api/billing/refreshSubscriptionStatus.ts.example +0 -20
  71. package/templates/functions-vercel/functions-vercel/src/api/crud/createEntity.ts.example +0 -20
  72. package/templates/functions-vercel/functions-vercel/src/api/crud/deleteEntity.ts.example +0 -20
  73. package/templates/functions-vercel/functions-vercel/src/api/crud/getEntity.ts.example +0 -20
  74. package/templates/functions-vercel/functions-vercel/src/api/crud/listEntities.ts.example +0 -20
  75. package/templates/functions-vercel/functions-vercel/src/api/crud/updateEntity.ts.example +0 -20
  76. package/templates/functions-vercel/functions-vercel/src/api/oauth/checkGitHubAccess.ts.example +0 -20
  77. package/templates/functions-vercel/functions-vercel/src/api/oauth/disconnect.ts.example +0 -20
  78. package/templates/functions-vercel/functions-vercel/src/api/oauth/exchangeToken.ts.example +0 -20
  79. package/templates/functions-vercel/functions-vercel/src/api/oauth/getConnections.ts.example +0 -20
  80. package/templates/functions-vercel/functions-vercel/src/api/oauth/grantGitHubAccess.ts.example +0 -20
  81. package/templates/functions-vercel/functions-vercel/src/api/oauth/refreshToken.ts.example +0 -20
  82. package/templates/functions-vercel/functions-vercel/src/api/oauth/revokeGitHubAccess.ts.example +0 -20
  83. package/templates/functions-vercel/functions-vercel/tsconfig.json.example +0 -21
  84. package/templates/functions-vercel/functions-vercel/vercel.json.example +0 -14
  85. package/templates/github/github/workflows/firebase-deploy.yml.example +0 -79
  86. /package/templates/functions-firebase/{functions-firebase/.env.example.example → .env.example} +0 -0
  87. /package/templates/functions-vercel/{functions-vercel/.env.example.example → .env.example} +0 -0
package/dist/index.js CHANGED
@@ -8469,7 +8469,14 @@ function readServiceAccountKey(filePath) {
8469
8469
  throw new DoNotDevError(
8470
8470
  `Invalid service account key: missing required fields`,
8471
8471
  DO_NOT_DEV_ERROR_CODES.CONFIG_INVALID,
8472
- { context: { filePath, missingFields: ["project_id", "private_key", "client_email"].filter((f) => !key[f]) } }
8472
+ {
8473
+ context: {
8474
+ filePath,
8475
+ missingFields: ["project_id", "private_key", "client_email"].filter(
8476
+ (f) => !key[f]
8477
+ )
8478
+ }
8479
+ }
8473
8480
  );
8474
8481
  }
8475
8482
  return content;
@@ -8876,9 +8883,7 @@ function getCLIInstallInstructions(tool) {
8876
8883
  "Or: winget install Supabase.CLI",
8877
8884
  "Or download from: https://github.com/supabase/cli/releases"
8878
8885
  ],
8879
- darwin: [
8880
- "brew install supabase/tap/supabase"
8881
- ],
8886
+ darwin: ["brew install supabase/tap/supabase"],
8882
8887
  linux: [
8883
8888
  "brew install supabase/tap/supabase",
8884
8889
  "Or see: https://supabase.com/docs/guides/cli"
@@ -8886,7 +8891,11 @@ function getCLIInstallInstructions(tool) {
8886
8891
  },
8887
8892
  [CLI_TOOLS.VERCEL]: {
8888
8893
  win32: ["npm install -g vercel", "Or: npx vercel (no install)"],
8889
- darwin: ["npm install -g vercel", "Or: brew install vercel", "Or: npx vercel"],
8894
+ darwin: [
8895
+ "npm install -g vercel",
8896
+ "Or: brew install vercel",
8897
+ "Or: npx vercel"
8898
+ ],
8890
8899
  linux: ["npm install -g vercel", "Or: npx vercel"]
8891
8900
  },
8892
8901
  [CLI_TOOLS.SENTRY_CLI]: {
@@ -17034,7 +17043,9 @@ async function deploySupabaseFunctions(appDir, config) {
17034
17043
  const supabaseDir = joinPath(appDir, "supabase");
17035
17044
  const functionsDir = joinPath(supabaseDir, "functions");
17036
17045
  if (!pathExists(functionsDir)) {
17037
- log.warn("No supabase/functions/ directory found. Skipping Supabase functions deployment.");
17046
+ log.warn(
17047
+ "No supabase/functions/ directory found. Skipping Supabase functions deployment."
17048
+ );
17038
17049
  return;
17039
17050
  }
17040
17051
  requireCLI(
@@ -17050,10 +17061,14 @@ async function deploySupabaseFunctions(appDir, config) {
17050
17061
  return pathExists(indexPath);
17051
17062
  });
17052
17063
  if (functionDirs.length === 0) {
17053
- log.warn("No Edge Functions found in supabase/functions/. Skipping deployment.");
17064
+ log.warn(
17065
+ "No Edge Functions found in supabase/functions/. Skipping deployment."
17066
+ );
17054
17067
  return;
17055
17068
  }
17056
- log.info(`Found ${functionDirs.length} Edge Function(s): ${functionDirs.join(", ")}`);
17069
+ log.info(
17070
+ `Found ${functionDirs.length} Edge Function(s): ${functionDirs.join(", ")}`
17071
+ );
17057
17072
  const s = Y2();
17058
17073
  for (const functionName of functionDirs) {
17059
17074
  s.start(`Deploying Edge Function: ${functionName}...`);
@@ -17072,7 +17087,12 @@ async function deploySupabaseFunctions(appDir, config) {
17072
17087
  throw new DoNotDevError(
17073
17088
  `Failed to deploy Supabase Edge Function: ${functionName}`,
17074
17089
  "deployment-failed",
17075
- { context: { functionName, error: error2 instanceof Error ? error2.message : String(error2) } }
17090
+ {
17091
+ context: {
17092
+ functionName,
17093
+ error: error2 instanceof Error ? error2.message : String(error2)
17094
+ }
17095
+ }
17076
17096
  );
17077
17097
  }
17078
17098
  }
@@ -17892,10 +17912,7 @@ FIREBASE_AUTH_EMULATOR_HOST=${authEmulatorHost}
17892
17912
  // packages/tooling/src/apps/emu-supabase.ts
17893
17913
  init_utils();
17894
17914
  init_cli_output();
17895
- import {
17896
- spawn as spawn3,
17897
- execSync as execSync5
17898
- } from "node:child_process";
17915
+ import { spawn as spawn3, execSync as execSync5 } from "node:child_process";
17899
17916
  import { platform as platform3 } from "node:os";
17900
17917
  function isDockerRunning() {
17901
17918
  try {
@@ -17945,7 +17962,10 @@ async function startSupabase(app, _projectRoot) {
17945
17962
  if (!childProcess.pid) return;
17946
17963
  if (isWindows) {
17947
17964
  try {
17948
- execSync5(`taskkill /F /T /PID ${childProcess.pid}`, { stdio: "ignore", timeout: 2e3 });
17965
+ execSync5(`taskkill /F /T /PID ${childProcess.pid}`, {
17966
+ stdio: "ignore",
17967
+ timeout: 2e3
17968
+ });
17949
17969
  } catch {
17950
17970
  }
17951
17971
  } else {
@@ -17975,10 +17995,7 @@ async function startSupabase(app, _projectRoot) {
17975
17995
  // packages/tooling/src/apps/emu-vercel.ts
17976
17996
  init_utils();
17977
17997
  init_cli_output();
17978
- import {
17979
- spawn as spawn4,
17980
- execSync as execSync6
17981
- } from "node:child_process";
17998
+ import { spawn as spawn4, execSync as execSync6 } from "node:child_process";
17982
17999
  import { platform as platform4 } from "node:os";
17983
18000
  async function startVercel(app, _projectRoot) {
17984
18001
  log.info(`Starting Vercel dev server for ${app.name}...
@@ -17991,20 +18008,19 @@ async function startVercel(app, _projectRoot) {
17991
18008
  return 1;
17992
18009
  }
17993
18010
  const isWindows = platform4() === "win32";
17994
- const childProcess = spawn4(
17995
- "vercel",
17996
- ["dev"],
17997
- {
17998
- stdio: "inherit",
17999
- cwd: app.path,
18000
- shell: isWindows
18001
- }
18002
- );
18011
+ const childProcess = spawn4("vercel", ["dev"], {
18012
+ stdio: "inherit",
18013
+ cwd: app.path,
18014
+ shell: isWindows
18015
+ });
18003
18016
  const cleanup = () => {
18004
18017
  if (!childProcess.pid) return;
18005
18018
  if (isWindows) {
18006
18019
  try {
18007
- execSync6(`taskkill /F /T /PID ${childProcess.pid}`, { stdio: "ignore", timeout: 2e3 });
18020
+ execSync6(`taskkill /F /T /PID ${childProcess.pid}`, {
18021
+ stdio: "ignore",
18022
+ timeout: 2e3
18023
+ });
18008
18024
  } catch {
18009
18025
  }
18010
18026
  } else {
@@ -18051,7 +18067,9 @@ async function main3(options) {
18051
18067
  switch (app.platform) {
18052
18068
  case "firebase": {
18053
18069
  if (!app.hasFunctions) {
18054
- log.error(`App "${app.name}" has Firebase config but no functions directory.`);
18070
+ log.error(
18071
+ `App "${app.name}" has Firebase config but no functions directory.`
18072
+ );
18055
18073
  log.error('Use "dndev dev" for apps without backend functions.\n');
18056
18074
  return 1;
18057
18075
  }
@@ -18518,7 +18536,11 @@ function getMatrixPath(mode) {
18518
18536
  const executionMode = mode || detectExecutionMode();
18519
18537
  if (executionMode === "development") {
18520
18538
  const templatesRoot = getTemplatesRoot();
18521
- const devPath = normalizePath(templatesRoot, "..", "dependencies-matrix.json");
18539
+ const devPath = normalizePath(
18540
+ templatesRoot,
18541
+ "..",
18542
+ "dependencies-matrix.json"
18543
+ );
18522
18544
  if (pathExists(devPath)) {
18523
18545
  return devPath;
18524
18546
  }
@@ -18595,34 +18617,67 @@ var APP_QUESTIONNAIRE = [
18595
18617
  ]
18596
18618
  },
18597
18619
  {
18598
- id: "needsBackend",
18599
- question: "Include backend functions?",
18600
- type: "confirm",
18601
- defaultValue: false
18620
+ id: "host",
18621
+ question: "Where will you host?",
18622
+ type: "select",
18623
+ condition: (answers) => answers.framework !== "expo",
18624
+ defaultValue: (answers) => {
18625
+ return answers.framework === "nextjs" ? "vercel" : "none";
18626
+ },
18627
+ choices: [
18628
+ { title: "None \u2014 no hosting config", value: "none" },
18629
+ { title: "Vercel \u2014 edge CDN + serverless", value: "vercel" },
18630
+ { title: "Firebase Hosting \u2014 Google CDN", value: "firebase" }
18631
+ ]
18632
+ },
18633
+ {
18634
+ id: "functions",
18635
+ question: "Serverless functions?",
18636
+ type: "select",
18637
+ defaultValue: "none",
18638
+ // Choices filtered dynamically based on host
18639
+ choices: [
18640
+ { title: "None", value: "none" },
18641
+ { title: "Vercel Functions \u2014 edge + node.js", value: "vercel" },
18642
+ { title: "Firebase Functions \u2014 Google Cloud", value: "firebase" },
18643
+ { title: "Supabase Edge Functions \u2014 Deno", value: "supabase" }
18644
+ ],
18645
+ choicesFilter: (answers) => {
18646
+ const host = answers.host ?? "none";
18647
+ const framework = answers.framework;
18648
+ if (framework === "expo") return ["none", "firebase", "supabase"];
18649
+ if (host === "firebase") return ["none", "firebase"];
18650
+ if (host === "vercel") return ["none", "vercel", "supabase"];
18651
+ return ["none", "vercel", "firebase", "supabase"];
18652
+ }
18602
18653
  },
18603
18654
  {
18604
- id: "backendPlatform",
18605
- question: "Which backend platform?",
18655
+ id: "backend",
18656
+ question: "Backend / BaaS provider?",
18606
18657
  type: "select",
18607
- dependsOn: "needsBackend",
18608
- condition: (answers) => answers.needsBackend === true,
18609
18658
  defaultValue: (answers) => {
18610
- return answers.framework === "nextjs" ? "vercel" : "firebase";
18659
+ if (answers.functions === "firebase") return "firebase";
18660
+ if (answers.functions === "supabase") return "supabase";
18661
+ return "none";
18611
18662
  },
18612
18663
  choices: [
18664
+ { title: "None \u2014 frontend only", value: "none" },
18613
18665
  {
18614
- title: "Firebase \u2014 Full-stack platform (hosting + functions + auth + firestore)",
18666
+ title: "Firebase \u2014 Auth + Firestore + Storage",
18615
18667
  value: "firebase"
18616
18668
  },
18617
- {
18618
- title: "Vercel \u2014 Serverless functions (edge + node.js)",
18619
- value: "vercel"
18620
- },
18621
18669
  {
18622
18670
  title: "Supabase \u2014 Postgres + Auth + Storage",
18623
18671
  value: "supabase"
18624
18672
  }
18625
- ]
18673
+ ],
18674
+ choicesFilter: (answers) => {
18675
+ const functions = answers.functions ?? "none";
18676
+ if (functions === "firebase") return ["none", "firebase"];
18677
+ if (functions === "supabase") return ["none", "supabase"];
18678
+ if (functions === "vercel") return ["none"];
18679
+ return ["none", "firebase", "supabase"];
18680
+ }
18626
18681
  }
18627
18682
  ];
18628
18683
  var PROJECT_QUESTIONNAIRE = [
@@ -18640,12 +18695,13 @@ var PROJECT_QUESTIONNAIRE = [
18640
18695
  }
18641
18696
  ];
18642
18697
  function aggregateRequirements(answers) {
18698
+ const hasBackend = answers.backend && answers.backend !== "none" || answers.functions && answers.functions !== "none";
18643
18699
  const requirements = {
18644
18700
  entities: [],
18645
18701
  // Entities created in apps/<app>/entities/ (v1), root entities/ for v2
18646
18702
  billing: true,
18647
18703
  // Always on (useStripeBillingSafe handles missing package)
18648
- backend: answers.needsBackend || false,
18704
+ backend: hasBackend,
18649
18705
  features: [],
18650
18706
  template: "vite",
18651
18707
  // Only Vite for now
@@ -18669,6 +18725,10 @@ async function runQuestionnaire(questions, initialAnswers, showWIP, askFor) {
18669
18725
  if (!showWIP) {
18670
18726
  choices = choices.filter((choice) => !choice.wip);
18671
18727
  }
18728
+ if (question.choicesFilter) {
18729
+ const allowed = question.choicesFilter(answers);
18730
+ choices = choices.filter((c) => allowed.includes(c.value));
18731
+ }
18672
18732
  if (choices.length === 0) continue;
18673
18733
  answer = await askFor.selection(question.question, choices);
18674
18734
  break;
@@ -18689,7 +18749,12 @@ async function runQuestionnaire(questions, initialAnswers, showWIP, askFor) {
18689
18749
  async function deployFrontend(appDir, serviceAccountPath, projectId, config) {
18690
18750
  const s = Y2();
18691
18751
  s.start("Deploying frontend to Firebase Hosting...");
18692
- const args = buildFirebaseDeployArgs("hosting", projectId, config.debug, config.force ?? true);
18752
+ const args = buildFirebaseDeployArgs(
18753
+ "hosting",
18754
+ projectId,
18755
+ config.debug,
18756
+ config.force ?? true
18757
+ );
18693
18758
  const result = executeFirebaseCommand(args, {
18694
18759
  cwd: appDir,
18695
18760
  serviceAccountPath,
@@ -18736,7 +18801,9 @@ async function deployVercelFrontend(appDir, _config) {
18736
18801
  if (result.status !== 0) {
18737
18802
  s.stop("Vercel deployment failed");
18738
18803
  const errOutput = result.stderr?.trim();
18739
- throw new Error(errOutput || `Vercel deploy exited with code ${result.status}`);
18804
+ throw new Error(
18805
+ errOutput || `Vercel deploy exited with code ${result.status}`
18806
+ );
18740
18807
  }
18741
18808
  s.stop("Frontend deployed to Vercel");
18742
18809
  } catch (err) {
@@ -19193,7 +19260,12 @@ async function deployRules(appDir, serviceAccountPath, projectId, config, option
19193
19260
  const targetNames = targets.join(", ");
19194
19261
  const s = Y2();
19195
19262
  s.start(`Deploying ${targetNames}...`);
19196
- const args = buildFirebaseDeployArgs(targets, projectId, config.debug, config.force ?? true);
19263
+ const args = buildFirebaseDeployArgs(
19264
+ targets,
19265
+ projectId,
19266
+ config.debug,
19267
+ config.force ?? true
19268
+ );
19197
19269
  const result = executeFirebaseCommand(args, {
19198
19270
  cwd: appDir,
19199
19271
  serviceAccountPath,
@@ -19567,13 +19639,17 @@ async function main6(options = {}) {
19567
19639
  const availableApps = detectAvailableApps();
19568
19640
  if (availableApps.length === 0) {
19569
19641
  if (providerInfo.hasSupabase && !providerInfo.hasFirebase) {
19570
- log.info("Supabase project detected. Deploying Supabase resources...");
19642
+ log.info(
19643
+ "Supabase project detected. Deploying Supabase resources..."
19644
+ );
19571
19645
  } else {
19572
19646
  log.info("No apps with firebase.json or supabase/ directory found.");
19573
19647
  log.info(
19574
19648
  "To deploy, ensure your app has a firebase.json or supabase/ configuration."
19575
19649
  );
19576
- log.info("Run from a DoNotDev project, or create one with: dndev init");
19650
+ log.info(
19651
+ "Run from a DoNotDev project, or create one with: dndev init"
19652
+ );
19577
19653
  return;
19578
19654
  }
19579
19655
  }
@@ -19853,12 +19929,7 @@ async function main6(options = {}) {
19853
19929
  }
19854
19930
  }
19855
19931
  }
19856
- await deployFunctions(
19857
- appDir,
19858
- serviceAccountPath,
19859
- config.project,
19860
- config
19861
- );
19932
+ await deployFunctions(appDir, serviceAccountPath, config.project, config);
19862
19933
  }
19863
19934
  if (shouldDeployFirebaseRules && serviceAccountPath && config.project && appProviderInfo.firebaseConfig) {
19864
19935
  await deployRules(appDir, serviceAccountPath, config.project, config, {
@@ -20081,34 +20152,22 @@ init_pathResolver();
20081
20152
 
20082
20153
  // packages/tooling/src/scaffolding/scaffold-matrix.ts
20083
20154
  init_utils();
20084
- var MATRIX = [
20085
- { builder: "vite", backend: "none", baseTemplate: "app-vite", overlay: null, deployConfig: null, functionsTemplate: null },
20086
- { builder: "vite", backend: "firebase", baseTemplate: "app-vite", overlay: "overlay-firebase", deployConfig: "firebase", functionsTemplate: "functions-firebase" },
20087
- { builder: "vite", backend: "supabase", baseTemplate: "app-vite", overlay: "overlay-supabase", deployConfig: "vercel-supabase", functionsTemplate: "functions-supabase" },
20088
- { builder: "vite", backend: "vercel", baseTemplate: "app-vite", overlay: "overlay-vercel", deployConfig: "vercel-vercel", functionsTemplate: "functions-vercel" },
20089
- { builder: "nextjs", backend: "none", baseTemplate: "app-next", overlay: null, deployConfig: null, functionsTemplate: null },
20090
- { builder: "nextjs", backend: "firebase", baseTemplate: "app-next", overlay: "overlay-firebase", deployConfig: "firebase", functionsTemplate: "functions-firebase" },
20091
- { builder: "nextjs", backend: "supabase", baseTemplate: "app-next", overlay: "overlay-supabase", deployConfig: "vercel-supabase", functionsTemplate: "functions-supabase" },
20092
- { builder: "nextjs", backend: "vercel", baseTemplate: "app-next", overlay: "overlay-vercel", deployConfig: "vercel-vercel", functionsTemplate: "functions-vercel" },
20093
- { builder: "expo", backend: "none", baseTemplate: "app-expo", overlay: null, deployConfig: null, functionsTemplate: null },
20094
- { builder: "expo", backend: "firebase", baseTemplate: "app-expo", overlay: "overlay-firebase", deployConfig: null, functionsTemplate: "functions-firebase" },
20095
- { builder: "expo", backend: "supabase", baseTemplate: "app-expo", overlay: "overlay-supabase", deployConfig: null, functionsTemplate: "functions-supabase" },
20096
- { builder: "demo", backend: "none", baseTemplate: "app-demo", overlay: null, deployConfig: null, functionsTemplate: null }
20097
- ];
20098
- function comboKey(builder, backend) {
20099
- return `${builder}-${backend}`;
20100
- }
20101
- var ROWS_BY_KEY = /* @__PURE__ */ new Map();
20102
- for (const row of MATRIX) {
20103
- ROWS_BY_KEY.set(comboKey(row.builder, row.backend), row);
20155
+ function resolveDeployConfig(host, backend) {
20156
+ if (host === "none") return null;
20157
+ if (host === "firebase") return "firebase";
20158
+ if (backend === "supabase") return "vercel-supabase";
20159
+ return "vercel-vercel";
20104
20160
  }
20105
- function getScaffoldRow(builder, backend) {
20106
- const key = comboKey(builder, backend);
20107
- const row = ROWS_BY_KEY.get(key);
20108
- if (!row) {
20109
- throw new Error(`Unsupported scaffold combo: ${key}. Supported: ${[...ROWS_BY_KEY.keys()].join(", ")}`);
20110
- }
20111
- return row;
20161
+ function getScaffoldParts(builder, host, functions, backend) {
20162
+ const baseTemplate = builder === "nextjs" ? "app-next" : builder === "demo" ? "app-demo" : `app-${builder}`;
20163
+ return {
20164
+ builder,
20165
+ backend,
20166
+ baseTemplate,
20167
+ overlay: backend !== "none" ? `overlay-${backend}` : null,
20168
+ deployConfig: resolveDeployConfig(host, backend),
20169
+ functionsTemplate: functions !== "none" ? `functions-${functions}` : null
20170
+ };
20112
20171
  }
20113
20172
 
20114
20173
  // packages/tooling/src/scaffolding/create-app.ts
@@ -20148,11 +20207,15 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20148
20207
  input: askForInput
20149
20208
  });
20150
20209
  const requirements = aggregateRequirements(answers);
20151
- const defaultPlatform = answers.framework === "nextjs" ? "vercel" : "firebase";
20210
+ const framework = answers.framework || "vite";
20211
+ const host2 = answers.host || "none";
20212
+ const functions2 = answers.functions || "none";
20213
+ const backend2 = answers.backend || "none";
20152
20214
  appConfig = {
20153
- template: answers.framework || "vite",
20154
- needsBackend: answers.needsBackend || false,
20155
- backendPlatform: answers.needsBackend ? answers.backendPlatform || defaultPlatform : void 0,
20215
+ template: framework,
20216
+ host: host2,
20217
+ functions: functions2,
20218
+ backend: backend2,
20156
20219
  needsCRUD: true,
20157
20220
  // Always on
20158
20221
  selectedEntities: [],
@@ -20198,12 +20261,14 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20198
20261
  s.start(`Creating app: ${appName}`);
20199
20262
  await ensureDir(appDir);
20200
20263
  const builder = appTemplate;
20201
- const backend = appConfig.needsBackend && appConfig.backendPlatform ? appConfig.backendPlatform : "none";
20202
- const row = getScaffoldRow(builder, backend);
20264
+ const host = appConfig.host ?? "none";
20265
+ const functions = appConfig.functions ?? "none";
20266
+ const backend = appConfig.backend ?? "none";
20267
+ const row = getScaffoldParts(builder, host, functions, backend);
20203
20268
  const templateDir = row.baseTemplate;
20204
20269
  const firebaseProjectId = (appConfig?.firebaseProjectId ?? "").trim() || appName.toLowerCase().replace(/\s+/g, "-");
20205
20270
  const firebaseRegion = appConfig?.firebaseRegion ?? "europe-west1";
20206
- const backendPlatform = appConfig.backendPlatform ?? "firebase";
20271
+ const backendPlatform = backend !== "none" ? backend : "firebase";
20207
20272
  const deployConfig = row.deployConfig;
20208
20273
  const replacements = {
20209
20274
  projectName: appName,
@@ -20262,13 +20327,16 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20262
20327
  const variantFile = `src/config/providers.${appTemplate}.ts.example`;
20263
20328
  if (overlayFiles.includes(variantFile)) continue;
20264
20329
  }
20265
- const providersVariant = file.match(/^src\/config\/providers\.(\w+)\.ts\.example$/);
20330
+ const providersVariant = file.match(
20331
+ /^src\/config\/providers\.(\w+)\.ts\.example$/
20332
+ );
20266
20333
  if (providersVariant) {
20267
20334
  if (providersVariant[1] !== appTemplate) continue;
20268
20335
  const destPath2 = joinPath(appDir, "src/config/providers.ts");
20269
20336
  await ensureDir(getDirname(destPath2));
20270
20337
  await copy(joinPath(overlayDir, file), destPath2, { overwrite: true });
20271
- if (await isTextFile(destPath2)) await replacePlaceholders(destPath2, replacements);
20338
+ if (await isTextFile(destPath2))
20339
+ await replacePlaceholders(destPath2, replacements);
20272
20340
  continue;
20273
20341
  }
20274
20342
  const sourcePath = joinPath(overlayDir, file);
@@ -20293,16 +20361,28 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20293
20361
  }
20294
20362
  if (deployConfig === "vercel-supabase") {
20295
20363
  const vercelPath = joinPath(appDir, "vercel.json");
20296
- const headersFragmentPath = joinPath(overlayDir, "vercel.headers.example");
20364
+ const headersFragmentPath = joinPath(
20365
+ overlayDir,
20366
+ "vercel.headers.example"
20367
+ );
20297
20368
  const fullVercelPath = joinPath(overlayDir, "vercel.json.example");
20298
20369
  if (pathExists(vercelPath) && pathExists(headersFragmentPath)) {
20299
20370
  const vercelJson = readSync(vercelPath, { format: "json" });
20300
- const headersFragment = readSync(headersFragmentPath, { format: "json" });
20301
- vercelJson.headers = [...vercelJson.headers ?? [], ...headersFragment];
20302
- await write(vercelPath, vercelJson, { format: "json", overwrite: true });
20371
+ const headersFragment = readSync(headersFragmentPath, {
20372
+ format: "json"
20373
+ });
20374
+ vercelJson.headers = [
20375
+ ...vercelJson.headers ?? [],
20376
+ ...headersFragment
20377
+ ];
20378
+ await write(vercelPath, vercelJson, {
20379
+ format: "json",
20380
+ overwrite: true
20381
+ });
20303
20382
  } else if (pathExists(fullVercelPath)) {
20304
20383
  await copy(fullVercelPath, vercelPath);
20305
- if (await isTextFile(vercelPath)) await replacePlaceholders(vercelPath, replacements);
20384
+ if (await isTextFile(vercelPath))
20385
+ await replacePlaceholders(vercelPath, replacements);
20306
20386
  }
20307
20387
  }
20308
20388
  }
@@ -20322,9 +20402,13 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20322
20402
  });
20323
20403
  if (row.functionsTemplate) {
20324
20404
  const functionsTemplateName = row.functionsTemplate;
20325
- const functionsTemplateExists = pathExists(joinPath(templatesRoot, functionsTemplateName));
20405
+ const functionsTemplateExists = pathExists(
20406
+ joinPath(templatesRoot, functionsTemplateName)
20407
+ );
20326
20408
  if (!functionsTemplateExists) {
20327
- log.warn(`Functions template "${functionsTemplateName}" not found \u2014 skipping functions scaffolding.`);
20409
+ log.warn(
20410
+ `Functions template "${functionsTemplateName}" not found \u2014 skipping functions scaffolding.`
20411
+ );
20328
20412
  } else {
20329
20413
  const isSupabaseFunctions = functionsTemplateName === "functions-supabase";
20330
20414
  const functionsRootDir = isSupabaseFunctions ? appDir : joinPath(appDir, "functions");
@@ -20332,7 +20416,10 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20332
20416
  const functionsPackageJson = generatePackageJson(
20333
20417
  functionsTemplateName,
20334
20418
  executionMode,
20335
- { appName, platform: row.functionsTemplate.replace("functions-", "") }
20419
+ {
20420
+ appName,
20421
+ platform: row.functionsTemplate.replace("functions-", "")
20422
+ }
20336
20423
  );
20337
20424
  const packageJsonPath2 = joinPath(functionsRootDir, "package.json");
20338
20425
  await ensureDir(functionsRootDir);
@@ -20368,13 +20455,19 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20368
20455
  }
20369
20456
  const deploymentTemplateDir = joinPath(templatesRoot, "root-consumer");
20370
20457
  if (deployConfig === "firebase") {
20371
- const firebaseJsonSource = joinPath(deploymentTemplateDir, "firebase.json.example");
20458
+ const firebaseJsonSource = joinPath(
20459
+ deploymentTemplateDir,
20460
+ "firebase.json.example"
20461
+ );
20372
20462
  if (pathExists(firebaseJsonSource)) {
20373
20463
  await copy(firebaseJsonSource, joinPath(appDir, "firebase.json"));
20374
20464
  const firebaseJsonDest = joinPath(appDir, "firebase.json");
20375
- if (await isTextFile(firebaseJsonDest)) await replacePlaceholders(firebaseJsonDest, replacements);
20465
+ if (await isTextFile(firebaseJsonDest))
20466
+ await replacePlaceholders(firebaseJsonDest, replacements);
20376
20467
  if (appTemplate === "nextjs") {
20377
- const firebaseJson = readSync(firebaseJsonDest, { format: "json" });
20468
+ const firebaseJson = readSync(firebaseJsonDest, {
20469
+ format: "json"
20470
+ });
20378
20471
  if (firebaseJson.hosting?.rewrites) {
20379
20472
  firebaseJson.hosting.rewrites = firebaseJson.hosting.rewrites.filter(
20380
20473
  (r2) => r2.destination !== "/index.html"
@@ -20383,17 +20476,28 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20383
20476
  if (firebaseJson.hosting) {
20384
20477
  firebaseJson.hosting.public = "out";
20385
20478
  }
20386
- await write(firebaseJsonDest, firebaseJson, { format: "json", overwrite: true });
20479
+ await write(firebaseJsonDest, firebaseJson, {
20480
+ format: "json",
20481
+ overwrite: true
20482
+ });
20387
20483
  }
20388
20484
  }
20389
- const firebasercSource = joinPath(deploymentTemplateDir, ".firebaserc.example");
20485
+ const firebasercSource = joinPath(
20486
+ deploymentTemplateDir,
20487
+ ".firebaserc.example"
20488
+ );
20390
20489
  if (pathExists(firebasercSource)) {
20391
20490
  await copy(firebasercSource, joinPath(appDir, ".firebaserc"));
20392
20491
  const firebasercDest = joinPath(appDir, ".firebaserc");
20393
- if (await isTextFile(firebasercDest)) await replacePlaceholders(firebasercDest, replacements);
20492
+ if (await isTextFile(firebasercDest))
20493
+ await replacePlaceholders(firebasercDest, replacements);
20394
20494
  }
20395
20495
  if (row.functionsTemplate === "functions-firebase") {
20396
- for (const example of ["firestore.rules.example", "firestore.indexes.json.example", "storage.rules.example"]) {
20496
+ for (const example of [
20497
+ "firestore.rules.example",
20498
+ "firestore.indexes.json.example",
20499
+ "storage.rules.example"
20500
+ ]) {
20397
20501
  const src = joinPath(deploymentTemplateDir, example);
20398
20502
  if (pathExists(src)) {
20399
20503
  await copy(src, joinPath(appDir, example.replace(".example", "")));
@@ -20409,11 +20513,22 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20409
20513
  {
20410
20514
  source: "functions",
20411
20515
  codebase: "default",
20412
- ignore: ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log", "**/.*", "**/*.test.ts", "**/__tests__/**"],
20516
+ ignore: [
20517
+ "node_modules",
20518
+ ".git",
20519
+ "firebase-debug.log",
20520
+ "firebase-debug.*.log",
20521
+ "**/.*",
20522
+ "**/*.test.ts",
20523
+ "**/__tests__/**"
20524
+ ],
20413
20525
  runtime: "nodejs22"
20414
20526
  }
20415
20527
  ],
20416
- firestore: { rules: "firestore.rules", indexes: "firestore.indexes.json" },
20528
+ firestore: {
20529
+ rules: "firestore.rules",
20530
+ indexes: "firestore.indexes.json"
20531
+ },
20417
20532
  storage: { rules: "storage.rules" },
20418
20533
  emulators: {
20419
20534
  auth: { port: 9099 },
@@ -20423,15 +20538,26 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20423
20538
  ui: { enabled: true, port: 4e3 }
20424
20539
  }
20425
20540
  };
20426
- await write(firebaseJsonPath, expoFirebaseJson, { format: "json", overwrite: true });
20541
+ await write(firebaseJsonPath, expoFirebaseJson, {
20542
+ format: "json",
20543
+ overwrite: true
20544
+ });
20427
20545
  }
20428
- const firebasercSource = joinPath(deploymentTemplateDir, ".firebaserc.example");
20546
+ const firebasercSource = joinPath(
20547
+ deploymentTemplateDir,
20548
+ ".firebaserc.example"
20549
+ );
20429
20550
  const firebasercDest = joinPath(appDir, ".firebaserc");
20430
20551
  if (pathExists(firebasercSource) && !pathExists(firebasercDest)) {
20431
20552
  await copy(firebasercSource, firebasercDest);
20432
- if (await isTextFile(firebasercDest)) await replacePlaceholders(firebasercDest, replacements);
20433
- }
20434
- for (const example of ["firestore.rules.example", "firestore.indexes.json.example", "storage.rules.example"]) {
20553
+ if (await isTextFile(firebasercDest))
20554
+ await replacePlaceholders(firebasercDest, replacements);
20555
+ }
20556
+ for (const example of [
20557
+ "firestore.rules.example",
20558
+ "firestore.indexes.json.example",
20559
+ "storage.rules.example"
20560
+ ]) {
20435
20561
  const src = joinPath(deploymentTemplateDir, example);
20436
20562
  const dest = joinPath(appDir, example.replace(".example", ""));
20437
20563
  if (pathExists(src) && !pathExists(dest)) {
@@ -20440,11 +20566,15 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
20440
20566
  }
20441
20567
  }
20442
20568
  if (deployConfig === "vercel-vercel") {
20443
- const vercelJsonSource = joinPath(deploymentTemplateDir, "vercel.json.example");
20569
+ const vercelJsonSource = joinPath(
20570
+ deploymentTemplateDir,
20571
+ "vercel.json.example"
20572
+ );
20444
20573
  if (pathExists(vercelJsonSource)) {
20445
20574
  await copy(vercelJsonSource, joinPath(appDir, "vercel.json"));
20446
20575
  const vercelJsonDest = joinPath(appDir, "vercel.json");
20447
- if (await isTextFile(vercelJsonDest)) await replacePlaceholders(vercelJsonDest, replacements);
20576
+ if (await isTextFile(vercelJsonDest))
20577
+ await replacePlaceholders(vercelJsonDest, replacements);
20448
20578
  }
20449
20579
  }
20450
20580
  const backendInfo = row.functionsTemplate ? ` with ${row.functionsTemplate.replace("functions-", "")} functions` : "";
@@ -20476,11 +20606,13 @@ Happy coding!`,
20476
20606
  }
20477
20607
  async function main8(options) {
20478
20608
  try {
20479
- const hasExplicitFlags = options?.builder !== void 0 || options?.functions !== void 0;
20609
+ const hasExplicitFlags = options?.builder !== void 0 || options?.host !== void 0 || options?.functions !== void 0 || options?.backend !== void 0;
20480
20610
  if (hasExplicitFlags && options?.name) {
20481
20611
  const appName = options.name;
20482
20612
  const builder = options.builder || "vite";
20483
- const includeFunctions = options.functions ?? false;
20613
+ const hostOpt = options.host ?? "none";
20614
+ const functionsOpt = options.functions ?? "none";
20615
+ const backendOpt = options.backend ?? "none";
20484
20616
  if (!isValidFileName(appName)) {
20485
20617
  throw new Error(
20486
20618
  `Invalid app name "${appName}". Use only letters, numbers, dashes (-), and underscores (_).`
@@ -20493,8 +20625,9 @@ async function main8(options) {
20493
20625
  }
20494
20626
  const appConfig = {
20495
20627
  template: builder === "next" ? "nextjs" : builder === "expo" ? "expo" : "vite",
20496
- needsBackend: includeFunctions,
20497
- backendPlatform: includeFunctions ? "firebase" : void 0,
20628
+ host: hostOpt,
20629
+ functions: functionsOpt,
20630
+ backend: backendOpt,
20498
20631
  needsCRUD: true,
20499
20632
  selectedEntities: [],
20500
20633
  userAuth: "social",
@@ -20526,12 +20659,11 @@ async function collectAppConfig(appName) {
20526
20659
  input: askForInput
20527
20660
  });
20528
20661
  const framework = answers.framework || "vite";
20529
- const needsBackend = answers.needsBackend || false;
20530
- const defaultPlatform = framework === "nextjs" ? "vercel" : "firebase";
20531
20662
  return {
20532
20663
  template: framework,
20533
- needsBackend,
20534
- backendPlatform: needsBackend ? answers.backendPlatform || defaultPlatform : void 0,
20664
+ host: answers.host || "none",
20665
+ functions: answers.functions || "none",
20666
+ backend: answers.backend || "none",
20535
20667
  needsCRUD: false,
20536
20668
  selectedEntities: [],
20537
20669
  userAuth: "none",
@@ -20678,7 +20810,7 @@ async function main9(options) {
20678
20810
  Me(`Configuring "${trimmedName}"...`, "\u2699\uFE0F");
20679
20811
  const config = await collectAppConfig(trimmedName);
20680
20812
  appConfigs[trimmedName] = config;
20681
- if (config.needsBackend) anyAppNeedsBackend = true;
20813
+ if (config.backend !== "none" || config.functions !== "none") anyAppNeedsBackend = true;
20682
20814
  }
20683
20815
  let installDemoApp = await askForConfirmation(
20684
20816
  "Would you like to install the demo app? (component showcase)",
@@ -20754,8 +20886,9 @@ async function main9(options) {
20754
20886
  log.warn(`Missing config for app "${appName}", using defaults`);
20755
20887
  appConfigs[appName] = {
20756
20888
  template: "vite",
20757
- needsBackend: false,
20758
- backendPlatform: void 0,
20889
+ host: "none",
20890
+ functions: "none",
20891
+ backend: "none",
20759
20892
  needsCRUD: false,
20760
20893
  selectedEntities: [],
20761
20894
  userAuth: "none",
@@ -20803,7 +20936,7 @@ async function main9(options) {
20803
20936
  overwrite: true
20804
20937
  });
20805
20938
  const relativeMonorepoPath = executionMode === "development" ? calculateRelativePath(projectDirNormalized, monorepoRoot) : "";
20806
- const primaryPlatform = Object.values(appConfigs).find((c) => c.backendPlatform)?.backendPlatform ?? "firebase";
20939
+ const primaryPlatform = Object.values(appConfigs).find((c) => c.backend !== "none")?.backend ?? "firebase";
20807
20940
  const rootPackageJson = generatePackageJson(
20808
20941
  "consumer-root",
20809
20942
  executionMode,