clawfire 0.5.0 → 0.5.1
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-5ATZVQJT.js → dev-server-7S6FZNJT.js} +244 -3
- package/dist/dev.cjs +244 -3
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +244 -3
- package/dist/dev.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -209,7 +209,7 @@ async function runDevServer() {
|
|
|
209
209
|
const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
|
|
210
210
|
const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
|
|
211
211
|
const noHotReload = args.includes("--no-hot-reload");
|
|
212
|
-
const { startDevServer } = await import("./dev-server-
|
|
212
|
+
const { startDevServer } = await import("./dev-server-7S6FZNJT.js");
|
|
213
213
|
await startDevServer({
|
|
214
214
|
projectDir,
|
|
215
215
|
port,
|
|
@@ -1382,7 +1382,29 @@ function generateDashboardHtml(options) {
|
|
|
1382
1382
|
<div id="service-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;"></div>
|
|
1383
1383
|
</div>
|
|
1384
1384
|
|
|
1385
|
-
<!-- Section 2:
|
|
1385
|
+
<!-- Section 2: Deploy -->
|
|
1386
|
+
<div style="margin-bottom:32px;">
|
|
1387
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Deploy</h2>
|
|
1388
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;">
|
|
1389
|
+
<div style="padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;">
|
|
1390
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
1391
|
+
<span style="font-size:16px;">🌐</span>
|
|
1392
|
+
<span style="font-weight:600;color:#e5e5e5;">Hosting</span>
|
|
1393
|
+
</div>
|
|
1394
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Deploy your app to Firebase Hosting</div>
|
|
1395
|
+
<button id="deploy-hosting-btn" onclick="deployHosting()" style="padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1396
|
+
Deploy Hosting
|
|
1397
|
+
</button>
|
|
1398
|
+
<div id="deploy-hosting-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
|
|
1399
|
+
<div id="deploy-hosting-url" style="display:none;margin-top:8px;padding:10px 14px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;">
|
|
1400
|
+
<div style="font-size:11px;color:#a3a3a3;margin-bottom:4px;">Live URL</div>
|
|
1401
|
+
<a id="deploy-hosting-link" href="#" target="_blank" style="color:#22c55e;font-family:monospace;font-size:14px;text-decoration:none;word-break:break-all;"></a>
|
|
1402
|
+
</div>
|
|
1403
|
+
</div>
|
|
1404
|
+
</div>
|
|
1405
|
+
</div>
|
|
1406
|
+
|
|
1407
|
+
<!-- Section 3: Config Overview -->
|
|
1386
1408
|
<div style="margin-bottom:32px;">
|
|
1387
1409
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1388
1410
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
@@ -1406,7 +1428,7 @@ function generateDashboardHtml(options) {
|
|
|
1406
1428
|
</div>
|
|
1407
1429
|
</div>
|
|
1408
1430
|
|
|
1409
|
-
<!-- Section
|
|
1431
|
+
<!-- Section 4: Environment Variables -->
|
|
1410
1432
|
<div style="margin-bottom:32px;">
|
|
1411
1433
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1412
1434
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Environment Variables</h2>
|
|
@@ -2006,16 +2028,22 @@ function generateDashboardHtml(options) {
|
|
|
2006
2028
|
var statusColors = { configured: '#22c55e', placeholder: '#eab308', missing: '#666' };
|
|
2007
2029
|
var statusLabels = { configured: 'Ready', placeholder: 'Needs Setup', missing: 'Not Configured' };
|
|
2008
2030
|
|
|
2031
|
+
var enableableServices = { 'Hosting': 'hosting', 'Firestore': 'firestore', 'Storage': 'storage' };
|
|
2009
2032
|
data.services.forEach(function(svc) {
|
|
2010
2033
|
var card = document.createElement('div');
|
|
2011
2034
|
card.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;';
|
|
2035
|
+
var enableBtn = '';
|
|
2036
|
+
if (svc.status === 'missing' && enableableServices[svc.name]) {
|
|
2037
|
+
enableBtn = '<button onclick="enableService(\\'' + enableableServices[svc.name] + '\\')" style="margin-top:8px;padding:4px 12px;background:#f97316;color:#000;border:none;border-radius:4px;font-size:11px;font-weight:600;cursor:pointer;">Enable</button>';
|
|
2038
|
+
}
|
|
2012
2039
|
card.innerHTML =
|
|
2013
2040
|
'<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">' +
|
|
2014
2041
|
'<span style="width:8px;height:8px;border-radius:50%;background:' + statusColors[svc.status] + ';display:inline-block;"></span>' +
|
|
2015
2042
|
'<span style="font-weight:600;color:#e5e5e5;">' + svc.name + '</span>' +
|
|
2016
2043
|
'</div>' +
|
|
2017
2044
|
'<div style="font-size:12px;color:' + statusColors[svc.status] + ';">' + statusLabels[svc.status] + '</div>' +
|
|
2018
|
-
(svc.detail ? '<div style="font-size:11px;color:#666;margin-top:4px;">' + svc.detail + '</div>' : '')
|
|
2045
|
+
(svc.detail ? '<div style="font-size:11px;color:#666;margin-top:4px;">' + svc.detail + '</div>' : '') +
|
|
2046
|
+
enableBtn;
|
|
2019
2047
|
grid.appendChild(card);
|
|
2020
2048
|
});
|
|
2021
2049
|
|
|
@@ -2200,6 +2228,67 @@ function generateDashboardHtml(options) {
|
|
|
2200
2228
|
});
|
|
2201
2229
|
}
|
|
2202
2230
|
|
|
2231
|
+
// \u2500\u2500\u2500 Deploy Hosting \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
|
|
2232
|
+
window.deployHosting = function() {
|
|
2233
|
+
var btn = document.getElementById('deploy-hosting-btn');
|
|
2234
|
+
var status = document.getElementById('deploy-hosting-status');
|
|
2235
|
+
var urlBox = document.getElementById('deploy-hosting-url');
|
|
2236
|
+
btn.disabled = true;
|
|
2237
|
+
btn.textContent = 'Deploying...';
|
|
2238
|
+
status.textContent = 'Deploying to Firebase Hosting... This may take up to 2 minutes.';
|
|
2239
|
+
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;';
|
|
2240
|
+
urlBox.style.display = 'none';
|
|
2241
|
+
|
|
2242
|
+
fetch(API + '/__dev/deploy/hosting', { method: 'POST' })
|
|
2243
|
+
.then(function(r) { return r.json(); })
|
|
2244
|
+
.then(function(data) {
|
|
2245
|
+
if (data.success) {
|
|
2246
|
+
status.textContent = data.message;
|
|
2247
|
+
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;';
|
|
2248
|
+
if (data.url) {
|
|
2249
|
+
var link = document.getElementById('deploy-hosting-link');
|
|
2250
|
+
link.href = data.url;
|
|
2251
|
+
link.textContent = data.url;
|
|
2252
|
+
urlBox.style.display = 'block';
|
|
2253
|
+
}
|
|
2254
|
+
} else {
|
|
2255
|
+
status.textContent = data.message;
|
|
2256
|
+
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;';
|
|
2257
|
+
}
|
|
2258
|
+
btn.disabled = false;
|
|
2259
|
+
btn.textContent = 'Deploy Hosting';
|
|
2260
|
+
// Refresh firebase status
|
|
2261
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2262
|
+
})
|
|
2263
|
+
.catch(function(err) {
|
|
2264
|
+
status.textContent = 'Error: ' + err.message;
|
|
2265
|
+
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;';
|
|
2266
|
+
btn.disabled = false;
|
|
2267
|
+
btn.textContent = 'Deploy Hosting';
|
|
2268
|
+
});
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
// \u2500\u2500\u2500 Enable Service \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\u2500\u2500
|
|
2272
|
+
window.enableService = function(service) {
|
|
2273
|
+
fetch(API + '/__dev/enable-service', {
|
|
2274
|
+
method: 'POST',
|
|
2275
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2276
|
+
body: JSON.stringify({ service: service })
|
|
2277
|
+
})
|
|
2278
|
+
.then(function(r) { return r.json(); })
|
|
2279
|
+
.then(function(data) {
|
|
2280
|
+
if (data.success) {
|
|
2281
|
+
// Refresh firebase status to show updated service cards
|
|
2282
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2283
|
+
} else {
|
|
2284
|
+
alert('Failed to enable ' + service + ': ' + data.message);
|
|
2285
|
+
}
|
|
2286
|
+
})
|
|
2287
|
+
.catch(function(err) {
|
|
2288
|
+
alert('Error: ' + err.message);
|
|
2289
|
+
});
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2203
2292
|
// \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
|
|
2204
2293
|
function renderEnvVars(data) {
|
|
2205
2294
|
envData = data.variables || [];
|
|
@@ -2669,6 +2758,113 @@ var FirebaseSetup = class {
|
|
|
2669
2758
|
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
2670
2759
|
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
2671
2760
|
}
|
|
2761
|
+
// ─── Deploy ─────────────────────────────────────────────────────────
|
|
2762
|
+
async deployHosting() {
|
|
2763
|
+
const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
|
|
2764
|
+
if (!existsSync5(firebaseJsonPath)) {
|
|
2765
|
+
return { success: false, message: "firebase.json not found. Enable hosting first." };
|
|
2766
|
+
}
|
|
2767
|
+
try {
|
|
2768
|
+
const config = JSON.parse(readFileSync4(firebaseJsonPath, "utf-8"));
|
|
2769
|
+
if (!config.hosting) {
|
|
2770
|
+
return { success: false, message: "Hosting not configured in firebase.json. Click Enable first." };
|
|
2771
|
+
}
|
|
2772
|
+
} catch {
|
|
2773
|
+
return { success: false, message: "Invalid firebase.json." };
|
|
2774
|
+
}
|
|
2775
|
+
try {
|
|
2776
|
+
const output = await this.execTimeout(
|
|
2777
|
+
"firebase",
|
|
2778
|
+
["deploy", "--only", "hosting", "--json"],
|
|
2779
|
+
12e4
|
|
2780
|
+
// 2 min timeout
|
|
2781
|
+
);
|
|
2782
|
+
let url = "";
|
|
2783
|
+
try {
|
|
2784
|
+
const data = JSON.parse(output);
|
|
2785
|
+
if (data?.result) {
|
|
2786
|
+
for (const key of Object.keys(data.result)) {
|
|
2787
|
+
if (key.startsWith("hosting") && typeof data.result[key] === "object") {
|
|
2788
|
+
url = data.result[key]?.url || data.result[key]?.site?.url || "";
|
|
2789
|
+
if (url) break;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
} catch {
|
|
2794
|
+
const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.web\.app/i);
|
|
2795
|
+
if (urlMatch) url = urlMatch[0];
|
|
2796
|
+
}
|
|
2797
|
+
if (!url) {
|
|
2798
|
+
const state = this.loadState();
|
|
2799
|
+
if (state.projectId) {
|
|
2800
|
+
url = `https://${state.projectId}.web.app`;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
return { success: true, url: url || void 0, message: "Hosting deployed successfully!" };
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2806
|
+
return { success: false, message: `Deploy failed: ${msg}` };
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
// ─── Service Enable ────────────────────────────────────────────────
|
|
2810
|
+
enableService(service) {
|
|
2811
|
+
const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
|
|
2812
|
+
let config = {};
|
|
2813
|
+
if (existsSync5(firebaseJsonPath)) {
|
|
2814
|
+
try {
|
|
2815
|
+
config = JSON.parse(readFileSync4(firebaseJsonPath, "utf-8"));
|
|
2816
|
+
} catch {
|
|
2817
|
+
config = {};
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
switch (service) {
|
|
2821
|
+
case "hosting": {
|
|
2822
|
+
if (!config.hosting) {
|
|
2823
|
+
config.hosting = {
|
|
2824
|
+
public: "public",
|
|
2825
|
+
ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
|
|
2826
|
+
rewrites: [{ source: "**", destination: "/index.html" }]
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
break;
|
|
2830
|
+
}
|
|
2831
|
+
case "firestore": {
|
|
2832
|
+
if (!config.firestore) {
|
|
2833
|
+
config.firestore = {
|
|
2834
|
+
rules: "firestore.rules",
|
|
2835
|
+
indexes: "firestore.indexes.json"
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
const rulesPath = resolve4(this.projectDir, "firestore.rules");
|
|
2839
|
+
if (!existsSync5(rulesPath)) {
|
|
2840
|
+
writeFileSync2(
|
|
2841
|
+
rulesPath,
|
|
2842
|
+
"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"
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
const indexesPath = resolve4(this.projectDir, "firestore.indexes.json");
|
|
2846
|
+
if (!existsSync5(indexesPath)) {
|
|
2847
|
+
writeFileSync2(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
|
|
2848
|
+
}
|
|
2849
|
+
break;
|
|
2850
|
+
}
|
|
2851
|
+
case "storage": {
|
|
2852
|
+
if (!config.storage) {
|
|
2853
|
+
config.storage = { rules: "storage.rules" };
|
|
2854
|
+
}
|
|
2855
|
+
const storageRulesPath = resolve4(this.projectDir, "storage.rules");
|
|
2856
|
+
if (!existsSync5(storageRulesPath)) {
|
|
2857
|
+
writeFileSync2(
|
|
2858
|
+
storageRulesPath,
|
|
2859
|
+
"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"
|
|
2860
|
+
);
|
|
2861
|
+
}
|
|
2862
|
+
break;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
writeFileSync2(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2866
|
+
return { success: true, message: `${service} enabled in firebase.json.` };
|
|
2867
|
+
}
|
|
2672
2868
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
2673
2869
|
execTimeout(command, args, timeoutMs) {
|
|
2674
2870
|
return new Promise((resolve6, reject) => {
|
|
@@ -3191,7 +3387,23 @@ function switchTab(tab) {
|
|
|
3191
3387
|
// Lazy-load dashboard data on first click
|
|
3192
3388
|
if (window._loadDashboard) window._loadDashboard();
|
|
3193
3389
|
}
|
|
3390
|
+
// Persist active tab across reloads (auto-fill writes config \u2192 watcher reloads page)
|
|
3391
|
+
try { localStorage.setItem('clawfire-active-tab', tab); } catch(e) {}
|
|
3194
3392
|
}
|
|
3393
|
+
// Restore saved tab on page load
|
|
3394
|
+
(function() {
|
|
3395
|
+
try {
|
|
3396
|
+
var saved = localStorage.getItem('clawfire-active-tab');
|
|
3397
|
+
if (saved === 'dashboard') {
|
|
3398
|
+
var fn = function() { switchTab('dashboard'); };
|
|
3399
|
+
if (document.readyState === 'loading') {
|
|
3400
|
+
document.addEventListener('DOMContentLoaded', fn);
|
|
3401
|
+
} else {
|
|
3402
|
+
fn();
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
} catch(e) {}
|
|
3406
|
+
})();
|
|
3195
3407
|
</script>`;
|
|
3196
3408
|
const liveReloadScript = `
|
|
3197
3409
|
<script>
|
|
@@ -3670,6 +3882,35 @@ ${liveReloadScript}
|
|
|
3670
3882
|
});
|
|
3671
3883
|
return;
|
|
3672
3884
|
}
|
|
3885
|
+
if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
|
|
3886
|
+
this.firebaseSetup.deployHosting().then((result) => {
|
|
3887
|
+
clearFirebaseStatusCache();
|
|
3888
|
+
sendJson(result);
|
|
3889
|
+
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
|
|
3893
|
+
let body = "";
|
|
3894
|
+
req.on("data", (chunk) => {
|
|
3895
|
+
body += chunk;
|
|
3896
|
+
});
|
|
3897
|
+
req.on("end", () => {
|
|
3898
|
+
try {
|
|
3899
|
+
const data = JSON.parse(body);
|
|
3900
|
+
const service = data.service;
|
|
3901
|
+
if (!service || !["hosting", "firestore", "storage"].includes(service)) {
|
|
3902
|
+
sendJson({ success: false, message: "Invalid service. Use: hosting, firestore, storage" }, 400);
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
const result = this.firebaseSetup.enableService(service);
|
|
3906
|
+
clearFirebaseStatusCache();
|
|
3907
|
+
sendJson(result);
|
|
3908
|
+
} catch {
|
|
3909
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
3910
|
+
}
|
|
3911
|
+
});
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3673
3914
|
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
3674
3915
|
let body = "";
|
|
3675
3916
|
req.on("data", (chunk) => {
|
package/dist/dev.cjs
CHANGED
|
@@ -1794,7 +1794,29 @@ function generateDashboardHtml(options) {
|
|
|
1794
1794
|
<div id="service-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;"></div>
|
|
1795
1795
|
</div>
|
|
1796
1796
|
|
|
1797
|
-
<!-- Section 2:
|
|
1797
|
+
<!-- Section 2: Deploy -->
|
|
1798
|
+
<div style="margin-bottom:32px;">
|
|
1799
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;margin-bottom:16px;">Deploy</h2>
|
|
1800
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;">
|
|
1801
|
+
<div style="padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;">
|
|
1802
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
1803
|
+
<span style="font-size:16px;">🌐</span>
|
|
1804
|
+
<span style="font-weight:600;color:#e5e5e5;">Hosting</span>
|
|
1805
|
+
</div>
|
|
1806
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:12px;">Deploy your app to Firebase Hosting</div>
|
|
1807
|
+
<button id="deploy-hosting-btn" onclick="deployHosting()" style="padding:8px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1808
|
+
Deploy Hosting
|
|
1809
|
+
</button>
|
|
1810
|
+
<div id="deploy-hosting-status" style="display:none;margin-top:10px;font-size:13px;padding:10px 14px;border-radius:6px;"></div>
|
|
1811
|
+
<div id="deploy-hosting-url" style="display:none;margin-top:8px;padding:10px 14px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;">
|
|
1812
|
+
<div style="font-size:11px;color:#a3a3a3;margin-bottom:4px;">Live URL</div>
|
|
1813
|
+
<a id="deploy-hosting-link" href="#" target="_blank" style="color:#22c55e;font-family:monospace;font-size:14px;text-decoration:none;word-break:break-all;"></a>
|
|
1814
|
+
</div>
|
|
1815
|
+
</div>
|
|
1816
|
+
</div>
|
|
1817
|
+
</div>
|
|
1818
|
+
|
|
1819
|
+
<!-- Section 3: Config Overview -->
|
|
1798
1820
|
<div style="margin-bottom:32px;">
|
|
1799
1821
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1800
1822
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
@@ -1818,7 +1840,7 @@ function generateDashboardHtml(options) {
|
|
|
1818
1840
|
</div>
|
|
1819
1841
|
</div>
|
|
1820
1842
|
|
|
1821
|
-
<!-- Section
|
|
1843
|
+
<!-- Section 4: Environment Variables -->
|
|
1822
1844
|
<div style="margin-bottom:32px;">
|
|
1823
1845
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1824
1846
|
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Environment Variables</h2>
|
|
@@ -2418,16 +2440,22 @@ function generateDashboardHtml(options) {
|
|
|
2418
2440
|
var statusColors = { configured: '#22c55e', placeholder: '#eab308', missing: '#666' };
|
|
2419
2441
|
var statusLabels = { configured: 'Ready', placeholder: 'Needs Setup', missing: 'Not Configured' };
|
|
2420
2442
|
|
|
2443
|
+
var enableableServices = { 'Hosting': 'hosting', 'Firestore': 'firestore', 'Storage': 'storage' };
|
|
2421
2444
|
data.services.forEach(function(svc) {
|
|
2422
2445
|
var card = document.createElement('div');
|
|
2423
2446
|
card.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #2a2a2a;background:#141414;';
|
|
2447
|
+
var enableBtn = '';
|
|
2448
|
+
if (svc.status === 'missing' && enableableServices[svc.name]) {
|
|
2449
|
+
enableBtn = '<button onclick="enableService(\\'' + enableableServices[svc.name] + '\\')" style="margin-top:8px;padding:4px 12px;background:#f97316;color:#000;border:none;border-radius:4px;font-size:11px;font-weight:600;cursor:pointer;">Enable</button>';
|
|
2450
|
+
}
|
|
2424
2451
|
card.innerHTML =
|
|
2425
2452
|
'<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">' +
|
|
2426
2453
|
'<span style="width:8px;height:8px;border-radius:50%;background:' + statusColors[svc.status] + ';display:inline-block;"></span>' +
|
|
2427
2454
|
'<span style="font-weight:600;color:#e5e5e5;">' + svc.name + '</span>' +
|
|
2428
2455
|
'</div>' +
|
|
2429
2456
|
'<div style="font-size:12px;color:' + statusColors[svc.status] + ';">' + statusLabels[svc.status] + '</div>' +
|
|
2430
|
-
(svc.detail ? '<div style="font-size:11px;color:#666;margin-top:4px;">' + svc.detail + '</div>' : '')
|
|
2457
|
+
(svc.detail ? '<div style="font-size:11px;color:#666;margin-top:4px;">' + svc.detail + '</div>' : '') +
|
|
2458
|
+
enableBtn;
|
|
2431
2459
|
grid.appendChild(card);
|
|
2432
2460
|
});
|
|
2433
2461
|
|
|
@@ -2612,6 +2640,67 @@ function generateDashboardHtml(options) {
|
|
|
2612
2640
|
});
|
|
2613
2641
|
}
|
|
2614
2642
|
|
|
2643
|
+
// \u2500\u2500\u2500 Deploy Hosting \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
|
|
2644
|
+
window.deployHosting = function() {
|
|
2645
|
+
var btn = document.getElementById('deploy-hosting-btn');
|
|
2646
|
+
var status = document.getElementById('deploy-hosting-status');
|
|
2647
|
+
var urlBox = document.getElementById('deploy-hosting-url');
|
|
2648
|
+
btn.disabled = true;
|
|
2649
|
+
btn.textContent = 'Deploying...';
|
|
2650
|
+
status.textContent = 'Deploying to Firebase Hosting... This may take up to 2 minutes.';
|
|
2651
|
+
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;';
|
|
2652
|
+
urlBox.style.display = 'none';
|
|
2653
|
+
|
|
2654
|
+
fetch(API + '/__dev/deploy/hosting', { method: 'POST' })
|
|
2655
|
+
.then(function(r) { return r.json(); })
|
|
2656
|
+
.then(function(data) {
|
|
2657
|
+
if (data.success) {
|
|
2658
|
+
status.textContent = data.message;
|
|
2659
|
+
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;';
|
|
2660
|
+
if (data.url) {
|
|
2661
|
+
var link = document.getElementById('deploy-hosting-link');
|
|
2662
|
+
link.href = data.url;
|
|
2663
|
+
link.textContent = data.url;
|
|
2664
|
+
urlBox.style.display = 'block';
|
|
2665
|
+
}
|
|
2666
|
+
} else {
|
|
2667
|
+
status.textContent = data.message;
|
|
2668
|
+
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;';
|
|
2669
|
+
}
|
|
2670
|
+
btn.disabled = false;
|
|
2671
|
+
btn.textContent = 'Deploy Hosting';
|
|
2672
|
+
// Refresh firebase status
|
|
2673
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2674
|
+
})
|
|
2675
|
+
.catch(function(err) {
|
|
2676
|
+
status.textContent = 'Error: ' + err.message;
|
|
2677
|
+
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;';
|
|
2678
|
+
btn.disabled = false;
|
|
2679
|
+
btn.textContent = 'Deploy Hosting';
|
|
2680
|
+
});
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// \u2500\u2500\u2500 Enable Service \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\u2500\u2500
|
|
2684
|
+
window.enableService = function(service) {
|
|
2685
|
+
fetch(API + '/__dev/enable-service', {
|
|
2686
|
+
method: 'POST',
|
|
2687
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2688
|
+
body: JSON.stringify({ service: service })
|
|
2689
|
+
})
|
|
2690
|
+
.then(function(r) { return r.json(); })
|
|
2691
|
+
.then(function(data) {
|
|
2692
|
+
if (data.success) {
|
|
2693
|
+
// Refresh firebase status to show updated service cards
|
|
2694
|
+
fetch(API + '/__dev/firebase-status').then(function(r) { return r.json(); }).then(function(d) { renderFirebaseStatus(d); }).catch(function(){});
|
|
2695
|
+
} else {
|
|
2696
|
+
alert('Failed to enable ' + service + ': ' + data.message);
|
|
2697
|
+
}
|
|
2698
|
+
})
|
|
2699
|
+
.catch(function(err) {
|
|
2700
|
+
alert('Error: ' + err.message);
|
|
2701
|
+
});
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2615
2704
|
// \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
|
|
2616
2705
|
function renderEnvVars(data) {
|
|
2617
2706
|
envData = data.variables || [];
|
|
@@ -3081,6 +3170,113 @@ var FirebaseSetup = class {
|
|
|
3081
3170
|
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
3082
3171
|
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
3083
3172
|
}
|
|
3173
|
+
// ─── Deploy ─────────────────────────────────────────────────────────
|
|
3174
|
+
async deployHosting() {
|
|
3175
|
+
const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
|
|
3176
|
+
if (!(0, import_node_fs4.existsSync)(firebaseJsonPath)) {
|
|
3177
|
+
return { success: false, message: "firebase.json not found. Enable hosting first." };
|
|
3178
|
+
}
|
|
3179
|
+
try {
|
|
3180
|
+
const config = JSON.parse((0, import_node_fs4.readFileSync)(firebaseJsonPath, "utf-8"));
|
|
3181
|
+
if (!config.hosting) {
|
|
3182
|
+
return { success: false, message: "Hosting not configured in firebase.json. Click Enable first." };
|
|
3183
|
+
}
|
|
3184
|
+
} catch {
|
|
3185
|
+
return { success: false, message: "Invalid firebase.json." };
|
|
3186
|
+
}
|
|
3187
|
+
try {
|
|
3188
|
+
const output = await this.execTimeout(
|
|
3189
|
+
"firebase",
|
|
3190
|
+
["deploy", "--only", "hosting", "--json"],
|
|
3191
|
+
12e4
|
|
3192
|
+
// 2 min timeout
|
|
3193
|
+
);
|
|
3194
|
+
let url = "";
|
|
3195
|
+
try {
|
|
3196
|
+
const data = JSON.parse(output);
|
|
3197
|
+
if (data?.result) {
|
|
3198
|
+
for (const key of Object.keys(data.result)) {
|
|
3199
|
+
if (key.startsWith("hosting") && typeof data.result[key] === "object") {
|
|
3200
|
+
url = data.result[key]?.url || data.result[key]?.site?.url || "";
|
|
3201
|
+
if (url) break;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
} catch {
|
|
3206
|
+
const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.web\.app/i);
|
|
3207
|
+
if (urlMatch) url = urlMatch[0];
|
|
3208
|
+
}
|
|
3209
|
+
if (!url) {
|
|
3210
|
+
const state = this.loadState();
|
|
3211
|
+
if (state.projectId) {
|
|
3212
|
+
url = `https://${state.projectId}.web.app`;
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
return { success: true, url: url || void 0, message: "Hosting deployed successfully!" };
|
|
3216
|
+
} catch (err) {
|
|
3217
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3218
|
+
return { success: false, message: `Deploy failed: ${msg}` };
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
// ─── Service Enable ────────────────────────────────────────────────
|
|
3222
|
+
enableService(service) {
|
|
3223
|
+
const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
|
|
3224
|
+
let config = {};
|
|
3225
|
+
if ((0, import_node_fs4.existsSync)(firebaseJsonPath)) {
|
|
3226
|
+
try {
|
|
3227
|
+
config = JSON.parse((0, import_node_fs4.readFileSync)(firebaseJsonPath, "utf-8"));
|
|
3228
|
+
} catch {
|
|
3229
|
+
config = {};
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
switch (service) {
|
|
3233
|
+
case "hosting": {
|
|
3234
|
+
if (!config.hosting) {
|
|
3235
|
+
config.hosting = {
|
|
3236
|
+
public: "public",
|
|
3237
|
+
ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
|
|
3238
|
+
rewrites: [{ source: "**", destination: "/index.html" }]
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
break;
|
|
3242
|
+
}
|
|
3243
|
+
case "firestore": {
|
|
3244
|
+
if (!config.firestore) {
|
|
3245
|
+
config.firestore = {
|
|
3246
|
+
rules: "firestore.rules",
|
|
3247
|
+
indexes: "firestore.indexes.json"
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
const rulesPath = (0, import_node_path4.resolve)(this.projectDir, "firestore.rules");
|
|
3251
|
+
if (!(0, import_node_fs4.existsSync)(rulesPath)) {
|
|
3252
|
+
(0, import_node_fs4.writeFileSync)(
|
|
3253
|
+
rulesPath,
|
|
3254
|
+
"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"
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
const indexesPath = (0, import_node_path4.resolve)(this.projectDir, "firestore.indexes.json");
|
|
3258
|
+
if (!(0, import_node_fs4.existsSync)(indexesPath)) {
|
|
3259
|
+
(0, import_node_fs4.writeFileSync)(indexesPath, JSON.stringify({ indexes: [], fieldOverrides: [] }, null, 2) + "\n");
|
|
3260
|
+
}
|
|
3261
|
+
break;
|
|
3262
|
+
}
|
|
3263
|
+
case "storage": {
|
|
3264
|
+
if (!config.storage) {
|
|
3265
|
+
config.storage = { rules: "storage.rules" };
|
|
3266
|
+
}
|
|
3267
|
+
const storageRulesPath = (0, import_node_path4.resolve)(this.projectDir, "storage.rules");
|
|
3268
|
+
if (!(0, import_node_fs4.existsSync)(storageRulesPath)) {
|
|
3269
|
+
(0, import_node_fs4.writeFileSync)(
|
|
3270
|
+
storageRulesPath,
|
|
3271
|
+
"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"
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
break;
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
(0, import_node_fs4.writeFileSync)(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3278
|
+
return { success: true, message: `${service} enabled in firebase.json.` };
|
|
3279
|
+
}
|
|
3084
3280
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
3085
3281
|
execTimeout(command, args, timeoutMs) {
|
|
3086
3282
|
return new Promise((resolve7, reject) => {
|
|
@@ -3603,7 +3799,23 @@ function switchTab(tab) {
|
|
|
3603
3799
|
// Lazy-load dashboard data on first click
|
|
3604
3800
|
if (window._loadDashboard) window._loadDashboard();
|
|
3605
3801
|
}
|
|
3802
|
+
// Persist active tab across reloads (auto-fill writes config \u2192 watcher reloads page)
|
|
3803
|
+
try { localStorage.setItem('clawfire-active-tab', tab); } catch(e) {}
|
|
3606
3804
|
}
|
|
3805
|
+
// Restore saved tab on page load
|
|
3806
|
+
(function() {
|
|
3807
|
+
try {
|
|
3808
|
+
var saved = localStorage.getItem('clawfire-active-tab');
|
|
3809
|
+
if (saved === 'dashboard') {
|
|
3810
|
+
var fn = function() { switchTab('dashboard'); };
|
|
3811
|
+
if (document.readyState === 'loading') {
|
|
3812
|
+
document.addEventListener('DOMContentLoaded', fn);
|
|
3813
|
+
} else {
|
|
3814
|
+
fn();
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
} catch(e) {}
|
|
3818
|
+
})();
|
|
3607
3819
|
</script>`;
|
|
3608
3820
|
const liveReloadScript = `
|
|
3609
3821
|
<script>
|
|
@@ -4082,6 +4294,35 @@ ${liveReloadScript}
|
|
|
4082
4294
|
});
|
|
4083
4295
|
return;
|
|
4084
4296
|
}
|
|
4297
|
+
if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
|
|
4298
|
+
this.firebaseSetup.deployHosting().then((result) => {
|
|
4299
|
+
clearFirebaseStatusCache();
|
|
4300
|
+
sendJson(result);
|
|
4301
|
+
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4302
|
+
return;
|
|
4303
|
+
}
|
|
4304
|
+
if (url.pathname === "/__dev/enable-service" && req.method === "POST") {
|
|
4305
|
+
let body = "";
|
|
4306
|
+
req.on("data", (chunk) => {
|
|
4307
|
+
body += chunk;
|
|
4308
|
+
});
|
|
4309
|
+
req.on("end", () => {
|
|
4310
|
+
try {
|
|
4311
|
+
const data = JSON.parse(body);
|
|
4312
|
+
const service = data.service;
|
|
4313
|
+
if (!service || !["hosting", "firestore", "storage"].includes(service)) {
|
|
4314
|
+
sendJson({ success: false, message: "Invalid service. Use: hosting, firestore, storage" }, 400);
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4317
|
+
const result = this.firebaseSetup.enableService(service);
|
|
4318
|
+
clearFirebaseStatusCache();
|
|
4319
|
+
sendJson(result);
|
|
4320
|
+
} catch {
|
|
4321
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
4322
|
+
}
|
|
4323
|
+
});
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4085
4326
|
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
4086
4327
|
let body = "";
|
|
4087
4328
|
req.on("data", (chunk) => {
|