clawfire 0.3.1 → 0.3.2
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-TGEKP4YE.js → dev-server-MIUKPOMC.js} +253 -19
- package/dist/dev.cjs +252 -18
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +3 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +253 -19
- 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-MIUKPOMC.js");
|
|
213
213
|
await startDevServer({
|
|
214
214
|
projectDir,
|
|
215
215
|
port,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
// src/dev/dev-server.ts
|
|
10
10
|
import http from "http";
|
|
11
11
|
import { resolve as resolve4, relative as relative2, extname as extname3 } from "path";
|
|
12
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
12
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
13
13
|
import { pathToFileURL } from "url";
|
|
14
14
|
|
|
15
15
|
// src/core/schema.ts
|
|
@@ -1082,6 +1082,34 @@ async function checkCli(projectDir) {
|
|
|
1082
1082
|
}
|
|
1083
1083
|
return result;
|
|
1084
1084
|
}
|
|
1085
|
+
async function fetchFirebaseSdkConfig(projectDir) {
|
|
1086
|
+
const output = await execWithTimeout(
|
|
1087
|
+
"firebase",
|
|
1088
|
+
["apps:sdkconfig", "web", "--json"],
|
|
1089
|
+
projectDir,
|
|
1090
|
+
15e3
|
|
1091
|
+
);
|
|
1092
|
+
const data = JSON.parse(output);
|
|
1093
|
+
if (data?.result?.sdkConfig) {
|
|
1094
|
+
return data.result.sdkConfig;
|
|
1095
|
+
}
|
|
1096
|
+
if (data?.result?.fileContents) {
|
|
1097
|
+
const contents = data.result.fileContents;
|
|
1098
|
+
const config = {};
|
|
1099
|
+
const extract = (key) => {
|
|
1100
|
+
const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
1101
|
+
return match ? match[1] : void 0;
|
|
1102
|
+
};
|
|
1103
|
+
config.apiKey = extract("apiKey");
|
|
1104
|
+
config.authDomain = extract("authDomain");
|
|
1105
|
+
config.projectId = extract("projectId");
|
|
1106
|
+
config.storageBucket = extract("storageBucket");
|
|
1107
|
+
config.messagingSenderId = extract("messagingSenderId");
|
|
1108
|
+
config.appId = extract("appId");
|
|
1109
|
+
return config;
|
|
1110
|
+
}
|
|
1111
|
+
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1112
|
+
}
|
|
1085
1113
|
function execWithTimeout(command, args, cwd, timeoutMs) {
|
|
1086
1114
|
return new Promise((resolve5, reject) => {
|
|
1087
1115
|
const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
|
|
@@ -1130,9 +1158,25 @@ function generateDashboardHtml(options) {
|
|
|
1130
1158
|
|
|
1131
1159
|
<!-- Section 2: Config Overview -->
|
|
1132
1160
|
<div style="margin-bottom:32px;">
|
|
1133
|
-
<
|
|
1161
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1162
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
1163
|
+
<button id="autofill-btn" onclick="autoFillConfig()" style="padding:6px 14px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Auto-fill from Firebase</button>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div id="autofill-status" style="display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;"></div>
|
|
1134
1166
|
<div id="config-section" style="border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;">
|
|
1135
|
-
<
|
|
1167
|
+
<table id="config-table" style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
1168
|
+
<thead>
|
|
1169
|
+
<tr style="border-bottom:1px solid #2a2a2a;">
|
|
1170
|
+
<th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;">Key</th>
|
|
1171
|
+
<th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;">Value</th>
|
|
1172
|
+
<th style="padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;">Action</th>
|
|
1173
|
+
</tr>
|
|
1174
|
+
</thead>
|
|
1175
|
+
<tbody id="config-tbody"></tbody>
|
|
1176
|
+
</table>
|
|
1177
|
+
<div id="config-empty" style="display:none;padding:32px;text-align:center;color:#666;">
|
|
1178
|
+
No clawfire.config.ts found.
|
|
1179
|
+
</div>
|
|
1136
1180
|
</div>
|
|
1137
1181
|
</div>
|
|
1138
1182
|
|
|
@@ -1162,7 +1206,7 @@ function generateDashboardHtml(options) {
|
|
|
1162
1206
|
</div>
|
|
1163
1207
|
|
|
1164
1208
|
<!-- Env Modal -->
|
|
1165
|
-
<div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;
|
|
1209
|
+
<div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;align-items:center;justify-content:center;">
|
|
1166
1210
|
<div style="background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;">
|
|
1167
1211
|
<h3 id="modal-title" style="font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;">Add Variable</h3>
|
|
1168
1212
|
<div style="margin-bottom:12px;">
|
|
@@ -1190,6 +1234,7 @@ function generateDashboardHtml(options) {
|
|
|
1190
1234
|
(function() {
|
|
1191
1235
|
var API = 'http://localhost:${apiPort}';
|
|
1192
1236
|
var envData = [];
|
|
1237
|
+
var configData = [];
|
|
1193
1238
|
var editingKey = null;
|
|
1194
1239
|
|
|
1195
1240
|
// \u2500\u2500\u2500 Load Dashboard Data \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
|
|
@@ -1215,7 +1260,6 @@ function generateDashboardHtml(options) {
|
|
|
1215
1260
|
|
|
1216
1261
|
// \u2500\u2500\u2500 Firebase Status \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
|
|
1217
1262
|
function renderFirebaseStatus(data) {
|
|
1218
|
-
// CLI Banner
|
|
1219
1263
|
var dot = document.getElementById('cli-dot');
|
|
1220
1264
|
var text = document.getElementById('cli-text');
|
|
1221
1265
|
var proj = document.getElementById('cli-project');
|
|
@@ -1257,7 +1301,6 @@ function generateDashboardHtml(options) {
|
|
|
1257
1301
|
grid.appendChild(card);
|
|
1258
1302
|
});
|
|
1259
1303
|
|
|
1260
|
-
// Config Warnings
|
|
1261
1304
|
if (data.configWarnings && data.configWarnings.length > 0) {
|
|
1262
1305
|
var warningCard = document.createElement('div');
|
|
1263
1306
|
warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';
|
|
@@ -1270,26 +1313,167 @@ function generateDashboardHtml(options) {
|
|
|
1270
1313
|
}
|
|
1271
1314
|
}
|
|
1272
1315
|
|
|
1273
|
-
// \u2500\u2500\u2500 Config Overview \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
|
|
1316
|
+
// \u2500\u2500\u2500 Config Overview (editable) \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
|
|
1274
1317
|
function renderConfig(data) {
|
|
1275
|
-
var
|
|
1276
|
-
|
|
1277
|
-
|
|
1318
|
+
var tbody = document.getElementById('config-tbody');
|
|
1319
|
+
var empty = document.getElementById('config-empty');
|
|
1320
|
+
var table = document.getElementById('config-table');
|
|
1321
|
+
|
|
1322
|
+
configData = (data && data.fields) ? data.fields : [];
|
|
1323
|
+
|
|
1324
|
+
if (configData.length === 0) {
|
|
1325
|
+
table.style.display = 'none';
|
|
1326
|
+
empty.style.display = 'block';
|
|
1278
1327
|
return;
|
|
1279
1328
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1329
|
+
|
|
1330
|
+
table.style.display = 'table';
|
|
1331
|
+
empty.style.display = 'none';
|
|
1332
|
+
tbody.innerHTML = '';
|
|
1333
|
+
|
|
1334
|
+
configData.forEach(function(field) {
|
|
1335
|
+
var tr = document.createElement('tr');
|
|
1336
|
+
tr.style.borderBottom = '1px solid #2a2a2a';
|
|
1337
|
+
tr.id = 'cfg-row-' + field.key;
|
|
1282
1338
|
var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';
|
|
1283
1339
|
var badge = field.isPlaceholder ? ' <span style="background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;">PLACEHOLDER</span>' : '';
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1340
|
+
tr.innerHTML =
|
|
1341
|
+
'<td style="padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;">' + escHtml(field.key) + '</td>' +
|
|
1342
|
+
'<td style="padding:10px 16px;font-family:monospace;">' +
|
|
1343
|
+
'<span id="cfg-val-' + field.key + '" style="color:' + color + ';">' + escHtml(field.value) + '</span>' +
|
|
1344
|
+
badge +
|
|
1345
|
+
'<input id="cfg-input-' + field.key + '" type="text" value="' + escHtml(field.value) + '" style="display:none;width:100%;padding:4px 8px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:4px;color:#e5e5e5;font-family:monospace;font-size:13px;outline:none;" />' +
|
|
1346
|
+
'</td>' +
|
|
1347
|
+
'<td style="padding:10px 16px;text-align:right;white-space:nowrap;">' +
|
|
1348
|
+
'<button id="cfg-edit-' + field.key + '" onclick="editConfigField(\\'' + escHtml(field.key) + '\\')" style="background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;">Edit</button>' +
|
|
1349
|
+
'<button id="cfg-save-' + field.key + '" onclick="saveConfigField(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#22c55e;cursor:pointer;font-size:12px;padding:4px 8px;">Save</button>' +
|
|
1350
|
+
'<button id="cfg-cancel-' + field.key + '" onclick="cancelConfigEdit(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#a3a3a3;cursor:pointer;font-size:12px;padding:4px 8px;">Cancel</button>' +
|
|
1351
|
+
'</td>';
|
|
1352
|
+
tbody.appendChild(tr);
|
|
1289
1353
|
});
|
|
1290
|
-
el.innerHTML = html;
|
|
1291
1354
|
}
|
|
1292
1355
|
|
|
1356
|
+
window.editConfigField = function(key) {
|
|
1357
|
+
var valSpan = document.getElementById('cfg-val-' + key);
|
|
1358
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1359
|
+
var editBtn = document.getElementById('cfg-edit-' + key);
|
|
1360
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1361
|
+
var cancelBtn = document.getElementById('cfg-cancel-' + key);
|
|
1362
|
+
if (!valSpan || !input) return;
|
|
1363
|
+
// Hide all badges in this cell
|
|
1364
|
+
var badges = valSpan.parentNode.querySelectorAll('span[style*="PLACEHOLDER"]');
|
|
1365
|
+
for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';
|
|
1366
|
+
valSpan.style.display = 'none';
|
|
1367
|
+
input.style.display = 'block';
|
|
1368
|
+
input.focus();
|
|
1369
|
+
input.select();
|
|
1370
|
+
editBtn.style.display = 'none';
|
|
1371
|
+
saveBtn.style.display = 'inline';
|
|
1372
|
+
cancelBtn.style.display = 'inline';
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
window.cancelConfigEdit = function(key) {
|
|
1376
|
+
var valSpan = document.getElementById('cfg-val-' + key);
|
|
1377
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1378
|
+
var editBtn = document.getElementById('cfg-edit-' + key);
|
|
1379
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1380
|
+
var cancelBtn = document.getElementById('cfg-cancel-' + key);
|
|
1381
|
+
if (!valSpan || !input) return;
|
|
1382
|
+
var badges = valSpan.parentNode.querySelectorAll('span[style*="border-radius"]');
|
|
1383
|
+
for (var i = 0; i < badges.length; i++) badges[i].style.display = '';
|
|
1384
|
+
valSpan.style.display = '';
|
|
1385
|
+
input.style.display = 'none';
|
|
1386
|
+
input.value = valSpan.textContent;
|
|
1387
|
+
editBtn.style.display = 'inline';
|
|
1388
|
+
saveBtn.style.display = 'none';
|
|
1389
|
+
cancelBtn.style.display = 'none';
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
window.saveConfigField = function(key) {
|
|
1393
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1394
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1395
|
+
if (!input) return;
|
|
1396
|
+
var value = input.value;
|
|
1397
|
+
saveBtn.textContent = '...';
|
|
1398
|
+
fetch(API + '/__dev/config', {
|
|
1399
|
+
method: 'POST',
|
|
1400
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1401
|
+
body: JSON.stringify({ action: 'set', key: key, value: value })
|
|
1402
|
+
}).then(function(r) { return r.json(); })
|
|
1403
|
+
.then(function(data) {
|
|
1404
|
+
if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }
|
|
1405
|
+
// Reload config
|
|
1406
|
+
return fetch(API + '/__dev/config').then(function(r) { return r.json(); });
|
|
1407
|
+
})
|
|
1408
|
+
.then(function(data) { if (data) renderConfig(data); })
|
|
1409
|
+
.catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// \u2500\u2500\u2500 Auto-fill from Firebase \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
|
|
1413
|
+
window.autoFillConfig = function() {
|
|
1414
|
+
var btn = document.getElementById('autofill-btn');
|
|
1415
|
+
var status = document.getElementById('autofill-status');
|
|
1416
|
+
btn.disabled = true;
|
|
1417
|
+
btn.textContent = 'Fetching...';
|
|
1418
|
+
status.style.display = 'none';
|
|
1419
|
+
|
|
1420
|
+
fetch(API + '/__dev/firebase-sdk-config')
|
|
1421
|
+
.then(function(r) { return r.json(); })
|
|
1422
|
+
.then(function(data) {
|
|
1423
|
+
if (data.error) {
|
|
1424
|
+
status.textContent = data.error;
|
|
1425
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1426
|
+
btn.disabled = false;
|
|
1427
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Map SDK config keys to clawfire.config.ts keys
|
|
1432
|
+
var fields = {};
|
|
1433
|
+
if (data.apiKey) fields.apiKey = data.apiKey;
|
|
1434
|
+
if (data.authDomain) fields.authDomain = data.authDomain;
|
|
1435
|
+
if (data.projectId) fields.projectId = data.projectId;
|
|
1436
|
+
if (data.storageBucket) fields.storageBucket = data.storageBucket;
|
|
1437
|
+
if (data.appId) fields.appId = data.appId;
|
|
1438
|
+
|
|
1439
|
+
var count = Object.keys(fields).length;
|
|
1440
|
+
if (count === 0) {
|
|
1441
|
+
status.textContent = 'No web app config found. Create a Web app in Firebase Console first.';
|
|
1442
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
|
|
1443
|
+
btn.disabled = false;
|
|
1444
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// Save all fields at once
|
|
1449
|
+
return fetch(API + '/__dev/config', {
|
|
1450
|
+
method: 'POST',
|
|
1451
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1452
|
+
body: JSON.stringify({ action: 'set-multiple', fields: fields })
|
|
1453
|
+
}).then(function(r) { return r.json(); })
|
|
1454
|
+
.then(function(result) {
|
|
1455
|
+
if (result.error) {
|
|
1456
|
+
status.textContent = 'Error saving: ' + result.error;
|
|
1457
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1458
|
+
} else {
|
|
1459
|
+
status.textContent = count + ' fields updated from Firebase project!';
|
|
1460
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
1461
|
+
// Reload config view
|
|
1462
|
+
return fetch(API + '/__dev/config').then(function(r) { return r.json(); })
|
|
1463
|
+
.then(function(cfg) { renderConfig(cfg); });
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
})
|
|
1467
|
+
.catch(function(err) {
|
|
1468
|
+
status.textContent = 'Failed: ' + err.message;
|
|
1469
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1470
|
+
})
|
|
1471
|
+
.finally(function() {
|
|
1472
|
+
btn.disabled = false;
|
|
1473
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1474
|
+
});
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1293
1477
|
// \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
|
|
1294
1478
|
function renderEnvVars(data) {
|
|
1295
1479
|
envData = data.variables || [];
|
|
@@ -1328,7 +1512,7 @@ function generateDashboardHtml(options) {
|
|
|
1328
1512
|
});
|
|
1329
1513
|
}
|
|
1330
1514
|
|
|
1331
|
-
// \u2500\u2500\u2500 Modal \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\u2500\u2500\u2500
|
|
1515
|
+
// \u2500\u2500\u2500 Env Modal \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\u2500\u2500\u2500
|
|
1332
1516
|
window.showEnvModal = function(key) {
|
|
1333
1517
|
editingKey = key || null;
|
|
1334
1518
|
var modal = document.getElementById('env-modal');
|
|
@@ -2273,6 +2457,37 @@ ${liveReloadScript}
|
|
|
2273
2457
|
sendJson(this.readProjectConfig());
|
|
2274
2458
|
return;
|
|
2275
2459
|
}
|
|
2460
|
+
if (url.pathname === "/__dev/config" && req.method === "POST") {
|
|
2461
|
+
let body = "";
|
|
2462
|
+
req.on("data", (chunk) => {
|
|
2463
|
+
body += chunk;
|
|
2464
|
+
});
|
|
2465
|
+
req.on("end", () => {
|
|
2466
|
+
try {
|
|
2467
|
+
const data = JSON.parse(body);
|
|
2468
|
+
if (data.action === "set" && data.key && data.value !== void 0) {
|
|
2469
|
+
this.updateProjectConfig(data.key, String(data.value));
|
|
2470
|
+
clearFirebaseStatusCache();
|
|
2471
|
+
sendJson({ ok: true });
|
|
2472
|
+
} else if (data.action === "set-multiple" && data.fields) {
|
|
2473
|
+
for (const [key, value] of Object.entries(data.fields)) {
|
|
2474
|
+
this.updateProjectConfig(key, String(value));
|
|
2475
|
+
}
|
|
2476
|
+
clearFirebaseStatusCache();
|
|
2477
|
+
sendJson({ ok: true });
|
|
2478
|
+
} else {
|
|
2479
|
+
sendJson({ error: "Invalid action" }, 400);
|
|
2480
|
+
}
|
|
2481
|
+
} catch (err) {
|
|
2482
|
+
sendJson({ error: err instanceof Error ? err.message : "Failed" }, 400);
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
|
|
2488
|
+
fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2276
2491
|
if (url.pathname === "/__dev/env" && req.method === "GET") {
|
|
2277
2492
|
try {
|
|
2278
2493
|
sendJson(this.envManager.read());
|
|
@@ -2328,6 +2543,25 @@ ${liveReloadScript}
|
|
|
2328
2543
|
}
|
|
2329
2544
|
return { fields };
|
|
2330
2545
|
}
|
|
2546
|
+
/** Update a single key's value in clawfire.config.ts */
|
|
2547
|
+
updateProjectConfig(key, value) {
|
|
2548
|
+
const configPath = resolve4(this.options.projectDir, "clawfire.config.ts");
|
|
2549
|
+
if (!existsSync5(configPath)) {
|
|
2550
|
+
throw new Error("clawfire.config.ts not found");
|
|
2551
|
+
}
|
|
2552
|
+
let content = readFileSync4(configPath, "utf-8");
|
|
2553
|
+
const pattern = new RegExp(
|
|
2554
|
+
`(${this.escapeRegex(key)}\\s*:\\s*)["'\`][^"'\`]*["'\`]`
|
|
2555
|
+
);
|
|
2556
|
+
if (!pattern.test(content)) {
|
|
2557
|
+
throw new Error(`Key "${key}" not found in config`);
|
|
2558
|
+
}
|
|
2559
|
+
content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
|
|
2560
|
+
writeFileSync2(configPath, content, "utf-8");
|
|
2561
|
+
}
|
|
2562
|
+
escapeRegex(s) {
|
|
2563
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2564
|
+
}
|
|
2331
2565
|
};
|
|
2332
2566
|
async function startDevServer(options) {
|
|
2333
2567
|
const server = new DevServer(options);
|