clawfire 0.6.6 → 0.6.8

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"
@@ -2581,7 +2637,7 @@ function generateDashboardHtml(options) {
2581
2637
 
2582
2638
  // src/dev/firebase-setup.ts
2583
2639
  import { execFile as execFile2, spawn } from "child_process";
2584
- 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";
2585
2641
  import { resolve as resolve4, join as join3 } from "path";
2586
2642
  import { tmpdir, platform, homedir } from "os";
2587
2643
  var FirebaseSetup = class {
@@ -2604,7 +2660,7 @@ var FirebaseSetup = class {
2604
2660
  saveState(partial) {
2605
2661
  const current = this.loadState();
2606
2662
  const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2607
- writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2663
+ writeFileSync3(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2608
2664
  }
2609
2665
  // ─── Status Check ──────────────────────────────────────────────────
2610
2666
  async getStatus() {
@@ -2706,7 +2762,7 @@ var FirebaseSetup = class {
2706
2762
  try {
2707
2763
  if (os === "darwin") {
2708
2764
  const scriptPath = join3(tmpdir(), "clawfire-firebase-login.command");
2709
- writeFileSync2(scriptPath, [
2765
+ writeFileSync3(scriptPath, [
2710
2766
  "#!/bin/bash",
2711
2767
  `cd "${this.projectDir}"`,
2712
2768
  cmd,
@@ -2728,7 +2784,7 @@ var FirebaseSetup = class {
2728
2784
  child.unref();
2729
2785
  } else {
2730
2786
  const scriptPath = join3(tmpdir(), "clawfire-firebase-login.sh");
2731
- writeFileSync2(scriptPath, [
2787
+ writeFileSync3(scriptPath, [
2732
2788
  "#!/bin/bash",
2733
2789
  `cd "${this.projectDir}"`,
2734
2790
  cmd,
@@ -2837,7 +2893,7 @@ var FirebaseSetup = class {
2837
2893
  rc.projects = {};
2838
2894
  }
2839
2895
  rc.projects.default = projectId;
2840
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2896
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2841
2897
  return { success: true, message: `Active project set to "${projectId}".` };
2842
2898
  } catch {
2843
2899
  try {
@@ -2845,7 +2901,7 @@ var FirebaseSetup = class {
2845
2901
  const rc = {
2846
2902
  projects: { default: projectId }
2847
2903
  };
2848
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2904
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
2849
2905
  return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
2850
2906
  } catch (writeErr) {
2851
2907
  const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
@@ -2964,7 +3020,11 @@ var FirebaseSetup = class {
2964
3020
  config.hosting = {
2965
3021
  public: "public",
2966
3022
  ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
2967
- rewrites: [{ source: "**", destination: "/index.html" }]
3023
+ cleanUrls: true,
3024
+ rewrites: [
3025
+ { source: "/api/**", function: "api" },
3026
+ { source: "**", destination: "/index.html" }
3027
+ ]
2968
3028
  };
2969
3029
  }
2970
3030
  break;
@@ -2978,14 +3038,14 @@ var FirebaseSetup = class {
2978
3038
  }
2979
3039
  const rulesPath = resolve4(this.projectDir, "firestore.rules");
2980
3040
  if (!existsSync5(rulesPath)) {
2981
- writeFileSync2(
3041
+ writeFileSync3(
2982
3042
  rulesPath,
2983
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"
2984
3044
  );
2985
3045
  }
2986
3046
  const indexesPath = resolve4(this.projectDir, "firestore.indexes.json");
2987
3047
  if (!existsSync5(indexesPath)) {
2988
- writeFileSync2(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3048
+ writeFileSync3(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
2989
3049
  }
2990
3050
  break;
2991
3051
  }
@@ -2995,7 +3055,7 @@ var FirebaseSetup = class {
2995
3055
  }
2996
3056
  const storageRulesPath = resolve4(this.projectDir, "storage.rules");
2997
3057
  if (!existsSync5(storageRulesPath)) {
2998
- writeFileSync2(
3058
+ writeFileSync3(
2999
3059
  storageRulesPath,
3000
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"
3001
3061
  );
@@ -3003,7 +3063,7 @@ var FirebaseSetup = class {
3003
3063
  break;
3004
3064
  }
3005
3065
  }
3006
- writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3066
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3007
3067
  return { success: true, message: `${service} enabled in firebase.json.` };
3008
3068
  }
3009
3069
  // ─── Firestore Database Automation ──────────────────────────────────
@@ -3174,7 +3234,7 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3174
3234
  const rulesPath = resolve4(this.projectDir, "firestore.rules");
3175
3235
  try {
3176
3236
  if (openForDev) {
3177
- writeFileSync2(
3237
+ writeFileSync3(
3178
3238
  rulesPath,
3179
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"
3180
3240
  );
@@ -3440,6 +3500,92 @@ function generateRouterScript() {
3440
3500
  })();
3441
3501
  </script>`;
3442
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
+ }
3443
3589
  var DevServer = class {
3444
3590
  frontendServer = null;
3445
3591
  apiServer = null;
@@ -4268,10 +4414,38 @@ ${liveReloadScript}
4268
4414
  return;
4269
4415
  }
4270
4416
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4271
- this.firebaseSetup.deployHosting().then((result) => {
4272
- clearFirebaseStatusCache();
4273
- sendJson(result);
4274
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4417
+ (async () => {
4418
+ 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
+ }
4426
+ }
4427
+ const configScript = Object.keys(firebaseConfig).length > 0 ? `
4428
+ <script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
4429
+ const routerScript = generateProductionRouterScript();
4430
+ const scriptToInject = configScript + routerScript;
4431
+ const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
4432
+ console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4433
+ if (configScript) {
4434
+ console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
4435
+ }
4436
+ if (buildResult.errors.length > 0) {
4437
+ for (const err of buildResult.errors) {
4438
+ console.log(` \x1B[31m\u2717\x1B[0m ${err}`);
4439
+ }
4440
+ }
4441
+ }
4442
+ const result = await this.firebaseSetup.deployHosting();
4443
+ clearFirebaseStatusCache();
4444
+ sendJson(result);
4445
+ } catch (err) {
4446
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4447
+ }
4448
+ })();
4275
4449
  return;
4276
4450
  }
4277
4451
  if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
@@ -4418,7 +4592,7 @@ ${liveReloadScript}
4418
4592
  throw new Error(`Key "${key}" not found in config`);
4419
4593
  }
4420
4594
  content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
4421
- writeFileSync3(configPath, content, "utf-8");
4595
+ writeFileSync4(configPath, content, "utf-8");
4422
4596
  }
4423
4597
  escapeRegex(s) {
4424
4598
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
package/dist/dev.cjs CHANGED
@@ -1153,6 +1153,62 @@ var PageCompiler = class {
1153
1153
  path: pathname
1154
1154
  };
1155
1155
  }
1156
+ /**
1157
+ * Build all pages into static HTML for production deployment.
1158
+ * Walks app/pages/ recursively, compiles each page, and writes to outputDir.
1159
+ *
1160
+ * - Skips _layout.html (wrapper-only, not standalone)
1161
+ * - _404.html → 404.html (Firebase auto-serves for 404s)
1162
+ * - Injects optional script (e.g. production router) before </body>
1163
+ */
1164
+ buildForProduction(outputDir, scriptToInject) {
1165
+ const pages = [];
1166
+ const errors = [];
1167
+ if (!this.isActive()) {
1168
+ return { pages, errors };
1169
+ }
1170
+ const walk = (dir) => {
1171
+ const entries = (0, import_node_fs.readdirSync)(dir);
1172
+ for (const entry of entries) {
1173
+ const fullPath = (0, import_node_path.join)(dir, entry);
1174
+ const stat = (0, import_node_fs.statSync)(fullPath);
1175
+ if (stat.isDirectory()) {
1176
+ walk(fullPath);
1177
+ continue;
1178
+ }
1179
+ if (!entry.endsWith(".html")) continue;
1180
+ if (entry === "_layout.html") continue;
1181
+ try {
1182
+ const compiled = this.compile(fullPath);
1183
+ let html = compiled.html;
1184
+ if (scriptToInject) {
1185
+ if (html.includes("</body>")) {
1186
+ html = html.replace("</body>", scriptToInject + "\n</body>");
1187
+ } else {
1188
+ html += scriptToInject;
1189
+ }
1190
+ }
1191
+ const relPath = (0, import_node_path.relative)(this.pagesDir, fullPath);
1192
+ let outputPath;
1193
+ if (entry === "_404.html") {
1194
+ const parentRel = (0, import_node_path.relative)(this.pagesDir, dir);
1195
+ outputPath = parentRel ? (0, import_node_path.join)(outputDir, parentRel, "404.html") : (0, import_node_path.join)(outputDir, "404.html");
1196
+ } else {
1197
+ outputPath = (0, import_node_path.join)(outputDir, relPath);
1198
+ }
1199
+ const outputDirPath = (0, import_node_path.dirname)(outputPath);
1200
+ (0, import_node_fs.mkdirSync)(outputDirPath, { recursive: true });
1201
+ (0, import_node_fs.writeFileSync)(outputPath, html, "utf-8");
1202
+ pages.push(relPath);
1203
+ } catch (err) {
1204
+ const relPath = (0, import_node_path.relative)(this.pagesDir, fullPath);
1205
+ errors.push(`${relPath}: ${err instanceof Error ? err.message : "Unknown error"}`);
1206
+ }
1207
+ }
1208
+ };
1209
+ walk(this.pagesDir);
1210
+ return { pages, errors };
1211
+ }
1156
1212
  // ─── Internal Methods ────────────────────────────────────────────
1157
1213
  /**
1158
1214
  * Extract <!-- @key: value --> metadata from HTML.
@@ -3376,7 +3432,11 @@ var FirebaseSetup = class {
3376
3432
  config.hosting = {
3377
3433
  public: "public",
3378
3434
  ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
3379
- rewrites: [{ source: "**", destination: "/index.html" }]
3435
+ cleanUrls: true,
3436
+ rewrites: [
3437
+ { source: "/api/**", function: "api" },
3438
+ { source: "**", destination: "/index.html" }
3439
+ ]
3380
3440
  };
3381
3441
  }
3382
3442
  break;
@@ -3852,6 +3912,92 @@ function generateRouterScript() {
3852
3912
  })();
3853
3913
  </script>`;
3854
3914
  }
3915
+ function generateProductionRouterScript() {
3916
+ return `
3917
+ <script data-clawfire-router>
3918
+ (function() {
3919
+ function updateActiveNav() {
3920
+ var path = location.pathname;
3921
+ var links = document.querySelectorAll('#nav-links a[href]');
3922
+ for (var i = 0; i < links.length; i++) {
3923
+ var href = links[i].getAttribute('href');
3924
+ if (!href || href.startsWith('http')) continue;
3925
+ var isActive = (path === '/' && href === '/') || (href !== '/' && path.startsWith(href));
3926
+ links[i].setAttribute('data-active', isActive ? 'true' : 'false');
3927
+ }
3928
+ }
3929
+
3930
+ function navigate(url) {
3931
+ var target = new URL(url, location.href);
3932
+ if (target.origin !== location.origin) return false;
3933
+ if (target.pathname === location.pathname && target.hash) return false;
3934
+
3935
+ fetch(target.pathname)
3936
+ .then(function(res) {
3937
+ if (!res.ok) throw new Error('Page not found');
3938
+ return res.text();
3939
+ })
3940
+ .then(function(html) {
3941
+ // Parse the fetched full HTML and extract #clawfire-page content
3942
+ var parser = new DOMParser();
3943
+ var doc = parser.parseFromString(html, 'text/html');
3944
+ var newPage = doc.getElementById('clawfire-page');
3945
+ if (!newPage) throw new Error('No #clawfire-page found');
3946
+
3947
+ var container = document.getElementById('clawfire-page');
3948
+ if (container) {
3949
+ container.innerHTML = newPage.innerHTML;
3950
+ // Execute scripts in new content
3951
+ var scripts = container.querySelectorAll('script');
3952
+ for (var i = 0; i < scripts.length; i++) {
3953
+ var newScript = document.createElement('script');
3954
+ if (scripts[i].src) {
3955
+ newScript.src = scripts[i].src;
3956
+ } else {
3957
+ newScript.textContent = scripts[i].textContent;
3958
+ }
3959
+ scripts[i].parentNode.replaceChild(newScript, scripts[i]);
3960
+ }
3961
+ }
3962
+ // Update title from fetched document
3963
+ var newTitle = doc.querySelector('title');
3964
+ if (newTitle) document.title = newTitle.textContent || '';
3965
+ // Update URL
3966
+ history.pushState(null, '', target.pathname);
3967
+ updateActiveNav();
3968
+ document.dispatchEvent(new CustomEvent('clawfire:navigate', { detail: { path: target.pathname } }));
3969
+ window.scrollTo(0, 0);
3970
+ })
3971
+ .catch(function() {
3972
+ // Fallback: full page navigation
3973
+ location.href = url;
3974
+ });
3975
+
3976
+ return true;
3977
+ }
3978
+
3979
+ document.addEventListener('click', function(e) {
3980
+ var anchor = e.target.closest ? e.target.closest('a[href]') : null;
3981
+ if (!anchor) return;
3982
+ var href = anchor.getAttribute('href');
3983
+ if (!href) return;
3984
+ if (href.startsWith('http') || href.startsWith('//')) return;
3985
+ if (anchor.target === '_blank') return;
3986
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
3987
+ if (anchor.hasAttribute('download')) return;
3988
+
3989
+ e.preventDefault();
3990
+ navigate(href);
3991
+ });
3992
+
3993
+ window.addEventListener('popstate', function() {
3994
+ navigate(location.pathname);
3995
+ });
3996
+
3997
+ updateActiveNav();
3998
+ })();
3999
+ </script>`;
4000
+ }
3855
4001
  var DevServer = class {
3856
4002
  frontendServer = null;
3857
4003
  apiServer = null;
@@ -4680,10 +4826,38 @@ ${liveReloadScript}
4680
4826
  return;
4681
4827
  }
4682
4828
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4683
- this.firebaseSetup.deployHosting().then((result) => {
4684
- clearFirebaseStatusCache();
4685
- sendJson(result);
4686
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4829
+ (async () => {
4830
+ 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
+ }
4838
+ }
4839
+ const configScript = Object.keys(firebaseConfig).length > 0 ? `
4840
+ <script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
4841
+ const routerScript = generateProductionRouterScript();
4842
+ const scriptToInject = configScript + routerScript;
4843
+ const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
4844
+ console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4845
+ if (configScript) {
4846
+ console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
4847
+ }
4848
+ if (buildResult.errors.length > 0) {
4849
+ for (const err of buildResult.errors) {
4850
+ console.log(` \x1B[31m\u2717\x1B[0m ${err}`);
4851
+ }
4852
+ }
4853
+ }
4854
+ const result = await this.firebaseSetup.deployHosting();
4855
+ clearFirebaseStatusCache();
4856
+ sendJson(result);
4857
+ } catch (err) {
4858
+ sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4859
+ }
4860
+ })();
4687
4861
  return;
4688
4862
  }
4689
4863
  if (url.pathname === "/__dev/enable-service" && req.method === "POST") {