clawfire 0.6.1 → 0.6.3
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/cli.js +1 -1
- package/dist/{dev-server-GD445Q6F.js → dev-server-NWB66EJT.js} +259 -1
- package/dist/dev.cjs +259 -1
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +259 -1
- package/dist/dev.js.map +1 -1
- package/package.json +2 -2
- package/templates/starter/functions/routes/todos/create.ts +1 -1
- package/templates/starter/functions/routes/todos/delete.ts +1 -1
- package/templates/starter/functions/routes/todos/list.ts +1 -1
- package/templates/starter/functions/routes/todos/update.ts +1 -1
- package/templates/starter/functions/store.ts +95 -21
- package/templates/starter/package.json +2 -1
package/dist/dev.js
CHANGED
|
@@ -1778,7 +1778,48 @@ function generateDashboardHtml(options) {
|
|
|
1778
1778
|
</div>
|
|
1779
1779
|
</div>
|
|
1780
1780
|
|
|
1781
|
-
<!-- Section 3:
|
|
1781
|
+
<!-- Section 3: Database -->
|
|
1782
|
+
<div style="margin-bottom:32px;">
|
|
1783
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
|
|
1784
|
+
<div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
|
|
1785
|
+
<!-- Mode Toggle -->
|
|
1786
|
+
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
|
|
1787
|
+
<span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
|
|
1788
|
+
<label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
|
|
1789
|
+
<span id="db-radio-memory" style="width:16px;height:16px;border-radius:50%;border:2px solid #f97316;display:inline-flex;align-items:center;justify-content:center;">
|
|
1790
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
|
|
1791
|
+
</span>
|
|
1792
|
+
<span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
|
|
1793
|
+
</label>
|
|
1794
|
+
<label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
|
|
1795
|
+
<span id="db-radio-firestore" style="width:16px;height:16px;border-radius:50%;border:2px solid #666;display:inline-flex;align-items:center;justify-content:center;">
|
|
1796
|
+
</span>
|
|
1797
|
+
<span style="color:#a3a3a3;font-size:13px;">Firestore</span>
|
|
1798
|
+
</label>
|
|
1799
|
+
</div>
|
|
1800
|
+
|
|
1801
|
+
<!-- Connect to Firestore panel -->
|
|
1802
|
+
<div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
|
|
1803
|
+
<div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
|
|
1804
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
|
|
1805
|
+
<button id="db-connect-btn" onclick="connectFirestore()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1806
|
+
Connect to Firestore
|
|
1807
|
+
</button>
|
|
1808
|
+
<div style="margin-top:10px;font-size:12px;color:#eab308;">
|
|
1809
|
+
⚠ Open rules (allow all) for dev only. Use /clawfire-model for production rules.
|
|
1810
|
+
</div>
|
|
1811
|
+
<div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
|
|
1812
|
+
</div>
|
|
1813
|
+
|
|
1814
|
+
<!-- Firestore Connected banner -->
|
|
1815
|
+
<div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
|
|
1816
|
+
<div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
|
|
1817
|
+
<div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
|
|
1818
|
+
</div>
|
|
1819
|
+
</div>
|
|
1820
|
+
</div>
|
|
1821
|
+
|
|
1822
|
+
<!-- Section 4: Config Overview -->
|
|
1782
1823
|
<div style="margin-bottom:32px;">
|
|
1783
1824
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1784
1825
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
@@ -1874,11 +1915,13 @@ function generateDashboardHtml(options) {
|
|
|
1874
1915
|
fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
|
|
1875
1916
|
fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
|
|
1876
1917
|
fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
|
|
1918
|
+
fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
|
|
1877
1919
|
]).then(function(results) {
|
|
1878
1920
|
renderFirebaseStatus(results[0]);
|
|
1879
1921
|
renderConfig(results[1]);
|
|
1880
1922
|
renderEnvVars(results[2]);
|
|
1881
1923
|
renderSetupWizard(results[3]);
|
|
1924
|
+
renderDbMode(results[4].mode || 'memory');
|
|
1882
1925
|
document.getElementById('dash-loading').style.display = 'none';
|
|
1883
1926
|
document.getElementById('dash-loaded').style.display = 'block';
|
|
1884
1927
|
}).catch(function(err) {
|
|
@@ -2663,6 +2706,100 @@ function generateDashboardHtml(options) {
|
|
|
2663
2706
|
});
|
|
2664
2707
|
};
|
|
2665
2708
|
|
|
2709
|
+
// \u2500\u2500\u2500 Database Mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2710
|
+
function loadDbMode() {
|
|
2711
|
+
fetch(API + '/__dev/db-mode')
|
|
2712
|
+
.then(function(r) { return r.json(); })
|
|
2713
|
+
.then(function(data) { renderDbMode(data.mode || 'memory'); })
|
|
2714
|
+
.catch(function() { renderDbMode('memory'); });
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function renderDbMode(mode) {
|
|
2718
|
+
var radioMem = document.getElementById('db-radio-memory');
|
|
2719
|
+
var radioFs = document.getElementById('db-radio-firestore');
|
|
2720
|
+
var connectPanel = document.getElementById('db-connect-panel');
|
|
2721
|
+
var connectedBanner = document.getElementById('db-connected-banner');
|
|
2722
|
+
var memLabel = document.getElementById('db-mode-memory');
|
|
2723
|
+
var fsLabel = document.getElementById('db-mode-firestore');
|
|
2724
|
+
|
|
2725
|
+
if (mode === 'firestore') {
|
|
2726
|
+
radioMem.innerHTML = '';
|
|
2727
|
+
radioMem.style.borderColor = '#666';
|
|
2728
|
+
radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2729
|
+
radioFs.style.borderColor = '#f97316';
|
|
2730
|
+
memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2731
|
+
fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2732
|
+
connectPanel.style.display = 'none';
|
|
2733
|
+
connectedBanner.style.display = 'block';
|
|
2734
|
+
} else {
|
|
2735
|
+
radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2736
|
+
radioMem.style.borderColor = '#f97316';
|
|
2737
|
+
radioFs.innerHTML = '';
|
|
2738
|
+
radioFs.style.borderColor = '#666';
|
|
2739
|
+
memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2740
|
+
fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2741
|
+
connectPanel.style.display = 'block';
|
|
2742
|
+
connectedBanner.style.display = 'none';
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
window.switchDbMode = function(mode) {
|
|
2747
|
+
var status = document.getElementById('db-connect-status');
|
|
2748
|
+
status.style.display = 'none';
|
|
2749
|
+
|
|
2750
|
+
fetch(API + '/__dev/db-mode', {
|
|
2751
|
+
method: 'POST',
|
|
2752
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2753
|
+
body: JSON.stringify({ mode: mode })
|
|
2754
|
+
})
|
|
2755
|
+
.then(function(r) { return r.json(); })
|
|
2756
|
+
.then(function(data) {
|
|
2757
|
+
if (data.success) {
|
|
2758
|
+
renderDbMode(data.mode);
|
|
2759
|
+
} else {
|
|
2760
|
+
status.textContent = data.message;
|
|
2761
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
2762
|
+
}
|
|
2763
|
+
})
|
|
2764
|
+
.catch(function(err) {
|
|
2765
|
+
status.textContent = 'Error: ' + err.message;
|
|
2766
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
2767
|
+
});
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
window.connectFirestore = function() {
|
|
2771
|
+
var btn = document.getElementById('db-connect-btn');
|
|
2772
|
+
var status = document.getElementById('db-connect-status');
|
|
2773
|
+
btn.disabled = true;
|
|
2774
|
+
btn.textContent = 'Connecting...';
|
|
2775
|
+
status.textContent = 'Creating Firestore database... This may take a minute.';
|
|
2776
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';
|
|
2777
|
+
|
|
2778
|
+
fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
|
|
2779
|
+
.then(function(r) { return r.json(); })
|
|
2780
|
+
.then(function(data) {
|
|
2781
|
+
if (data.success) {
|
|
2782
|
+
status.textContent = data.message;
|
|
2783
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
2784
|
+
// Switch UI to firestore mode
|
|
2785
|
+
setTimeout(function() { renderDbMode('firestore'); }, 1500);
|
|
2786
|
+
// Refresh firebase status
|
|
2787
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2788
|
+
} else {
|
|
2789
|
+
status.textContent = data.message;
|
|
2790
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
2791
|
+
}
|
|
2792
|
+
btn.disabled = false;
|
|
2793
|
+
btn.textContent = 'Connect to Firestore';
|
|
2794
|
+
})
|
|
2795
|
+
.catch(function(err) {
|
|
2796
|
+
status.textContent = 'Error: ' + err.message;
|
|
2797
|
+
status.style.cssText = 'display:block;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
2798
|
+
btn.disabled = false;
|
|
2799
|
+
btn.textContent = 'Connect to Firestore';
|
|
2800
|
+
});
|
|
2801
|
+
};
|
|
2802
|
+
|
|
2666
2803
|
// \u2500\u2500\u2500 Environment Variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2667
2804
|
function renderEnvVars(data) {
|
|
2668
2805
|
envData = data.variables || [];
|
|
@@ -3239,6 +3376,51 @@ var FirebaseSetup = class {
|
|
|
3239
3376
|
writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3240
3377
|
return { success: true, message: `${service} enabled in firebase.json.` };
|
|
3241
3378
|
}
|
|
3379
|
+
// ─── Firestore Database Automation ──────────────────────────────────
|
|
3380
|
+
/**
|
|
3381
|
+
* Create Firestore database via CLI.
|
|
3382
|
+
* Handles "ALREADY_EXISTS" gracefully — returns success.
|
|
3383
|
+
*/
|
|
3384
|
+
async createFirestoreDatabase(location = "nam5") {
|
|
3385
|
+
try {
|
|
3386
|
+
await this.execTimeout(
|
|
3387
|
+
"firebase",
|
|
3388
|
+
["firestore:databases:create", "(default)", "--location", location, "--json"],
|
|
3389
|
+
6e4
|
|
3390
|
+
);
|
|
3391
|
+
return { success: true, message: `Firestore database created (location: ${location}).` };
|
|
3392
|
+
} catch (err) {
|
|
3393
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3394
|
+
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3395
|
+
return { success: true, message: "Firestore database already exists." };
|
|
3396
|
+
}
|
|
3397
|
+
return { success: false, message: `Failed to create Firestore database: ${msg}` };
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Deploy open Firestore security rules for dev testing.
|
|
3402
|
+
* Writes `allow read, write: if true` rules and deploys them.
|
|
3403
|
+
*/
|
|
3404
|
+
async deployFirestoreRules(openForDev = true) {
|
|
3405
|
+
const rulesPath = resolve5(this.projectDir, "firestore.rules");
|
|
3406
|
+
try {
|
|
3407
|
+
if (openForDev) {
|
|
3408
|
+
writeFileSync2(
|
|
3409
|
+
rulesPath,
|
|
3410
|
+
"rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
|
|
3411
|
+
);
|
|
3412
|
+
}
|
|
3413
|
+
await this.execTimeout(
|
|
3414
|
+
"firebase",
|
|
3415
|
+
["deploy", "--only", "firestore:rules", "--json"],
|
|
3416
|
+
6e4
|
|
3417
|
+
);
|
|
3418
|
+
return { success: true, message: "Firestore rules deployed." };
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3421
|
+
return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3242
3424
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
3243
3425
|
execTimeout(command, args, timeoutMs) {
|
|
3244
3426
|
return new Promise((resolve7, reject) => {
|
|
@@ -3575,6 +3757,19 @@ var DevServer = class {
|
|
|
3575
3757
|
}
|
|
3576
3758
|
// ─── Route Loading ─────────────────────────────────────────────────
|
|
3577
3759
|
async loadRoutes() {
|
|
3760
|
+
const setupState = this.firebaseSetup.loadState();
|
|
3761
|
+
if (setupState.dbMode === "firestore") {
|
|
3762
|
+
const config = this.readProjectConfig();
|
|
3763
|
+
const firebaseConfig = {};
|
|
3764
|
+
for (const field of config.fields) {
|
|
3765
|
+
if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
|
|
3766
|
+
}
|
|
3767
|
+
if (firebaseConfig.apiKey && firebaseConfig.projectId) {
|
|
3768
|
+
process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
|
|
3769
|
+
}
|
|
3770
|
+
} else {
|
|
3771
|
+
delete process.env.CLAWFIRE_FIREBASE_CONFIG;
|
|
3772
|
+
}
|
|
3578
3773
|
this.router.destroy();
|
|
3579
3774
|
this.router = createRouter({
|
|
3580
3775
|
cors: ["*"],
|
|
@@ -4285,6 +4480,69 @@ ${liveReloadScript}
|
|
|
4285
4480
|
});
|
|
4286
4481
|
return;
|
|
4287
4482
|
}
|
|
4483
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
|
|
4484
|
+
const state = this.firebaseSetup.loadState();
|
|
4485
|
+
sendJson({ mode: state.dbMode || "memory" });
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
|
|
4489
|
+
let body = "";
|
|
4490
|
+
req.on("data", (chunk) => {
|
|
4491
|
+
body += chunk;
|
|
4492
|
+
});
|
|
4493
|
+
req.on("end", async () => {
|
|
4494
|
+
try {
|
|
4495
|
+
const data = JSON.parse(body);
|
|
4496
|
+
const mode = data.mode;
|
|
4497
|
+
if (mode !== "memory" && mode !== "firestore") {
|
|
4498
|
+
sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
this.firebaseSetup.saveState({ dbMode: mode });
|
|
4502
|
+
await this.loadRoutes();
|
|
4503
|
+
this.regeneratePlayground();
|
|
4504
|
+
sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
|
|
4505
|
+
} catch (err) {
|
|
4506
|
+
sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
|
|
4507
|
+
}
|
|
4508
|
+
});
|
|
4509
|
+
return;
|
|
4510
|
+
}
|
|
4511
|
+
if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
|
|
4512
|
+
(async () => {
|
|
4513
|
+
try {
|
|
4514
|
+
const createResult = await this.firebaseSetup.createFirestoreDatabase();
|
|
4515
|
+
if (!createResult.success) {
|
|
4516
|
+
sendJson(createResult);
|
|
4517
|
+
return;
|
|
4518
|
+
}
|
|
4519
|
+
const enableResult = this.firebaseSetup.enableService("firestore");
|
|
4520
|
+
if (!enableResult.success) {
|
|
4521
|
+
sendJson(enableResult);
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
|
|
4525
|
+
if (!rulesResult.success) {
|
|
4526
|
+
sendJson(rulesResult);
|
|
4527
|
+
return;
|
|
4528
|
+
}
|
|
4529
|
+
this.firebaseSetup.saveState({ dbMode: "firestore" });
|
|
4530
|
+
await this.loadRoutes();
|
|
4531
|
+
this.regeneratePlayground();
|
|
4532
|
+
clearFirebaseStatusCache();
|
|
4533
|
+
sendJson({
|
|
4534
|
+
success: true,
|
|
4535
|
+
message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
|
|
4536
|
+
});
|
|
4537
|
+
} catch (err) {
|
|
4538
|
+
sendJson({
|
|
4539
|
+
success: false,
|
|
4540
|
+
message: err instanceof Error ? err.message : "Failed to setup Firestore"
|
|
4541
|
+
}, 500);
|
|
4542
|
+
}
|
|
4543
|
+
})();
|
|
4544
|
+
return;
|
|
4545
|
+
}
|
|
4288
4546
|
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
4289
4547
|
let body = "";
|
|
4290
4548
|
req.on("data", (chunk) => {
|