clawfire 0.6.8 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -269,7 +269,7 @@ async function runDevServer() {
269
269
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
270
270
  const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
271
271
  const noHotReload = args.includes("--no-hot-reload");
272
- const { startDevServer } = await import("./dev-server-ESMIOA6C.js");
272
+ const { startDevServer } = await import("./dev-server-FNRFWJD3.js");
273
273
  await startDevServer({
274
274
  projectDir,
275
275
  port,
@@ -2956,7 +2956,7 @@ var FirebaseSetup = class {
2956
2956
  return { success: true, message: `Web app "${displayName}" selected.` };
2957
2957
  }
2958
2958
  // ─── Deploy ─────────────────────────────────────────────────────────
2959
- async deployHosting() {
2959
+ async deployHosting(targets = "hosting") {
2960
2960
  const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
2961
2961
  if (!existsSync5(firebaseJsonPath)) {
2962
2962
  return { success: false, message: "firebase.json not found. Enable hosting first." };
@@ -2969,12 +2969,12 @@ var FirebaseSetup = class {
2969
2969
  } catch {
2970
2970
  return { success: false, message: "Invalid firebase.json." };
2971
2971
  }
2972
+ const timeoutMs = targets.includes("functions") ? 3e5 : 12e4;
2972
2973
  try {
2973
2974
  const output = await this.execTimeout(
2974
2975
  "firebase",
2975
- ["deploy", "--only", "hosting", "--json"],
2976
- 12e4
2977
- // 2 min timeout
2976
+ ["deploy", "--only", targets, "--json"],
2977
+ timeoutMs
2978
2978
  );
2979
2979
  let url = "";
2980
2980
  try {
@@ -2997,12 +2997,147 @@ var FirebaseSetup = class {
2997
2997
  url = `https://${state.projectId}.web.app`;
2998
2998
  }
2999
2999
  }
3000
- return { success: true, url: url || void 0, message: "Hosting deployed successfully!" };
3000
+ const label = targets.includes("functions") ? "Hosting + Functions" : "Hosting";
3001
+ return { success: true, url: url || void 0, message: `${label} deployed successfully!` };
3001
3002
  } catch (err) {
3002
3003
  const msg = err instanceof Error ? err.message : "Unknown error";
3003
3004
  return { success: false, message: `Deploy failed: ${msg}` };
3004
3005
  }
3005
3006
  }
3007
+ // ─── Full Deploy Pipeline ────────────────────────────────────────────
3008
+ /**
3009
+ * Sync environment variables to functions/.env for production.
3010
+ * Copies project root .env vars + injects CLAWFIRE_FIREBASE_CONFIG.
3011
+ */
3012
+ syncEnvToFunctions(firebaseConfig) {
3013
+ const functionsDir = resolve4(this.projectDir, "functions");
3014
+ if (!existsSync5(functionsDir)) {
3015
+ return { success: false, message: "functions/ directory not found.", count: 0 };
3016
+ }
3017
+ const lines = [];
3018
+ const rootEnvPath = resolve4(this.projectDir, ".env");
3019
+ if (existsSync5(rootEnvPath)) {
3020
+ const content = readFileSync4(rootEnvPath, "utf-8");
3021
+ for (const rawLine of content.split("\n")) {
3022
+ let trimmed = rawLine.trim();
3023
+ if (!trimmed || trimmed.startsWith("#")) continue;
3024
+ if (trimmed.startsWith("export ")) {
3025
+ trimmed = trimmed.slice(7);
3026
+ }
3027
+ const eqIdx = trimmed.indexOf("=");
3028
+ if (eqIdx === -1) continue;
3029
+ const key = trimmed.slice(0, eqIdx).trim();
3030
+ if (key === "NPM_TOKEN" || key === "NODE_ENV") continue;
3031
+ lines.push(trimmed);
3032
+ }
3033
+ }
3034
+ if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
3035
+ const filtered = lines.filter((l) => !l.startsWith("CLAWFIRE_FIREBASE_CONFIG="));
3036
+ filtered.push(`CLAWFIRE_FIREBASE_CONFIG=${JSON.stringify(firebaseConfig)}`);
3037
+ lines.length = 0;
3038
+ lines.push(...filtered);
3039
+ }
3040
+ const functionsEnvPath = resolve4(functionsDir, ".env");
3041
+ writeFileSync3(functionsEnvPath, lines.join("\n") + "\n", "utf-8");
3042
+ return { success: true, message: `Synced ${lines.length} env vars to functions/.env`, count: lines.length };
3043
+ }
3044
+ /**
3045
+ * Ensure firebase.json is fully configured for deployment:
3046
+ * - functions config exists
3047
+ * - hosting has cleanUrls
3048
+ * - hosting has API rewrite (/api/** → api function) before catch-all
3049
+ *
3050
+ * Fixes existing configs created by older versions.
3051
+ */
3052
+ ensureDeployConfig() {
3053
+ const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
3054
+ if (!existsSync5(firebaseJsonPath)) {
3055
+ return { success: false, message: "firebase.json not found.", changes: [] };
3056
+ }
3057
+ try {
3058
+ const config = JSON.parse(readFileSync4(firebaseJsonPath, "utf-8"));
3059
+ const changes = [];
3060
+ if (!config.functions) {
3061
+ config.functions = {
3062
+ source: "functions",
3063
+ runtime: "nodejs20",
3064
+ codebase: "clawfire"
3065
+ };
3066
+ changes.push("Added functions config");
3067
+ }
3068
+ if (config.hosting && !config.hosting.cleanUrls) {
3069
+ config.hosting.cleanUrls = true;
3070
+ changes.push("Added cleanUrls");
3071
+ }
3072
+ if (config.hosting) {
3073
+ if (!config.hosting.rewrites) {
3074
+ config.hosting.rewrites = [];
3075
+ }
3076
+ const rewrites = config.hosting.rewrites;
3077
+ const hasApiRewrite = rewrites.some(
3078
+ (r) => r.source === "/api/**" && r.function === "api"
3079
+ );
3080
+ if (!hasApiRewrite) {
3081
+ const apiRewrite = { source: "/api/**", function: "api" };
3082
+ const catchAllIndex = rewrites.findIndex((r) => r.source === "**");
3083
+ if (catchAllIndex >= 0) {
3084
+ rewrites.splice(catchAllIndex, 0, apiRewrite);
3085
+ } else {
3086
+ rewrites.push(apiRewrite);
3087
+ rewrites.push({ source: "**", destination: "/index.html" });
3088
+ }
3089
+ changes.push("Added API rewrite (/api/** \u2192 Cloud Function)");
3090
+ }
3091
+ }
3092
+ if (changes.length > 0) {
3093
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3094
+ }
3095
+ return {
3096
+ success: true,
3097
+ message: changes.length > 0 ? changes.join(", ") : "Deploy config up to date.",
3098
+ changes
3099
+ };
3100
+ } catch {
3101
+ return { success: false, message: "Invalid firebase.json.", changes: [] };
3102
+ }
3103
+ }
3104
+ /**
3105
+ * Install dependencies and build functions for deployment.
3106
+ */
3107
+ async buildFunctions() {
3108
+ const functionsDir = resolve4(this.projectDir, "functions");
3109
+ if (!existsSync5(functionsDir)) {
3110
+ return { success: false, message: "functions/ directory not found." };
3111
+ }
3112
+ const pkgPath = resolve4(functionsDir, "package.json");
3113
+ if (!existsSync5(pkgPath)) {
3114
+ return { success: false, message: "functions/package.json not found." };
3115
+ }
3116
+ const nodeModulesPath = resolve4(functionsDir, "node_modules");
3117
+ if (!existsSync5(nodeModulesPath)) {
3118
+ try {
3119
+ await this.execTimeout("npm", ["install"], 12e4, functionsDir);
3120
+ } catch (err) {
3121
+ const msg = err instanceof Error ? err.message : "Unknown error";
3122
+ return { success: false, message: `npm install in functions/ failed: ${msg}` };
3123
+ }
3124
+ }
3125
+ try {
3126
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
3127
+ if (pkg.scripts?.build) {
3128
+ await this.execTimeout("npm", ["run", "build"], 6e4, functionsDir);
3129
+ } else {
3130
+ const tsconfigPath = resolve4(functionsDir, "tsconfig.json");
3131
+ if (existsSync5(tsconfigPath)) {
3132
+ await this.execTimeout("npx", ["tsc"], 6e4, functionsDir);
3133
+ }
3134
+ }
3135
+ return { success: true, message: "Functions built successfully." };
3136
+ } catch (err) {
3137
+ const msg = err instanceof Error ? err.message : "Unknown error";
3138
+ return { success: false, message: `Functions build failed: ${msg}` };
3139
+ }
3140
+ }
3006
3141
  // ─── Service Enable ────────────────────────────────────────────────
3007
3142
  enableService(service) {
3008
3143
  const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
@@ -3251,9 +3386,9 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3251
3386
  }
3252
3387
  }
3253
3388
  // ─── Helpers ───────────────────────────────────────────────────────
3254
- execTimeout(command, args, timeoutMs) {
3389
+ execTimeout(command, args, timeoutMs, cwd) {
3255
3390
  return new Promise((resolve6, reject) => {
3256
- const proc = execFile2(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3391
+ const proc = execFile2(command, args, { cwd: cwd || this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3257
3392
  if (err) {
3258
3393
  const detail = stderr?.trim() || stdout?.trim() || "";
3259
3394
  const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
@@ -4416,22 +4551,27 @@ ${liveReloadScript}
4416
4551
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4417
4552
  (async () => {
4418
4553
  try {
4419
- if (this.pageCompiler.isActive()) {
4420
- const projectConfig = this.readProjectConfig();
4421
- const firebaseConfig = {};
4422
- for (const field of projectConfig.fields) {
4423
- if (!field.isPlaceholder) {
4424
- firebaseConfig[field.key] = field.value;
4425
- }
4554
+ const steps = [];
4555
+ const functionsDir = resolve5(this.options.projectDir, "functions");
4556
+ const hasFunctions = existsSync6(functionsDir);
4557
+ const projectConfig = this.readProjectConfig();
4558
+ const firebaseConfig = {};
4559
+ for (const field of projectConfig.fields) {
4560
+ if (!field.isPlaceholder) {
4561
+ firebaseConfig[field.key] = field.value;
4426
4562
  }
4563
+ }
4564
+ if (this.pageCompiler.isActive()) {
4427
4565
  const configScript = Object.keys(firebaseConfig).length > 0 ? `
4428
4566
  <script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
4429
4567
  const routerScript = generateProductionRouterScript();
4430
4568
  const scriptToInject = configScript + routerScript;
4431
4569
  const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
4432
4570
  console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4571
+ steps.push(`${buildResult.pages.length} pages built`);
4433
4572
  if (configScript) {
4434
4573
  console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
4574
+ steps.push("Firebase config injected");
4435
4575
  }
4436
4576
  if (buildResult.errors.length > 0) {
4437
4577
  for (const err of buildResult.errors) {
@@ -4439,9 +4579,42 @@ ${liveReloadScript}
4439
4579
  }
4440
4580
  }
4441
4581
  }
4442
- const result = await this.firebaseSetup.deployHosting();
4582
+ const deployConfig = this.firebaseSetup.ensureDeployConfig();
4583
+ if (deployConfig.success && deployConfig.changes.length > 0) {
4584
+ for (const change of deployConfig.changes) {
4585
+ console.log(` \x1B[32m\u2713\x1B[0m firebase.json: ${change}`);
4586
+ }
4587
+ steps.push(`firebase.json updated (${deployConfig.changes.length} fixes)`);
4588
+ }
4589
+ let deployFunctions = false;
4590
+ if (hasFunctions) {
4591
+ const syncResult = this.firebaseSetup.syncEnvToFunctions(firebaseConfig);
4592
+ if (syncResult.success) {
4593
+ console.log(` \x1B[32m\u2713\x1B[0m ${syncResult.message}`);
4594
+ steps.push(`${syncResult.count} env vars synced`);
4595
+ } else {
4596
+ console.log(` \x1B[33m\u26A0\x1B[0m Env sync: ${syncResult.message}`);
4597
+ }
4598
+ console.log(" Building functions...");
4599
+ const buildResult = await this.firebaseSetup.buildFunctions();
4600
+ if (buildResult.success) {
4601
+ console.log(` \x1B[32m\u2713\x1B[0m ${buildResult.message}`);
4602
+ steps.push("Functions built");
4603
+ deployFunctions = true;
4604
+ } else {
4605
+ console.log(` \x1B[31m\u2717\x1B[0m ${buildResult.message}`);
4606
+ console.log(" \x1B[33m\u26A0\x1B[0m Deploying hosting only (functions build failed)");
4607
+ steps.push("Functions build failed \u2014 hosting only");
4608
+ }
4609
+ }
4610
+ const targets = deployFunctions ? "hosting,functions" : "hosting";
4611
+ console.log(` Deploying ${targets}...`);
4612
+ const result = await this.firebaseSetup.deployHosting(targets);
4443
4613
  clearFirebaseStatusCache();
4444
- sendJson(result);
4614
+ sendJson({
4615
+ ...result,
4616
+ message: result.message + (steps.length > 0 ? ` (${steps.join(", ")})` : "")
4617
+ });
4445
4618
  } catch (err) {
4446
4619
  sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4447
4620
  }
package/dist/dev.cjs CHANGED
@@ -3368,7 +3368,7 @@ var FirebaseSetup = class {
3368
3368
  return { success: true, message: `Web app "${displayName}" selected.` };
3369
3369
  }
3370
3370
  // ─── Deploy ─────────────────────────────────────────────────────────
3371
- async deployHosting() {
3371
+ async deployHosting(targets = "hosting") {
3372
3372
  const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
3373
3373
  if (!(0, import_node_fs4.existsSync)(firebaseJsonPath)) {
3374
3374
  return { success: false, message: "firebase.json not found. Enable hosting first." };
@@ -3381,12 +3381,12 @@ var FirebaseSetup = class {
3381
3381
  } catch {
3382
3382
  return { success: false, message: "Invalid firebase.json." };
3383
3383
  }
3384
+ const timeoutMs = targets.includes("functions") ? 3e5 : 12e4;
3384
3385
  try {
3385
3386
  const output = await this.execTimeout(
3386
3387
  "firebase",
3387
- ["deploy", "--only", "hosting", "--json"],
3388
- 12e4
3389
- // 2 min timeout
3388
+ ["deploy", "--only", targets, "--json"],
3389
+ timeoutMs
3390
3390
  );
3391
3391
  let url = "";
3392
3392
  try {
@@ -3409,12 +3409,147 @@ var FirebaseSetup = class {
3409
3409
  url = `https://${state.projectId}.web.app`;
3410
3410
  }
3411
3411
  }
3412
- return { success: true, url: url || void 0, message: "Hosting deployed successfully!" };
3412
+ const label = targets.includes("functions") ? "Hosting + Functions" : "Hosting";
3413
+ return { success: true, url: url || void 0, message: `${label} deployed successfully!` };
3413
3414
  } catch (err) {
3414
3415
  const msg = err instanceof Error ? err.message : "Unknown error";
3415
3416
  return { success: false, message: `Deploy failed: ${msg}` };
3416
3417
  }
3417
3418
  }
3419
+ // ─── Full Deploy Pipeline ────────────────────────────────────────────
3420
+ /**
3421
+ * Sync environment variables to functions/.env for production.
3422
+ * Copies project root .env vars + injects CLAWFIRE_FIREBASE_CONFIG.
3423
+ */
3424
+ syncEnvToFunctions(firebaseConfig) {
3425
+ const functionsDir = (0, import_node_path4.resolve)(this.projectDir, "functions");
3426
+ if (!(0, import_node_fs4.existsSync)(functionsDir)) {
3427
+ return { success: false, message: "functions/ directory not found.", count: 0 };
3428
+ }
3429
+ const lines = [];
3430
+ const rootEnvPath = (0, import_node_path4.resolve)(this.projectDir, ".env");
3431
+ if ((0, import_node_fs4.existsSync)(rootEnvPath)) {
3432
+ const content = (0, import_node_fs4.readFileSync)(rootEnvPath, "utf-8");
3433
+ for (const rawLine of content.split("\n")) {
3434
+ let trimmed = rawLine.trim();
3435
+ if (!trimmed || trimmed.startsWith("#")) continue;
3436
+ if (trimmed.startsWith("export ")) {
3437
+ trimmed = trimmed.slice(7);
3438
+ }
3439
+ const eqIdx = trimmed.indexOf("=");
3440
+ if (eqIdx === -1) continue;
3441
+ const key = trimmed.slice(0, eqIdx).trim();
3442
+ if (key === "NPM_TOKEN" || key === "NODE_ENV") continue;
3443
+ lines.push(trimmed);
3444
+ }
3445
+ }
3446
+ if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
3447
+ const filtered = lines.filter((l) => !l.startsWith("CLAWFIRE_FIREBASE_CONFIG="));
3448
+ filtered.push(`CLAWFIRE_FIREBASE_CONFIG=${JSON.stringify(firebaseConfig)}`);
3449
+ lines.length = 0;
3450
+ lines.push(...filtered);
3451
+ }
3452
+ const functionsEnvPath = (0, import_node_path4.resolve)(functionsDir, ".env");
3453
+ (0, import_node_fs4.writeFileSync)(functionsEnvPath, lines.join("\n") + "\n", "utf-8");
3454
+ return { success: true, message: `Synced ${lines.length} env vars to functions/.env`, count: lines.length };
3455
+ }
3456
+ /**
3457
+ * Ensure firebase.json is fully configured for deployment:
3458
+ * - functions config exists
3459
+ * - hosting has cleanUrls
3460
+ * - hosting has API rewrite (/api/** → api function) before catch-all
3461
+ *
3462
+ * Fixes existing configs created by older versions.
3463
+ */
3464
+ ensureDeployConfig() {
3465
+ const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
3466
+ if (!(0, import_node_fs4.existsSync)(firebaseJsonPath)) {
3467
+ return { success: false, message: "firebase.json not found.", changes: [] };
3468
+ }
3469
+ try {
3470
+ const config = JSON.parse((0, import_node_fs4.readFileSync)(firebaseJsonPath, "utf-8"));
3471
+ const changes = [];
3472
+ if (!config.functions) {
3473
+ config.functions = {
3474
+ source: "functions",
3475
+ runtime: "nodejs20",
3476
+ codebase: "clawfire"
3477
+ };
3478
+ changes.push("Added functions config");
3479
+ }
3480
+ if (config.hosting && !config.hosting.cleanUrls) {
3481
+ config.hosting.cleanUrls = true;
3482
+ changes.push("Added cleanUrls");
3483
+ }
3484
+ if (config.hosting) {
3485
+ if (!config.hosting.rewrites) {
3486
+ config.hosting.rewrites = [];
3487
+ }
3488
+ const rewrites = config.hosting.rewrites;
3489
+ const hasApiRewrite = rewrites.some(
3490
+ (r) => r.source === "/api/**" && r.function === "api"
3491
+ );
3492
+ if (!hasApiRewrite) {
3493
+ const apiRewrite = { source: "/api/**", function: "api" };
3494
+ const catchAllIndex = rewrites.findIndex((r) => r.source === "**");
3495
+ if (catchAllIndex >= 0) {
3496
+ rewrites.splice(catchAllIndex, 0, apiRewrite);
3497
+ } else {
3498
+ rewrites.push(apiRewrite);
3499
+ rewrites.push({ source: "**", destination: "/index.html" });
3500
+ }
3501
+ changes.push("Added API rewrite (/api/** \u2192 Cloud Function)");
3502
+ }
3503
+ }
3504
+ if (changes.length > 0) {
3505
+ (0, import_node_fs4.writeFileSync)(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3506
+ }
3507
+ return {
3508
+ success: true,
3509
+ message: changes.length > 0 ? changes.join(", ") : "Deploy config up to date.",
3510
+ changes
3511
+ };
3512
+ } catch {
3513
+ return { success: false, message: "Invalid firebase.json.", changes: [] };
3514
+ }
3515
+ }
3516
+ /**
3517
+ * Install dependencies and build functions for deployment.
3518
+ */
3519
+ async buildFunctions() {
3520
+ const functionsDir = (0, import_node_path4.resolve)(this.projectDir, "functions");
3521
+ if (!(0, import_node_fs4.existsSync)(functionsDir)) {
3522
+ return { success: false, message: "functions/ directory not found." };
3523
+ }
3524
+ const pkgPath = (0, import_node_path4.resolve)(functionsDir, "package.json");
3525
+ if (!(0, import_node_fs4.existsSync)(pkgPath)) {
3526
+ return { success: false, message: "functions/package.json not found." };
3527
+ }
3528
+ const nodeModulesPath = (0, import_node_path4.resolve)(functionsDir, "node_modules");
3529
+ if (!(0, import_node_fs4.existsSync)(nodeModulesPath)) {
3530
+ try {
3531
+ await this.execTimeout("npm", ["install"], 12e4, functionsDir);
3532
+ } catch (err) {
3533
+ const msg = err instanceof Error ? err.message : "Unknown error";
3534
+ return { success: false, message: `npm install in functions/ failed: ${msg}` };
3535
+ }
3536
+ }
3537
+ try {
3538
+ const pkg = JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf-8"));
3539
+ if (pkg.scripts?.build) {
3540
+ await this.execTimeout("npm", ["run", "build"], 6e4, functionsDir);
3541
+ } else {
3542
+ const tsconfigPath = (0, import_node_path4.resolve)(functionsDir, "tsconfig.json");
3543
+ if ((0, import_node_fs4.existsSync)(tsconfigPath)) {
3544
+ await this.execTimeout("npx", ["tsc"], 6e4, functionsDir);
3545
+ }
3546
+ }
3547
+ return { success: true, message: "Functions built successfully." };
3548
+ } catch (err) {
3549
+ const msg = err instanceof Error ? err.message : "Unknown error";
3550
+ return { success: false, message: `Functions build failed: ${msg}` };
3551
+ }
3552
+ }
3418
3553
  // ─── Service Enable ────────────────────────────────────────────────
3419
3554
  enableService(service) {
3420
3555
  const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
@@ -3663,9 +3798,9 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3663
3798
  }
3664
3799
  }
3665
3800
  // ─── Helpers ───────────────────────────────────────────────────────
3666
- execTimeout(command, args, timeoutMs) {
3801
+ execTimeout(command, args, timeoutMs, cwd) {
3667
3802
  return new Promise((resolve7, reject) => {
3668
- const proc = (0, import_node_child_process2.execFile)(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3803
+ const proc = (0, import_node_child_process2.execFile)(command, args, { cwd: cwd || this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3669
3804
  if (err) {
3670
3805
  const detail = stderr?.trim() || stdout?.trim() || "";
3671
3806
  const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
@@ -4828,22 +4963,27 @@ ${liveReloadScript}
4828
4963
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4829
4964
  (async () => {
4830
4965
  try {
4831
- if (this.pageCompiler.isActive()) {
4832
- const projectConfig = this.readProjectConfig();
4833
- const firebaseConfig = {};
4834
- for (const field of projectConfig.fields) {
4835
- if (!field.isPlaceholder) {
4836
- firebaseConfig[field.key] = field.value;
4837
- }
4966
+ const steps = [];
4967
+ const functionsDir = (0, import_node_path5.resolve)(this.options.projectDir, "functions");
4968
+ const hasFunctions = (0, import_node_fs5.existsSync)(functionsDir);
4969
+ const projectConfig = this.readProjectConfig();
4970
+ const firebaseConfig = {};
4971
+ for (const field of projectConfig.fields) {
4972
+ if (!field.isPlaceholder) {
4973
+ firebaseConfig[field.key] = field.value;
4838
4974
  }
4975
+ }
4976
+ if (this.pageCompiler.isActive()) {
4839
4977
  const configScript = Object.keys(firebaseConfig).length > 0 ? `
4840
4978
  <script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
4841
4979
  const routerScript = generateProductionRouterScript();
4842
4980
  const scriptToInject = configScript + routerScript;
4843
4981
  const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
4844
4982
  console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4983
+ steps.push(`${buildResult.pages.length} pages built`);
4845
4984
  if (configScript) {
4846
4985
  console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
4986
+ steps.push("Firebase config injected");
4847
4987
  }
4848
4988
  if (buildResult.errors.length > 0) {
4849
4989
  for (const err of buildResult.errors) {
@@ -4851,9 +4991,42 @@ ${liveReloadScript}
4851
4991
  }
4852
4992
  }
4853
4993
  }
4854
- const result = await this.firebaseSetup.deployHosting();
4994
+ const deployConfig = this.firebaseSetup.ensureDeployConfig();
4995
+ if (deployConfig.success && deployConfig.changes.length > 0) {
4996
+ for (const change of deployConfig.changes) {
4997
+ console.log(` \x1B[32m\u2713\x1B[0m firebase.json: ${change}`);
4998
+ }
4999
+ steps.push(`firebase.json updated (${deployConfig.changes.length} fixes)`);
5000
+ }
5001
+ let deployFunctions = false;
5002
+ if (hasFunctions) {
5003
+ const syncResult = this.firebaseSetup.syncEnvToFunctions(firebaseConfig);
5004
+ if (syncResult.success) {
5005
+ console.log(` \x1B[32m\u2713\x1B[0m ${syncResult.message}`);
5006
+ steps.push(`${syncResult.count} env vars synced`);
5007
+ } else {
5008
+ console.log(` \x1B[33m\u26A0\x1B[0m Env sync: ${syncResult.message}`);
5009
+ }
5010
+ console.log(" Building functions...");
5011
+ const buildResult = await this.firebaseSetup.buildFunctions();
5012
+ if (buildResult.success) {
5013
+ console.log(` \x1B[32m\u2713\x1B[0m ${buildResult.message}`);
5014
+ steps.push("Functions built");
5015
+ deployFunctions = true;
5016
+ } else {
5017
+ console.log(` \x1B[31m\u2717\x1B[0m ${buildResult.message}`);
5018
+ console.log(" \x1B[33m\u26A0\x1B[0m Deploying hosting only (functions build failed)");
5019
+ steps.push("Functions build failed \u2014 hosting only");
5020
+ }
5021
+ }
5022
+ const targets = deployFunctions ? "hosting,functions" : "hosting";
5023
+ console.log(` Deploying ${targets}...`);
5024
+ const result = await this.firebaseSetup.deployHosting(targets);
4855
5025
  clearFirebaseStatusCache();
4856
- sendJson(result);
5026
+ sendJson({
5027
+ ...result,
5028
+ message: result.message + (steps.length > 0 ? ` (${steps.join(", ")})` : "")
5029
+ });
4857
5030
  } catch (err) {
4858
5031
  sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4859
5032
  }