clawfire 0.6.5 → 0.6.7

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/dev.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/dev/dev-server.ts
2
2
  import http from "http";
3
3
  import { resolve as resolve6, relative as relative3, extname as extname3 } from "path";
4
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
4
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
5
5
  import { pathToFileURL } from "url";
6
6
 
7
7
  // src/core/schema.ts
@@ -1019,8 +1019,8 @@ var FileWatcher = class extends EventEmitter {
1019
1019
  };
1020
1020
 
1021
1021
  // src/dev/page-compiler.ts
1022
- import { resolve as resolve2, join as join3, dirname, basename } from "path";
1023
- import { existsSync as existsSync3, readFileSync } from "fs";
1022
+ import { resolve as resolve2, join as join3, relative as relative2, dirname, basename } from "path";
1023
+ import { existsSync as existsSync3, readFileSync, readdirSync as readdirSync3, statSync as statSync3, writeFileSync, mkdirSync } from "fs";
1024
1024
  var MAX_COMPONENT_DEPTH = 10;
1025
1025
  var META_REGEX = /<!--\s*@(\w+):\s*(.+?)\s*-->/g;
1026
1026
  var COMPONENT_REGEX = /<c-([a-z][a-z0-9-]*)\s*\/>/g;
@@ -1115,6 +1115,62 @@ var PageCompiler = class {
1115
1115
  path: pathname
1116
1116
  };
1117
1117
  }
1118
+ /**
1119
+ * Build all pages into static HTML for production deployment.
1120
+ * Walks app/pages/ recursively, compiles each page, and writes to outputDir.
1121
+ *
1122
+ * - Skips _layout.html (wrapper-only, not standalone)
1123
+ * - _404.html → 404.html (Firebase auto-serves for 404s)
1124
+ * - Injects optional script (e.g. production router) before </body>
1125
+ */
1126
+ buildForProduction(outputDir, scriptToInject) {
1127
+ const pages = [];
1128
+ const errors = [];
1129
+ if (!this.isActive()) {
1130
+ return { pages, errors };
1131
+ }
1132
+ const walk = (dir) => {
1133
+ const entries = readdirSync3(dir);
1134
+ for (const entry of entries) {
1135
+ const fullPath = join3(dir, entry);
1136
+ const stat = statSync3(fullPath);
1137
+ if (stat.isDirectory()) {
1138
+ walk(fullPath);
1139
+ continue;
1140
+ }
1141
+ if (!entry.endsWith(".html")) continue;
1142
+ if (entry === "_layout.html") continue;
1143
+ try {
1144
+ const compiled = this.compile(fullPath);
1145
+ let html = compiled.html;
1146
+ if (scriptToInject) {
1147
+ if (html.includes("</body>")) {
1148
+ html = html.replace("</body>", scriptToInject + "\n</body>");
1149
+ } else {
1150
+ html += scriptToInject;
1151
+ }
1152
+ }
1153
+ const relPath = relative2(this.pagesDir, fullPath);
1154
+ let outputPath;
1155
+ if (entry === "_404.html") {
1156
+ const parentRel = relative2(this.pagesDir, dir);
1157
+ outputPath = parentRel ? join3(outputDir, parentRel, "404.html") : join3(outputDir, "404.html");
1158
+ } else {
1159
+ outputPath = join3(outputDir, relPath);
1160
+ }
1161
+ const outputDirPath = dirname(outputPath);
1162
+ mkdirSync(outputDirPath, { recursive: true });
1163
+ writeFileSync(outputPath, html, "utf-8");
1164
+ pages.push(relPath);
1165
+ } catch (err) {
1166
+ const relPath = relative2(this.pagesDir, fullPath);
1167
+ errors.push(`${relPath}: ${err instanceof Error ? err.message : "Unknown error"}`);
1168
+ }
1169
+ }
1170
+ };
1171
+ walk(this.pagesDir);
1172
+ return { pages, errors };
1173
+ }
1118
1174
  // ─── Internal Methods ────────────────────────────────────────────
1119
1175
  /**
1120
1176
  * Extract <!-- @key: value --> metadata from HTML.
@@ -1193,7 +1249,7 @@ ${html}
1193
1249
  };
1194
1250
 
1195
1251
  // src/dev/env-manager.ts
1196
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync4 } from "fs";
1252
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
1197
1253
  import { resolve as resolve3 } from "path";
1198
1254
  var KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/i;
1199
1255
  var PLACEHOLDER_PATTERNS = [
@@ -1267,7 +1323,7 @@ var EnvManager = class {
1267
1323
  lines.push(`${key}=${value}`);
1268
1324
  }
1269
1325
  }
1270
- writeFileSync(this.envPath, lines.join("\n"), "utf-8");
1326
+ writeFileSync2(this.envPath, lines.join("\n"), "utf-8");
1271
1327
  if (description !== void 0) {
1272
1328
  const descriptions = this.readDescriptions();
1273
1329
  descriptions[key] = description;
@@ -1285,7 +1341,7 @@ var EnvManager = class {
1285
1341
  if (eqIdx === -1) return true;
1286
1342
  return trimmed.slice(0, eqIdx).trim() !== key;
1287
1343
  });
1288
- writeFileSync(this.envPath, filtered.join("\n"), "utf-8");
1344
+ writeFileSync2(this.envPath, filtered.join("\n"), "utf-8");
1289
1345
  const descriptions = this.readDescriptions();
1290
1346
  if (key in descriptions) {
1291
1347
  delete descriptions[key];
@@ -1302,7 +1358,7 @@ var EnvManager = class {
1302
1358
  }
1303
1359
  }
1304
1360
  writeDescriptions(descriptions) {
1305
- writeFileSync(
1361
+ writeFileSync2(
1306
1362
  this.descriptionsPath,
1307
1363
  JSON.stringify(descriptions, null, 2) + "\n",
1308
1364
  "utf-8"
@@ -2230,8 +2286,9 @@ function generateDashboardHtml(options) {
2230
2286
  }
2231
2287
 
2232
2288
  btn.disabled = true;
2233
- btn.textContent = 'Setting...';
2234
- status.style.display = 'none';
2289
+ btn.textContent = 'Setting up...';
2290
+ status.textContent = 'Selecting project, detecting web app, auto-filling config...';
2291
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1c;border:1px solid #3b82f6;color:#93c5fd;';
2235
2292
 
2236
2293
  fetch(API + '/__dev/setup/select-project', {
2237
2294
  method: 'POST',
@@ -2241,8 +2298,11 @@ function generateDashboardHtml(options) {
2241
2298
  .then(function(r) { return r.json(); })
2242
2299
  .then(function(data) {
2243
2300
  if (data.success) {
2244
- status.textContent = data.message;
2245
- status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2301
+ var msg = data.steps ? data.steps.join('\\n') : data.message;
2302
+ status.textContent = msg;
2303
+ status.style.whiteSpace = 'pre-line';
2304
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;white-space:pre-line;';
2305
+ btn.textContent = 'Done';
2246
2306
  setTimeout(refreshSetupStatus, 1000);
2247
2307
  } else {
2248
2308
  status.textContent = data.message;
@@ -2951,7 +3011,7 @@ function generateDashboardHtml(options) {
2951
3011
 
2952
3012
  // src/dev/firebase-setup.ts
2953
3013
  import { execFile as execFile2, spawn } from "child_process";
2954
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
3014
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2955
3015
  import { resolve as resolve5, join as join4 } from "path";
2956
3016
  import { tmpdir, platform, homedir } from "os";
2957
3017
  var FirebaseSetup = class {
@@ -2974,7 +3034,7 @@ var FirebaseSetup = class {
2974
3034
  saveState(partial) {
2975
3035
  const current = this.loadState();
2976
3036
  const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2977
- writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3037
+ writeFileSync3(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2978
3038
  }
2979
3039
  // ─── Status Check ──────────────────────────────────────────────────
2980
3040
  async getStatus() {
@@ -3076,7 +3136,7 @@ var FirebaseSetup = class {
3076
3136
  try {
3077
3137
  if (os === "darwin") {
3078
3138
  const scriptPath = join4(tmpdir(), "clawfire-firebase-login.command");
3079
- writeFileSync2(scriptPath, [
3139
+ writeFileSync3(scriptPath, [
3080
3140
  "#!/bin/bash",
3081
3141
  `cd "${this.projectDir}"`,
3082
3142
  cmd,
@@ -3098,7 +3158,7 @@ var FirebaseSetup = class {
3098
3158
  child.unref();
3099
3159
  } else {
3100
3160
  const scriptPath = join4(tmpdir(), "clawfire-firebase-login.sh");
3101
- writeFileSync2(scriptPath, [
3161
+ writeFileSync3(scriptPath, [
3102
3162
  "#!/bin/bash",
3103
3163
  `cd "${this.projectDir}"`,
3104
3164
  cmd,
@@ -3207,7 +3267,7 @@ var FirebaseSetup = class {
3207
3267
  rc.projects = {};
3208
3268
  }
3209
3269
  rc.projects.default = projectId;
3210
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3270
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3211
3271
  return { success: true, message: `Active project set to "${projectId}".` };
3212
3272
  } catch {
3213
3273
  try {
@@ -3215,7 +3275,7 @@ var FirebaseSetup = class {
3215
3275
  const rc = {
3216
3276
  projects: { default: projectId }
3217
3277
  };
3218
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3278
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3219
3279
  return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
3220
3280
  } catch (writeErr) {
3221
3281
  const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
@@ -3334,7 +3394,11 @@ var FirebaseSetup = class {
3334
3394
  config.hosting = {
3335
3395
  public: "public",
3336
3396
  ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
3337
- rewrites: [{ source: "**", destination: "/index.html" }]
3397
+ cleanUrls: true,
3398
+ rewrites: [
3399
+ { source: "/api/**", function: "api" },
3400
+ { source: "**", destination: "/index.html" }
3401
+ ]
3338
3402
  };
3339
3403
  }
3340
3404
  break;
@@ -3348,14 +3412,14 @@ var FirebaseSetup = class {
3348
3412
  }
3349
3413
  const rulesPath = resolve5(this.projectDir, "firestore.rules");
3350
3414
  if (!existsSync6(rulesPath)) {
3351
- writeFileSync2(
3415
+ writeFileSync3(
3352
3416
  rulesPath,
3353
3417
  "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if request.auth != null;\n }\n }\n}\n"
3354
3418
  );
3355
3419
  }
3356
3420
  const indexesPath = resolve5(this.projectDir, "firestore.indexes.json");
3357
3421
  if (!existsSync6(indexesPath)) {
3358
- writeFileSync2(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3422
+ writeFileSync3(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3359
3423
  }
3360
3424
  break;
3361
3425
  }
@@ -3365,7 +3429,7 @@ var FirebaseSetup = class {
3365
3429
  }
3366
3430
  const storageRulesPath = resolve5(this.projectDir, "storage.rules");
3367
3431
  if (!existsSync6(storageRulesPath)) {
3368
- writeFileSync2(
3432
+ writeFileSync3(
3369
3433
  storageRulesPath,
3370
3434
  "rules_version = '2';\nservice firebase.storage {\n match /b/{bucket}/o {\n match /{allPaths=**} {\n allow read, write: if request.auth != null;\n }\n }\n}\n"
3371
3435
  );
@@ -3373,7 +3437,7 @@ var FirebaseSetup = class {
3373
3437
  break;
3374
3438
  }
3375
3439
  }
3376
- writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3440
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3377
3441
  return { success: true, message: `${service} enabled in firebase.json.` };
3378
3442
  }
3379
3443
  // ─── Firestore Database Automation ──────────────────────────────────
@@ -3493,41 +3557,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3493
3557
  * Handles "ALREADY_EXISTS" gracefully — returns success.
3494
3558
  */
3495
3559
  async createFirestoreDatabase(location = "nam5") {
3496
- try {
3497
- await this.execTimeout(
3498
- "firebase",
3499
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3500
- 6e4
3501
- );
3502
- return { success: true, message: `Firestore database created (location: ${location}).` };
3503
- } catch (err) {
3504
- const msg = err instanceof Error ? err.message : "Unknown error";
3505
- if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3506
- return { success: true, message: "Firestore database already exists." };
3507
- }
3508
- if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3509
- const enableResult = await this.enableFirestoreApi();
3510
- if (!enableResult.success) {
3511
- return enableResult;
3560
+ const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
3561
+ const tryCreate = async () => {
3562
+ try {
3563
+ await this.execTimeout("firebase", dbArgs, 6e4);
3564
+ return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
3565
+ } catch (err) {
3566
+ const msg = err instanceof Error ? err.message : "Unknown error";
3567
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3568
+ return { ok: true, alreadyExists: true, needsApi: false, msg };
3512
3569
  }
3513
- await new Promise((r) => setTimeout(r, 5e3));
3514
- try {
3515
- await this.execTimeout(
3516
- "firebase",
3517
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3518
- 6e4
3519
- );
3520
- return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3521
- } catch (retryErr) {
3522
- const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
3523
- if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
3524
- return { success: true, message: "Firestore database already exists." };
3525
- }
3526
- return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
3570
+ if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3571
+ return { ok: false, alreadyExists: false, needsApi: true, msg };
3527
3572
  }
3573
+ return { ok: false, alreadyExists: false, needsApi: false, msg };
3574
+ }
3575
+ };
3576
+ const first = await tryCreate();
3577
+ if (first.ok) {
3578
+ return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
3579
+ }
3580
+ if (!first.needsApi) {
3581
+ return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
3582
+ }
3583
+ const enableResult = await this.enableFirestoreApi();
3584
+ if (!enableResult.success) {
3585
+ return enableResult;
3586
+ }
3587
+ const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
3588
+ for (let i = 0; i < waits.length; i++) {
3589
+ await new Promise((r) => setTimeout(r, waits[i]));
3590
+ const retry = await tryCreate();
3591
+ if (retry.ok) {
3592
+ return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3593
+ }
3594
+ if (!retry.needsApi) {
3595
+ return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
3528
3596
  }
3529
- return { success: false, message: `Failed to create Firestore database: ${msg}` };
3530
3597
  }
3598
+ return {
3599
+ success: false,
3600
+ message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
3601
+ };
3531
3602
  }
3532
3603
  /**
3533
3604
  * Deploy open Firestore security rules for dev testing.
@@ -3537,7 +3608,7 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3537
3608
  const rulesPath = resolve5(this.projectDir, "firestore.rules");
3538
3609
  try {
3539
3610
  if (openForDev) {
3540
- writeFileSync2(
3611
+ writeFileSync3(
3541
3612
  rulesPath,
3542
3613
  "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
3543
3614
  );
@@ -3803,6 +3874,92 @@ function generateRouterScript() {
3803
3874
  })();
3804
3875
  </script>`;
3805
3876
  }
3877
+ function generateProductionRouterScript() {
3878
+ return `
3879
+ <script data-clawfire-router>
3880
+ (function() {
3881
+ function updateActiveNav() {
3882
+ var path = location.pathname;
3883
+ var links = document.querySelectorAll('#nav-links a[href]');
3884
+ for (var i = 0; i < links.length; i++) {
3885
+ var href = links[i].getAttribute('href');
3886
+ if (!href || href.startsWith('http')) continue;
3887
+ var isActive = (path === '/' && href === '/') || (href !== '/' && path.startsWith(href));
3888
+ links[i].setAttribute('data-active', isActive ? 'true' : 'false');
3889
+ }
3890
+ }
3891
+
3892
+ function navigate(url) {
3893
+ var target = new URL(url, location.href);
3894
+ if (target.origin !== location.origin) return false;
3895
+ if (target.pathname === location.pathname && target.hash) return false;
3896
+
3897
+ fetch(target.pathname)
3898
+ .then(function(res) {
3899
+ if (!res.ok) throw new Error('Page not found');
3900
+ return res.text();
3901
+ })
3902
+ .then(function(html) {
3903
+ // Parse the fetched full HTML and extract #clawfire-page content
3904
+ var parser = new DOMParser();
3905
+ var doc = parser.parseFromString(html, 'text/html');
3906
+ var newPage = doc.getElementById('clawfire-page');
3907
+ if (!newPage) throw new Error('No #clawfire-page found');
3908
+
3909
+ var container = document.getElementById('clawfire-page');
3910
+ if (container) {
3911
+ container.innerHTML = newPage.innerHTML;
3912
+ // Execute scripts in new content
3913
+ var scripts = container.querySelectorAll('script');
3914
+ for (var i = 0; i < scripts.length; i++) {
3915
+ var newScript = document.createElement('script');
3916
+ if (scripts[i].src) {
3917
+ newScript.src = scripts[i].src;
3918
+ } else {
3919
+ newScript.textContent = scripts[i].textContent;
3920
+ }
3921
+ scripts[i].parentNode.replaceChild(newScript, scripts[i]);
3922
+ }
3923
+ }
3924
+ // Update title from fetched document
3925
+ var newTitle = doc.querySelector('title');
3926
+ if (newTitle) document.title = newTitle.textContent || '';
3927
+ // Update URL
3928
+ history.pushState(null, '', target.pathname);
3929
+ updateActiveNav();
3930
+ document.dispatchEvent(new CustomEvent('clawfire:navigate', { detail: { path: target.pathname } }));
3931
+ window.scrollTo(0, 0);
3932
+ })
3933
+ .catch(function() {
3934
+ // Fallback: full page navigation
3935
+ location.href = url;
3936
+ });
3937
+
3938
+ return true;
3939
+ }
3940
+
3941
+ document.addEventListener('click', function(e) {
3942
+ var anchor = e.target.closest ? e.target.closest('a[href]') : null;
3943
+ if (!anchor) return;
3944
+ var href = anchor.getAttribute('href');
3945
+ if (!href) return;
3946
+ if (href.startsWith('http') || href.startsWith('//')) return;
3947
+ if (anchor.target === '_blank') return;
3948
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
3949
+ if (anchor.hasAttribute('download')) return;
3950
+
3951
+ e.preventDefault();
3952
+ navigate(href);
3953
+ });
3954
+
3955
+ window.addEventListener('popstate', function() {
3956
+ navigate(location.pathname);
3957
+ });
3958
+
3959
+ updateActiveNav();
3960
+ })();
3961
+ </script>`;
3962
+ }
3806
3963
  var DevServer = class {
3807
3964
  frontendServer = null;
3808
3965
  apiServer = null;
@@ -4547,10 +4704,57 @@ ${liveReloadScript}
4547
4704
  sendJson({ success: false, message: "projectId is required" }, 400);
4548
4705
  return;
4549
4706
  }
4550
- this.firebaseSetup.selectProject(data.projectId).then((result) => {
4707
+ (async () => {
4708
+ const selectResult = await this.firebaseSetup.selectProject(data.projectId);
4709
+ if (!selectResult.success) {
4710
+ sendJson(selectResult);
4711
+ return;
4712
+ }
4713
+ const steps = [selectResult.message];
4714
+ try {
4715
+ const { apps } = await this.firebaseSetup.listWebApps();
4716
+ let webAppId = "";
4717
+ if (apps.length > 0) {
4718
+ webAppId = apps[0].appId;
4719
+ this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
4720
+ steps.push(`Web app "${apps[0].displayName}" selected.`);
4721
+ } else {
4722
+ const createResult = await this.firebaseSetup.createWebApp(data.projectId);
4723
+ if (createResult.success && createResult.appId) {
4724
+ webAppId = createResult.appId;
4725
+ steps.push(`Web app "${data.projectId}" created.`);
4726
+ } else {
4727
+ steps.push(`Web app creation skipped: ${createResult.message}`);
4728
+ }
4729
+ }
4730
+ if (webAppId) {
4731
+ try {
4732
+ const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
4733
+ const fields = {};
4734
+ for (const [key, value] of Object.entries(sdkConfig)) {
4735
+ if (value && typeof value === "string") {
4736
+ fields[key] = value;
4737
+ }
4738
+ }
4739
+ if (Object.keys(fields).length > 0) {
4740
+ for (const [key, value] of Object.entries(fields)) {
4741
+ try {
4742
+ this.updateProjectConfig(key, value);
4743
+ } catch {
4744
+ }
4745
+ }
4746
+ steps.push("Config auto-filled in clawfire.config.ts.");
4747
+ }
4748
+ } catch (configErr) {
4749
+ steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
4750
+ }
4751
+ }
4752
+ } catch (autoFillErr) {
4753
+ steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
4754
+ }
4551
4755
  clearFirebaseStatusCache();
4552
- sendJson(result);
4553
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4756
+ sendJson({ success: true, message: steps.join(" "), steps });
4757
+ })().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4554
4758
  } catch {
4555
4759
  sendJson({ success: false, message: "Invalid JSON body" }, 400);
4556
4760
  }
@@ -4584,10 +4788,25 @@ ${liveReloadScript}
4584
4788
  return;
4585
4789
  }
4586
4790
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4587
- this.firebaseSetup.deployHosting().then((result) => {
4588
- clearFirebaseStatusCache();
4589
- sendJson(result);
4590
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4791
+ (async () => {
4792
+ try {
4793
+ if (this.pageCompiler.isActive()) {
4794
+ const routerScript = generateProductionRouterScript();
4795
+ const buildResult = this.pageCompiler.buildForProduction(this.publicDir, routerScript);
4796
+ console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4797
+ if (buildResult.errors.length > 0) {
4798
+ for (const err of buildResult.errors) {
4799
+ console.log(` \x1B[31m\u2717\x1B[0m ${err}`);
4800
+ }
4801
+ }
4802
+ }
4803
+ const result = await this.firebaseSetup.deployHosting();
4804
+ clearFirebaseStatusCache();
4805
+ sendJson(result);
4806
+ } catch (err) {
4807
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4808
+ }
4809
+ })();
4591
4810
  return;
4592
4811
  }
4593
4812
  if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
@@ -4734,7 +4953,7 @@ ${liveReloadScript}
4734
4953
  throw new Error(`Key "${key}" not found in config`);
4735
4954
  }
4736
4955
  content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
4737
- writeFileSync3(configPath, content, "utf-8");
4956
+ writeFileSync4(configPath, content, "utf-8");
4738
4957
  }
4739
4958
  escapeRegex(s) {
4740
4959
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");