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.
- package/dist/admin.cjs +1 -0
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +1 -0
- package/dist/admin.d.ts +1 -0
- package/dist/admin.js +1 -0
- package/dist/admin.js.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/{dev-server-PQP33VSE.js → dev-server-ESMIOA6C.js} +198 -24
- package/dist/dev.cjs +179 -5
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +198 -24
- package/dist/dev.js.map +1 -1
- package/dist/functions.cjs +1 -0
- package/dist/functions.cjs.map +1 -1
- package/dist/functions.js +1 -0
- package/dist/functions.js.map +1 -1
- package/dist/{hosting-7WVFHAYJ.js → hosting-GHMSBL2N.js} +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
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") {
|