clawfire 0.6.6 → 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"
@@ -2955,7 +3011,7 @@ function generateDashboardHtml(options) {
2955
3011
 
2956
3012
  // src/dev/firebase-setup.ts
2957
3013
  import { execFile as execFile2, spawn } from "child_process";
2958
- 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";
2959
3015
  import { resolve as resolve5, join as join4 } from "path";
2960
3016
  import { tmpdir, platform, homedir } from "os";
2961
3017
  var FirebaseSetup = class {
@@ -2978,7 +3034,7 @@ var FirebaseSetup = class {
2978
3034
  saveState(partial) {
2979
3035
  const current = this.loadState();
2980
3036
  const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
2981
- writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3037
+ writeFileSync3(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
2982
3038
  }
2983
3039
  // ─── Status Check ──────────────────────────────────────────────────
2984
3040
  async getStatus() {
@@ -3080,7 +3136,7 @@ var FirebaseSetup = class {
3080
3136
  try {
3081
3137
  if (os === "darwin") {
3082
3138
  const scriptPath = join4(tmpdir(), "clawfire-firebase-login.command");
3083
- writeFileSync2(scriptPath, [
3139
+ writeFileSync3(scriptPath, [
3084
3140
  "#!/bin/bash",
3085
3141
  `cd "${this.projectDir}"`,
3086
3142
  cmd,
@@ -3102,7 +3158,7 @@ var FirebaseSetup = class {
3102
3158
  child.unref();
3103
3159
  } else {
3104
3160
  const scriptPath = join4(tmpdir(), "clawfire-firebase-login.sh");
3105
- writeFileSync2(scriptPath, [
3161
+ writeFileSync3(scriptPath, [
3106
3162
  "#!/bin/bash",
3107
3163
  `cd "${this.projectDir}"`,
3108
3164
  cmd,
@@ -3211,7 +3267,7 @@ var FirebaseSetup = class {
3211
3267
  rc.projects = {};
3212
3268
  }
3213
3269
  rc.projects.default = projectId;
3214
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3270
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3215
3271
  return { success: true, message: `Active project set to "${projectId}".` };
3216
3272
  } catch {
3217
3273
  try {
@@ -3219,7 +3275,7 @@ var FirebaseSetup = class {
3219
3275
  const rc = {
3220
3276
  projects: { default: projectId }
3221
3277
  };
3222
- writeFileSync2(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3278
+ writeFileSync3(firebasercPath, JSON.stringify(rc, null, 2) + "\n", "utf-8");
3223
3279
  return { success: true, message: `Active project set to "${projectId}" (via .firebaserc).` };
3224
3280
  } catch (writeErr) {
3225
3281
  const msg = writeErr instanceof Error ? writeErr.message : "Unknown error";
@@ -3338,7 +3394,11 @@ var FirebaseSetup = class {
3338
3394
  config.hosting = {
3339
3395
  public: "public",
3340
3396
  ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
3341
- rewrites: [{ source: "**", destination: "/index.html" }]
3397
+ cleanUrls: true,
3398
+ rewrites: [
3399
+ { source: "/api/**", function: "api" },
3400
+ { source: "**", destination: "/index.html" }
3401
+ ]
3342
3402
  };
3343
3403
  }
3344
3404
  break;
@@ -3352,14 +3412,14 @@ var FirebaseSetup = class {
3352
3412
  }
3353
3413
  const rulesPath = resolve5(this.projectDir, "firestore.rules");
3354
3414
  if (!existsSync6(rulesPath)) {
3355
- writeFileSync2(
3415
+ writeFileSync3(
3356
3416
  rulesPath,
3357
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"
3358
3418
  );
3359
3419
  }
3360
3420
  const indexesPath = resolve5(this.projectDir, "firestore.indexes.json");
3361
3421
  if (!existsSync6(indexesPath)) {
3362
- writeFileSync2(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3422
+ writeFileSync3(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
3363
3423
  }
3364
3424
  break;
3365
3425
  }
@@ -3369,7 +3429,7 @@ var FirebaseSetup = class {
3369
3429
  }
3370
3430
  const storageRulesPath = resolve5(this.projectDir, "storage.rules");
3371
3431
  if (!existsSync6(storageRulesPath)) {
3372
- writeFileSync2(
3432
+ writeFileSync3(
3373
3433
  storageRulesPath,
3374
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"
3375
3435
  );
@@ -3377,7 +3437,7 @@ var FirebaseSetup = class {
3377
3437
  break;
3378
3438
  }
3379
3439
  }
3380
- writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3440
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3381
3441
  return { success: true, message: `${service} enabled in firebase.json.` };
3382
3442
  }
3383
3443
  // ─── Firestore Database Automation ──────────────────────────────────
@@ -3548,7 +3608,7 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3548
3608
  const rulesPath = resolve5(this.projectDir, "firestore.rules");
3549
3609
  try {
3550
3610
  if (openForDev) {
3551
- writeFileSync2(
3611
+ writeFileSync3(
3552
3612
  rulesPath,
3553
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"
3554
3614
  );
@@ -3814,6 +3874,92 @@ function generateRouterScript() {
3814
3874
  })();
3815
3875
  </script>`;
3816
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
+ }
3817
3963
  var DevServer = class {
3818
3964
  frontendServer = null;
3819
3965
  apiServer = null;
@@ -4642,10 +4788,25 @@ ${liveReloadScript}
4642
4788
  return;
4643
4789
  }
4644
4790
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4645
- this.firebaseSetup.deployHosting().then((result) => {
4646
- clearFirebaseStatusCache();
4647
- sendJson(result);
4648
- }).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
+ })();
4649
4810
  return;
4650
4811
  }
4651
4812
  if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
@@ -4792,7 +4953,7 @@ ${liveReloadScript}
4792
4953
  throw new Error(`Key "${key}" not found in config`);
4793
4954
  }
4794
4955
  content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
4795
- writeFileSync3(configPath, content, "utf-8");
4956
+ writeFileSync4(configPath, content, "utf-8");
4796
4957
  }
4797
4958
  escapeRegex(s) {
4798
4959
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");