@donotdev/cli 0.0.8 → 0.0.9

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.
@@ -8834,7 +8834,7 @@ function generatePackageJson(templateName, mode, options = {}) {
8834
8834
  "dependencies-matrix.json not found. This command requires the matrix file."
8835
8835
  );
8836
8836
  }
8837
- const { matrix, cliVersion } = matrixResult;
8837
+ const { matrix } = matrixResult;
8838
8838
  const template = matrix.templateMapping?.[templateName];
8839
8839
  if (!template) {
8840
8840
  throw new Error(`Template "${templateName}" not found in matrix`);
@@ -8898,6 +8898,7 @@ function generatePackageJson(templateName, mode, options = {}) {
8898
8898
  }
8899
8899
  }
8900
8900
  if (templateName.includes("functions")) {
8901
+ result.main = "lib/index.js";
8901
8902
  result.engines = { node: "20" };
8902
8903
  if (options.appName) {
8903
8904
  const platform = templateName.includes("vercel") ? "Vercel" : "Firebase";
@@ -9025,6 +9026,8 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
9025
9026
  s.start(`Creating app: ${appName}`);
9026
9027
  await ensureDir(appDir);
9027
9028
  const templateDir = appTemplate === "demo" ? "app-demo" : appTemplate === "nextjs" ? "app-next" : "app-vite";
9029
+ const firebaseProjectId = (appConfig?.firebaseProjectId ?? "").trim() || appName.toLowerCase().replace(/\s+/g, "-");
9030
+ const firebaseRegion = appConfig?.firebaseRegion ?? "europe-west1";
9028
9031
  const replacements = {
9029
9032
  projectName: appName,
9030
9033
  appName,
@@ -9033,11 +9036,14 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
9033
9036
  needsCRUD: Boolean(appConfig.needsCRUD),
9034
9037
  setupGithubActions: false,
9035
9038
  appNames: [appName],
9036
- firebaseProjectId: appName.toLowerCase(),
9039
+ firebaseProjectId,
9040
+ firebaseRegion,
9037
9041
  firebaseSecretName: appName.toUpperCase().replace(/-/g, "_"),
9038
9042
  monorepoRelativePath: "../../packages/tooling",
9039
9043
  appTemplate,
9040
- isNextjs: appTemplate === "nextjs"
9044
+ isNextjs: appTemplate === "nextjs",
9045
+ YOUR_FIREBASE_PROJECT_ID: firebaseProjectId,
9046
+ YOUR_REGION: firebaseRegion
9041
9047
  };
9042
9048
  const templateSourceDir = joinPath(templatesRoot, templateDir);
9043
9049
  const templateFiles = await glob("**/*", {
@@ -9130,6 +9136,32 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
9130
9136
  await replacePlaceholders(firebaseJsonDest, replacements);
9131
9137
  }
9132
9138
  }
9139
+ const firebasercSource = joinPath(
9140
+ deploymentTemplateDir,
9141
+ ".firebaserc.example"
9142
+ );
9143
+ if (pathExists(firebasercSource)) {
9144
+ const firebasercDest = joinPath(appDir, ".firebaserc");
9145
+ await copy(firebasercSource, firebasercDest);
9146
+ if (await isTextFile(firebasercDest)) {
9147
+ await replacePlaceholders(firebasercDest, replacements);
9148
+ }
9149
+ }
9150
+ if (appConfig.needsBackend && appConfig.backendPlatform === "firebase") {
9151
+ const rulesFiles = [
9152
+ "firestore.rules.example",
9153
+ "firestore.indexes.json.example",
9154
+ "storage.rules.example"
9155
+ ];
9156
+ for (const example of rulesFiles) {
9157
+ const src = joinPath(deploymentTemplateDir, example);
9158
+ if (pathExists(src)) {
9159
+ const destName = example.replace(".example", "");
9160
+ const dest = joinPath(appDir, destName);
9161
+ await copy(src, dest);
9162
+ }
9163
+ }
9164
+ }
9133
9165
  if (appTemplate === "nextjs" || appConfig.needsBackend && appConfig.backendPlatform === "vercel") {
9134
9166
  const vercelJsonSource = joinPath(
9135
9167
  deploymentTemplateDir,
@@ -9155,12 +9187,15 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
9155
9187
  }
9156
9188
  if (isInteractive) {
9157
9189
  Se("\u{1F389} App created successfully!");
9190
+ const firebaseStep = appConfig.needsBackend && appConfig.backendPlatform === "firebase" ? `2. Set Firebase project: cd apps/${appName} && firebase use --add (or edit .firebase rc)
9191
+ 3. bun install
9192
+ 4. bun run dev` : `2. bun install
9193
+ 3. bun run dev`;
9158
9194
  Me(
9159
9195
  `Next steps:
9160
9196
 
9161
9197
  1. cd apps/${appName}
9162
- 2. bun install
9163
- 3. bun run dev
9198
+ ${firebaseStep}
9164
9199
 
9165
9200
  Happy coding!`,
9166
9201
  "\u{1F4CB} Next Steps"
@@ -9496,6 +9531,8 @@ async function main(options) {
9496
9531
  overwrite: true
9497
9532
  });
9498
9533
  const rootTemplateDir = joinPath(templatesRoot, "root-consumer");
9534
+ const firebaseProjectId = projectName.toLowerCase().replace(/\s+/g, "-");
9535
+ const firebaseRegion = "europe-west1";
9499
9536
  const rootReplacements = {
9500
9537
  projectName,
9501
9538
  appNames: isMergeMode ? allAppNames : appNames,
@@ -9504,12 +9541,22 @@ async function main(options) {
9504
9541
  monorepoRelativePath: relativeMonorepoPath,
9505
9542
  appTemplate: "vite",
9506
9543
  isNextjs: false,
9507
- firebaseProjectId: projectName.toLowerCase(),
9544
+ firebaseProjectId,
9545
+ firebaseRegion,
9508
9546
  firebaseSecretName: projectName.toUpperCase().replace(/-/g, "_"),
9547
+ YOUR_FIREBASE_PROJECT_ID: firebaseProjectId,
9548
+ YOUR_REGION: firebaseRegion,
9509
9549
  needsAuth,
9510
9550
  needsOAuth,
9511
9551
  needsBilling
9512
9552
  };
9553
+ const firebaseRootFiles = /* @__PURE__ */ new Set([
9554
+ "firebase.json.example",
9555
+ ".firebaserc.example",
9556
+ "firestore.rules.example",
9557
+ "firestore.indexes.json.example",
9558
+ "storage.rules.example"
9559
+ ]);
9513
9560
  const files = await glob("**/*", {
9514
9561
  cwd: rootTemplateDir,
9515
9562
  dot: true,
@@ -9517,6 +9564,7 @@ async function main(options) {
9517
9564
  });
9518
9565
  for (const file of files) {
9519
9566
  if (file === "package.json.example") continue;
9567
+ if (firebaseRootFiles.has(file)) continue;
9520
9568
  const sourcePath = joinPath(rootTemplateDir, file);
9521
9569
  let destFileName = file;
9522
9570
  if (destFileName.endsWith(".example")) {
@@ -9591,6 +9639,8 @@ async function main(options) {
9591
9639
  }
9592
9640
  }
9593
9641
  if (installDemoApp) {
9642
+ const demoFirebaseProjectId = projectName.toLowerCase().replace(/\s+/g, "-");
9643
+ const demoFirebaseRegion = "europe-west1";
9594
9644
  await copyTemplateFiles(demoTemplateDir, demoAppDir, {
9595
9645
  projectName,
9596
9646
  appName: "demo",
@@ -9598,8 +9648,11 @@ async function main(options) {
9598
9648
  needsCRUD: false,
9599
9649
  setupGithubActions: false,
9600
9650
  appNames,
9601
- firebaseProjectId: projectName.toLowerCase(),
9651
+ firebaseProjectId: demoFirebaseProjectId,
9652
+ firebaseRegion: demoFirebaseRegion,
9602
9653
  firebaseSecretName: projectName.toUpperCase().replace(/-/g, "_"),
9654
+ YOUR_FIREBASE_PROJECT_ID: demoFirebaseProjectId,
9655
+ YOUR_REGION: demoFirebaseRegion,
9603
9656
  monorepoRelativePath: executionMode === "development" ? calculateRelativePath(projectDirNormalized, monorepoRoot) : "",
9604
9657
  appTemplate: "demo"
9605
9658
  });
@@ -7960,8 +7960,10 @@ function executeFirebaseCommand(args, options) {
7960
7960
  errorOutput
7961
7961
  };
7962
7962
  }
7963
- function buildFirebaseDeployArgs(deployType, projectId, debug, force) {
7964
- const args = ["deploy", "--only", deployType, "--non-interactive"];
7963
+ function buildFirebaseDeployArgs(deployTargets, projectId, debug, force) {
7964
+ const targets = Array.isArray(deployTargets) ? deployTargets : [deployTargets];
7965
+ const targetString = targets.join(",");
7966
+ const args = ["deploy", "--only", targetString, "--non-interactive"];
7965
7967
  if (projectId) {
7966
7968
  args.push("--project", projectId);
7967
7969
  }
@@ -8523,6 +8525,47 @@ To fix this, run:
8523
8525
  }
8524
8526
  }
8525
8527
 
8528
+ // packages/tooling/src/apps/deploy-rules.ts
8529
+ init_utils();
8530
+ async function deployRules(appDir, serviceAccountPath, projectId, config, options) {
8531
+ const targets = [];
8532
+ if (options.firestore) {
8533
+ targets.push("firestore:rules");
8534
+ }
8535
+ if (options.firestoreIndexes) {
8536
+ targets.push("firestore:indexes");
8537
+ }
8538
+ if (options.storage) {
8539
+ targets.push("storage");
8540
+ }
8541
+ if (targets.length === 0) {
8542
+ return;
8543
+ }
8544
+ const targetNames = targets.join(", ");
8545
+ const s = Y2();
8546
+ s.start(`Deploying ${targetNames}...`);
8547
+ const args = buildFirebaseDeployArgs(targets, projectId, config.debug);
8548
+ const result = executeFirebaseCommand(args, {
8549
+ cwd: appDir,
8550
+ serviceAccountPath,
8551
+ projectId,
8552
+ debug: config.debug
8553
+ });
8554
+ if (result.error) {
8555
+ s.stop("Rules deployment failed");
8556
+ throw result.error;
8557
+ }
8558
+ if (!result.success) {
8559
+ s.stop("Rules deployment failed");
8560
+ handleDeploymentFailure(
8561
+ result,
8562
+ `firebase ${args.join(" ")}`,
8563
+ serviceAccountPath
8564
+ );
8565
+ }
8566
+ s.stop(`${targetNames} deployed successfully`);
8567
+ }
8568
+
8526
8569
  // packages/tooling/src/apps/deploy-utils.ts
8527
8570
  init_utils();
8528
8571
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -8643,6 +8686,9 @@ function validateFirebaseJson(appDir) {
8643
8686
  valid: false,
8644
8687
  hasHosting: false,
8645
8688
  hasFunctions: false,
8689
+ hasFirestoreRules: false,
8690
+ hasFirestoreIndexes: false,
8691
+ hasStorageRules: false,
8646
8692
  errors: [`firebase.json not found at: ${firebaseJsonPath}`]
8647
8693
  };
8648
8694
  }
@@ -8655,11 +8701,17 @@ function validateFirebaseJson(appDir) {
8655
8701
  }
8656
8702
  const hasHosting = !!config.hosting;
8657
8703
  const hasFunctions = !!config.functions && Array.isArray(config.functions);
8704
+ const hasFirestoreRules = !!config.firestore?.rules && pathExists(joinPath(appDir, config.firestore.rules));
8705
+ const hasFirestoreIndexes = !!config.firestore?.indexes && pathExists(joinPath(appDir, config.firestore.indexes));
8706
+ const hasStorageRules = !!config.storage?.rules && pathExists(joinPath(appDir, config.storage.rules));
8658
8707
  return {
8659
8708
  valid: true,
8660
8709
  projectId: config.projectId,
8661
8710
  hasHosting,
8662
8711
  hasFunctions,
8712
+ hasFirestoreRules,
8713
+ hasFirestoreIndexes,
8714
+ hasStorageRules,
8663
8715
  errors: []
8664
8716
  };
8665
8717
  } catch (error2) {
@@ -8667,6 +8719,9 @@ function validateFirebaseJson(appDir) {
8667
8719
  valid: false,
8668
8720
  hasHosting: false,
8669
8721
  hasFunctions: false,
8722
+ hasFirestoreRules: false,
8723
+ hasFirestoreIndexes: false,
8724
+ hasStorageRules: false,
8670
8725
  errors: [
8671
8726
  `Invalid JSON format: ${error2 instanceof DoNotDevError ? error2.message : error2 instanceof Error ? error2.message : String(error2)}`
8672
8727
  ]
@@ -8831,16 +8886,36 @@ async function main(options = {}) {
8831
8886
  hint: "Deploy cloud functions"
8832
8887
  });
8833
8888
  }
8889
+ const hasRules = firebaseConfig2.hasFirestoreRules || firebaseConfig2.hasFirestoreIndexes || firebaseConfig2.hasStorageRules;
8890
+ if (hasRules) {
8891
+ choices.push({
8892
+ title: "Rules only",
8893
+ value: "rules",
8894
+ hint: "Deploy Firestore/Storage rules"
8895
+ });
8896
+ }
8834
8897
  if (firebaseConfig2.hasHosting && firebaseConfig2.hasFunctions) {
8835
8898
  choices.push({
8836
- title: "Both frontend and functions",
8899
+ title: "Frontend + Functions",
8837
8900
  value: "both",
8838
- hint: "Deploy everything"
8901
+ hint: "Deploy hosting and functions"
8902
+ });
8903
+ }
8904
+ const deployableKindsCount = [
8905
+ firebaseConfig2.hasHosting,
8906
+ firebaseConfig2.hasFunctions,
8907
+ hasRules
8908
+ ].filter(Boolean).length;
8909
+ if (deployableKindsCount > 1) {
8910
+ choices.push({
8911
+ title: "All",
8912
+ value: "all",
8913
+ hint: "Deploy everything (hosting, functions, rules)"
8839
8914
  });
8840
8915
  }
8841
8916
  if (choices.length === 0) {
8842
8917
  log.error(
8843
- "No deployment targets found. firebase.json must have hosting or functions configuration."
8918
+ "No deployment targets found. firebase.json must have hosting, functions, or rules configuration."
8844
8919
  );
8845
8920
  process.exit(1);
8846
8921
  }
@@ -8885,13 +8960,28 @@ async function main(options = {}) {
8885
8960
  showFirebaseJsonError(firebaseConfig.errors, appDir, deploymentType);
8886
8961
  process.exit(1);
8887
8962
  }
8888
- if (deploymentType === "frontend" || deploymentType === "both") {
8889
- if (!firebaseConfig.hasHosting) {
8890
- log.error(
8891
- "firebase.json does not contain hosting configuration, but frontend deployment was requested."
8892
- );
8893
- process.exit(1);
8894
- }
8963
+ const shouldDeployFrontend = (deploymentType === "frontend" || deploymentType === "both" || deploymentType === "all") && firebaseConfig.hasHosting;
8964
+ const shouldDeployFunctions = (deploymentType === "functions" || deploymentType === "both" || deploymentType === "all") && firebaseConfig.hasFunctions;
8965
+ const shouldDeployRules = (deploymentType === "rules" || deploymentType === "all") && (firebaseConfig.hasFirestoreRules || firebaseConfig.hasFirestoreIndexes || firebaseConfig.hasStorageRules);
8966
+ if (deploymentType === "frontend" && !firebaseConfig.hasHosting) {
8967
+ log.error(
8968
+ "firebase.json does not contain hosting configuration, but frontend deployment was requested."
8969
+ );
8970
+ process.exit(1);
8971
+ }
8972
+ if (deploymentType === "functions" && !firebaseConfig.hasFunctions) {
8973
+ log.error(
8974
+ "firebase.json does not contain functions configuration, but functions deployment was requested."
8975
+ );
8976
+ process.exit(1);
8977
+ }
8978
+ if (deploymentType === "rules" && !shouldDeployRules) {
8979
+ log.error(
8980
+ "firebase.json does not contain rules configuration, but rules deployment was requested."
8981
+ );
8982
+ process.exit(1);
8983
+ }
8984
+ if (shouldDeployFrontend) {
8895
8985
  const buildStatus = validateBuild(appDir);
8896
8986
  let shouldBuild = false;
8897
8987
  if (!buildStatus.exists || buildStatus.isEmpty) {
@@ -8934,14 +9024,6 @@ async function main(options = {}) {
8934
9024
  }
8935
9025
  }
8936
9026
  }
8937
- if (deploymentType === "functions" || deploymentType === "both") {
8938
- if (!firebaseConfig.hasFunctions) {
8939
- log.error(
8940
- "firebase.json does not contain functions configuration, but functions deployment was requested."
8941
- );
8942
- process.exit(1);
8943
- }
8944
- }
8945
9027
  clearFirebaseCache(appDir);
8946
9028
  Me(
8947
9029
  `App: ${appName}
@@ -8950,10 +9032,10 @@ Deployment: ${deploymentType}
8950
9032
  Service Account: ${serviceAccountResult.info.clientEmail}`,
8951
9033
  "Deployment Configuration"
8952
9034
  );
8953
- if (deploymentType === "frontend" || deploymentType === "both") {
9035
+ if (shouldDeployFrontend) {
8954
9036
  await deployFrontend(appDir, serviceAccountPath, config.project, config);
8955
9037
  }
8956
- if (deploymentType === "functions" || deploymentType === "both") {
9038
+ if (shouldDeployFunctions) {
8957
9039
  await deployFunctions(
8958
9040
  appDir,
8959
9041
  serviceAccountPath,
@@ -8961,6 +9043,13 @@ Service Account: ${serviceAccountResult.info.clientEmail}`,
8961
9043
  config
8962
9044
  );
8963
9045
  }
9046
+ if (shouldDeployRules) {
9047
+ await deployRules(appDir, serviceAccountPath, config.project, config, {
9048
+ firestore: firebaseConfig.hasFirestoreRules,
9049
+ firestoreIndexes: firebaseConfig.hasFirestoreIndexes,
9050
+ storage: firebaseConfig.hasStorageRules
9051
+ });
9052
+ }
8964
9053
  Se("Deployment completed successfully!");
8965
9054
  } catch (error2) {
8966
9055
  if (error2 instanceof DoNotDevError) {
package/dist/bin/dndev.js CHANGED
@@ -46,7 +46,7 @@ Usage: dndev <command>[:<app>] [options]
46
46
 
47
47
  Commands:
48
48
  init, create-project Create a new DoNotDev project
49
- create-app [name] Add app to existing project (--builder vite|next, --functions)
49
+ create-app [name] Add app (--builder vite|next, --functions, --project <id>)
50
50
  dev [app] Start development server
51
51
  build [app] Build for production
52
52
  preview [app] Preview production build
@@ -66,7 +66,8 @@ Examples:
66
66
  dndev init my-project Create a new project
67
67
  dndev create-app Interactive app creation
68
68
  dndev create-app my-app Create 'my-app' with defaults (vite, no functions)
69
- dndev create-app my-app --builder next --functions Create Next.js app with functions
69
+ dndev create-app my-app --builder next --functions Next.js + functions
70
+ dndev create-app my-app --functions --project my-fb-id Set Firebase project (2-min setup)
70
71
  dndev dev Start dev server
71
72
  dndev dev:web Start dev server for 'web' app
72
73
  dndev wai Output WAI-WAY activation prompt
@@ -90,14 +91,16 @@ program.command("init [name]").alias("create-project").description("Create a new
90
91
  const { main } = await import("./commands/create-project.js");
91
92
  await main({ projectName: name });
92
93
  });
93
- program.command("create-app [name]").description("Add a new app to existing project").option("--name <name>", "App name (non-interactive)").option("--builder <builder>", "Framework: vite or next (default: vite)").option("--functions", "Include Firebase functions").action(async (name, options) => {
94
+ program.command("create-app [name]").description("Add a new app to existing project").option("--name <name>", "App name (non-interactive)").option("--builder <builder>", "Framework: vite or next (default: vite)").option("--functions", "Include Firebase functions").option("--project <id>", "Firebase project ID (default: app name)").option("--region <region>", "Firebase region (default: europe-west1)").action(async (name, options) => {
94
95
  const { main } = await import("./commands/create-app.js");
95
96
  const appName = name || options.name;
96
97
  if (appName) {
97
98
  await main({
98
99
  name: appName,
99
100
  builder: options.builder,
100
- functions: options.functions
101
+ functions: options.functions,
102
+ firebaseProjectId: options.project,
103
+ firebaseRegion: options.region
101
104
  });
102
105
  } else {
103
106
  await main();
@@ -46,7 +46,7 @@ Usage: dndev <command>[:<app>] [options]
46
46
 
47
47
  Commands:
48
48
  init, create-project Create a new DoNotDev project
49
- create-app [name] Add app to existing project (--builder vite|next, --functions)
49
+ create-app [name] Add app (--builder vite|next, --functions, --project <id>)
50
50
  dev [app] Start development server
51
51
  build [app] Build for production
52
52
  preview [app] Preview production build
@@ -66,7 +66,8 @@ Examples:
66
66
  dndev init my-project Create a new project
67
67
  dndev create-app Interactive app creation
68
68
  dndev create-app my-app Create 'my-app' with defaults (vite, no functions)
69
- dndev create-app my-app --builder next --functions Create Next.js app with functions
69
+ dndev create-app my-app --builder next --functions Next.js + functions
70
+ dndev create-app my-app --functions --project my-fb-id Set Firebase project (2-min setup)
70
71
  dndev dev Start dev server
71
72
  dndev dev:web Start dev server for 'web' app
72
73
  dndev wai Output WAI-WAY activation prompt
@@ -90,14 +91,16 @@ program.command("init [name]").alias("create-project").description("Create a new
90
91
  const { main } = await import("./commands/create-project.js");
91
92
  await main({ projectName: name });
92
93
  });
93
- program.command("create-app [name]").description("Add a new app to existing project").option("--name <name>", "App name (non-interactive)").option("--builder <builder>", "Framework: vite or next (default: vite)").option("--functions", "Include Firebase functions").action(async (name, options) => {
94
+ program.command("create-app [name]").description("Add a new app to existing project").option("--name <name>", "App name (non-interactive)").option("--builder <builder>", "Framework: vite or next (default: vite)").option("--functions", "Include Firebase functions").option("--project <id>", "Firebase project ID (default: app name)").option("--region <region>", "Firebase region (default: europe-west1)").action(async (name, options) => {
94
95
  const { main } = await import("./commands/create-app.js");
95
96
  const appName = name || options.name;
96
97
  if (appName) {
97
98
  await main({
98
99
  name: appName,
99
100
  builder: options.builder,
100
- functions: options.functions
101
+ functions: options.functions,
102
+ firebaseProjectId: options.project,
103
+ firebaseRegion: options.region
101
104
  });
102
105
  } else {
103
106
  await main();