clawfire 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/{dev-server-DRZ52WIA.js → dev-server-PAI4XU2S.js} +136 -314
- package/dist/dev.cjs +133 -311
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +136 -314
- 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-PAI4XU2S.js");
|
|
213
213
|
await startDevServer({
|
|
214
214
|
projectDir,
|
|
215
215
|
port,
|
|
@@ -1195,34 +1195,15 @@ function generateDashboardHtml(options) {
|
|
|
1195
1195
|
<button id="reauth-btn" onclick="startFirebaseLogin(true)" style="display:none;padding:8px 20px;background:#eab308;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">
|
|
1196
1196
|
Re-authenticate
|
|
1197
1197
|
</button>
|
|
1198
|
-
<button id="cancel-login-btn" onclick="cancelFirebaseLogin()" style="display:none;padding:8px 20px;background:transparent;border:1px solid #2a2a2a;border-radius:6px;color:#a3a3a3;font-size:13px;cursor:pointer;margin-left:8px;">
|
|
1199
|
-
Cancel
|
|
1200
|
-
</button>
|
|
1201
1198
|
</div>
|
|
1202
|
-
<!-- Login
|
|
1203
|
-
<div id="login-
|
|
1199
|
+
<!-- Login waiting message -->
|
|
1200
|
+
<div id="login-waiting" style="display:none;margin-top:12px;padding:16px;border-radius:8px;background:#0a0a1a;border:1px solid #3b82f6;">
|
|
1204
1201
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
1205
|
-
<span
|
|
1206
|
-
<span
|
|
1207
|
-
</div>
|
|
1208
|
-
<div id="login-url-box" style="display:none;margin-top:12px;">
|
|
1209
|
-
<div style="font-size:13px;color:#e5e5e5;margin-bottom:6px;font-weight:600;">Step 1: Open this link to authenticate</div>
|
|
1210
|
-
<a id="login-url-link" href="#" target="_blank" rel="noopener" style="display:inline-block;padding:8px 16px;background:#3b82f6;color:#fff;border-radius:6px;font-size:13px;text-decoration:none;margin-bottom:8px;">Open Google Login</a>
|
|
1211
|
-
<div style="font-size:11px;color:#666;margin-top:4px;word-break:break-all;font-family:monospace;" id="login-url-raw"></div>
|
|
1202
|
+
<span style="display:inline-block;width:14px;height:14px;border:2px solid #3b82f6;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></span>
|
|
1203
|
+
<span style="color:#e5e5e5;font-size:13px;font-weight:600;">Waiting for login...</span>
|
|
1212
1204
|
</div>
|
|
1213
|
-
<div
|
|
1214
|
-
|
|
1215
|
-
<div style="font-size:12px;color:#a3a3a3;margin-bottom:8px;">After signing in, Google will show an authorization code. Copy it and paste below.</div>
|
|
1216
|
-
<div style="display:flex;gap:8px;">
|
|
1217
|
-
<input id="auth-code-input" type="text" placeholder="Paste authorization code here"
|
|
1218
|
-
style="flex:1;padding:8px 12px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:6px;color:#e5e5e5;font-family:monospace;font-size:14px;outline:none;" />
|
|
1219
|
-
<button id="auth-code-submit" onclick="submitAuthCode()" style="padding:8px 16px;background:#22c55e;color:#000;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;">
|
|
1220
|
-
Submit Code
|
|
1221
|
-
</button>
|
|
1222
|
-
</div>
|
|
1223
|
-
<div id="auth-code-error" style="display:none;margin-top:6px;font-size:12px;color:#ef4444;"></div>
|
|
1224
|
-
</div>
|
|
1225
|
-
<div id="login-output" style="display:none;margin-top:8px;max-height:80px;overflow-y:auto;padding:8px;background:#000;border-radius:4px;font-family:monospace;font-size:11px;color:#666;white-space:pre-wrap;"></div>
|
|
1205
|
+
<div style="font-size:13px;color:#a3a3a3;">A terminal window has been opened. Please complete the Firebase login there.</div>
|
|
1206
|
+
<div style="font-size:12px;color:#666;margin-top:6px;">This page will update automatically when login is detected.</div>
|
|
1226
1207
|
</div>
|
|
1227
1208
|
<div id="login-result" style="display:none;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;"></div>
|
|
1228
1209
|
</div>
|
|
@@ -1432,6 +1413,9 @@ function generateDashboardHtml(options) {
|
|
|
1432
1413
|
document.getElementById('step-' + i).style.display = 'none';
|
|
1433
1414
|
}
|
|
1434
1415
|
document.getElementById('setup-done').style.display = 'none';
|
|
1416
|
+
// Reset login UI state from previous interactions
|
|
1417
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1418
|
+
document.getElementById('login-result').style.display = 'none';
|
|
1435
1419
|
|
|
1436
1420
|
if (status.nextStep === 'done') {
|
|
1437
1421
|
// All done!
|
|
@@ -1469,14 +1453,14 @@ function generateDashboardHtml(options) {
|
|
|
1469
1453
|
document.getElementById('step2-action').style.display = 'block';
|
|
1470
1454
|
document.getElementById('login-btn').style.display = 'none';
|
|
1471
1455
|
document.getElementById('reauth-btn').style.display = 'inline-block';
|
|
1472
|
-
document.getElementById('
|
|
1456
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1473
1457
|
} else {
|
|
1474
1458
|
document.getElementById('step2-icon').textContent = RED;
|
|
1475
1459
|
document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';
|
|
1476
1460
|
document.getElementById('step2-action').style.display = 'block';
|
|
1477
1461
|
document.getElementById('login-btn').style.display = 'inline-block';
|
|
1478
1462
|
document.getElementById('reauth-btn').style.display = 'none';
|
|
1479
|
-
document.getElementById('
|
|
1463
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1480
1464
|
}
|
|
1481
1465
|
|
|
1482
1466
|
if (status.nextStep === 'login') {
|
|
@@ -1545,22 +1529,17 @@ function generateDashboardHtml(options) {
|
|
|
1545
1529
|
});
|
|
1546
1530
|
};
|
|
1547
1531
|
|
|
1548
|
-
// \u2500\u2500\u2500 Step 2: Login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1532
|
+
// \u2500\u2500\u2500 Step 2: Login (opens terminal window) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1549
1533
|
window.startFirebaseLogin = function(reauth) {
|
|
1550
1534
|
var loginBtn = document.getElementById('login-btn');
|
|
1551
1535
|
var reauthBtn = document.getElementById('reauth-btn');
|
|
1552
|
-
var
|
|
1553
|
-
var progress = document.getElementById('login-progress');
|
|
1536
|
+
var waiting = document.getElementById('login-waiting');
|
|
1554
1537
|
var result = document.getElementById('login-result');
|
|
1555
1538
|
|
|
1556
1539
|
loginBtn.style.display = 'none';
|
|
1557
1540
|
reauthBtn.style.display = 'none';
|
|
1558
|
-
|
|
1559
|
-
progress.style.display = 'block';
|
|
1541
|
+
waiting.style.display = 'block';
|
|
1560
1542
|
result.style.display = 'none';
|
|
1561
|
-
document.getElementById('login-url-box').style.display = 'none';
|
|
1562
|
-
document.getElementById('auth-code-box').style.display = 'none';
|
|
1563
|
-
document.getElementById('login-spinner-text').textContent = 'Starting login...';
|
|
1564
1543
|
|
|
1565
1544
|
fetch(API + '/__dev/setup/login', {
|
|
1566
1545
|
method: 'POST',
|
|
@@ -1568,131 +1547,53 @@ function generateDashboardHtml(options) {
|
|
|
1568
1547
|
body: JSON.stringify({ reauth: !!reauth })
|
|
1569
1548
|
})
|
|
1570
1549
|
.then(function(r) { return r.json(); })
|
|
1571
|
-
.then(function() {
|
|
1572
|
-
|
|
1550
|
+
.then(function(data) {
|
|
1551
|
+
if (!data.success) {
|
|
1552
|
+
waiting.style.display = 'none';
|
|
1553
|
+
result.textContent = data.message;
|
|
1554
|
+
result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1555
|
+
loginBtn.style.display = 'inline-block';
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
// Poll setup status until login is detected
|
|
1573
1559
|
startLoginPoll();
|
|
1574
1560
|
})
|
|
1575
1561
|
.catch(function(err) {
|
|
1576
|
-
|
|
1577
|
-
result.textContent = 'Failed
|
|
1562
|
+
waiting.style.display = 'none';
|
|
1563
|
+
result.textContent = 'Failed: ' + err.message;
|
|
1578
1564
|
result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1579
1565
|
loginBtn.style.display = 'inline-block';
|
|
1580
|
-
cancelBtn.style.display = 'none';
|
|
1581
1566
|
});
|
|
1582
1567
|
};
|
|
1583
1568
|
|
|
1584
1569
|
function startLoginPoll() {
|
|
1585
1570
|
if (loginPollTimer) clearInterval(loginPollTimer);
|
|
1586
1571
|
loginPollTimer = setInterval(function() {
|
|
1587
|
-
fetch(API + '/__dev/setup/
|
|
1572
|
+
fetch(API + '/__dev/setup/status')
|
|
1588
1573
|
.then(function(r) { return r.json(); })
|
|
1589
|
-
.then(function(
|
|
1590
|
-
|
|
1591
|
-
if (state.authUrl) {
|
|
1592
|
-
var urlBox = document.getElementById('login-url-box');
|
|
1593
|
-
var urlLink = document.getElementById('login-url-link');
|
|
1594
|
-
var urlRaw = document.getElementById('login-url-raw');
|
|
1595
|
-
urlBox.style.display = 'block';
|
|
1596
|
-
urlLink.href = state.authUrl;
|
|
1597
|
-
urlLink.textContent = 'Open Google Login';
|
|
1598
|
-
if (urlRaw) urlRaw.textContent = state.authUrl;
|
|
1599
|
-
document.getElementById('login-spinner-text').textContent = 'Waiting for authentication...';
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
// Show auth code input when waiting for code
|
|
1603
|
-
if (state.waitingForCode) {
|
|
1604
|
-
document.getElementById('auth-code-box').style.display = 'block';
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
// Show output log
|
|
1608
|
-
if (state.output) {
|
|
1609
|
-
var outputEl = document.getElementById('login-output');
|
|
1610
|
-
outputEl.style.display = 'block';
|
|
1611
|
-
outputEl.textContent = state.output.slice(-500);
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
// Check if done
|
|
1615
|
-
if (state.completed) {
|
|
1574
|
+
.then(function(status) {
|
|
1575
|
+
if (status.auth.authenticated) {
|
|
1616
1576
|
clearInterval(loginPollTimer);
|
|
1617
1577
|
loginPollTimer = null;
|
|
1618
|
-
document.getElementById('login-
|
|
1619
|
-
document.getElementById('cancel-login-btn').style.display = 'none';
|
|
1578
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1620
1579
|
var result = document.getElementById('login-result');
|
|
1621
|
-
result.textContent = 'Login successful!';
|
|
1580
|
+
result.textContent = 'Login successful! Logged in as ' + status.auth.user;
|
|
1622
1581
|
result.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
1623
|
-
//
|
|
1624
|
-
setTimeout(
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
loginPollTimer = null;
|
|
1630
|
-
document.getElementById('login-progress').style.display = 'none';
|
|
1631
|
-
document.getElementById('cancel-login-btn').style.display = 'none';
|
|
1632
|
-
var result2 = document.getElementById('login-result');
|
|
1633
|
-
result2.textContent = state.error;
|
|
1634
|
-
result2.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1635
|
-
document.getElementById('login-btn').style.display = 'inline-block';
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
if (!state.active && !state.completed && !state.error) {
|
|
1639
|
-
clearInterval(loginPollTimer);
|
|
1640
|
-
loginPollTimer = null;
|
|
1582
|
+
// Wait for token to fully settle, then refresh wizard + force-load projects
|
|
1583
|
+
setTimeout(function() {
|
|
1584
|
+
refreshSetupStatus();
|
|
1585
|
+
// Extra delay for project list \u2014 Firebase CLI needs time after fresh login
|
|
1586
|
+
setTimeout(function() { loadProjectList(''); }, 2500);
|
|
1587
|
+
}, 2000);
|
|
1641
1588
|
}
|
|
1642
1589
|
})
|
|
1643
1590
|
.catch(function() {});
|
|
1644
|
-
},
|
|
1591
|
+
}, 3000);
|
|
1645
1592
|
}
|
|
1646
1593
|
|
|
1647
|
-
window.submitAuthCode = function() {
|
|
1648
|
-
var input = document.getElementById('auth-code-input');
|
|
1649
|
-
var errEl = document.getElementById('auth-code-error');
|
|
1650
|
-
var btn = document.getElementById('auth-code-submit');
|
|
1651
|
-
var code = input ? input.value.trim() : '';
|
|
1652
|
-
if (!code) {
|
|
1653
|
-
errEl.textContent = 'Please paste the authorization code.';
|
|
1654
|
-
errEl.style.display = 'block';
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
errEl.style.display = 'none';
|
|
1658
|
-
btn.disabled = true;
|
|
1659
|
-
btn.textContent = 'Submitting...';
|
|
1660
|
-
|
|
1661
|
-
fetch(API + '/__dev/setup/submit-auth-code', {
|
|
1662
|
-
method: 'POST',
|
|
1663
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1664
|
-
body: JSON.stringify({ code: code })
|
|
1665
|
-
})
|
|
1666
|
-
.then(function(r) { return r.json(); })
|
|
1667
|
-
.then(function(data) {
|
|
1668
|
-
if (data.success) {
|
|
1669
|
-
document.getElementById('login-spinner-text').textContent = 'Verifying authorization...';
|
|
1670
|
-
document.getElementById('auth-code-box').style.display = 'none';
|
|
1671
|
-
} else {
|
|
1672
|
-
errEl.textContent = data.message || 'Failed to submit code.';
|
|
1673
|
-
errEl.style.display = 'block';
|
|
1674
|
-
}
|
|
1675
|
-
btn.disabled = false;
|
|
1676
|
-
btn.textContent = 'Submit Code';
|
|
1677
|
-
})
|
|
1678
|
-
.catch(function(err) {
|
|
1679
|
-
errEl.textContent = 'Error: ' + err.message;
|
|
1680
|
-
errEl.style.display = 'block';
|
|
1681
|
-
btn.disabled = false;
|
|
1682
|
-
btn.textContent = 'Submit Code';
|
|
1683
|
-
});
|
|
1684
|
-
};
|
|
1685
|
-
|
|
1686
|
-
window.cancelFirebaseLogin = function() {
|
|
1687
|
-
if (loginPollTimer) { clearInterval(loginPollTimer); loginPollTimer = null; }
|
|
1688
|
-
fetch(API + '/__dev/setup/cancel-login', { method: 'POST' }).catch(function() {});
|
|
1689
|
-
document.getElementById('login-progress').style.display = 'none';
|
|
1690
|
-
document.getElementById('cancel-login-btn').style.display = 'none';
|
|
1691
|
-
document.getElementById('login-btn').style.display = 'inline-block';
|
|
1692
|
-
};
|
|
1693
|
-
|
|
1694
1594
|
// \u2500\u2500\u2500 Step 3: Project Selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1695
|
-
function loadProjectList(currentProjectId) {
|
|
1595
|
+
function loadProjectList(currentProjectId, retryCount) {
|
|
1596
|
+
retryCount = retryCount || 0;
|
|
1696
1597
|
var select = document.getElementById('project-select');
|
|
1697
1598
|
select.innerHTML = '<option value="">Loading projects...</option>';
|
|
1698
1599
|
select.disabled = true;
|
|
@@ -1702,10 +1603,21 @@ function generateDashboardHtml(options) {
|
|
|
1702
1603
|
.then(function(data) {
|
|
1703
1604
|
select.innerHTML = '';
|
|
1704
1605
|
if (data.error) {
|
|
1606
|
+
// Auto-retry up to 2 times on error (token might not be ready yet after fresh login)
|
|
1607
|
+
if (retryCount < 2) {
|
|
1608
|
+
select.innerHTML = '<option value="">Loading projects... (retry)</option>';
|
|
1609
|
+
setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1705
1612
|
select.innerHTML = '<option value="">Error: ' + escHtml(data.error) + '</option>';
|
|
1706
1613
|
return;
|
|
1707
1614
|
}
|
|
1708
1615
|
if (!data.projects || data.projects.length === 0) {
|
|
1616
|
+
if (retryCount < 2) {
|
|
1617
|
+
select.innerHTML = '<option value="">Loading projects... (retry)</option>';
|
|
1618
|
+
setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1709
1621
|
select.innerHTML = '<option value="">No projects found</option>';
|
|
1710
1622
|
return;
|
|
1711
1623
|
}
|
|
@@ -1721,6 +1633,11 @@ function generateDashboardHtml(options) {
|
|
|
1721
1633
|
select.disabled = false;
|
|
1722
1634
|
})
|
|
1723
1635
|
.catch(function(err) {
|
|
1636
|
+
if (retryCount < 2) {
|
|
1637
|
+
select.innerHTML = '<option value="">Loading projects... (retry)</option>';
|
|
1638
|
+
setTimeout(function() { loadProjectList(currentProjectId, retryCount + 1); }, 3000);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1724
1641
|
select.innerHTML = '<option value="">Failed to load</option>';
|
|
1725
1642
|
});
|
|
1726
1643
|
}
|
|
@@ -2155,21 +2072,11 @@ function generateDashboardHtml(options) {
|
|
|
2155
2072
|
|
|
2156
2073
|
// src/dev/firebase-setup.ts
|
|
2157
2074
|
import { execFile as execFile2, spawn } from "child_process";
|
|
2158
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2
|
|
2159
|
-
import { resolve as resolve4, join as join3
|
|
2160
|
-
import {
|
|
2075
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2076
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
2077
|
+
import { tmpdir, platform } from "os";
|
|
2161
2078
|
var FirebaseSetup = class {
|
|
2162
2079
|
projectDir;
|
|
2163
|
-
loginProcess = null;
|
|
2164
|
-
loginSession = {
|
|
2165
|
-
active: false,
|
|
2166
|
-
authUrl: null,
|
|
2167
|
-
waitingForCode: false,
|
|
2168
|
-
completed: false,
|
|
2169
|
-
error: null,
|
|
2170
|
-
output: ""
|
|
2171
|
-
};
|
|
2172
|
-
loginTimeout = null;
|
|
2173
2080
|
constructor(projectDir) {
|
|
2174
2081
|
this.projectDir = projectDir;
|
|
2175
2082
|
}
|
|
@@ -2249,149 +2156,93 @@ var FirebaseSetup = class {
|
|
|
2249
2156
|
return { success: false, message: `Failed to install Firebase CLI: ${msg}` };
|
|
2250
2157
|
}
|
|
2251
2158
|
}
|
|
2252
|
-
// ───
|
|
2159
|
+
// ─── Login via Terminal ───────────────────────────────────────────
|
|
2253
2160
|
/**
|
|
2254
|
-
*
|
|
2255
|
-
*
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
const home = homedir();
|
|
2260
|
-
const configPath = join3(home, ".config", "configstore", "firebase-tools.json");
|
|
2261
|
-
if (existsSync5(configPath)) {
|
|
2262
|
-
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
2263
|
-
if (config.usage === void 0) {
|
|
2264
|
-
config.usage = false;
|
|
2265
|
-
writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
2266
|
-
}
|
|
2267
|
-
} else {
|
|
2268
|
-
const dir = dirname2(configPath);
|
|
2269
|
-
mkdirSync(dir, { recursive: true });
|
|
2270
|
-
writeFileSync2(configPath, JSON.stringify({ usage: false }, null, 2), "utf-8");
|
|
2271
|
-
}
|
|
2272
|
-
} catch {
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
// ─── Login Flow ────────────────────────────────────────────────────
|
|
2276
|
-
/**
|
|
2277
|
-
* Start login using `firebase login --no-localhost`.
|
|
2161
|
+
* Opens a new terminal window and runs `firebase login`.
|
|
2162
|
+
*
|
|
2163
|
+
* Firebase CLI requires a real TTY for interactive login.
|
|
2164
|
+
* Instead of trying to fake a TTY, we open an actual terminal.
|
|
2165
|
+
* The dashboard polls getStatus() to detect when login completes.
|
|
2278
2166
|
*
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
* 3. Google shows an authorization code on screen
|
|
2283
|
-
* 4. User pastes code back → call submitAuthCode()
|
|
2167
|
+
* macOS: Creates a .command file and opens it (Terminal.app)
|
|
2168
|
+
* Linux: Tries common terminal emulators
|
|
2169
|
+
* Windows: Opens a new cmd window
|
|
2284
2170
|
*/
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
this.loginSession = {
|
|
2289
|
-
active: true,
|
|
2290
|
-
authUrl: null,
|
|
2291
|
-
waitingForCode: false,
|
|
2292
|
-
completed: false,
|
|
2293
|
-
error: null,
|
|
2294
|
-
output: ""
|
|
2295
|
-
};
|
|
2171
|
+
openLoginTerminal(reauth = false) {
|
|
2172
|
+
const cmd = reauth ? "firebase login --reauth" : "firebase login";
|
|
2173
|
+
const os = platform();
|
|
2296
2174
|
try {
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
this.
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2175
|
+
if (os === "darwin") {
|
|
2176
|
+
const scriptPath = join3(tmpdir(), "clawfire-firebase-login.command");
|
|
2177
|
+
writeFileSync2(scriptPath, [
|
|
2178
|
+
"#!/bin/bash",
|
|
2179
|
+
`cd "${this.projectDir}"`,
|
|
2180
|
+
cmd,
|
|
2181
|
+
'echo ""',
|
|
2182
|
+
'echo "Login complete! Closing in 3 seconds..."',
|
|
2183
|
+
"sleep 3",
|
|
2184
|
+
// Spawn osascript in background to close this specific terminal window, then exit
|
|
2185
|
+
`(sleep 1 && osascript -e 'tell application "Terminal" to close (every window whose name contains "clawfire-firebase-login")' 2>/dev/null) &`,
|
|
2186
|
+
"exit 0"
|
|
2187
|
+
].join("\n"), { mode: 493 });
|
|
2188
|
+
const child = spawn("open", [scriptPath], { detached: true, stdio: "ignore" });
|
|
2189
|
+
child.unref();
|
|
2190
|
+
} else if (os === "win32") {
|
|
2191
|
+
const child = spawn("cmd", ["/c", "start", "cmd", "/c", `${cmd} && timeout /t 3 >nul`], {
|
|
2192
|
+
cwd: this.projectDir,
|
|
2193
|
+
detached: true,
|
|
2194
|
+
stdio: "ignore"
|
|
2195
|
+
});
|
|
2196
|
+
child.unref();
|
|
2197
|
+
} else {
|
|
2198
|
+
const scriptPath = join3(tmpdir(), "clawfire-firebase-login.sh");
|
|
2199
|
+
writeFileSync2(scriptPath, [
|
|
2200
|
+
"#!/bin/bash",
|
|
2201
|
+
`cd "${this.projectDir}"`,
|
|
2202
|
+
cmd,
|
|
2203
|
+
'echo ""',
|
|
2204
|
+
'echo "Login complete! Closing in 3 seconds..."',
|
|
2205
|
+
"sleep 3",
|
|
2206
|
+
"exit 0"
|
|
2207
|
+
].join("\n"), { mode: 493 });
|
|
2208
|
+
const terminals = [
|
|
2209
|
+
{ cmd: "x-terminal-emulator", args: ["-e", scriptPath] },
|
|
2210
|
+
{ cmd: "gnome-terminal", args: ["--", "bash", scriptPath] },
|
|
2211
|
+
{ cmd: "konsole", args: ["-e", "bash", scriptPath] },
|
|
2212
|
+
{ cmd: "xfce4-terminal", args: ["-e", scriptPath] },
|
|
2213
|
+
{ cmd: "xterm", args: ["-e", scriptPath] }
|
|
2214
|
+
];
|
|
2215
|
+
let opened = false;
|
|
2216
|
+
for (const t of terminals) {
|
|
2318
2217
|
try {
|
|
2319
|
-
|
|
2218
|
+
const child = spawn(t.cmd, t.args, { detached: true, stdio: "ignore" });
|
|
2219
|
+
child.unref();
|
|
2220
|
+
child.on("error", () => {
|
|
2221
|
+
});
|
|
2222
|
+
opened = true;
|
|
2223
|
+
break;
|
|
2320
2224
|
} catch {
|
|
2225
|
+
continue;
|
|
2321
2226
|
}
|
|
2322
2227
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
this.loginSession.waitingForCode = false;
|
|
2329
|
-
if (code === 0) {
|
|
2330
|
-
this.loginSession.completed = true;
|
|
2331
|
-
} else if (!this.loginSession.completed) {
|
|
2332
|
-
const lines = this.loginSession.output.trim().split("\n").filter((l) => l.trim());
|
|
2333
|
-
const lastLines = lines.slice(-3).join(" | ");
|
|
2334
|
-
this.loginSession.error = `Login failed (exit code ${code}).` + (lastLines ? ` Details: ${lastLines}` : "") + ` Try running "firebase login" in your terminal.`;
|
|
2335
|
-
}
|
|
2336
|
-
this.loginProcess = null;
|
|
2337
|
-
});
|
|
2338
|
-
proc.on("error", (err) => {
|
|
2339
|
-
this.loginSession.active = false;
|
|
2340
|
-
this.loginSession.error = err.message;
|
|
2341
|
-
this.loginProcess = null;
|
|
2342
|
-
});
|
|
2343
|
-
this.loginTimeout = setTimeout(() => {
|
|
2344
|
-
if (this.loginSession.active) {
|
|
2345
|
-
this.loginSession.error = "Login timed out (5 minutes). Please try again.";
|
|
2346
|
-
this.cleanupLogin();
|
|
2228
|
+
if (!opened) {
|
|
2229
|
+
return {
|
|
2230
|
+
success: false,
|
|
2231
|
+
message: `Could not find a terminal emulator. Please run "${cmd}" manually in your terminal.`
|
|
2232
|
+
};
|
|
2347
2233
|
}
|
|
2348
|
-
}, 5 * 60 * 1e3);
|
|
2349
|
-
} catch (err) {
|
|
2350
|
-
this.loginSession.active = false;
|
|
2351
|
-
this.loginSession.error = err instanceof Error ? err.message : "Failed to start login";
|
|
2352
|
-
}
|
|
2353
|
-
return this.loginSession;
|
|
2354
|
-
}
|
|
2355
|
-
getLoginState() {
|
|
2356
|
-
return { ...this.loginSession };
|
|
2357
|
-
}
|
|
2358
|
-
/**
|
|
2359
|
-
* Submit the authorization code that the user copied from the browser.
|
|
2360
|
-
* This writes the code to the Firebase CLI process stdin.
|
|
2361
|
-
*/
|
|
2362
|
-
submitAuthCode(code) {
|
|
2363
|
-
if (!this.loginProcess || !this.loginProcess.stdin) {
|
|
2364
|
-
return { success: false, message: "No active login process. Please start login again." };
|
|
2365
|
-
}
|
|
2366
|
-
if (!code || !code.trim()) {
|
|
2367
|
-
return { success: false, message: "Authorization code is required." };
|
|
2368
|
-
}
|
|
2369
|
-
try {
|
|
2370
|
-
this.loginProcess.stdin.write(code.trim() + "\n");
|
|
2371
|
-
this.loginSession.waitingForCode = false;
|
|
2372
|
-
return { success: true, message: "Authorization code submitted. Waiting for verification..." };
|
|
2373
|
-
} catch (err) {
|
|
2374
|
-
return { success: false, message: "Failed to submit code: " + (err instanceof Error ? err.message : "unknown error") };
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
cancelLogin() {
|
|
2378
|
-
this.cleanupLogin();
|
|
2379
|
-
this.loginSession.error = "Login cancelled by user.";
|
|
2380
|
-
}
|
|
2381
|
-
cleanupLogin() {
|
|
2382
|
-
if (this.loginProcess) {
|
|
2383
|
-
try {
|
|
2384
|
-
this.loginProcess.kill("SIGTERM");
|
|
2385
|
-
} catch {
|
|
2386
2234
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2235
|
+
return {
|
|
2236
|
+
success: true,
|
|
2237
|
+
message: "Terminal window opened. Please complete the login in the new terminal."
|
|
2238
|
+
};
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2241
|
+
return {
|
|
2242
|
+
success: false,
|
|
2243
|
+
message: `Failed to open terminal: ${msg}. Please run "${cmd}" manually in your terminal.`
|
|
2244
|
+
};
|
|
2392
2245
|
}
|
|
2393
|
-
this.loginSession.active = false;
|
|
2394
|
-
this.loginSession.waitingForCode = false;
|
|
2395
2246
|
}
|
|
2396
2247
|
// ─── Project Listing ───────────────────────────────────────────────
|
|
2397
2248
|
async listProjects() {
|
|
@@ -2485,7 +2336,6 @@ var FirebaseSetup = class {
|
|
|
2485
2336
|
}
|
|
2486
2337
|
/** Cleanup resources */
|
|
2487
2338
|
destroy() {
|
|
2488
|
-
this.cleanupLogin();
|
|
2489
2339
|
}
|
|
2490
2340
|
};
|
|
2491
2341
|
|
|
@@ -3408,36 +3258,8 @@ ${liveReloadScript}
|
|
|
3408
3258
|
reauth = !!data.reauth;
|
|
3409
3259
|
} catch {
|
|
3410
3260
|
}
|
|
3411
|
-
const
|
|
3412
|
-
sendJson(
|
|
3413
|
-
});
|
|
3414
|
-
return;
|
|
3415
|
-
}
|
|
3416
|
-
if (url.pathname === "/__dev/setup/login-state" && req.method === "GET") {
|
|
3417
|
-
sendJson(this.firebaseSetup.getLoginState());
|
|
3418
|
-
return;
|
|
3419
|
-
}
|
|
3420
|
-
if (url.pathname === "/__dev/setup/cancel-login" && req.method === "POST") {
|
|
3421
|
-
this.firebaseSetup.cancelLogin();
|
|
3422
|
-
sendJson({ ok: true });
|
|
3423
|
-
return;
|
|
3424
|
-
}
|
|
3425
|
-
if (url.pathname === "/__dev/setup/submit-auth-code" && req.method === "POST") {
|
|
3426
|
-
let body = "";
|
|
3427
|
-
req.on("data", (chunk) => {
|
|
3428
|
-
body += chunk;
|
|
3429
|
-
});
|
|
3430
|
-
req.on("end", () => {
|
|
3431
|
-
try {
|
|
3432
|
-
const data = JSON.parse(body);
|
|
3433
|
-
if (!data.code) {
|
|
3434
|
-
sendJson({ success: false, message: "code is required" }, 400);
|
|
3435
|
-
return;
|
|
3436
|
-
}
|
|
3437
|
-
sendJson(this.firebaseSetup.submitAuthCode(data.code));
|
|
3438
|
-
} catch {
|
|
3439
|
-
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
3440
|
-
}
|
|
3261
|
+
const result = this.firebaseSetup.openLoginTerminal(reauth);
|
|
3262
|
+
sendJson(result);
|
|
3441
3263
|
});
|
|
3442
3264
|
return;
|
|
3443
3265
|
}
|