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/dev.js
CHANGED
|
@@ -1456,39 +1456,106 @@ async function checkCli(projectDir) {
|
|
|
1456
1456
|
}
|
|
1457
1457
|
return result;
|
|
1458
1458
|
}
|
|
1459
|
-
async function fetchFirebaseSdkConfig(projectDir) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1459
|
+
async function fetchFirebaseSdkConfig(projectDir, appId) {
|
|
1460
|
+
if (appId) {
|
|
1461
|
+
try {
|
|
1462
|
+
const output = await execWithTimeout(
|
|
1463
|
+
"firebase",
|
|
1464
|
+
["apps:sdkconfig", "web", appId, "--json"],
|
|
1465
|
+
projectDir,
|
|
1466
|
+
15e3
|
|
1467
|
+
);
|
|
1468
|
+
const config = parseSdkConfigOutput(output);
|
|
1469
|
+
if (config) return config;
|
|
1470
|
+
} catch (err) {
|
|
1471
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1472
|
+
throw new Error(`Failed to fetch config for web app ${appId}: ${msg}`);
|
|
1473
|
+
}
|
|
1474
|
+
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1475
|
+
}
|
|
1476
|
+
try {
|
|
1477
|
+
const output = await execWithTimeout(
|
|
1478
|
+
"firebase",
|
|
1479
|
+
["apps:sdkconfig", "web", "--json"],
|
|
1480
|
+
projectDir,
|
|
1481
|
+
15e3
|
|
1482
|
+
);
|
|
1483
|
+
const config = parseSdkConfigOutput(output);
|
|
1484
|
+
if (config) return config;
|
|
1485
|
+
} catch {
|
|
1486
|
+
}
|
|
1487
|
+
let webApps = [];
|
|
1488
|
+
try {
|
|
1489
|
+
const appsOutput = await execWithTimeout(
|
|
1490
|
+
"firebase",
|
|
1491
|
+
["apps:list", "--json"],
|
|
1492
|
+
projectDir,
|
|
1493
|
+
15e3
|
|
1494
|
+
);
|
|
1495
|
+
const appsData = JSON.parse(appsOutput);
|
|
1496
|
+
const allApps = appsData?.result || [];
|
|
1497
|
+
webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
|
|
1498
|
+
appId: String(a.appId || ""),
|
|
1499
|
+
displayName: String(a.displayName || "")
|
|
1500
|
+
})).filter((a) => a.appId);
|
|
1501
|
+
} catch {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
"Failed to fetch Firebase SDK config. Make sure you are logged in and have an active project selected."
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
if (webApps.length === 0) {
|
|
1507
|
+
throw new Error(
|
|
1508
|
+
"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."
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
const firstAppId = webApps[0].appId;
|
|
1512
|
+
try {
|
|
1513
|
+
const output = await execWithTimeout(
|
|
1514
|
+
"firebase",
|
|
1515
|
+
["apps:sdkconfig", "web", firstAppId, "--json"],
|
|
1516
|
+
projectDir,
|
|
1517
|
+
15e3
|
|
1518
|
+
);
|
|
1519
|
+
const config = parseSdkConfigOutput(output);
|
|
1520
|
+
if (config) return config;
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
1523
|
+
throw new Error(`Failed to fetch config for web app ${firstAppId}: ${msg}`);
|
|
1484
1524
|
}
|
|
1485
1525
|
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1486
1526
|
}
|
|
1527
|
+
function parseSdkConfigOutput(output) {
|
|
1528
|
+
try {
|
|
1529
|
+
const data = JSON.parse(output);
|
|
1530
|
+
if (data?.result?.sdkConfig) {
|
|
1531
|
+
return data.result.sdkConfig;
|
|
1532
|
+
}
|
|
1533
|
+
if (data?.result?.fileContents) {
|
|
1534
|
+
const contents = data.result.fileContents;
|
|
1535
|
+
const config = {};
|
|
1536
|
+
const extract = (key) => {
|
|
1537
|
+
const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
1538
|
+
return match ? match[1] : void 0;
|
|
1539
|
+
};
|
|
1540
|
+
config.apiKey = extract("apiKey");
|
|
1541
|
+
config.authDomain = extract("authDomain");
|
|
1542
|
+
config.projectId = extract("projectId");
|
|
1543
|
+
config.storageBucket = extract("storageBucket");
|
|
1544
|
+
config.messagingSenderId = extract("messagingSenderId");
|
|
1545
|
+
config.appId = extract("appId");
|
|
1546
|
+
if (config.apiKey || config.projectId) return config;
|
|
1547
|
+
}
|
|
1548
|
+
} catch {
|
|
1549
|
+
}
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1487
1552
|
function execWithTimeout(command, args, cwd, timeoutMs) {
|
|
1488
1553
|
return new Promise((resolve7, reject) => {
|
|
1489
|
-
const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
|
|
1554
|
+
const proc = execFile(command, args, { cwd, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
1490
1555
|
if (err) {
|
|
1491
|
-
|
|
1556
|
+
const detail = stderr?.trim() || stdout?.trim() || "";
|
|
1557
|
+
const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
|
|
1558
|
+
reject(enriched);
|
|
1492
1559
|
} else {
|
|
1493
1560
|
resolve7(stdout);
|
|
1494
1561
|
}
|
|
@@ -1522,17 +1589,20 @@ function generateDashboardHtml(options) {
|
|
|
1522
1589
|
|
|
1523
1590
|
<!-- Step indicators -->
|
|
1524
1591
|
<div id="setup-steps" style="display:flex;gap:0;margin-bottom:20px;overflow:hidden;border-radius:8px;border:1px solid #2a2a2a;">
|
|
1525
|
-
<div id="step-ind-1" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1526
|
-
1. CLI
|
|
1592
|
+
<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;">
|
|
1593
|
+
1. CLI
|
|
1527
1594
|
</div>
|
|
1528
|
-
<div id="step-ind-2" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1595
|
+
<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;">
|
|
1529
1596
|
2. Login
|
|
1530
1597
|
</div>
|
|
1531
|
-
<div id="step-ind-3" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1532
|
-
3.
|
|
1598
|
+
<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;">
|
|
1599
|
+
3. Project
|
|
1533
1600
|
</div>
|
|
1534
|
-
<div id="step-ind-4" class="step-ind" style="flex:1;padding:8px 12px;text-align:center;font-size:
|
|
1535
|
-
4.
|
|
1601
|
+
<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;">
|
|
1602
|
+
4. Web App
|
|
1603
|
+
</div>
|
|
1604
|
+
<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;">
|
|
1605
|
+
5. Auto-fill
|
|
1536
1606
|
</div>
|
|
1537
1607
|
</div>
|
|
1538
1608
|
|
|
@@ -1607,16 +1677,54 @@ function generateDashboardHtml(options) {
|
|
|
1607
1677
|
</div>
|
|
1608
1678
|
</div>
|
|
1609
1679
|
|
|
1610
|
-
<!-- Step 4:
|
|
1680
|
+
<!-- Step 4: Web App -->
|
|
1611
1681
|
<div id="step-4" style="display:none;">
|
|
1612
1682
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
|
1613
1683
|
<span id="step4-icon" style="font-size:20px;">⚪</span>
|
|
1684
|
+
<div>
|
|
1685
|
+
<div style="font-weight:600;color:#e5e5e5;font-size:15px;">Web App</div>
|
|
1686
|
+
<div id="step4-detail" style="font-size:13px;color:#a3a3a3;">Checking web apps...</div>
|
|
1687
|
+
</div>
|
|
1688
|
+
</div>
|
|
1689
|
+
<div id="step4-action" style="display:none;">
|
|
1690
|
+
<!-- Existing web apps list -->
|
|
1691
|
+
<div id="webapp-list" style="display:none;">
|
|
1692
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
1693
|
+
<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;">
|
|
1694
|
+
<option value="">Loading web apps...</option>
|
|
1695
|
+
</select>
|
|
1696
|
+
<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;">
|
|
1697
|
+
Use This App
|
|
1698
|
+
</button>
|
|
1699
|
+
<button onclick="showCreateWebApp()" style="padding:8px 12px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;">
|
|
1700
|
+
+ Create New
|
|
1701
|
+
</button>
|
|
1702
|
+
</div>
|
|
1703
|
+
</div>
|
|
1704
|
+
<!-- Create new web app form -->
|
|
1705
|
+
<div id="webapp-create" style="display:none;">
|
|
1706
|
+
<div style="font-size:13px;color:#a3a3a3;margin-bottom:8px;">No web app found. Create one to get Firebase SDK config:</div>
|
|
1707
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|
1708
|
+
<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;" />
|
|
1709
|
+
<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;">
|
|
1710
|
+
Create Web App
|
|
1711
|
+
</button>
|
|
1712
|
+
</div>
|
|
1713
|
+
</div>
|
|
1714
|
+
<div id="webapp-status" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
|
|
1715
|
+
</div>
|
|
1716
|
+
</div>
|
|
1717
|
+
|
|
1718
|
+
<!-- Step 5: Done / Auto-fill -->
|
|
1719
|
+
<div id="step-5" style="display:none;">
|
|
1720
|
+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;">
|
|
1721
|
+
<span id="step5-icon" style="font-size:20px;">⚪</span>
|
|
1614
1722
|
<div>
|
|
1615
1723
|
<div style="font-weight:600;color:#e5e5e5;font-size:15px;">Auto-fill Config</div>
|
|
1616
|
-
<div id="
|
|
1724
|
+
<div id="step5-detail" style="font-size:13px;color:#a3a3a3;">Ready to auto-fill your clawfire.config.ts</div>
|
|
1617
1725
|
</div>
|
|
1618
1726
|
</div>
|
|
1619
|
-
<div id="
|
|
1727
|
+
<div id="step5-action">
|
|
1620
1728
|
<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;">
|
|
1621
1729
|
Auto-fill Config Now
|
|
1622
1730
|
</button>
|
|
@@ -1761,7 +1869,7 @@ function generateDashboardHtml(options) {
|
|
|
1761
1869
|
// \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
|
|
1762
1870
|
|
|
1763
1871
|
function setStepIndicator(activeStep) {
|
|
1764
|
-
for (var i = 1; i <=
|
|
1872
|
+
for (var i = 1; i <= 5; i++) {
|
|
1765
1873
|
var el = document.getElementById('step-ind-' + i);
|
|
1766
1874
|
if (i < activeStep) {
|
|
1767
1875
|
el.style.background = '#0a1a0a';
|
|
@@ -1783,7 +1891,7 @@ function generateDashboardHtml(options) {
|
|
|
1783
1891
|
var CIRCLE = '\\u26AA';
|
|
1784
1892
|
|
|
1785
1893
|
// Hide all steps first
|
|
1786
|
-
for (var i = 1; i <=
|
|
1894
|
+
for (var i = 1; i <= 5; i++) {
|
|
1787
1895
|
document.getElementById('step-' + i).style.display = 'none';
|
|
1788
1896
|
}
|
|
1789
1897
|
document.getElementById('setup-done').style.display = 'none';
|
|
@@ -1793,10 +1901,13 @@ function generateDashboardHtml(options) {
|
|
|
1793
1901
|
|
|
1794
1902
|
if (status.nextStep === 'done') {
|
|
1795
1903
|
// All done!
|
|
1796
|
-
setStepIndicator(
|
|
1904
|
+
setStepIndicator(6);
|
|
1797
1905
|
document.getElementById('setup-done').style.display = 'block';
|
|
1798
|
-
|
|
1799
|
-
|
|
1906
|
+
var detail = 'Logged in as ' + status.auth.user + ' | Project: ' + status.project.id;
|
|
1907
|
+
if (status.webApp && status.webApp.displayName) {
|
|
1908
|
+
detail += ' | App: ' + status.webApp.displayName;
|
|
1909
|
+
}
|
|
1910
|
+
document.getElementById('setup-done-detail').textContent = detail;
|
|
1800
1911
|
return;
|
|
1801
1912
|
}
|
|
1802
1913
|
|
|
@@ -1863,11 +1974,32 @@ function generateDashboardHtml(options) {
|
|
|
1863
1974
|
return;
|
|
1864
1975
|
}
|
|
1865
1976
|
|
|
1866
|
-
// Step 4:
|
|
1977
|
+
// Step 4: Web App
|
|
1867
1978
|
var step4 = document.getElementById('step-4');
|
|
1868
1979
|
step4.style.display = 'block';
|
|
1869
|
-
|
|
1870
|
-
|
|
1980
|
+
if (status.webApp && status.webApp.appId) {
|
|
1981
|
+
document.getElementById('step4-icon').textContent = GREEN;
|
|
1982
|
+
document.getElementById('step4-detail').textContent = 'Web app: ' + (status.webApp.displayName || status.webApp.appId);
|
|
1983
|
+
document.getElementById('step4-action').style.display = 'block';
|
|
1984
|
+
// Show list with current selection
|
|
1985
|
+
loadWebAppList(status.webApp.appId);
|
|
1986
|
+
} else {
|
|
1987
|
+
document.getElementById('step4-icon').textContent = YELLOW;
|
|
1988
|
+
document.getElementById('step4-detail').textContent = 'No web app selected';
|
|
1989
|
+
document.getElementById('step4-action').style.display = 'block';
|
|
1990
|
+
loadWebAppList('');
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
if (status.nextStep === 'create-web-app') {
|
|
1994
|
+
setStepIndicator(4);
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// Step 5: Auto-fill
|
|
1999
|
+
var step5 = document.getElementById('step-5');
|
|
2000
|
+
step5.style.display = 'block';
|
|
2001
|
+
document.getElementById('step5-icon').textContent = CIRCLE;
|
|
2002
|
+
setStepIndicator(5);
|
|
1871
2003
|
}
|
|
1872
2004
|
|
|
1873
2005
|
// \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
|
|
@@ -2062,6 +2194,148 @@ function generateDashboardHtml(options) {
|
|
|
2062
2194
|
});
|
|
2063
2195
|
};
|
|
2064
2196
|
|
|
2197
|
+
// \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
|
|
2198
|
+
function loadWebAppList(currentAppId, retryCount) {
|
|
2199
|
+
retryCount = retryCount || 0;
|
|
2200
|
+
var listDiv = document.getElementById('webapp-list');
|
|
2201
|
+
var createDiv = document.getElementById('webapp-create');
|
|
2202
|
+
var select = document.getElementById('webapp-select');
|
|
2203
|
+
|
|
2204
|
+
fetch(API + '/__dev/setup/web-apps')
|
|
2205
|
+
.then(function(r) { return r.json(); })
|
|
2206
|
+
.then(function(data) {
|
|
2207
|
+
if (data.error) {
|
|
2208
|
+
if (retryCount < 2) {
|
|
2209
|
+
setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
createDiv.style.display = 'block';
|
|
2213
|
+
listDiv.style.display = 'none';
|
|
2214
|
+
document.getElementById('webapp-create').querySelector('div').textContent = 'Error loading apps: ' + data.error;
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
if (!data.apps || data.apps.length === 0) {
|
|
2218
|
+
// No web apps \u2014 show create form
|
|
2219
|
+
createDiv.style.display = 'block';
|
|
2220
|
+
listDiv.style.display = 'none';
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// Show list
|
|
2225
|
+
listDiv.style.display = 'block';
|
|
2226
|
+
createDiv.style.display = 'none';
|
|
2227
|
+
select.innerHTML = '<option value="">-- Select a web app --</option>';
|
|
2228
|
+
data.apps.forEach(function(app) {
|
|
2229
|
+
var opt = document.createElement('option');
|
|
2230
|
+
opt.value = app.appId;
|
|
2231
|
+
opt.setAttribute('data-name', app.displayName || '');
|
|
2232
|
+
opt.textContent = (app.displayName || 'Unnamed') + ' (' + app.appId.slice(-12) + ')';
|
|
2233
|
+
if (app.appId === currentAppId) opt.selected = true;
|
|
2234
|
+
select.appendChild(opt);
|
|
2235
|
+
});
|
|
2236
|
+
select.disabled = false;
|
|
2237
|
+
})
|
|
2238
|
+
.catch(function() {
|
|
2239
|
+
if (retryCount < 2) {
|
|
2240
|
+
setTimeout(function() { loadWebAppList(currentAppId, retryCount + 1); }, 3000);
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
createDiv.style.display = 'block';
|
|
2244
|
+
listDiv.style.display = 'none';
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
window.showCreateWebApp = function() {
|
|
2249
|
+
document.getElementById('webapp-list').style.display = 'none';
|
|
2250
|
+
document.getElementById('webapp-create').style.display = 'block';
|
|
2251
|
+
document.getElementById('webapp-name').focus();
|
|
2252
|
+
};
|
|
2253
|
+
|
|
2254
|
+
window.selectWebApp = function() {
|
|
2255
|
+
var select = document.getElementById('webapp-select');
|
|
2256
|
+
var status = document.getElementById('webapp-status');
|
|
2257
|
+
var appId = select.value;
|
|
2258
|
+
if (!appId) {
|
|
2259
|
+
status.textContent = 'Please select a web app first.';
|
|
2260
|
+
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;';
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
var selectedOption = select.options[select.selectedIndex];
|
|
2264
|
+
var displayName = selectedOption.getAttribute('data-name') || '';
|
|
2265
|
+
|
|
2266
|
+
var btn = document.getElementById('select-webapp-btn');
|
|
2267
|
+
btn.disabled = true;
|
|
2268
|
+
btn.textContent = 'Saving...';
|
|
2269
|
+
|
|
2270
|
+
fetch(API + '/__dev/setup/select-web-app', {
|
|
2271
|
+
method: 'POST',
|
|
2272
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2273
|
+
body: JSON.stringify({ appId: appId, displayName: displayName })
|
|
2274
|
+
})
|
|
2275
|
+
.then(function(r) { return r.json(); })
|
|
2276
|
+
.then(function(data) {
|
|
2277
|
+
if (data.success) {
|
|
2278
|
+
status.textContent = data.message;
|
|
2279
|
+
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;';
|
|
2280
|
+
setTimeout(refreshSetupStatus, 1000);
|
|
2281
|
+
} else {
|
|
2282
|
+
status.textContent = data.message;
|
|
2283
|
+
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;';
|
|
2284
|
+
btn.disabled = false;
|
|
2285
|
+
btn.textContent = 'Use This App';
|
|
2286
|
+
}
|
|
2287
|
+
})
|
|
2288
|
+
.catch(function(err) {
|
|
2289
|
+
status.textContent = 'Error: ' + err.message;
|
|
2290
|
+
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;';
|
|
2291
|
+
btn.disabled = false;
|
|
2292
|
+
btn.textContent = 'Use This App';
|
|
2293
|
+
});
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
window.createWebApp = function() {
|
|
2297
|
+
var nameInput = document.getElementById('webapp-name');
|
|
2298
|
+
var btn = document.getElementById('create-webapp-btn');
|
|
2299
|
+
var status = document.getElementById('webapp-status');
|
|
2300
|
+
var displayName = nameInput.value.trim();
|
|
2301
|
+
|
|
2302
|
+
if (!displayName) {
|
|
2303
|
+
status.textContent = 'Please enter a name for the web app.';
|
|
2304
|
+
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;';
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
btn.disabled = true;
|
|
2309
|
+
btn.textContent = 'Creating...';
|
|
2310
|
+
status.textContent = 'Creating web app... This may take a moment.';
|
|
2311
|
+
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;';
|
|
2312
|
+
|
|
2313
|
+
fetch(API + '/__dev/setup/create-web-app', {
|
|
2314
|
+
method: 'POST',
|
|
2315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2316
|
+
body: JSON.stringify({ displayName: displayName })
|
|
2317
|
+
})
|
|
2318
|
+
.then(function(r) { return r.json(); })
|
|
2319
|
+
.then(function(data) {
|
|
2320
|
+
if (data.success) {
|
|
2321
|
+
status.textContent = data.message;
|
|
2322
|
+
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;';
|
|
2323
|
+
setTimeout(refreshSetupStatus, 1000);
|
|
2324
|
+
} else {
|
|
2325
|
+
status.textContent = data.message;
|
|
2326
|
+
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;';
|
|
2327
|
+
btn.disabled = false;
|
|
2328
|
+
btn.textContent = 'Create Web App';
|
|
2329
|
+
}
|
|
2330
|
+
})
|
|
2331
|
+
.catch(function(err) {
|
|
2332
|
+
status.textContent = 'Error: ' + err.message;
|
|
2333
|
+
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;';
|
|
2334
|
+
btn.disabled = false;
|
|
2335
|
+
btn.textContent = 'Create Web App';
|
|
2336
|
+
});
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2065
2339
|
// \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
|
|
2066
2340
|
function refreshSetupStatus() {
|
|
2067
2341
|
fetch(API + '/__dev/setup/status')
|
|
@@ -2237,7 +2511,12 @@ function generateDashboardHtml(options) {
|
|
|
2237
2511
|
if (wizStatus) wizStatus.style.display = 'none';
|
|
2238
2512
|
|
|
2239
2513
|
fetch(API + '/__dev/firebase-sdk-config')
|
|
2240
|
-
.then(function(r) {
|
|
2514
|
+
.then(function(r) {
|
|
2515
|
+
if (!r.ok) {
|
|
2516
|
+
return r.json().catch(function() { return { error: 'Server error (' + r.status + ')' }; });
|
|
2517
|
+
}
|
|
2518
|
+
return r.json();
|
|
2519
|
+
})
|
|
2241
2520
|
.then(function(data) {
|
|
2242
2521
|
if (data.error) {
|
|
2243
2522
|
showAutoFillResult(false, data.error);
|
|
@@ -2451,15 +2730,34 @@ import { resolve as resolve5, join as join4 } from "path";
|
|
|
2451
2730
|
import { tmpdir, platform } from "os";
|
|
2452
2731
|
var FirebaseSetup = class {
|
|
2453
2732
|
projectDir;
|
|
2733
|
+
stateFilePath;
|
|
2454
2734
|
constructor(projectDir) {
|
|
2455
2735
|
this.projectDir = projectDir;
|
|
2736
|
+
this.stateFilePath = resolve5(projectDir, ".clawfire-setup.json");
|
|
2737
|
+
}
|
|
2738
|
+
// ─── State Persistence ────────────────────────────────────────────
|
|
2739
|
+
loadState() {
|
|
2740
|
+
try {
|
|
2741
|
+
if (existsSync6(this.stateFilePath)) {
|
|
2742
|
+
return JSON.parse(readFileSync4(this.stateFilePath, "utf-8"));
|
|
2743
|
+
}
|
|
2744
|
+
} catch {
|
|
2745
|
+
}
|
|
2746
|
+
return {};
|
|
2747
|
+
}
|
|
2748
|
+
saveState(partial) {
|
|
2749
|
+
const current = this.loadState();
|
|
2750
|
+
const merged = { ...current, ...partial, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2751
|
+
writeFileSync2(this.stateFilePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
2456
2752
|
}
|
|
2457
2753
|
// ─── Status Check ──────────────────────────────────────────────────
|
|
2458
2754
|
async getStatus() {
|
|
2755
|
+
const savedState = this.loadState();
|
|
2459
2756
|
const status = {
|
|
2460
2757
|
cli: { installed: false, version: "" },
|
|
2461
2758
|
auth: { authenticated: false, user: "" },
|
|
2462
2759
|
project: { id: "", hasFirebaserc: false },
|
|
2760
|
+
webApp: { appId: savedState.webAppId || "", displayName: savedState.webAppDisplayName || "" },
|
|
2463
2761
|
ready: false,
|
|
2464
2762
|
nextStep: "install-cli"
|
|
2465
2763
|
};
|
|
@@ -2501,6 +2799,10 @@ var FirebaseSetup = class {
|
|
|
2501
2799
|
status.nextStep = "select-project";
|
|
2502
2800
|
return status;
|
|
2503
2801
|
}
|
|
2802
|
+
if (!status.webApp.appId) {
|
|
2803
|
+
status.nextStep = "create-web-app";
|
|
2804
|
+
return status;
|
|
2805
|
+
}
|
|
2504
2806
|
status.ready = true;
|
|
2505
2807
|
status.nextStep = "done";
|
|
2506
2808
|
return status;
|
|
@@ -2654,6 +2956,12 @@ var FirebaseSetup = class {
|
|
|
2654
2956
|
if (!projectId || !/^[a-z0-9-]+$/.test(projectId)) {
|
|
2655
2957
|
return { success: false, message: "Invalid project ID format." };
|
|
2656
2958
|
}
|
|
2959
|
+
const savedState = this.loadState();
|
|
2960
|
+
if (savedState.projectId && savedState.projectId !== projectId) {
|
|
2961
|
+
this.saveState({ projectId, webAppId: void 0, webAppDisplayName: void 0 });
|
|
2962
|
+
} else {
|
|
2963
|
+
this.saveState({ projectId });
|
|
2964
|
+
}
|
|
2657
2965
|
try {
|
|
2658
2966
|
await this.execTimeout(
|
|
2659
2967
|
"firebase",
|
|
@@ -2689,6 +2997,52 @@ var FirebaseSetup = class {
|
|
|
2689
2997
|
}
|
|
2690
2998
|
}
|
|
2691
2999
|
}
|
|
3000
|
+
// ─── Web App Management ───────────────────────────────────────────
|
|
3001
|
+
async listWebApps() {
|
|
3002
|
+
try {
|
|
3003
|
+
const output = await this.execTimeout(
|
|
3004
|
+
"firebase",
|
|
3005
|
+
["apps:list", "--json"],
|
|
3006
|
+
15e3
|
|
3007
|
+
);
|
|
3008
|
+
const data = JSON.parse(output);
|
|
3009
|
+
const allApps = data?.result || [];
|
|
3010
|
+
const webApps = allApps.filter((a) => a.platform === "WEB").map((a) => ({
|
|
3011
|
+
appId: String(a.appId || ""),
|
|
3012
|
+
displayName: String(a.displayName || "")
|
|
3013
|
+
})).filter((a) => a.appId);
|
|
3014
|
+
return { apps: webApps };
|
|
3015
|
+
} catch (err) {
|
|
3016
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3017
|
+
return { apps: [], error: msg };
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
async createWebApp(displayName) {
|
|
3021
|
+
if (!displayName || displayName.length < 1) {
|
|
3022
|
+
return { success: false, message: "Display name is required." };
|
|
3023
|
+
}
|
|
3024
|
+
try {
|
|
3025
|
+
const output = await this.execTimeout(
|
|
3026
|
+
"firebase",
|
|
3027
|
+
["apps:create", "web", displayName, "--json"],
|
|
3028
|
+
3e4
|
|
3029
|
+
);
|
|
3030
|
+
const data = JSON.parse(output);
|
|
3031
|
+
const appId = data?.result?.appId || "";
|
|
3032
|
+
if (appId) {
|
|
3033
|
+
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
3034
|
+
return { success: true, appId, message: `Web app "${displayName}" created successfully.` };
|
|
3035
|
+
}
|
|
3036
|
+
return { success: false, message: "App created but could not retrieve app ID." };
|
|
3037
|
+
} catch (err) {
|
|
3038
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3039
|
+
return { success: false, message: `Failed to create web app: ${msg}` };
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
selectWebApp(appId, displayName) {
|
|
3043
|
+
this.saveState({ webAppId: appId, webAppDisplayName: displayName });
|
|
3044
|
+
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
3045
|
+
}
|
|
2692
3046
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
2693
3047
|
execTimeout(command, args, timeoutMs) {
|
|
2694
3048
|
return new Promise((resolve7, reject) => {
|
|
@@ -3578,7 +3932,8 @@ ${liveReloadScript}
|
|
|
3578
3932
|
return;
|
|
3579
3933
|
}
|
|
3580
3934
|
if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
|
|
3581
|
-
|
|
3935
|
+
const savedState = this.firebaseSetup.loadState();
|
|
3936
|
+
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));
|
|
3582
3937
|
return;
|
|
3583
3938
|
}
|
|
3584
3939
|
if (url.pathname === "/__dev/env" && req.method === "GET") {
|
|
@@ -3663,6 +4018,53 @@ ${liveReloadScript}
|
|
|
3663
4018
|
});
|
|
3664
4019
|
return;
|
|
3665
4020
|
}
|
|
4021
|
+
if (url.pathname === "/__dev/setup/web-apps" && req.method === "GET") {
|
|
4022
|
+
this.firebaseSetup.listWebApps().then((result) => sendJson(result)).catch((err) => sendJson({ apps: [], error: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
if (url.pathname === "/__dev/setup/create-web-app" && req.method === "POST") {
|
|
4026
|
+
let body = "";
|
|
4027
|
+
req.on("data", (chunk) => {
|
|
4028
|
+
body += chunk;
|
|
4029
|
+
});
|
|
4030
|
+
req.on("end", () => {
|
|
4031
|
+
try {
|
|
4032
|
+
const data = JSON.parse(body);
|
|
4033
|
+
if (!data.displayName) {
|
|
4034
|
+
sendJson({ success: false, message: "displayName is required" }, 400);
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
this.firebaseSetup.createWebApp(data.displayName).then((result) => {
|
|
4038
|
+
clearFirebaseStatusCache();
|
|
4039
|
+
sendJson(result);
|
|
4040
|
+
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4041
|
+
} catch {
|
|
4042
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
4043
|
+
}
|
|
4044
|
+
});
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
if (url.pathname === "/__dev/setup/select-web-app" && req.method === "POST") {
|
|
4048
|
+
let body = "";
|
|
4049
|
+
req.on("data", (chunk) => {
|
|
4050
|
+
body += chunk;
|
|
4051
|
+
});
|
|
4052
|
+
req.on("end", () => {
|
|
4053
|
+
try {
|
|
4054
|
+
const data = JSON.parse(body);
|
|
4055
|
+
if (!data.appId) {
|
|
4056
|
+
sendJson({ success: false, message: "appId is required" }, 400);
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
const result = this.firebaseSetup.selectWebApp(data.appId, data.displayName || "");
|
|
4060
|
+
clearFirebaseStatusCache();
|
|
4061
|
+
sendJson(result);
|
|
4062
|
+
} catch {
|
|
4063
|
+
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
4064
|
+
}
|
|
4065
|
+
});
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
3666
4068
|
res.writeHead(404);
|
|
3667
4069
|
res.end("Not found");
|
|
3668
4070
|
}
|