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.
@@ -9,7 +9,7 @@ import {
9
9
  // src/dev/dev-server.ts
10
10
  import http from "http";
11
11
  import { resolve as resolve5, relative as relative2, extname as extname3 } from "path";
12
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
12
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
13
13
  import { pathToFileURL } from "url";
14
14
 
15
15
  // src/core/schema.ts
@@ -645,8 +645,8 @@ var FileWatcher = class extends EventEmitter {
645
645
  };
646
646
 
647
647
  // src/dev/page-compiler.ts
648
- import { resolve, join as join2, dirname, basename } from "path";
649
- import { existsSync as existsSync2, readFileSync } from "fs";
648
+ import { resolve, join as join2, relative, dirname, basename } from "path";
649
+ import { existsSync as existsSync2, readFileSync, readdirSync as readdirSync2, statSync as statSync2, writeFileSync, mkdirSync } from "fs";
650
650
  var MAX_COMPONENT_DEPTH = 10;
651
651
  var META_REGEX = /<!--\s*@(\w+):\s*(.+?)\s*-->/g;
652
652
  var COMPONENT_REGEX = /<c-([a-z][a-z0-9-]*)\s*\/>/g;
@@ -741,6 +741,62 @@ var PageCompiler = class {
741
741
  path: pathname
742
742
  };
743
743
  }
744
+ /**
745
+ * Build all pages into static HTML for production deployment.
746
+ * Walks app/pages/ recursively, compiles each page, and writes to outputDir.
747
+ *
748
+ * - Skips _layout.html (wrapper-only, not standalone)
749
+ * - _404.html → 404.html (Firebase auto-serves for 404s)
750
+ * - Injects optional script (e.g. production router) before </body>
751
+ */
752
+ buildForProduction(outputDir, scriptToInject) {
753
+ const pages = [];
754
+ const errors = [];
755
+ if (!this.isActive()) {
756
+ return { pages, errors };
757
+ }
758
+ const walk = (dir) => {
759
+ const entries = readdirSync2(dir);
760
+ for (const entry of entries) {
761
+ const fullPath = join2(dir, entry);
762
+ const stat = statSync2(fullPath);
763
+ if (stat.isDirectory()) {
764
+ walk(fullPath);
765
+ continue;
766
+ }
767
+ if (!entry.endsWith(".html")) continue;
768
+ if (entry === "_layout.html") continue;
769
+ try {
770
+ const compiled = this.compile(fullPath);
771
+ let html = compiled.html;
772
+ if (scriptToInject) {
773
+ if (html.includes("</body>")) {
774
+ html = html.replace("</body>", scriptToInject + "\n</body>");
775
+ } else {
776
+ html += scriptToInject;
777
+ }
778
+ }
779
+ const relPath = relative(this.pagesDir, fullPath);
780
+ let outputPath;
781
+ if (entry === "_404.html") {
782
+ const parentRel = relative(this.pagesDir, dir);
783
+ outputPath = parentRel ? join2(outputDir, parentRel, "404.html") : join2(outputDir, "404.html");
784
+ } else {
785
+ outputPath = join2(outputDir, relPath);
786
+ }
787
+ const outputDirPath = dirname(outputPath);
788
+ mkdirSync(outputDirPath, { recursive: true });
789
+ writeFileSync(outputPath, html, "utf-8");
790
+ pages.push(relPath);
791
+ } catch (err) {
792
+ const relPath = relative(this.pagesDir, fullPath);
793
+ errors.push(`${relPath}: ${err instanceof Error ? err.message : "Unknown error"}`);
794
+ }
795
+ }
796
+ };
797
+ walk(this.pagesDir);
798
+ return { pages, errors };
799
+ }
744
800
  // ─── Internal Methods ────────────────────────────────────────────
745
801
  /**
746
802
  * Extract <!-- @key: value --> metadata from HTML.
@@ -819,7 +875,7 @@ ${html}
819
875
  };
820
876
 
821
877
  // src/dev/env-manager.ts
822
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
878
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
823
879
  import { resolve as resolve2 } from "path";
824
880
  var KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/i;
825
881
  var PLACEHOLDER_PATTERNS = [
@@ -893,7 +949,7 @@ var EnvManager = class {
893
949
  lines.push(`${key}=${value}`);
894
950
  }
895
951
  }
896
- writeFileSync(this.envPath, lines.join("\n"), "utf-8");
952
+ writeFileSync2(this.envPath, lines.join("\n"), "utf-8");
897
953
  if (description !== void 0) {
898
954
  const descriptions = this.readDescriptions();
899
955
  descriptions[key] = description;
@@ -911,7 +967,7 @@ var EnvManager = class {
911
967
  if (eqIdx === -1) return true;
912
968
  return trimmed.slice(0, eqIdx).trim() !== key;
913
969
  });
914
- writeFileSync(this.envPath, filtered.join("\n"), "utf-8");
970
+ writeFileSync2(this.envPath, filtered.join("\n"), "utf-8");
915
971
  const descriptions = this.readDescriptions();
916
972
  if (key in descriptions) {
917
973
  delete descriptions[key];
@@ -928,7 +984,7 @@ var EnvManager = class {
928
984
  }
929
985
  }
930
986
  writeDescriptions(descriptions) {
931
- writeFileSync(
987
+ writeFileSync2(
932
988
  this.descriptionsPath,
933
989
  JSON.stringify(descriptions, null, 2) + "\n",
934
990
  "utf-8"
@@ -1856,8 +1912,9 @@ function generateDashboardHtml(options) {
1856
1912
  }
1857
1913
 
1858
1914
  btn.disabled = true;
1859
- btn.textContent = 'Setting...';
1860
- status.style.display = 'none';
1915
+ btn.textContent = 'Setting up...';
1916
+ status.textContent = 'Selecting project, detecting web app, auto-filling config...';
1917
+ 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;';
1861
1918
 
1862
1919
  fetch(API + '/__dev/setup/select-project', {
1863
1920
  method: 'POST',
@@ -1867,8 +1924,11 @@ function generateDashboardHtml(options) {
1867
1924
  .then(function(r) { return r.json(); })
1868
1925
  .then(function(data) {
1869
1926
  if (data.success) {
1870
- status.textContent = data.message;
1871
- 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;';
1927
+ var msg = data.steps ? data.steps.join('\\n') : data.message;
1928
+ status.textContent = msg;
1929
+ status.style.whiteSpace = 'pre-line';
1930
+ 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;';
1931
+ btn.textContent = 'Done';
1872
1932
  setTimeout(refreshSetupStatus, 1000);
1873
1933
  } else {
1874
1934
  status.textContent = data.message;
@@ -2577,7 +2637,7 @@ function generateDashboardHtml(options) {
2577
2637
 
2578
2638
  // src/dev/firebase-setup.ts
2579
2639
  import { execFile as execFile2, spawn } from "child_process";
2580
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2640
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2581
2641
  import { resolve as resolve4, join as join3 } from "path";
2582
2642
  import { tmpdir, platform, homedir } from "os";
2583
2643
  var FirebaseSetup = class {
@@ -2600,7 +2660,7 @@ var FirebaseSetup = class {
2600
2660
  saveState(partial) {
2601
2661
  const current = this.loadState();
2602
2662
  const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2603
- writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2663
+ writeFileSync3(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2604
2664
  }
2605
2665
  // ─── Status Check ──────────────────────────────────────────────────
2606
2666
  async getStatus() {
@@ -2702,7 +2762,7 @@ var FirebaseSetup = class {
2702
2762
  try {
2703
2763
  if (os === "darwin") {
2704
2764
  const scriptPath = join3(tmpdir(), "clawfire-firebase-login.command");
2705
- writeFileSync2(scriptPath, [
2765
+ writeFileSync3(scriptPath, [
2706
2766
  "#!/bin/bash",
2707
2767
  `cd "${this.projectDir}"`,
2708
2768
  cmd,
@@ -2724,7 +2784,7 @@ var FirebaseSetup = class {
2724
2784
  child.unref();
2725
2785
  } else {
2726
2786
  const scriptPath = join3(tmpdir(), "clawfire-firebase-login.sh");
2727
- writeFileSync2(scriptPath, [
2787
+ writeFileSync3(scriptPath, [
2728
2788
  "#!/bin/bash",
2729
2789
  `cd "${this.projectDir}"`,
2730
2790
  cmd,
@@ -2833,7 +2893,7 @@ var FirebaseSetup = class {
2833
2893
  rc.projects = {};
2834
2894
  }
2835
2895
  rc.projects.default = projectId;
2836
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2896
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2837
2897
  return { success: true, message: `Active project set to "${projectId}".` };
2838
2898
  } catch {
2839
2899
  try {
@@ -2841,7 +2901,7 @@ var FirebaseSetup = class {
2841
2901
  const rc = {
2842
2902
  projects: { default: projectId }
2843
2903
  };
2844
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2904
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2845
2905
  return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
2846
2906
  } catch (writeErr) {
2847
2907
  const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
@@ -2960,7 +3020,11 @@ var FirebaseSetup = class {
2960
3020
  config.hosting = {
2961
3021
  public: "public",
2962
3022
  ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
2963
- rewrites: [{ source: "**", destination: "/index.html" }]
3023
+ cleanUrls: true,
3024
+ rewrites: [
3025
+ { source: "/api/**", function: "api" },
3026
+ { source: "**", destination: "/index.html" }
3027
+ ]
2964
3028
  };
2965
3029
  }
2966
3030
  break;
@@ -2974,14 +3038,14 @@ var FirebaseSetup = class {
2974
3038
  }
2975
3039
  const rulesPath = resolve4(this.projectDir, "firestore.rules");
2976
3040
  if (!existsSync5(rulesPath)) {
2977
- writeFileSync2(
3041
+ writeFileSync3(
2978
3042
  rulesPath,
2979
3043
  "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"
2980
3044
  );
2981
3045
  }
2982
3046
  const indexesPath = resolve4(this.projectDir, "firestore.indexes.json");
2983
3047
  if (!existsSync5(indexesPath)) {
2984
- writeFileSync2(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3048
+ writeFileSync3(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
2985
3049
  }
2986
3050
  break;
2987
3051
  }
@@ -2991,7 +3055,7 @@ var FirebaseSetup = class {
2991
3055
  }
2992
3056
  const storageRulesPath = resolve4(this.projectDir, "storage.rules");
2993
3057
  if (!existsSync5(storageRulesPath)) {
2994
- writeFileSync2(
3058
+ writeFileSync3(
2995
3059
  storageRulesPath,
2996
3060
  "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"
2997
3061
  );
@@ -2999,7 +3063,7 @@ var FirebaseSetup = class {
2999
3063
  break;
3000
3064
  }
3001
3065
  }
3002
- writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3066
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3003
3067
  return { success: true, message: `${service} enabled in firebase.json.` };
3004
3068
  }
3005
3069
  // ─── Firestore Database Automation ──────────────────────────────────
@@ -3119,41 +3183,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3119
3183
  * Handles "ALREADY_EXISTS" gracefully — returns success.
3120
3184
  */
3121
3185
  async createFirestoreDatabase(location = "nam5") {
3122
- try {
3123
- await this.execTimeout(
3124
- "firebase",
3125
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3126
- 6e4
3127
- );
3128
- return { success: true, message: `Firestore database created (location: ${location}).` };
3129
- } catch (err) {
3130
- const msg = err instanceof Error ? err.message : "Unknown error";
3131
- if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3132
- return { success: true, message: "Firestore database already exists." };
3133
- }
3134
- if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3135
- const enableResult = await this.enableFirestoreApi();
3136
- if (!enableResult.success) {
3137
- return enableResult;
3186
+ const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
3187
+ const tryCreate = async () => {
3188
+ try {
3189
+ await this.execTimeout("firebase", dbArgs, 6e4);
3190
+ return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
3191
+ } catch (err) {
3192
+ const msg = err instanceof Error ? err.message : "Unknown error";
3193
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3194
+ return { ok: true, alreadyExists: true, needsApi: false, msg };
3138
3195
  }
3139
- await new Promise((r) => setTimeout(r, 5e3));
3140
- try {
3141
- await this.execTimeout(
3142
- "firebase",
3143
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3144
- 6e4
3145
- );
3146
- return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3147
- } catch (retryErr) {
3148
- const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
3149
- if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
3150
- return { success: true, message: "Firestore database already exists." };
3151
- }
3152
- return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
3196
+ if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3197
+ return { ok: false, alreadyExists: false, needsApi: true, msg };
3153
3198
  }
3199
+ return { ok: false, alreadyExists: false, needsApi: false, msg };
3200
+ }
3201
+ };
3202
+ const first = await tryCreate();
3203
+ if (first.ok) {
3204
+ return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
3205
+ }
3206
+ if (!first.needsApi) {
3207
+ return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
3208
+ }
3209
+ const enableResult = await this.enableFirestoreApi();
3210
+ if (!enableResult.success) {
3211
+ return enableResult;
3212
+ }
3213
+ const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
3214
+ for (let i = 0; i < waits.length; i++) {
3215
+ await new Promise((r) => setTimeout(r, waits[i]));
3216
+ const retry = await tryCreate();
3217
+ if (retry.ok) {
3218
+ return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3219
+ }
3220
+ if (!retry.needsApi) {
3221
+ return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
3154
3222
  }
3155
- return { success: false, message: `Failed to create Firestore database: ${msg}` };
3156
3223
  }
3224
+ return {
3225
+ success: false,
3226
+ message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
3227
+ };
3157
3228
  }
3158
3229
  /**
3159
3230
  * Deploy open Firestore security rules for dev testing.
@@ -3163,7 +3234,7 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3163
3234
  const rulesPath = resolve4(this.projectDir, "firestore.rules");
3164
3235
  try {
3165
3236
  if (openForDev) {
3166
- writeFileSync2(
3237
+ writeFileSync3(
3167
3238
  rulesPath,
3168
3239
  "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
3169
3240
  );
@@ -3429,6 +3500,92 @@ function generateRouterScript() {
3429
3500
  })();
3430
3501
  </script>`;
3431
3502
  }
3503
+ function generateProductionRouterScript() {
3504
+ return `
3505
+ <script data-clawfire-router>
3506
+ (function() {
3507
+ function updateActiveNav() {
3508
+ var path = location.pathname;
3509
+ var links = document.querySelectorAll('#nav-links a[href]');
3510
+ for (var i = 0; i < links.length; i++) {
3511
+ var href = links[i].getAttribute('href');
3512
+ if (!href || href.startsWith('http')) continue;
3513
+ var isActive = (path === '/' && href === '/') || (href !== '/' && path.startsWith(href));
3514
+ links[i].setAttribute('data-active', isActive ? 'true' : 'false');
3515
+ }
3516
+ }
3517
+
3518
+ function navigate(url) {
3519
+ var target = new URL(url, location.href);
3520
+ if (target.origin !== location.origin) return false;
3521
+ if (target.pathname === location.pathname && target.hash) return false;
3522
+
3523
+ fetch(target.pathname)
3524
+ .then(function(res) {
3525
+ if (!res.ok) throw new Error('Page not found');
3526
+ return res.text();
3527
+ })
3528
+ .then(function(html) {
3529
+ // Parse the fetched full HTML and extract #clawfire-page content
3530
+ var parser = new DOMParser();
3531
+ var doc = parser.parseFromString(html, 'text/html');
3532
+ var newPage = doc.getElementById('clawfire-page');
3533
+ if (!newPage) throw new Error('No #clawfire-page found');
3534
+
3535
+ var container = document.getElementById('clawfire-page');
3536
+ if (container) {
3537
+ container.innerHTML = newPage.innerHTML;
3538
+ // Execute scripts in new content
3539
+ var scripts = container.querySelectorAll('script');
3540
+ for (var i = 0; i < scripts.length; i++) {
3541
+ var newScript = document.createElement('script');
3542
+ if (scripts[i].src) {
3543
+ newScript.src = scripts[i].src;
3544
+ } else {
3545
+ newScript.textContent = scripts[i].textContent;
3546
+ }
3547
+ scripts[i].parentNode.replaceChild(newScript, scripts[i]);
3548
+ }
3549
+ }
3550
+ // Update title from fetched document
3551
+ var newTitle = doc.querySelector('title');
3552
+ if (newTitle) document.title = newTitle.textContent || '';
3553
+ // Update URL
3554
+ history.pushState(null, '', target.pathname);
3555
+ updateActiveNav();
3556
+ document.dispatchEvent(new CustomEvent('clawfire:navigate', { detail: { path: target.pathname } }));
3557
+ window.scrollTo(0, 0);
3558
+ })
3559
+ .catch(function() {
3560
+ // Fallback: full page navigation
3561
+ location.href = url;
3562
+ });
3563
+
3564
+ return true;
3565
+ }
3566
+
3567
+ document.addEventListener('click', function(e) {
3568
+ var anchor = e.target.closest ? e.target.closest('a[href]') : null;
3569
+ if (!anchor) return;
3570
+ var href = anchor.getAttribute('href');
3571
+ if (!href) return;
3572
+ if (href.startsWith('http') || href.startsWith('//')) return;
3573
+ if (anchor.target === '_blank') return;
3574
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
3575
+ if (anchor.hasAttribute('download')) return;
3576
+
3577
+ e.preventDefault();
3578
+ navigate(href);
3579
+ });
3580
+
3581
+ window.addEventListener('popstate', function() {
3582
+ navigate(location.pathname);
3583
+ });
3584
+
3585
+ updateActiveNav();
3586
+ })();
3587
+ </script>`;
3588
+ }
3432
3589
  var DevServer = class {
3433
3590
  frontendServer = null;
3434
3591
  apiServer = null;
@@ -4173,10 +4330,57 @@ ${liveReloadScript}
4173
4330
  sendJson({ success: false, message: "projectId is required" }, 400);
4174
4331
  return;
4175
4332
  }
4176
- this.firebaseSetup.selectProject(data.projectId).then((result) => {
4333
+ (async () => {
4334
+ const selectResult = await this.firebaseSetup.selectProject(data.projectId);
4335
+ if (!selectResult.success) {
4336
+ sendJson(selectResult);
4337
+ return;
4338
+ }
4339
+ const steps = [selectResult.message];
4340
+ try {
4341
+ const { apps } = await this.firebaseSetup.listWebApps();
4342
+ let webAppId = "";
4343
+ if (apps.length > 0) {
4344
+ webAppId = apps[0].appId;
4345
+ this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
4346
+ steps.push(`Web app "${apps[0].displayName}" selected.`);
4347
+ } else {
4348
+ const createResult = await this.firebaseSetup.createWebApp(data.projectId);
4349
+ if (createResult.success && createResult.appId) {
4350
+ webAppId = createResult.appId;
4351
+ steps.push(`Web app "${data.projectId}" created.`);
4352
+ } else {
4353
+ steps.push(`Web app creation skipped: ${createResult.message}`);
4354
+ }
4355
+ }
4356
+ if (webAppId) {
4357
+ try {
4358
+ const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
4359
+ const fields = {};
4360
+ for (const [key, value] of Object.entries(sdkConfig)) {
4361
+ if (value && typeof value === "string") {
4362
+ fields[key] = value;
4363
+ }
4364
+ }
4365
+ if (Object.keys(fields).length > 0) {
4366
+ for (const [key, value] of Object.entries(fields)) {
4367
+ try {
4368
+ this.updateProjectConfig(key, value);
4369
+ } catch {
4370
+ }
4371
+ }
4372
+ steps.push("Config auto-filled in clawfire.config.ts.");
4373
+ }
4374
+ } catch (configErr) {
4375
+ steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
4376
+ }
4377
+ }
4378
+ } catch (autoFillErr) {
4379
+ steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
4380
+ }
4177
4381
  clearFirebaseStatusCache();
4178
- sendJson(result);
4179
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4382
+ sendJson({ success: true, message: steps.join(" "), steps });
4383
+ })().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4180
4384
  } catch {
4181
4385
  sendJson({ success: false, message: "Invalid JSON body" }, 400);
4182
4386
  }
@@ -4210,10 +4414,25 @@ ${liveReloadScript}
4210
4414
  return;
4211
4415
  }
4212
4416
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4213
- this.firebaseSetup.deployHosting().then((result) => {
4214
- clearFirebaseStatusCache();
4215
- sendJson(result);
4216
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4417
+ (async () => {
4418
+ try {
4419
+ if (this.pageCompiler.isActive()) {
4420
+ const routerScript = generateProductionRouterScript();
4421
+ const buildResult = this.pageCompiler.buildForProduction(this.publicDir, routerScript);
4422
+ console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4423
+ if (buildResult.errors.length > 0) {
4424
+ for (const err of buildResult.errors) {
4425
+ console.log(` \x1B[31m\u2717\x1B[0m ${err}`);
4426
+ }
4427
+ }
4428
+ }
4429
+ const result = await this.firebaseSetup.deployHosting();
4430
+ clearFirebaseStatusCache();
4431
+ sendJson(result);
4432
+ } catch (err) {
4433
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4434
+ }
4435
+ })();
4217
4436
  return;
4218
4437
  }
4219
4438
  if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
@@ -4360,7 +4579,7 @@ ${liveReloadScript}
4360
4579
  throw new Error(`Key "${key}" not found in config`);
4361
4580
  }
4362
4581
  content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
4363
- writeFileSync3(configPath, content, "utf-8");
4582
+ writeFileSync4(configPath, content, "utf-8");
4364
4583
  }
4365
4584
  escapeRegex(s) {
4366
4585
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");