@donotdev/cli 0.0.8 → 0.0.11

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 (47) hide show
  1. package/dependencies-matrix.json +177 -76
  2. package/dist/bin/commands/build.js +2 -2
  3. package/dist/bin/commands/bump.js +578 -94
  4. package/dist/bin/commands/cacheout.js +2 -2
  5. package/dist/bin/commands/create-app.js +46 -9
  6. package/dist/bin/commands/create-project.js +63 -10
  7. package/dist/bin/commands/deploy.js +114 -25
  8. package/dist/bin/commands/dev.js +2 -2
  9. package/dist/bin/commands/emu.js +2 -2
  10. package/dist/bin/commands/format.js +2 -2
  11. package/dist/bin/commands/lint.js +2 -2
  12. package/dist/bin/commands/preview.js +2 -2
  13. package/dist/bin/commands/sync-secrets.js +2 -2
  14. package/dist/bin/dndev.js +7 -4
  15. package/dist/bin/donotdev.js +7 -4
  16. package/dist/index.js +177 -33
  17. package/package.json +5 -4
  18. package/templates/app-next/src/config/app.ts.example +1 -1
  19. package/templates/app-vite/index.html.example +24 -2
  20. package/templates/app-vite/src/config/app.ts.example +1 -1
  21. package/templates/app-vite/src/pages/FormPageExample.tsx.example +8 -5
  22. package/templates/app-vite/src/pages/ListPageExample.tsx.example +4 -7
  23. package/templates/root-consumer/.claude/agents/architect.md.example +313 -0
  24. package/templates/root-consumer/.claude/agents/builder.md.example +329 -0
  25. package/templates/root-consumer/.claude/agents/coder.md.example +87 -0
  26. package/templates/root-consumer/.claude/agents/extractor.md.example +235 -0
  27. package/templates/root-consumer/.claude/agents/polisher.md.example +359 -0
  28. package/templates/root-consumer/.claude/agents/prompt-engineer.md.example +85 -0
  29. package/templates/root-consumer/.claude/commands/brainstorm.md.example +133 -0
  30. package/templates/root-consumer/.claude/commands/build.md.example +109 -0
  31. package/templates/root-consumer/.claude/commands/design.md.example +136 -0
  32. package/templates/root-consumer/.claude/commands/polish.md.example +145 -0
  33. package/templates/root-consumer/.cursor/mcp.json.example +8 -0
  34. package/templates/root-consumer/.firebaserc.example +5 -0
  35. package/templates/root-consumer/.mcp.json.example +8 -0
  36. package/templates/root-consumer/CLAUDE.md.example +146 -0
  37. package/templates/root-consumer/entities/ExampleEntity.ts.example +2 -1
  38. package/templates/root-consumer/entities/demo.ts.example +1 -1
  39. package/templates/root-consumer/firestore.indexes.json.example +4 -0
  40. package/templates/root-consumer/firestore.rules.example +11 -0
  41. package/templates/root-consumer/guides/dndev/AGENT_START_HERE.md.example +15 -12
  42. package/templates/root-consumer/guides/dndev/COMPONENTS_CRUD.md.example +9 -6
  43. package/templates/root-consumer/guides/dndev/COMPONENT_API.md.example +195 -0
  44. package/templates/root-consumer/guides/dndev/INDEX.md.example +3 -1
  45. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +485 -57
  46. package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +1 -1
  47. package/templates/root-consumer/storage.rules.example +8 -0
@@ -163,7 +163,7 @@ var require_picocolors = __commonJS({
163
163
  }
164
164
  });
165
165
 
166
- // node_modules/.bun/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
166
+ // node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/node_modules/@clack/core/dist/index.mjs
167
167
  import { stdin as j, stdout as M } from "node:process";
168
168
  import O from "node:readline";
169
169
  import { Writable as X } from "node:stream";
@@ -257,7 +257,7 @@ function m(e2, u2) {
257
257
  }
258
258
  var import_sisteransi, 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, A, OD, PD, J, LD;
259
259
  var init_dist = __esm({
260
- "node_modules/.bun/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs"() {
260
+ "node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/node_modules/@clack/core/dist/index.mjs"() {
261
261
  init_utils();
262
262
  import_sisteransi = __toESM(require_src(), 1);
263
263
  uD = DD();
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();
package/dist/index.js CHANGED
@@ -167,7 +167,7 @@ var require_picocolors = __commonJS({
167
167
  }
168
168
  });
169
169
 
170
- // node_modules/.bun/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs
170
+ // node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/node_modules/@clack/core/dist/index.mjs
171
171
  import { stdin as j, stdout as M } from "node:process";
172
172
  import * as g from "node:readline";
173
173
  import O from "node:readline";
@@ -283,7 +283,7 @@ function fD({ input: e2 = j, output: u2 = M, overwrite: t = true, hideCursor: F2
283
283
  }
284
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, OD, PD, J, LD, RD;
285
285
  var init_dist = __esm({
286
- "node_modules/.bun/@clack+core@0.5.0/node_modules/@clack/core/dist/index.mjs"() {
286
+ "node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/node_modules/@clack/core/dist/index.mjs"() {
287
287
  init_utils();
288
288
  import_sisteransi = __toESM(require_src(), 1);
289
289
  import_picocolors = __toESM(require_picocolors(), 1);
@@ -8468,7 +8468,7 @@ async function askForInput(message, defaultValue = "") {
8468
8468
  const result = await he({
8469
8469
  message,
8470
8470
  placeholder: defaultValue || void 0,
8471
- defaultValue: defaultValue || void 0
8471
+ initialValue: defaultValue || void 0
8472
8472
  });
8473
8473
  if (pD(result)) {
8474
8474
  xe("Operation cancelled.");
@@ -8803,8 +8803,10 @@ function executeFirebaseCommand(args, options) {
8803
8803
  errorOutput
8804
8804
  };
8805
8805
  }
8806
- function buildFirebaseDeployArgs(deployType, projectId, debug, force) {
8807
- const args = ["deploy", "--only", deployType, "--non-interactive"];
8806
+ function buildFirebaseDeployArgs(deployTargets, projectId, debug, force) {
8807
+ const targets = Array.isArray(deployTargets) ? deployTargets : [deployTargets];
8808
+ const targetString = targets.join(",");
8809
+ const args = ["deploy", "--only", targetString, "--non-interactive"];
8808
8810
  if (projectId) {
8809
8811
  args.push("--project", projectId);
8810
8812
  }
@@ -10212,6 +10214,47 @@ To fix this, run:
10212
10214
  }
10213
10215
  }
10214
10216
 
10217
+ // packages/tooling/src/apps/deploy-rules.ts
10218
+ init_utils();
10219
+ async function deployRules(appDir, serviceAccountPath, projectId, config, options) {
10220
+ const targets = [];
10221
+ if (options.firestore) {
10222
+ targets.push("firestore:rules");
10223
+ }
10224
+ if (options.firestoreIndexes) {
10225
+ targets.push("firestore:indexes");
10226
+ }
10227
+ if (options.storage) {
10228
+ targets.push("storage");
10229
+ }
10230
+ if (targets.length === 0) {
10231
+ return;
10232
+ }
10233
+ const targetNames = targets.join(", ");
10234
+ const s = Y2();
10235
+ s.start(`Deploying ${targetNames}...`);
10236
+ const args = buildFirebaseDeployArgs(targets, projectId, config.debug);
10237
+ const result = executeFirebaseCommand(args, {
10238
+ cwd: appDir,
10239
+ serviceAccountPath,
10240
+ projectId,
10241
+ debug: config.debug
10242
+ });
10243
+ if (result.error) {
10244
+ s.stop("Rules deployment failed");
10245
+ throw result.error;
10246
+ }
10247
+ if (!result.success) {
10248
+ s.stop("Rules deployment failed");
10249
+ handleDeploymentFailure(
10250
+ result,
10251
+ `firebase ${args.join(" ")}`,
10252
+ serviceAccountPath
10253
+ );
10254
+ }
10255
+ s.stop(`${targetNames} deployed successfully`);
10256
+ }
10257
+
10215
10258
  // packages/tooling/src/apps/deploy-utils.ts
10216
10259
  init_utils();
10217
10260
  import { spawnSync as spawnSync7 } from "node:child_process";
@@ -10332,6 +10375,9 @@ function validateFirebaseJson2(appDir) {
10332
10375
  valid: false,
10333
10376
  hasHosting: false,
10334
10377
  hasFunctions: false,
10378
+ hasFirestoreRules: false,
10379
+ hasFirestoreIndexes: false,
10380
+ hasStorageRules: false,
10335
10381
  errors: [`firebase.json not found at: ${firebaseJsonPath}`]
10336
10382
  };
10337
10383
  }
@@ -10344,11 +10390,17 @@ function validateFirebaseJson2(appDir) {
10344
10390
  }
10345
10391
  const hasHosting = !!config.hosting;
10346
10392
  const hasFunctions = !!config.functions && Array.isArray(config.functions);
10393
+ const hasFirestoreRules = !!config.firestore?.rules && pathExists(joinPath(appDir, config.firestore.rules));
10394
+ const hasFirestoreIndexes = !!config.firestore?.indexes && pathExists(joinPath(appDir, config.firestore.indexes));
10395
+ const hasStorageRules = !!config.storage?.rules && pathExists(joinPath(appDir, config.storage.rules));
10347
10396
  return {
10348
10397
  valid: true,
10349
10398
  projectId: config.projectId,
10350
10399
  hasHosting,
10351
10400
  hasFunctions,
10401
+ hasFirestoreRules,
10402
+ hasFirestoreIndexes,
10403
+ hasStorageRules,
10352
10404
  errors: []
10353
10405
  };
10354
10406
  } catch (error2) {
@@ -10356,6 +10408,9 @@ function validateFirebaseJson2(appDir) {
10356
10408
  valid: false,
10357
10409
  hasHosting: false,
10358
10410
  hasFunctions: false,
10411
+ hasFirestoreRules: false,
10412
+ hasFirestoreIndexes: false,
10413
+ hasStorageRules: false,
10359
10414
  errors: [
10360
10415
  `Invalid JSON format: ${error2 instanceof DoNotDevError ? error2.message : error2 instanceof Error ? error2.message : String(error2)}`
10361
10416
  ]
@@ -10520,16 +10575,36 @@ async function main5(options = {}) {
10520
10575
  hint: "Deploy cloud functions"
10521
10576
  });
10522
10577
  }
10578
+ const hasRules = firebaseConfig2.hasFirestoreRules || firebaseConfig2.hasFirestoreIndexes || firebaseConfig2.hasStorageRules;
10579
+ if (hasRules) {
10580
+ choices.push({
10581
+ title: "Rules only",
10582
+ value: "rules",
10583
+ hint: "Deploy Firestore/Storage rules"
10584
+ });
10585
+ }
10523
10586
  if (firebaseConfig2.hasHosting && firebaseConfig2.hasFunctions) {
10524
10587
  choices.push({
10525
- title: "Both frontend and functions",
10588
+ title: "Frontend + Functions",
10526
10589
  value: "both",
10527
- hint: "Deploy everything"
10590
+ hint: "Deploy hosting and functions"
10591
+ });
10592
+ }
10593
+ const deployableKindsCount = [
10594
+ firebaseConfig2.hasHosting,
10595
+ firebaseConfig2.hasFunctions,
10596
+ hasRules
10597
+ ].filter(Boolean).length;
10598
+ if (deployableKindsCount > 1) {
10599
+ choices.push({
10600
+ title: "All",
10601
+ value: "all",
10602
+ hint: "Deploy everything (hosting, functions, rules)"
10528
10603
  });
10529
10604
  }
10530
10605
  if (choices.length === 0) {
10531
10606
  log.error(
10532
- "No deployment targets found. firebase.json must have hosting or functions configuration."
10607
+ "No deployment targets found. firebase.json must have hosting, functions, or rules configuration."
10533
10608
  );
10534
10609
  process.exit(1);
10535
10610
  }
@@ -10574,13 +10649,28 @@ async function main5(options = {}) {
10574
10649
  showFirebaseJsonError(firebaseConfig.errors, appDir, deploymentType);
10575
10650
  process.exit(1);
10576
10651
  }
10577
- if (deploymentType === "frontend" || deploymentType === "both") {
10578
- if (!firebaseConfig.hasHosting) {
10579
- log.error(
10580
- "firebase.json does not contain hosting configuration, but frontend deployment was requested."
10581
- );
10582
- process.exit(1);
10583
- }
10652
+ const shouldDeployFrontend = (deploymentType === "frontend" || deploymentType === "both" || deploymentType === "all") && firebaseConfig.hasHosting;
10653
+ const shouldDeployFunctions = (deploymentType === "functions" || deploymentType === "both" || deploymentType === "all") && firebaseConfig.hasFunctions;
10654
+ const shouldDeployRules = (deploymentType === "rules" || deploymentType === "all") && (firebaseConfig.hasFirestoreRules || firebaseConfig.hasFirestoreIndexes || firebaseConfig.hasStorageRules);
10655
+ if (deploymentType === "frontend" && !firebaseConfig.hasHosting) {
10656
+ log.error(
10657
+ "firebase.json does not contain hosting configuration, but frontend deployment was requested."
10658
+ );
10659
+ process.exit(1);
10660
+ }
10661
+ if (deploymentType === "functions" && !firebaseConfig.hasFunctions) {
10662
+ log.error(
10663
+ "firebase.json does not contain functions configuration, but functions deployment was requested."
10664
+ );
10665
+ process.exit(1);
10666
+ }
10667
+ if (deploymentType === "rules" && !shouldDeployRules) {
10668
+ log.error(
10669
+ "firebase.json does not contain rules configuration, but rules deployment was requested."
10670
+ );
10671
+ process.exit(1);
10672
+ }
10673
+ if (shouldDeployFrontend) {
10584
10674
  const buildStatus = validateBuild(appDir);
10585
10675
  let shouldBuild = false;
10586
10676
  if (!buildStatus.exists || buildStatus.isEmpty) {
@@ -10623,14 +10713,6 @@ async function main5(options = {}) {
10623
10713
  }
10624
10714
  }
10625
10715
  }
10626
- if (deploymentType === "functions" || deploymentType === "both") {
10627
- if (!firebaseConfig.hasFunctions) {
10628
- log.error(
10629
- "firebase.json does not contain functions configuration, but functions deployment was requested."
10630
- );
10631
- process.exit(1);
10632
- }
10633
- }
10634
10716
  clearFirebaseCache(appDir);
10635
10717
  Me(
10636
10718
  `App: ${appName}
@@ -10639,10 +10721,10 @@ Deployment: ${deploymentType}
10639
10721
  Service Account: ${serviceAccountResult.info.clientEmail}`,
10640
10722
  "Deployment Configuration"
10641
10723
  );
10642
- if (deploymentType === "frontend" || deploymentType === "both") {
10724
+ if (shouldDeployFrontend) {
10643
10725
  await deployFrontend(appDir, serviceAccountPath, config.project, config);
10644
10726
  }
10645
- if (deploymentType === "functions" || deploymentType === "both") {
10727
+ if (shouldDeployFunctions) {
10646
10728
  await deployFunctions(
10647
10729
  appDir,
10648
10730
  serviceAccountPath,
@@ -10650,6 +10732,13 @@ Service Account: ${serviceAccountResult.info.clientEmail}`,
10650
10732
  config
10651
10733
  );
10652
10734
  }
10735
+ if (shouldDeployRules) {
10736
+ await deployRules(appDir, serviceAccountPath, config.project, config, {
10737
+ firestore: firebaseConfig.hasFirestoreRules,
10738
+ firestoreIndexes: firebaseConfig.hasFirestoreIndexes,
10739
+ storage: firebaseConfig.hasStorageRules
10740
+ });
10741
+ }
10653
10742
  Se("Deployment completed successfully!");
10654
10743
  } catch (error2) {
10655
10744
  if (error2 instanceof DoNotDevError) {
@@ -11180,7 +11269,7 @@ function generatePackageJson(templateName, mode, options = {}) {
11180
11269
  "dependencies-matrix.json not found. This command requires the matrix file."
11181
11270
  );
11182
11271
  }
11183
- const { matrix, cliVersion } = matrixResult;
11272
+ const { matrix } = matrixResult;
11184
11273
  const template = matrix.templateMapping?.[templateName];
11185
11274
  if (!template) {
11186
11275
  throw new Error(`Template "${templateName}" not found in matrix`);
@@ -11244,6 +11333,7 @@ function generatePackageJson(templateName, mode, options = {}) {
11244
11333
  }
11245
11334
  }
11246
11335
  if (templateName.includes("functions")) {
11336
+ result.main = "lib/index.js";
11247
11337
  result.engines = { node: "20" };
11248
11338
  if (options.appName) {
11249
11339
  const platform = templateName.includes("vercel") ? "Vercel" : "Firebase";
@@ -11371,6 +11461,8 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
11371
11461
  s.start(`Creating app: ${appName}`);
11372
11462
  await ensureDir(appDir);
11373
11463
  const templateDir = appTemplate === "demo" ? "app-demo" : appTemplate === "nextjs" ? "app-next" : "app-vite";
11464
+ const firebaseProjectId = (appConfig?.firebaseProjectId ?? "").trim() || appName.toLowerCase().replace(/\s+/g, "-");
11465
+ const firebaseRegion = appConfig?.firebaseRegion ?? "europe-west1";
11374
11466
  const replacements = {
11375
11467
  projectName: appName,
11376
11468
  appName,
@@ -11379,11 +11471,14 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
11379
11471
  needsCRUD: Boolean(appConfig.needsCRUD),
11380
11472
  setupGithubActions: false,
11381
11473
  appNames: [appName],
11382
- firebaseProjectId: appName.toLowerCase(),
11474
+ firebaseProjectId,
11475
+ firebaseRegion,
11383
11476
  firebaseSecretName: appName.toUpperCase().replace(/-/g, "_"),
11384
11477
  monorepoRelativePath: "../../packages/tooling",
11385
11478
  appTemplate,
11386
- isNextjs: appTemplate === "nextjs"
11479
+ isNextjs: appTemplate === "nextjs",
11480
+ YOUR_FIREBASE_PROJECT_ID: firebaseProjectId,
11481
+ YOUR_REGION: firebaseRegion
11387
11482
  };
11388
11483
  const templateSourceDir = joinPath(templatesRoot, templateDir);
11389
11484
  const templateFiles = await glob("**/*", {
@@ -11476,6 +11571,32 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
11476
11571
  await replacePlaceholders(firebaseJsonDest, replacements);
11477
11572
  }
11478
11573
  }
11574
+ const firebasercSource = joinPath(
11575
+ deploymentTemplateDir,
11576
+ ".firebaserc.example"
11577
+ );
11578
+ if (pathExists(firebasercSource)) {
11579
+ const firebasercDest = joinPath(appDir, ".firebaserc");
11580
+ await copy(firebasercSource, firebasercDest);
11581
+ if (await isTextFile(firebasercDest)) {
11582
+ await replacePlaceholders(firebasercDest, replacements);
11583
+ }
11584
+ }
11585
+ if (appConfig.needsBackend && appConfig.backendPlatform === "firebase") {
11586
+ const rulesFiles = [
11587
+ "firestore.rules.example",
11588
+ "firestore.indexes.json.example",
11589
+ "storage.rules.example"
11590
+ ];
11591
+ for (const example of rulesFiles) {
11592
+ const src = joinPath(deploymentTemplateDir, example);
11593
+ if (pathExists(src)) {
11594
+ const destName = example.replace(".example", "");
11595
+ const dest = joinPath(appDir, destName);
11596
+ await copy(src, dest);
11597
+ }
11598
+ }
11599
+ }
11479
11600
  if (appTemplate === "nextjs" || appConfig.needsBackend && appConfig.backendPlatform === "vercel") {
11480
11601
  const vercelJsonSource = joinPath(
11481
11602
  deploymentTemplateDir,
@@ -11501,12 +11622,15 @@ async function createApp(appName, appConfig, workspaceRoot, templatesRoot) {
11501
11622
  }
11502
11623
  if (isInteractive) {
11503
11624
  Se("\u{1F389} App created successfully!");
11625
+ const firebaseStep = appConfig.needsBackend && appConfig.backendPlatform === "firebase" ? `2. Set Firebase project: cd apps/${appName} && firebase use --add (or edit .firebase rc)
11626
+ 3. bun install
11627
+ 4. bun run dev` : `2. bun install
11628
+ 3. bun run dev`;
11504
11629
  Me(
11505
11630
  `Next steps:
11506
11631
 
11507
11632
  1. cd apps/${appName}
11508
- 2. bun install
11509
- 3. bun run dev
11633
+ ${firebaseStep}
11510
11634
 
11511
11635
  Happy coding!`,
11512
11636
  "\u{1F4CB} Next Steps"
@@ -11538,7 +11662,9 @@ async function main7(options) {
11538
11662
  selectedEntities: [],
11539
11663
  userAuth: "social",
11540
11664
  billing: true,
11541
- features: []
11665
+ features: [],
11666
+ firebaseProjectId: options.firebaseProjectId,
11667
+ firebaseRegion: options.firebaseRegion
11542
11668
  };
11543
11669
  await createApp(appName, appConfig);
11544
11670
  } else {
@@ -11878,6 +12004,8 @@ async function main8(options) {
11878
12004
  overwrite: true
11879
12005
  });
11880
12006
  const rootTemplateDir = joinPath(templatesRoot, "root-consumer");
12007
+ const firebaseProjectId = projectName.toLowerCase().replace(/\s+/g, "-");
12008
+ const firebaseRegion = "europe-west1";
11881
12009
  const rootReplacements = {
11882
12010
  projectName,
11883
12011
  appNames: isMergeMode ? allAppNames : appNames,
@@ -11886,12 +12014,22 @@ async function main8(options) {
11886
12014
  monorepoRelativePath: relativeMonorepoPath,
11887
12015
  appTemplate: "vite",
11888
12016
  isNextjs: false,
11889
- firebaseProjectId: projectName.toLowerCase(),
12017
+ firebaseProjectId,
12018
+ firebaseRegion,
11890
12019
  firebaseSecretName: projectName.toUpperCase().replace(/-/g, "_"),
12020
+ YOUR_FIREBASE_PROJECT_ID: firebaseProjectId,
12021
+ YOUR_REGION: firebaseRegion,
11891
12022
  needsAuth,
11892
12023
  needsOAuth,
11893
12024
  needsBilling
11894
12025
  };
12026
+ const firebaseRootFiles = /* @__PURE__ */ new Set([
12027
+ "firebase.json.example",
12028
+ ".firebaserc.example",
12029
+ "firestore.rules.example",
12030
+ "firestore.indexes.json.example",
12031
+ "storage.rules.example"
12032
+ ]);
11895
12033
  const files = await glob("**/*", {
11896
12034
  cwd: rootTemplateDir,
11897
12035
  dot: true,
@@ -11899,6 +12037,7 @@ async function main8(options) {
11899
12037
  });
11900
12038
  for (const file of files) {
11901
12039
  if (file === "package.json.example") continue;
12040
+ if (firebaseRootFiles.has(file)) continue;
11902
12041
  const sourcePath = joinPath(rootTemplateDir, file);
11903
12042
  let destFileName = file;
11904
12043
  if (destFileName.endsWith(".example")) {
@@ -11973,6 +12112,8 @@ async function main8(options) {
11973
12112
  }
11974
12113
  }
11975
12114
  if (installDemoApp) {
12115
+ const demoFirebaseProjectId = projectName.toLowerCase().replace(/\s+/g, "-");
12116
+ const demoFirebaseRegion = "europe-west1";
11976
12117
  await copyTemplateFiles(demoTemplateDir, demoAppDir, {
11977
12118
  projectName,
11978
12119
  appName: "demo",
@@ -11980,8 +12121,11 @@ async function main8(options) {
11980
12121
  needsCRUD: false,
11981
12122
  setupGithubActions: false,
11982
12123
  appNames,
11983
- firebaseProjectId: projectName.toLowerCase(),
12124
+ firebaseProjectId: demoFirebaseProjectId,
12125
+ firebaseRegion: demoFirebaseRegion,
11984
12126
  firebaseSecretName: projectName.toUpperCase().replace(/-/g, "_"),
12127
+ YOUR_FIREBASE_PROJECT_ID: demoFirebaseProjectId,
12128
+ YOUR_REGION: demoFirebaseRegion,
11985
12129
  monorepoRelativePath: executionMode === "development" ? calculateRelativePath(projectDirNormalized, monorepoRoot) : "",
11986
12130
  appTemplate: "demo"
11987
12131
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.11",
4
4
  "description": "Command-line interface for DoNotDev Framework",
5
5
  "type": "module",
6
6
  "private": false,
@@ -33,8 +33,8 @@
33
33
  "type-check": "tsc --noEmit"
34
34
  },
35
35
  "dependencies": {
36
- "@clack/prompts": "^0.11.0",
37
- "commander": "^14.0.2",
36
+ "@clack/prompts": "^1.0.0",
37
+ "commander": "^14.0.3",
38
38
  "fast-glob": "^3.3.3"
39
39
  },
40
40
  "repository": {
@@ -59,5 +59,6 @@
59
59
  "publishConfig": {
60
60
  "registry": "https://registry.npmjs.org",
61
61
  "access": "public"
62
- }
62
+ },
63
+ "peerDependencies": {}
63
64
  }
@@ -29,7 +29,7 @@ export const appConfig: AppConfig = {
29
29
  name: APP_NAME,
30
30
  shortName: APP_SHORT_NAME,
31
31
  description: APP_DESCRIPTION,
32
- // url: 'https://yourapp.com', // Uncomment for production
32
+ // Note: URL comes from NEXT_PUBLIC_APP_URL in .env, not here
33
33
 
34
34
  // Footer legal links - remove any you don't need
35
35
  footer: {
@@ -16,8 +16,13 @@
16
16
  <!-- ✅ PWA: Manifest link (if exists) -->
17
17
  <link rel="manifest" href="/manifest.json" />
18
18
 
19
- <!-- ✅ PERFORMANCE: Load fonts early to avoid preload warnings -->
20
- <link rel="stylesheet" href="/fonts/fonts.css" />
19
+ <!-- ✅ PERFORMANCE: Preload critical fonts (non-blocking) -->
20
+ <link rel="preload" href="/fonts/Inter-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous">
21
+ <link rel="preload" href="/fonts/Roboto-400-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous">
22
+
23
+ <!-- ✅ PERFORMANCE: Load extended font subsets async (non-blocking) -->
24
+ <link rel="stylesheet" href="/fonts/fonts.css" media="print" onload="this.media='all'">
25
+ <noscript><link rel="stylesheet" href="/fonts/fonts.css"></noscript>
21
26
 
22
27
  <!-- ✅ PERFORMANCE: Preconnect to external domains (OAuth providers) -->
23
28
  <!-- GitHub OAuth -->
@@ -36,6 +41,23 @@
36
41
 
37
42
  <!-- ✅ PERFORMANCE: Critical CSS inlined here by build -->
38
43
  <style>
44
+ /* Critical @font-face declarations - must be inline for preloaded fonts to work */
45
+ @font-face {
46
+ font-family: Inter;
47
+ font-style: normal;
48
+ font-weight: 400 700;
49
+ font-display: swap;
50
+ src: url('/fonts/Inter-latin.woff2') format('woff2');
51
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
52
+ }
53
+ @font-face {
54
+ font-family: Roboto;
55
+ font-style: normal;
56
+ font-weight: 400;
57
+ font-display: swap;
58
+ src: url('/fonts/Roboto-400-latin.woff2') format('woff2');
59
+ unicode-range: U+0000-00FF;
60
+ }
39
61
  /* Critical above-the-fold styles */
40
62
  html, body {
41
63
  margin: 0;
@@ -29,7 +29,7 @@ export const appConfig: AppConfig = {
29
29
  name: APP_NAME,
30
30
  shortName: APP_SHORT_NAME,
31
31
  description: APP_DESCRIPTION,
32
- // url: 'https://yourapp.com', // Uncomment for production
32
+ // Note: URL comes from VITE_APP_URL in .env, not here
33
33
 
34
34
  // Footer legal links - remove any you don't need
35
35
  footer: {
@@ -13,10 +13,10 @@
13
13
  import { useEffect, useState } from 'react';
14
14
 
15
15
  import { Section, Button, Alert } from '@donotdev/components';
16
- import { EntityFormRenderer, useCrud } from '@donotdev/crud';
16
+ import { useCrud } from '@donotdev/crud';
17
17
  import { useTranslation } from '@donotdev/core';
18
18
  import type { PageMeta } from '@donotdev/core';
19
- import { PageContainer, Link, useNavigate } from '@donotdev/ui';
19
+ import { PageContainer, Link, EntityFormRenderer } from '@donotdev/ui';
20
20
 
21
21
  // Import your entity from root-level entities folder
22
22
  // import { productEntity } from 'entities/Product';
@@ -51,7 +51,6 @@ export const meta: PageMeta = {
51
51
  export default function ProductPage() {
52
52
  const { t } = useTranslation(NAMESPACE);
53
53
  const id = useParam('id');
54
- const navigate = useNavigate();
55
54
  const isNew = id === 'new';
56
55
 
57
56
  // useCrud provides CRUD operations with optimistic updates
@@ -84,8 +83,8 @@ export default function ProductPage() {
84
83
  update(id, data); // No await - fires in background
85
84
  }
86
85
 
87
- // Instant navigation - feels incredibly fast
88
- navigate('/products');
86
+ // Navigation happens automatically via cancelPath (defaults to /products)
87
+ // Or you can navigate manually if needed
89
88
  };
90
89
 
91
90
  // ==========================================================================
@@ -121,6 +120,8 @@ export default function ProductPage() {
121
120
  onSubmit={handleSubmit}
122
121
  defaultValues={{ status: 'draft' }} // Initial values for new items
123
122
  submitText={t('create')}
123
+ // Cancel automatically navigates to /products (or cancelPath if provided)
124
+ cancelPath="/products"
124
125
  />
125
126
  ) : (
126
127
  // EDIT MODE
@@ -130,6 +131,8 @@ export default function ProductPage() {
130
131
  onSubmit={handleSubmit}
131
132
  defaultValues={formData} // Loaded data
132
133
  submitText={t('update')}
134
+ // Cancel automatically navigates to /products (or cancelPath if provided)
135
+ cancelPath="/products"
133
136
  />
134
137
  )}
135
138
  </Section>