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/cli.js
CHANGED
|
@@ -269,7 +269,7 @@ async function runDevServer() {
|
|
|
269
269
|
const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
|
|
270
270
|
const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
|
|
271
271
|
const noHotReload = args.includes("--no-hot-reload");
|
|
272
|
-
const { startDevServer } = await import("./dev-server-
|
|
272
|
+
const { startDevServer } = await import("./dev-server-NWB66EJT.js");
|
|
273
273
|
await startDevServer({
|
|
274
274
|
projectDir,
|
|
275
275
|
port,
|
|
@@ -1404,7 +1404,48 @@ function generateDashboardHtml(options) {
|
|
|
1404
1404
|
</div>
|
|
1405
1405
|
</div>
|
|
1406
1406
|
|
|
1407
|
-
<!-- Section 3:
|
|
1407
|
+
<!-- Section 3: Database -->
|
|
1408
|
+
<div style="margin-bottom:32px;">
|
|
1409
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
|
|
1410
|
+
<div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
|
|
1411
|
+
<!-- Mode Toggle -->
|
|
1412
|
+
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
|
|
1413
|
+
<span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
|
|
1414
|
+
<label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
|
|
1415
|
+
<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;">
|
|
1416
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
|
|
1417
|
+
</span>
|
|
1418
|
+
<span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
|
|
1419
|
+
</label>
|
|
1420
|
+
<label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
|
|
1421
|
+
<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;">
|
|
1422
|
+
</span>
|
|
1423
|
+
<span style="color:#a3a3a3;font-size:13px;">Firestore</span>
|
|
1424
|
+
</label>
|
|
1425
|
+
</div>
|
|
1426
|
+
|
|
1427
|
+
<!-- Connect to Firestore panel -->
|
|
1428
|
+
<div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
|
|
1429
|
+
<div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
|
|
1430
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
|
|
1431
|
+
<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;">
|
|
1432
|
+
Connect to Firestore
|
|
1433
|
+
</button>
|
|
1434
|
+
<div style="margin-top:10px;font-size:12px;color:#eab308;">
|
|
1435
|
+
⚠ Open rules (allow all) for dev only. Use /clawfire-model for production rules.
|
|
1436
|
+
</div>
|
|
1437
|
+
<div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
|
|
1438
|
+
</div>
|
|
1439
|
+
|
|
1440
|
+
<!-- Firestore Connected banner -->
|
|
1441
|
+
<div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
|
|
1442
|
+
<div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
|
|
1443
|
+
<div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
|
|
1444
|
+
</div>
|
|
1445
|
+
</div>
|
|
1446
|
+
</div>
|
|
1447
|
+
|
|
1448
|
+
<!-- Section 4: Config Overview -->
|
|
1408
1449
|
<div style="margin-bottom:32px;">
|
|
1409
1450
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1410
1451
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
@@ -1500,11 +1541,13 @@ function generateDashboardHtml(options) {
|
|
|
1500
1541
|
fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
|
|
1501
1542
|
fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
|
|
1502
1543
|
fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
|
|
1544
|
+
fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
|
|
1503
1545
|
]).then(function(results) {
|
|
1504
1546
|
renderFirebaseStatus(results[0]);
|
|
1505
1547
|
renderConfig(results[1]);
|
|
1506
1548
|
renderEnvVars(results[2]);
|
|
1507
1549
|
renderSetupWizard(results[3]);
|
|
1550
|
+
renderDbMode(results[4].mode || 'memory');
|
|
1508
1551
|
document.getElementById('dash-loading').style.display = 'none';
|
|
1509
1552
|
document.getElementById('dash-loaded').style.display = 'block';
|
|
1510
1553
|
}).catch(function(err) {
|
|
@@ -2289,6 +2332,100 @@ function generateDashboardHtml(options) {
|
|
|
2289
2332
|
});
|
|
2290
2333
|
};
|
|
2291
2334
|
|
|
2335
|
+
// \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
|
|
2336
|
+
function loadDbMode() {
|
|
2337
|
+
fetch(API + '/__dev/db-mode')
|
|
2338
|
+
.then(function(r) { return r.json(); })
|
|
2339
|
+
.then(function(data) { renderDbMode(data.mode || 'memory'); })
|
|
2340
|
+
.catch(function() { renderDbMode('memory'); });
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
function renderDbMode(mode) {
|
|
2344
|
+
var radioMem = document.getElementById('db-radio-memory');
|
|
2345
|
+
var radioFs = document.getElementById('db-radio-firestore');
|
|
2346
|
+
var connectPanel = document.getElementById('db-connect-panel');
|
|
2347
|
+
var connectedBanner = document.getElementById('db-connected-banner');
|
|
2348
|
+
var memLabel = document.getElementById('db-mode-memory');
|
|
2349
|
+
var fsLabel = document.getElementById('db-mode-firestore');
|
|
2350
|
+
|
|
2351
|
+
if (mode === 'firestore') {
|
|
2352
|
+
radioMem.innerHTML = '';
|
|
2353
|
+
radioMem.style.borderColor = '#666';
|
|
2354
|
+
radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2355
|
+
radioFs.style.borderColor = '#f97316';
|
|
2356
|
+
memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2357
|
+
fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2358
|
+
connectPanel.style.display = 'none';
|
|
2359
|
+
connectedBanner.style.display = 'block';
|
|
2360
|
+
} else {
|
|
2361
|
+
radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2362
|
+
radioMem.style.borderColor = '#f97316';
|
|
2363
|
+
radioFs.innerHTML = '';
|
|
2364
|
+
radioFs.style.borderColor = '#666';
|
|
2365
|
+
memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2366
|
+
fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2367
|
+
connectPanel.style.display = 'block';
|
|
2368
|
+
connectedBanner.style.display = 'none';
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
window.switchDbMode = function(mode) {
|
|
2373
|
+
var status = document.getElementById('db-connect-status');
|
|
2374
|
+
status.style.display = 'none';
|
|
2375
|
+
|
|
2376
|
+
fetch(API + '/__dev/db-mode', {
|
|
2377
|
+
method: 'POST',
|
|
2378
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2379
|
+
body: JSON.stringify({ mode: mode })
|
|
2380
|
+
})
|
|
2381
|
+
.then(function(r) { return r.json(); })
|
|
2382
|
+
.then(function(data) {
|
|
2383
|
+
if (data.success) {
|
|
2384
|
+
renderDbMode(data.mode);
|
|
2385
|
+
} else {
|
|
2386
|
+
status.textContent = data.message;
|
|
2387
|
+
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;';
|
|
2388
|
+
}
|
|
2389
|
+
})
|
|
2390
|
+
.catch(function(err) {
|
|
2391
|
+
status.textContent = 'Error: ' + err.message;
|
|
2392
|
+
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;';
|
|
2393
|
+
});
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
window.connectFirestore = function() {
|
|
2397
|
+
var btn = document.getElementById('db-connect-btn');
|
|
2398
|
+
var status = document.getElementById('db-connect-status');
|
|
2399
|
+
btn.disabled = true;
|
|
2400
|
+
btn.textContent = 'Connecting...';
|
|
2401
|
+
status.textContent = 'Creating Firestore database... This may take a minute.';
|
|
2402
|
+
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;';
|
|
2403
|
+
|
|
2404
|
+
fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
|
|
2405
|
+
.then(function(r) { return r.json(); })
|
|
2406
|
+
.then(function(data) {
|
|
2407
|
+
if (data.success) {
|
|
2408
|
+
status.textContent = data.message;
|
|
2409
|
+
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;';
|
|
2410
|
+
// Switch UI to firestore mode
|
|
2411
|
+
setTimeout(function() { renderDbMode('firestore'); }, 1500);
|
|
2412
|
+
// Refresh firebase status
|
|
2413
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2414
|
+
} else {
|
|
2415
|
+
status.textContent = data.message;
|
|
2416
|
+
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;';
|
|
2417
|
+
}
|
|
2418
|
+
btn.disabled = false;
|
|
2419
|
+
btn.textContent = 'Connect to Firestore';
|
|
2420
|
+
})
|
|
2421
|
+
.catch(function(err) {
|
|
2422
|
+
status.textContent = 'Error: ' + err.message;
|
|
2423
|
+
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;';
|
|
2424
|
+
btn.disabled = false;
|
|
2425
|
+
btn.textContent = 'Connect to Firestore';
|
|
2426
|
+
});
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2292
2429
|
// \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
|
|
2293
2430
|
function renderEnvVars(data) {
|
|
2294
2431
|
envData = data.variables || [];
|
|
@@ -2865,6 +3002,51 @@ var FirebaseSetup = class {
|
|
|
2865
3002
|
writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2866
3003
|
return { success: true, message: `${service} enabled in firebase.json.` };
|
|
2867
3004
|
}
|
|
3005
|
+
// ─── Firestore Database Automation ──────────────────────────────────
|
|
3006
|
+
/**
|
|
3007
|
+
* Create Firestore database via CLI.
|
|
3008
|
+
* Handles "ALREADY_EXISTS" gracefully — returns success.
|
|
3009
|
+
*/
|
|
3010
|
+
async createFirestoreDatabase(location = "nam5") {
|
|
3011
|
+
try {
|
|
3012
|
+
await this.execTimeout(
|
|
3013
|
+
"firebase",
|
|
3014
|
+
["firestore:databases:create", "(default)", "--location", location, "--json"],
|
|
3015
|
+
6e4
|
|
3016
|
+
);
|
|
3017
|
+
return { success: true, message: `Firestore database created (location: ${location}).` };
|
|
3018
|
+
} catch (err) {
|
|
3019
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3020
|
+
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3021
|
+
return { success: true, message: "Firestore database already exists." };
|
|
3022
|
+
}
|
|
3023
|
+
return { success: false, message: `Failed to create Firestore database: ${msg}` };
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Deploy open Firestore security rules for dev testing.
|
|
3028
|
+
* Writes `allow read, write: if true` rules and deploys them.
|
|
3029
|
+
*/
|
|
3030
|
+
async deployFirestoreRules(openForDev = true) {
|
|
3031
|
+
const rulesPath = resolve4(this.projectDir, "firestore.rules");
|
|
3032
|
+
try {
|
|
3033
|
+
if (openForDev) {
|
|
3034
|
+
writeFileSync2(
|
|
3035
|
+
rulesPath,
|
|
3036
|
+
"rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
await this.execTimeout(
|
|
3040
|
+
"firebase",
|
|
3041
|
+
["deploy", "--only", "firestore:rules", "--json"],
|
|
3042
|
+
6e4
|
|
3043
|
+
);
|
|
3044
|
+
return { success: true, message: "Firestore rules deployed." };
|
|
3045
|
+
} catch (err) {
|
|
3046
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3047
|
+
return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
2868
3050
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
2869
3051
|
execTimeout(command, args, timeoutMs) {
|
|
2870
3052
|
return new Promise((resolve6, reject) => {
|
|
@@ -3201,6 +3383,19 @@ var DevServer = class {
|
|
|
3201
3383
|
}
|
|
3202
3384
|
// ─── Route Loading ─────────────────────────────────────────────────
|
|
3203
3385
|
async loadRoutes() {
|
|
3386
|
+
const setupState = this.firebaseSetup.loadState();
|
|
3387
|
+
if (setupState.dbMode === "firestore") {
|
|
3388
|
+
const config = this.readProjectConfig();
|
|
3389
|
+
const firebaseConfig = {};
|
|
3390
|
+
for (const field of config.fields) {
|
|
3391
|
+
if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
|
|
3392
|
+
}
|
|
3393
|
+
if (firebaseConfig.apiKey && firebaseConfig.projectId) {
|
|
3394
|
+
process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
|
|
3395
|
+
}
|
|
3396
|
+
} else {
|
|
3397
|
+
delete process.env.CLAWFIRE_FIREBASE_CONFIG;
|
|
3398
|
+
}
|
|
3204
3399
|
this.router.destroy();
|
|
3205
3400
|
this.router = createRouter({
|
|
3206
3401
|
cors: ["*"],
|
|
@@ -3911,6 +4106,69 @@ ${liveReloadScript}
|
|
|
3911
4106
|
});
|
|
3912
4107
|
return;
|
|
3913
4108
|
}
|
|
4109
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
|
|
4110
|
+
const state = this.firebaseSetup.loadState();
|
|
4111
|
+
sendJson({ mode: state.dbMode || "memory" });
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
|
|
4115
|
+
let body = "";
|
|
4116
|
+
req.on("data", (chunk) => {
|
|
4117
|
+
body += chunk;
|
|
4118
|
+
});
|
|
4119
|
+
req.on("end", async () => {
|
|
4120
|
+
try {
|
|
4121
|
+
const data = JSON.parse(body);
|
|
4122
|
+
const mode = data.mode;
|
|
4123
|
+
if (mode !== "memory" && mode !== "firestore") {
|
|
4124
|
+
sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
|
|
4125
|
+
return;
|
|
4126
|
+
}
|
|
4127
|
+
this.firebaseSetup.saveState({ dbMode: mode });
|
|
4128
|
+
await this.loadRoutes();
|
|
4129
|
+
this.regeneratePlayground();
|
|
4130
|
+
sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
|
|
4131
|
+
} catch (err) {
|
|
4132
|
+
sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
|
|
4133
|
+
}
|
|
4134
|
+
});
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
|
|
4138
|
+
(async () => {
|
|
4139
|
+
try {
|
|
4140
|
+
const createResult = await this.firebaseSetup.createFirestoreDatabase();
|
|
4141
|
+
if (!createResult.success) {
|
|
4142
|
+
sendJson(createResult);
|
|
4143
|
+
return;
|
|
4144
|
+
}
|
|
4145
|
+
const enableResult = this.firebaseSetup.enableService("firestore");
|
|
4146
|
+
if (!enableResult.success) {
|
|
4147
|
+
sendJson(enableResult);
|
|
4148
|
+
return;
|
|
4149
|
+
}
|
|
4150
|
+
const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
|
|
4151
|
+
if (!rulesResult.success) {
|
|
4152
|
+
sendJson(rulesResult);
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
this.firebaseSetup.saveState({ dbMode: "firestore" });
|
|
4156
|
+
await this.loadRoutes();
|
|
4157
|
+
this.regeneratePlayground();
|
|
4158
|
+
clearFirebaseStatusCache();
|
|
4159
|
+
sendJson({
|
|
4160
|
+
success: true,
|
|
4161
|
+
message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
|
|
4162
|
+
});
|
|
4163
|
+
} catch (err) {
|
|
4164
|
+
sendJson({
|
|
4165
|
+
success: false,
|
|
4166
|
+
message: err instanceof Error ? err.message : "Failed to setup Firestore"
|
|
4167
|
+
}, 500);
|
|
4168
|
+
}
|
|
4169
|
+
})();
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
3914
4172
|
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
3915
4173
|
let body = "";
|
|
3916
4174
|
req.on("data", (chunk) => {
|
package/dist/dev.cjs
CHANGED
|
@@ -1816,7 +1816,48 @@ function generateDashboardHtml(options) {
|
|
|
1816
1816
|
</div>
|
|
1817
1817
|
</div>
|
|
1818
1818
|
|
|
1819
|
-
<!-- Section 3:
|
|
1819
|
+
<!-- Section 3: Database -->
|
|
1820
|
+
<div style="margin-bottom:32px;">
|
|
1821
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Database</h2>
|
|
1822
|
+
<div id="db-card" style="padding:20px;border-radius:12px;border:1px solid #2a2a2a;background:#141414;">
|
|
1823
|
+
<!-- Mode Toggle -->
|
|
1824
|
+
<div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
|
|
1825
|
+
<span style="font-size:13px;color:#a3a3a3;font-weight:600;">Mode:</span>
|
|
1826
|
+
<label id="db-mode-memory" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('memory')">
|
|
1827
|
+
<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;">
|
|
1828
|
+
<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>
|
|
1829
|
+
</span>
|
|
1830
|
+
<span style="color:#e5e5e5;font-size:13px;">In-Memory</span>
|
|
1831
|
+
</label>
|
|
1832
|
+
<label id="db-mode-firestore" style="display:flex;align-items:center;gap:6px;cursor:pointer;" onclick="switchDbMode('firestore')">
|
|
1833
|
+
<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;">
|
|
1834
|
+
</span>
|
|
1835
|
+
<span style="color:#a3a3a3;font-size:13px;">Firestore</span>
|
|
1836
|
+
</label>
|
|
1837
|
+
</div>
|
|
1838
|
+
|
|
1839
|
+
<!-- Connect to Firestore panel -->
|
|
1840
|
+
<div id="db-connect-panel" style="padding:16px;border-radius:8px;background:#0a0a0a;border:1px solid #2a2a2a;">
|
|
1841
|
+
<div style="font-weight:600;color:#e5e5e5;font-size:14px;margin-bottom:6px;">Connect to Firestore</div>
|
|
1842
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Creates database, deploys open security rules, and enables Firestore mode.</div>
|
|
1843
|
+
<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;">
|
|
1844
|
+
Connect to Firestore
|
|
1845
|
+
</button>
|
|
1846
|
+
<div style="margin-top:10px;font-size:12px;color:#eab308;">
|
|
1847
|
+
⚠ Open rules (allow all) for dev only. Use /clawfire-model for production rules.
|
|
1848
|
+
</div>
|
|
1849
|
+
<div id="db-connect-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
|
|
1850
|
+
</div>
|
|
1851
|
+
|
|
1852
|
+
<!-- Firestore Connected banner -->
|
|
1853
|
+
<div id="db-connected-banner" style="display:none;padding:16px;border-radius:8px;background:#0a1a0a;border:1px solid #22c55e;text-align:center;">
|
|
1854
|
+
<div style="font-size:14px;color:#22c55e;font-weight:700;">Firestore Connected</div>
|
|
1855
|
+
<div style="font-size:12px;color:#a3a3a3;margin-top:4px;">Data persists across server restarts.</div>
|
|
1856
|
+
</div>
|
|
1857
|
+
</div>
|
|
1858
|
+
</div>
|
|
1859
|
+
|
|
1860
|
+
<!-- Section 4: Config Overview -->
|
|
1820
1861
|
<div style="margin-bottom:32px;">
|
|
1821
1862
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1822
1863
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
@@ -1912,11 +1953,13 @@ function generateDashboardHtml(options) {
|
|
|
1912
1953
|
fetch(API + '/__dev/config').then(function(r) { return r.json(); }),
|
|
1913
1954
|
fetch(API + '/__dev/env').then(function(r) { return r.json(); }),
|
|
1914
1955
|
fetch(API + '/__dev/setup/status').then(function(r) { return r.json(); }),
|
|
1956
|
+
fetch(API + '/__dev/db-mode').then(function(r) { return r.json(); }),
|
|
1915
1957
|
]).then(function(results) {
|
|
1916
1958
|
renderFirebaseStatus(results[0]);
|
|
1917
1959
|
renderConfig(results[1]);
|
|
1918
1960
|
renderEnvVars(results[2]);
|
|
1919
1961
|
renderSetupWizard(results[3]);
|
|
1962
|
+
renderDbMode(results[4].mode || 'memory');
|
|
1920
1963
|
document.getElementById('dash-loading').style.display = 'none';
|
|
1921
1964
|
document.getElementById('dash-loaded').style.display = 'block';
|
|
1922
1965
|
}).catch(function(err) {
|
|
@@ -2701,6 +2744,100 @@ function generateDashboardHtml(options) {
|
|
|
2701
2744
|
});
|
|
2702
2745
|
};
|
|
2703
2746
|
|
|
2747
|
+
// \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
|
|
2748
|
+
function loadDbMode() {
|
|
2749
|
+
fetch(API + '/__dev/db-mode')
|
|
2750
|
+
.then(function(r) { return r.json(); })
|
|
2751
|
+
.then(function(data) { renderDbMode(data.mode || 'memory'); })
|
|
2752
|
+
.catch(function() { renderDbMode('memory'); });
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
function renderDbMode(mode) {
|
|
2756
|
+
var radioMem = document.getElementById('db-radio-memory');
|
|
2757
|
+
var radioFs = document.getElementById('db-radio-firestore');
|
|
2758
|
+
var connectPanel = document.getElementById('db-connect-panel');
|
|
2759
|
+
var connectedBanner = document.getElementById('db-connected-banner');
|
|
2760
|
+
var memLabel = document.getElementById('db-mode-memory');
|
|
2761
|
+
var fsLabel = document.getElementById('db-mode-firestore');
|
|
2762
|
+
|
|
2763
|
+
if (mode === 'firestore') {
|
|
2764
|
+
radioMem.innerHTML = '';
|
|
2765
|
+
radioMem.style.borderColor = '#666';
|
|
2766
|
+
radioFs.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2767
|
+
radioFs.style.borderColor = '#f97316';
|
|
2768
|
+
memLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2769
|
+
fsLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2770
|
+
connectPanel.style.display = 'none';
|
|
2771
|
+
connectedBanner.style.display = 'block';
|
|
2772
|
+
} else {
|
|
2773
|
+
radioMem.innerHTML = '<span style="width:8px;height:8px;border-radius:50%;background:#f97316;"></span>';
|
|
2774
|
+
radioMem.style.borderColor = '#f97316';
|
|
2775
|
+
radioFs.innerHTML = '';
|
|
2776
|
+
radioFs.style.borderColor = '#666';
|
|
2777
|
+
memLabel.querySelector('span:last-child').style.color = '#e5e5e5';
|
|
2778
|
+
fsLabel.querySelector('span:last-child').style.color = '#a3a3a3';
|
|
2779
|
+
connectPanel.style.display = 'block';
|
|
2780
|
+
connectedBanner.style.display = 'none';
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
window.switchDbMode = function(mode) {
|
|
2785
|
+
var status = document.getElementById('db-connect-status');
|
|
2786
|
+
status.style.display = 'none';
|
|
2787
|
+
|
|
2788
|
+
fetch(API + '/__dev/db-mode', {
|
|
2789
|
+
method: 'POST',
|
|
2790
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2791
|
+
body: JSON.stringify({ mode: mode })
|
|
2792
|
+
})
|
|
2793
|
+
.then(function(r) { return r.json(); })
|
|
2794
|
+
.then(function(data) {
|
|
2795
|
+
if (data.success) {
|
|
2796
|
+
renderDbMode(data.mode);
|
|
2797
|
+
} else {
|
|
2798
|
+
status.textContent = data.message;
|
|
2799
|
+
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;';
|
|
2800
|
+
}
|
|
2801
|
+
})
|
|
2802
|
+
.catch(function(err) {
|
|
2803
|
+
status.textContent = 'Error: ' + err.message;
|
|
2804
|
+
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;';
|
|
2805
|
+
});
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
window.connectFirestore = function() {
|
|
2809
|
+
var btn = document.getElementById('db-connect-btn');
|
|
2810
|
+
var status = document.getElementById('db-connect-status');
|
|
2811
|
+
btn.disabled = true;
|
|
2812
|
+
btn.textContent = 'Connecting...';
|
|
2813
|
+
status.textContent = 'Creating Firestore database... This may take a minute.';
|
|
2814
|
+
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;';
|
|
2815
|
+
|
|
2816
|
+
fetch(API + '/__dev/setup/create-firestore', { method: 'POST' })
|
|
2817
|
+
.then(function(r) { return r.json(); })
|
|
2818
|
+
.then(function(data) {
|
|
2819
|
+
if (data.success) {
|
|
2820
|
+
status.textContent = data.message;
|
|
2821
|
+
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;';
|
|
2822
|
+
// Switch UI to firestore mode
|
|
2823
|
+
setTimeout(function() { renderDbMode('firestore'); }, 1500);
|
|
2824
|
+
// Refresh firebase status
|
|
2825
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2826
|
+
} else {
|
|
2827
|
+
status.textContent = data.message;
|
|
2828
|
+
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;';
|
|
2829
|
+
}
|
|
2830
|
+
btn.disabled = false;
|
|
2831
|
+
btn.textContent = 'Connect to Firestore';
|
|
2832
|
+
})
|
|
2833
|
+
.catch(function(err) {
|
|
2834
|
+
status.textContent = 'Error: ' + err.message;
|
|
2835
|
+
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;';
|
|
2836
|
+
btn.disabled = false;
|
|
2837
|
+
btn.textContent = 'Connect to Firestore';
|
|
2838
|
+
});
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2704
2841
|
// \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
|
|
2705
2842
|
function renderEnvVars(data) {
|
|
2706
2843
|
envData = data.variables || [];
|
|
@@ -3277,6 +3414,51 @@ var FirebaseSetup = class {
|
|
|
3277
3414
|
(0, import_node_fs4.writeFileSync)(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3278
3415
|
return { success: true, message: `${service} enabled in firebase.json.` };
|
|
3279
3416
|
}
|
|
3417
|
+
// ─── Firestore Database Automation ──────────────────────────────────
|
|
3418
|
+
/**
|
|
3419
|
+
* Create Firestore database via CLI.
|
|
3420
|
+
* Handles "ALREADY_EXISTS" gracefully — returns success.
|
|
3421
|
+
*/
|
|
3422
|
+
async createFirestoreDatabase(location = "nam5") {
|
|
3423
|
+
try {
|
|
3424
|
+
await this.execTimeout(
|
|
3425
|
+
"firebase",
|
|
3426
|
+
["firestore:databases:create", "(default)", "--location", location, "--json"],
|
|
3427
|
+
6e4
|
|
3428
|
+
);
|
|
3429
|
+
return { success: true, message: `Firestore database created (location: ${location}).` };
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3432
|
+
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3433
|
+
return { success: true, message: "Firestore database already exists." };
|
|
3434
|
+
}
|
|
3435
|
+
return { success: false, message: `Failed to create Firestore database: ${msg}` };
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Deploy open Firestore security rules for dev testing.
|
|
3440
|
+
* Writes `allow read, write: if true` rules and deploys them.
|
|
3441
|
+
*/
|
|
3442
|
+
async deployFirestoreRules(openForDev = true) {
|
|
3443
|
+
const rulesPath = (0, import_node_path4.resolve)(this.projectDir, "firestore.rules");
|
|
3444
|
+
try {
|
|
3445
|
+
if (openForDev) {
|
|
3446
|
+
(0, import_node_fs4.writeFileSync)(
|
|
3447
|
+
rulesPath,
|
|
3448
|
+
"rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}\n"
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
await this.execTimeout(
|
|
3452
|
+
"firebase",
|
|
3453
|
+
["deploy", "--only", "firestore:rules", "--json"],
|
|
3454
|
+
6e4
|
|
3455
|
+
);
|
|
3456
|
+
return { success: true, message: "Firestore rules deployed." };
|
|
3457
|
+
} catch (err) {
|
|
3458
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3459
|
+
return { success: false, message: `Failed to deploy Firestore rules: ${msg}` };
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3280
3462
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
3281
3463
|
execTimeout(command, args, timeoutMs) {
|
|
3282
3464
|
return new Promise((resolve7, reject) => {
|
|
@@ -3613,6 +3795,19 @@ var DevServer = class {
|
|
|
3613
3795
|
}
|
|
3614
3796
|
// ─── Route Loading ─────────────────────────────────────────────────
|
|
3615
3797
|
async loadRoutes() {
|
|
3798
|
+
const setupState = this.firebaseSetup.loadState();
|
|
3799
|
+
if (setupState.dbMode === "firestore") {
|
|
3800
|
+
const config = this.readProjectConfig();
|
|
3801
|
+
const firebaseConfig = {};
|
|
3802
|
+
for (const field of config.fields) {
|
|
3803
|
+
if (!field.isPlaceholder) firebaseConfig[field.key] = field.value;
|
|
3804
|
+
}
|
|
3805
|
+
if (firebaseConfig.apiKey && firebaseConfig.projectId) {
|
|
3806
|
+
process.env.CLAWFIRE_FIREBASE_CONFIG = JSON.stringify(firebaseConfig);
|
|
3807
|
+
}
|
|
3808
|
+
} else {
|
|
3809
|
+
delete process.env.CLAWFIRE_FIREBASE_CONFIG;
|
|
3810
|
+
}
|
|
3616
3811
|
this.router.destroy();
|
|
3617
3812
|
this.router = createRouter({
|
|
3618
3813
|
cors: ["*"],
|
|
@@ -4323,6 +4518,69 @@ ${liveReloadScript}
|
|
|
4323
4518
|
});
|
|
4324
4519
|
return;
|
|
4325
4520
|
}
|
|
4521
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "GET") {
|
|
4522
|
+
const state = this.firebaseSetup.loadState();
|
|
4523
|
+
sendJson({ mode: state.dbMode || "memory" });
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
if (url.pathname === "/__dev/db-mode" && req.method === "POST") {
|
|
4527
|
+
let body = "";
|
|
4528
|
+
req.on("data", (chunk) => {
|
|
4529
|
+
body += chunk;
|
|
4530
|
+
});
|
|
4531
|
+
req.on("end", async () => {
|
|
4532
|
+
try {
|
|
4533
|
+
const data = JSON.parse(body);
|
|
4534
|
+
const mode = data.mode;
|
|
4535
|
+
if (mode !== "memory" && mode !== "firestore") {
|
|
4536
|
+
sendJson({ success: false, message: "Invalid mode. Use: memory, firestore" }, 400);
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
this.firebaseSetup.saveState({ dbMode: mode });
|
|
4540
|
+
await this.loadRoutes();
|
|
4541
|
+
this.regeneratePlayground();
|
|
4542
|
+
sendJson({ success: true, mode, message: `Database mode switched to "${mode}".` });
|
|
4543
|
+
} catch (err) {
|
|
4544
|
+
sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 400);
|
|
4545
|
+
}
|
|
4546
|
+
});
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
if (url.pathname === "/__dev/setup/create-firestore" && req.method === "POST") {
|
|
4550
|
+
(async () => {
|
|
4551
|
+
try {
|
|
4552
|
+
const createResult = await this.firebaseSetup.createFirestoreDatabase();
|
|
4553
|
+
if (!createResult.success) {
|
|
4554
|
+
sendJson(createResult);
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
const enableResult = this.firebaseSetup.enableService("firestore");
|
|
4558
|
+
if (!enableResult.success) {
|
|
4559
|
+
sendJson(enableResult);
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
const rulesResult = await this.firebaseSetup.deployFirestoreRules(true);
|
|
4563
|
+
if (!rulesResult.success) {
|
|
4564
|
+
sendJson(rulesResult);
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
this.firebaseSetup.saveState({ dbMode: "firestore" });
|
|
4568
|
+
await this.loadRoutes();
|
|
4569
|
+
this.regeneratePlayground();
|
|
4570
|
+
clearFirebaseStatusCache();
|
|
4571
|
+
sendJson({
|
|
4572
|
+
success: true,
|
|
4573
|
+
message: "Firestore connected! Database created, rules deployed, mode switched to Firestore."
|
|
4574
|
+
});
|
|
4575
|
+
} catch (err) {
|
|
4576
|
+
sendJson({
|
|
4577
|
+
success: false,
|
|
4578
|
+
message: err instanceof Error ? err.message : "Failed to setup Firestore"
|
|
4579
|
+
}, 500);
|
|
4580
|
+
}
|
|
4581
|
+
})();
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4326
4584
|
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
4327
4585
|
let body = "";
|
|
4328
4586
|
req.on("data", (chunk) => {
|