clawfire 0.4.3 → 0.5.0
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-PAI4XU2S.js → dev-server-5ATZVQJT.js} +449 -47
- package/dist/dev.cjs +449 -47
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +449 -47
- 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-5ATZVQJT.js");
|
|
213
213
|
await startDevServer({
|
|
214
214
|
projectDir,
|
|
215
215
|
port,
|
|
@@ -1082,39 +1082,106 @@ async function checkCli(projectDir) {
|
|
|
1082
1082
|
}
|
|
1083
1083
|
return result;
|
|
1084
1084
|
}
|
|
1085
|
-
async function fetchFirebaseSdkConfig(projectDir) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1085
|
+
async function fetchFirebaseSdkConfig(projectDir, appId) {
|
|
1086
|
+
if (appId) {
|
|
1087
|
+
try {
|
|
1088
|
+
const output = await execWithTimeout(
|
|
1089
|
+
"firebase",
|
|
1090
|
+
["apps:sdkconfig", "web", appId, "--json"],
|
|
1091
|
+
projectDir,
|
|
1092
|
+
15e3
|
|
1093
|
+
);
|
|
1094
|
+
const config = parseSdkConfigOutput(output);
|
|
1095
|
+
if (config) return config;
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1098
|
+
throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
|
|
1099
|
+
}
|
|
1100
|
+
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
const output = await execWithTimeout(
|
|
1104
|
+
"firebase",
|
|
1105
|
+
["apps:sdkconfig", "web", "--json"],
|
|
1106
|
+
projectDir,
|
|
1107
|
+
15e3
|
|
1108
|
+
);
|
|
1109
|
+
const config = parseSdkConfigOutput(output);
|
|
1110
|
+
if (config) return config;
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
let webApps = [];
|
|
1114
|
+
try {
|
|
1115
|
+
const appsOutput = await execWithTimeout(
|
|
1116
|
+
"firebase",
|
|
1117
|
+
["apps:list", "--json"],
|
|
1118
|
+
projectDir,
|
|
1119
|
+
15e3
|
|
1120
|
+
);
|
|
1121
|
+
const appsData = JSON.parse(appsOutput);
|
|
1122
|
+
const allApps = appsData?.result || [];
|
|
1123
|
+
webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
|
|
1124
|
+
appId: String(a.appId || ""),
|
|
1125
|
+
displayName: String(a.displayName || "")
|
|
1126
|
+
})).filter((a) => a.appId);
|
|
1127
|
+
} catch {
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
"Failed to fetch Firebase SDK config. Make sure you are logged in and have an active project selected."
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (webApps.length === 0) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
"No web app registered in this Firebase project. Go to Firebase Console > Project Settings > General > Your apps > Add app (Web) to create one, then click Auto-fill again."
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
const firstAppId = webApps[0].appId;
|
|
1138
|
+
try {
|
|
1139
|
+
const output = await execWithTimeout(
|
|
1140
|
+
"firebase",
|
|
1141
|
+
["apps:sdkconfig", "web", firstAppId, "--json"],
|
|
1142
|
+
projectDir,
|
|
1143
|
+
15e3
|
|
1144
|
+
);
|
|
1145
|
+
const config = parseSdkConfigOutput(output);
|
|
1146
|
+
if (config) return config;
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1149
|
+
throw new Error(`Failed to fetch config for web app ${firstAppId}: ${msg}`);
|
|
1110
1150
|
}
|
|
1111
1151
|
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1112
1152
|
}
|
|
1153
|
+
function parseSdkConfigOutput(output) {
|
|
1154
|
+
try {
|
|
1155
|
+
const data = JSON.parse(output);
|
|
1156
|
+
if (data?.result?.sdkConfig) {
|
|
1157
|
+
return data.result.sdkConfig;
|
|
1158
|
+
}
|
|
1159
|
+
if (data?.result?.fileContents) {
|
|
1160
|
+
const contents = data.result.fileContents;
|
|
1161
|
+
const config = {};
|
|
1162
|
+
const extract = (key) => {
|
|
1163
|
+
const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
1164
|
+
return match ? match[1] : void 0;
|
|
1165
|
+
};
|
|
1166
|
+
config.apiKey = extract("apiKey");
|
|
1167
|
+
config.authDomain = extract("authDomain");
|
|
1168
|
+
config.projectId = extract("projectId");
|
|
1169
|
+
config.storageBucket = extract("storageBucket");
|
|
1170
|
+
config.messagingSenderId = extract("messagingSenderId");
|
|
1171
|
+
config.appId = extract("appId");
|
|
1172
|
+
if (config.apiKey || config.projectId) return config;
|
|
1173
|
+
}
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1113
1178
|
function execWithTimeout(command, args, cwd, timeoutMs) {
|
|
1114
1179
|
return new Promise((resolve6, reject) => {
|
|
1115
|
-
const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
|
|
1180
|
+
const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
1116
1181
|
if (err) {
|
|
1117
|
-
|
|
1182
|
+
const detail = stderr?.trim() || stdout?.trim() || "";
|
|
1183
|
+
const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
|
|
1184
|
+
reject(enriched);
|
|
1118
1185
|
} else {
|
|
1119
1186
|
resolve6(stdout);
|
|
1120
1187
|
}
|
|
@@ -1148,17 +1215,20 @@ function generateDashboardHtml(options) {
|
|
|
1148
1215
|
|
|
1149
1216
|
<!-- Step indicators -->
|
|
1150
1217
|
<div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
|
|
1151
|
-
<div id="step-ind-1" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1152
|
-
1. CLI
|
|
1218
|
+
<div id="step-ind-1" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#1a1a2e;color:#f97316;border-right:1px solid #2a2a2a;">
|
|
1219
|
+
1. CLI
|
|
1153
1220
|
</div>
|
|
1154
|
-
<div id="step-ind-2" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1221
|
+
<div id="step-ind-2" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
|
|
1155
1222
|
2. Login
|
|
1156
1223
|
</div>
|
|
1157
|
-
<div id="step-ind-3" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1158
|
-
3.
|
|
1224
|
+
<div id="step-ind-3" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
|
|
1225
|
+
3. Project
|
|
1159
1226
|
</div>
|
|
1160
|
-
<div id="step-ind-4" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1161
|
-
4.
|
|
1227
|
+
<div id="step-ind-4" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;border-right:1px solid #2a2a2a;">
|
|
1228
|
+
4. Web App
|
|
1229
|
+
</div>
|
|
1230
|
+
<div id="step-ind-5" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:11px;font-weight:600;background:#0a0a0a;color:#666;">
|
|
1231
|
+
5. Auto-fill
|
|
1162
1232
|
</div>
|
|
1163
1233
|
</div>
|
|
1164
1234
|
|
|
@@ -1233,16 +1303,54 @@ function generateDashboardHtml(options) {
|
|
|
1233
1303
|
</div>
|
|
1234
1304
|
</div>
|
|
1235
1305
|
|
|
1236
|
-
<!-- Step 4:
|
|
1306
|
+
<!-- Step 4: Web App -->
|
|
1237
1307
|
<div id="step-4" style="display:none;">
|
|
1238
1308
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
|
1239
1309
|
<span id="step4-icon" style="font-size:20px;">⚪</span>
|
|
1310
|
+
<div>
|
|
1311
|
+
<div style="font-weight:600;color:#e5e5e5;font-size:15px;">Web App</div>
|
|
1312
|
+
<div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Checking web apps...</div>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div id="step4-action" style="display:none;">
|
|
1316
|
+
<!-- Existing web apps list -->
|
|
1317
|
+
<div id="webapp-list" style="display:none;">
|
|
1318
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
1319
|
+
<select id="webapp-select" style="padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;font-family:monospace;min-width:280px;outline:none;">
|
|
1320
|
+
<option value="">Loading web apps...</option>
|
|
1321
|
+
</select>
|
|
1322
|
+
<button id="select-webapp-btn" onclick="selectWebApp()" style="padding:8px 20px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1323
|
+
Use This App
|
|
1324
|
+
</button>
|
|
1325
|
+
<button onclick="showCreateWebApp()" style="padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;">
|
|
1326
|
+
+ Create New
|
|
1327
|
+
</button>
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>
|
|
1330
|
+
<!-- Create new web app form -->
|
|
1331
|
+
<div id="webapp-create" style="display:none;">
|
|
1332
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:8px;">No web app found. Create one to get Firebase SDK config:</div>
|
|
1333
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
1334
|
+
<input id="webapp-name" type="text" placeholder="My Web App" style="padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-size:13px;min-width:220px;outline:none;" />
|
|
1335
|
+
<button id="create-webapp-btn" onclick="createWebApp()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1336
|
+
Create Web App
|
|
1337
|
+
</button>
|
|
1338
|
+
</div>
|
|
1339
|
+
</div>
|
|
1340
|
+
<div id="webapp-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
|
|
1341
|
+
</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
|
|
1344
|
+
<!-- Step 5: Done / Auto-fill -->
|
|
1345
|
+
<div id="step-5" style="display:none;">
|
|
1346
|
+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
|
1347
|
+
<span id="step5-icon" style="font-size:20px;">⚪</span>
|
|
1240
1348
|
<div>
|
|
1241
1349
|
<div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
|
|
1242
|
-
<div id="
|
|
1350
|
+
<div id="step5-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
|
|
1243
1351
|
</div>
|
|
1244
1352
|
</div>
|
|
1245
|
-
<div id="
|
|
1353
|
+
<div id="step5-action">
|
|
1246
1354
|
<button id="autofill-wizard-btn" onclick="autoFillConfig()" style="padding:8px 20px;background:#f97316;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1247
1355
|
Auto-fill Config Now
|
|
1248
1356
|
</button>
|
|
@@ -1387,7 +1495,7 @@ function generateDashboardHtml(options) {
|
|
|
1387
1495
|
// \u2500\u2500\u2500 Setup Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1388
1496
|
|
|
1389
1497
|
function setStepIndicator(activeStep) {
|
|
1390
|
-
for (var i = 1; i <=
|
|
1498
|
+
for (var i = 1; i <= 5; i++) {
|
|
1391
1499
|
var el = document.getElementById('step-ind-' + i);
|
|
1392
1500
|
if (i < activeStep) {
|
|
1393
1501
|
el.style.background = '#0a1a0a';
|
|
@@ -1409,7 +1517,7 @@ function generateDashboardHtml(options) {
|
|
|
1409
1517
|
var CIRCLE = '\\u26AA';
|
|
1410
1518
|
|
|
1411
1519
|
// Hide all steps first
|
|
1412
|
-
for (var i = 1; i <=
|
|
1520
|
+
for (var i = 1; i <= 5; i++) {
|
|
1413
1521
|
document.getElementById('step-' + i).style.display = 'none';
|
|
1414
1522
|
}
|
|
1415
1523
|
document.getElementById('setup-done').style.display = 'none';
|
|
@@ -1419,10 +1527,13 @@ function generateDashboardHtml(options) {
|
|
|
1419
1527
|
|
|
1420
1528
|
if (status.nextStep === 'done') {
|
|
1421
1529
|
// All done!
|
|
1422
|
-
setStepIndicator(
|
|
1530
|
+
setStepIndicator(6);
|
|
1423
1531
|
document.getElementById('setup-done').style.display = 'block';
|
|
1424
|
-
|
|
1425
|
-
|
|
1532
|
+
var detail = 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
|
|
1533
|
+
if (status.webApp && status.webApp.displayName) {
|
|
1534
|
+
detail += ' | App: ' + status.webApp.displayName;
|
|
1535
|
+
}
|
|
1536
|
+
document.getElementById('setup-done-detail').textContent = detail;
|
|
1426
1537
|
return;
|
|
1427
1538
|
}
|
|
1428
1539
|
|
|
@@ -1489,11 +1600,32 @@ function generateDashboardHtml(options) {
|
|
|
1489
1600
|
return;
|
|
1490
1601
|
}
|
|
1491
1602
|
|
|
1492
|
-
// Step 4:
|
|
1603
|
+
// Step 4: Web App
|
|
1493
1604
|
var step4 = document.getElementById('step-4');
|
|
1494
1605
|
step4.style.display = 'block';
|
|
1495
|
-
|
|
1496
|
-
|
|
1606
|
+
if (status.webApp && status.webApp.appId) {
|
|
1607
|
+
document.getElementById('step4-icon').textContent = GREEN;
|
|
1608
|
+
document.getElementById('step4-detail').textContent = 'Web app: ' + (status.webApp.displayName || status.webApp.appId);
|
|
1609
|
+
document.getElementById('step4-action').style.display = 'block';
|
|
1610
|
+
// Show list with current selection
|
|
1611
|
+
loadWebAppList(status.webApp.appId);
|
|
1612
|
+
} else {
|
|
1613
|
+
document.getElementById('step4-icon').textContent = YELLOW;
|
|
1614
|
+
document.getElementById('step4-detail').textContent = 'No web app selected';
|
|
1615
|
+
document.getElementById('step4-action').style.display = 'block';
|
|
1616
|
+
loadWebAppList('');
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (status.nextStep === 'create-web-app') {
|
|
1620
|
+
setStepIndicator(4);
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Step 5: Auto-fill
|
|
1625
|
+
var step5 = document.getElementById('step-5');
|
|
1626
|
+
step5.style.display = 'block';
|
|
1627
|
+
document.getElementById('step5-icon').textContent = CIRCLE;
|
|
1628
|
+
setStepIndicator(5);
|
|
1497
1629
|
}
|
|
1498
1630
|
|
|
1499
1631
|
// \u2500\u2500\u2500 Step 1: Install CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1688,6 +1820,148 @@ function generateDashboardHtml(options) {
|
|
|
1688
1820
|
});
|
|
1689
1821
|
};
|
|
1690
1822
|
|
|
1823
|
+
// \u2500\u2500\u2500 Step 4: Web App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1824
|
+
function loadWebAppList(currentAppId, retryCount) {
|
|
1825
|
+
retryCount = retryCount || 0;
|
|
1826
|
+
var listDiv = document.getElementById('webapp-list');
|
|
1827
|
+
var createDiv = document.getElementById('webapp-create');
|
|
1828
|
+
var select = document.getElementById('webapp-select');
|
|
1829
|
+
|
|
1830
|
+
fetch(API + '/__dev/setup/web-apps')
|
|
1831
|
+
.then(function(r) { return r.json(); })
|
|
1832
|
+
.then(function(data) {
|
|
1833
|
+
if (data.error) {
|
|
1834
|
+
if (retryCount < 2) {
|
|
1835
|
+
setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
createDiv.style.display = 'block';
|
|
1839
|
+
listDiv.style.display = 'none';
|
|
1840
|
+
document.getElementById('webapp-create').querySelector('div').textContent = 'Error loading apps: ' + data.error;
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
if (!data.apps || data.apps.length === 0) {
|
|
1844
|
+
// No web apps \u2014 show create form
|
|
1845
|
+
createDiv.style.display = 'block';
|
|
1846
|
+
listDiv.style.display = 'none';
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Show list
|
|
1851
|
+
listDiv.style.display = 'block';
|
|
1852
|
+
createDiv.style.display = 'none';
|
|
1853
|
+
select.innerHTML = '<option value="">-- Select a web app --</option>';
|
|
1854
|
+
data.apps.forEach(function(app) {
|
|
1855
|
+
var opt = document.createElement('option');
|
|
1856
|
+
opt.value = app.appId;
|
|
1857
|
+
opt.setAttribute('data-name', app.displayName || '');
|
|
1858
|
+
opt.textContent = (app.displayName || 'Unnamed') + ' (' + app.appId.slice(-12) + ')';
|
|
1859
|
+
if (app.appId === currentAppId) opt.selected = true;
|
|
1860
|
+
select.appendChild(opt);
|
|
1861
|
+
});
|
|
1862
|
+
select.disabled = false;
|
|
1863
|
+
})
|
|
1864
|
+
.catch(function() {
|
|
1865
|
+
if (retryCount < 2) {
|
|
1866
|
+
setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
createDiv.style.display = 'block';
|
|
1870
|
+
listDiv.style.display = 'none';
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
window.showCreateWebApp = function() {
|
|
1875
|
+
document.getElementById('webapp-list').style.display = 'none';
|
|
1876
|
+
document.getElementById('webapp-create').style.display = 'block';
|
|
1877
|
+
document.getElementById('webapp-name').focus();
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
window.selectWebApp = function() {
|
|
1881
|
+
var select = document.getElementById('webapp-select');
|
|
1882
|
+
var status = document.getElementById('webapp-status');
|
|
1883
|
+
var appId = select.value;
|
|
1884
|
+
if (!appId) {
|
|
1885
|
+
status.textContent = 'Please select a web app first.';
|
|
1886
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
var selectedOption = select.options[select.selectedIndex];
|
|
1890
|
+
var displayName = selectedOption.getAttribute('data-name') || '';
|
|
1891
|
+
|
|
1892
|
+
var btn = document.getElementById('select-webapp-btn');
|
|
1893
|
+
btn.disabled = true;
|
|
1894
|
+
btn.textContent = 'Saving...';
|
|
1895
|
+
|
|
1896
|
+
fetch(API + '/__dev/setup/select-web-app', {
|
|
1897
|
+
method: 'POST',
|
|
1898
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1899
|
+
body: JSON.stringify({ appId: appId, displayName: displayName })
|
|
1900
|
+
})
|
|
1901
|
+
.then(function(r) { return r.json(); })
|
|
1902
|
+
.then(function(data) {
|
|
1903
|
+
if (data.success) {
|
|
1904
|
+
status.textContent = data.message;
|
|
1905
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
1906
|
+
setTimeout(refreshSetupStatus, 1000);
|
|
1907
|
+
} else {
|
|
1908
|
+
status.textContent = data.message;
|
|
1909
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1910
|
+
btn.disabled = false;
|
|
1911
|
+
btn.textContent = 'Use This App';
|
|
1912
|
+
}
|
|
1913
|
+
})
|
|
1914
|
+
.catch(function(err) {
|
|
1915
|
+
status.textContent = 'Error: ' + err.message;
|
|
1916
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1917
|
+
btn.disabled = false;
|
|
1918
|
+
btn.textContent = 'Use This App';
|
|
1919
|
+
});
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1922
|
+
window.createWebApp = function() {
|
|
1923
|
+
var nameInput = document.getElementById('webapp-name');
|
|
1924
|
+
var btn = document.getElementById('create-webapp-btn');
|
|
1925
|
+
var status = document.getElementById('webapp-status');
|
|
1926
|
+
var displayName = nameInput.value.trim();
|
|
1927
|
+
|
|
1928
|
+
if (!displayName) {
|
|
1929
|
+
status.textContent = 'Please enter a name for the web app.';
|
|
1930
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
btn.disabled = true;
|
|
1935
|
+
btn.textContent = 'Creating...';
|
|
1936
|
+
status.textContent = 'Creating web app... This may take a moment.';
|
|
1937
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1a;border:1px solid #3b82f6;color:#3b82f6;';
|
|
1938
|
+
|
|
1939
|
+
fetch(API + '/__dev/setup/create-web-app', {
|
|
1940
|
+
method: 'POST',
|
|
1941
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1942
|
+
body: JSON.stringify({ displayName: displayName })
|
|
1943
|
+
})
|
|
1944
|
+
.then(function(r) { return r.json(); })
|
|
1945
|
+
.then(function(data) {
|
|
1946
|
+
if (data.success) {
|
|
1947
|
+
status.textContent = data.message;
|
|
1948
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
1949
|
+
setTimeout(refreshSetupStatus, 1000);
|
|
1950
|
+
} else {
|
|
1951
|
+
status.textContent = data.message;
|
|
1952
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1953
|
+
btn.disabled = false;
|
|
1954
|
+
btn.textContent = 'Create Web App';
|
|
1955
|
+
}
|
|
1956
|
+
})
|
|
1957
|
+
.catch(function(err) {
|
|
1958
|
+
status.textContent = 'Error: ' + err.message;
|
|
1959
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1960
|
+
btn.disabled = false;
|
|
1961
|
+
btn.textContent = 'Create Web App';
|
|
1962
|
+
});
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1691
1965
|
// \u2500\u2500\u2500 Refresh Setup 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
|
|
1692
1966
|
function refreshSetupStatus() {
|
|
1693
1967
|
fetch(API + '/__dev/setup/status')
|
|
@@ -1863,7 +2137,12 @@ function generateDashboardHtml(options) {
|
|
|
1863
2137
|
if (wizStatus) wizStatus.style.display = 'none';
|
|
1864
2138
|
|
|
1865
2139
|
fetch(API + '/__dev/firebase-sdk-config')
|
|
1866
|
-
.then(function(r) {
|
|
2140
|
+
.then(function(r) {
|
|
2141
|
+
if (!r.ok) {
|
|
2142
|
+
return r.json().catch(function() { return { error: 'Server error (' + r.status + ')' }; });
|
|
2143
|
+
}
|
|
2144
|
+
return r.json();
|
|
2145
|
+
})
|
|
1867
2146
|
.then(function(data) {
|
|
1868
2147
|
if (data.error) {
|
|
1869
2148
|
showAutoFillResult(false, data.error);
|
|
@@ -2077,15 +2356,34 @@ import { resolve as resolve4, join as join3 } from "path";
|
|
|
2077
2356
|
import { tmpdir, platform } from "os";
|
|
2078
2357
|
var FirebaseSetup = class {
|
|
2079
2358
|
projectDir;
|
|
2359
|
+
stateFilePath;
|
|
2080
2360
|
constructor(projectDir) {
|
|
2081
2361
|
this.projectDir = projectDir;
|
|
2362
|
+
this.stateFilePath = resolve4(projectDir, ".clawfire-setup.json");
|
|
2363
|
+
}
|
|
2364
|
+
// ─── State Persistence ────────────────────────────────────────────
|
|
2365
|
+
loadState() {
|
|
2366
|
+
try {
|
|
2367
|
+
if (existsSync5(this.stateFilePath)) {
|
|
2368
|
+
return JSON.parse(readFileSync4(this.stateFilePath, "utf-8"));
|
|
2369
|
+
}
|
|
2370
|
+
} catch {
|
|
2371
|
+
}
|
|
2372
|
+
return {};
|
|
2373
|
+
}
|
|
2374
|
+
saveState(partial) {
|
|
2375
|
+
const current = this.loadState();
|
|
2376
|
+
const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2377
|
+
writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
2082
2378
|
}
|
|
2083
2379
|
// ─── Status Check ──────────────────────────────────────────────────
|
|
2084
2380
|
async getStatus() {
|
|
2381
|
+
const savedState = this.loadState();
|
|
2085
2382
|
const status = {
|
|
2086
2383
|
cli: { installed: false, version: "" },
|
|
2087
2384
|
auth: { authenticated: false, user: "" },
|
|
2088
2385
|
project: { id: "", hasFirebaserc: false },
|
|
2386
|
+
webApp: { appId: savedState.webAppId || "", displayName: savedState.webAppDisplayName || "" },
|
|
2089
2387
|
ready: false,
|
|
2090
2388
|
nextStep: "install-cli"
|
|
2091
2389
|
};
|
|
@@ -2127,6 +2425,10 @@ var FirebaseSetup = class {
|
|
|
2127
2425
|
status.nextStep = "select-project";
|
|
2128
2426
|
return status;
|
|
2129
2427
|
}
|
|
2428
|
+
if (!status.webApp.appId) {
|
|
2429
|
+
status.nextStep = "create-web-app";
|
|
2430
|
+
return status;
|
|
2431
|
+
}
|
|
2130
2432
|
status.ready = true;
|
|
2131
2433
|
status.nextStep = "done";
|
|
2132
2434
|
return status;
|
|
@@ -2280,6 +2582,12 @@ var FirebaseSetup = class {
|
|
|
2280
2582
|
if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
|
|
2281
2583
|
return { success: false, message: "Invalid project ID format." };
|
|
2282
2584
|
}
|
|
2585
|
+
const savedState = this.loadState();
|
|
2586
|
+
if (savedState.projectId && savedState.projectId !== projectId) {
|
|
2587
|
+
this.saveState({ projectId, webAppId: void 0, webAppDisplayName: void 0 });
|
|
2588
|
+
} else {
|
|
2589
|
+
this.saveState({ projectId });
|
|
2590
|
+
}
|
|
2283
2591
|
try {
|
|
2284
2592
|
await this.execTimeout(
|
|
2285
2593
|
"firebase",
|
|
@@ -2315,6 +2623,52 @@ var FirebaseSetup = class {
|
|
|
2315
2623
|
}
|
|
2316
2624
|
}
|
|
2317
2625
|
}
|
|
2626
|
+
// ─── Web App Management ───────────────────────────────────────────
|
|
2627
|
+
async listWebApps() {
|
|
2628
|
+
try {
|
|
2629
|
+
const output = await this.execTimeout(
|
|
2630
|
+
"firebase",
|
|
2631
|
+
["apps:list", "--json"],
|
|
2632
|
+
15e3
|
|
2633
|
+
);
|
|
2634
|
+
const data = JSON.parse(output);
|
|
2635
|
+
const allApps = data?.result || [];
|
|
2636
|
+
const webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
|
|
2637
|
+
appId: String(a.appId || ""),
|
|
2638
|
+
displayName: String(a.displayName || "")
|
|
2639
|
+
})).filter((a) => a.appId);
|
|
2640
|
+
return { apps: webApps };
|
|
2641
|
+
} catch (err) {
|
|
2642
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2643
|
+
return { apps: [], error: msg };
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
async createWebApp(displayName) {
|
|
2647
|
+
if (!displayName || displayName.length < 1) {
|
|
2648
|
+
return { success: false, message: "Display name is required." };
|
|
2649
|
+
}
|
|
2650
|
+
try {
|
|
2651
|
+
const output = await this.execTimeout(
|
|
2652
|
+
"firebase",
|
|
2653
|
+
["apps:create", "web", displayName, "--json"],
|
|
2654
|
+
3e4
|
|
2655
|
+
);
|
|
2656
|
+
const data = JSON.parse(output);
|
|
2657
|
+
const appId = data?.result?.appId || "";
|
|
2658
|
+
if (appId) {
|
|
2659
|
+
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
2660
|
+
return { success: true, appId, message: `Web app "${displayName}" created successfully.` };
|
|
2661
|
+
}
|
|
2662
|
+
return { success: false, message: "App created but could not retrieve app ID." };
|
|
2663
|
+
} catch (err) {
|
|
2664
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2665
|
+
return { success: false, message: `Failed to create web app: ${msg}` };
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
selectWebApp(appId, displayName) {
|
|
2669
|
+
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
2670
|
+
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
2671
|
+
}
|
|
2318
2672
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
2319
2673
|
execTimeout(command, args, timeoutMs) {
|
|
2320
2674
|
return new Promise((resolve6, reject) => {
|
|
@@ -3204,7 +3558,8 @@ ${liveReloadScript}
|
|
|
3204
3558
|
return;
|
|
3205
3559
|
}
|
|
3206
3560
|
if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
|
|
3207
|
-
|
|
3561
|
+
const savedState = this.firebaseSetup.loadState();
|
|
3562
|
+
fetchFirebaseSdkConfig(this.options.projectDir, savedState.webAppId).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
|
|
3208
3563
|
return;
|
|
3209
3564
|
}
|
|
3210
3565
|
if (url.pathname === "/__dev/env" && req.method === "GET") {
|
|
@@ -3289,6 +3644,53 @@ ${liveReloadScript}
|
|
|
3289
3644
|
});
|
|
3290
3645
|
return;
|
|
3291
3646
|
}
|
|
3647
|
+
if (url.pathname === "/__dev/setup/web-apps" && req.method === "GET") {
|
|
3648
|
+
this.firebaseSetup.listWebApps().then((result) => sendJson(result)).catch((err) => sendJson({ apps: [], error: err instanceof Error ? err.message : "Failed" }, 500));
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
if (url.pathname === "/__dev/setup/create-web-app" && req.method === "POST") {
|
|
3652
|
+
let body = "";
|
|
3653
|
+
req.on("data", (chunk) => {
|
|
3654
|
+
body += chunk;
|
|
3655
|
+
});
|
|
3656
|
+
req.on("end", () => {
|
|
3657
|
+
try {
|
|
3658
|
+
const data = JSON.parse(body);
|
|
3659
|
+
if (!data.displayName) {
|
|
3660
|
+
sendJson({ success: false, message: "displayName is required" }, 400);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
this.firebaseSetup.createWebApp(data.displayName).then((result) => {
|
|
3664
|
+
clearFirebaseStatusCache();
|
|
3665
|
+
sendJson(result);
|
|
3666
|
+
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
3667
|
+
} catch {
|
|
3668
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
3669
|
+
}
|
|
3670
|
+
});
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
3674
|
+
let body = "";
|
|
3675
|
+
req.on("data", (chunk) => {
|
|
3676
|
+
body += chunk;
|
|
3677
|
+
});
|
|
3678
|
+
req.on("end", () => {
|
|
3679
|
+
try {
|
|
3680
|
+
const data = JSON.parse(body);
|
|
3681
|
+
if (!data.appId) {
|
|
3682
|
+
sendJson({ success: false, message: "appId is required" }, 400);
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
const result = this.firebaseSetup.selectWebApp(data.appId, data.displayName || "");
|
|
3686
|
+
clearFirebaseStatusCache();
|
|
3687
|
+
sendJson(result);
|
|
3688
|
+
} catch {
|
|
3689
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3292
3694
|
res.writeHead(404);
|
|
3293
3695
|
res.end("Not found");
|
|
3294
3696
|
}
|