clawfire 0.4.1 → 0.4.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-DRZ52WIA.js → dev-server-65H4AQFS.js} +105 -312
- package/dist/dev.cjs +102 -309
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +105 -312
- 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-65H4AQFS.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>
|
|
@@ -1469,14 +1450,14 @@ function generateDashboardHtml(options) {
|
|
|
1469
1450
|
document.getElementById('step2-action').style.display = 'block';
|
|
1470
1451
|
document.getElementById('login-btn').style.display = 'none';
|
|
1471
1452
|
document.getElementById('reauth-btn').style.display = 'inline-block';
|
|
1472
|
-
document.getElementById('
|
|
1453
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1473
1454
|
} else {
|
|
1474
1455
|
document.getElementById('step2-icon').textContent = RED;
|
|
1475
1456
|
document.getElementById('step2-detail').textContent = 'Not logged in to Firebase';
|
|
1476
1457
|
document.getElementById('step2-action').style.display = 'block';
|
|
1477
1458
|
document.getElementById('login-btn').style.display = 'inline-block';
|
|
1478
1459
|
document.getElementById('reauth-btn').style.display = 'none';
|
|
1479
|
-
document.getElementById('
|
|
1460
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1480
1461
|
}
|
|
1481
1462
|
|
|
1482
1463
|
if (status.nextStep === 'login') {
|
|
@@ -1545,22 +1526,17 @@ function generateDashboardHtml(options) {
|
|
|
1545
1526
|
});
|
|
1546
1527
|
};
|
|
1547
1528
|
|
|
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
|
|
1529
|
+
// \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
1530
|
window.startFirebaseLogin = function(reauth) {
|
|
1550
1531
|
var loginBtn = document.getElementById('login-btn');
|
|
1551
1532
|
var reauthBtn = document.getElementById('reauth-btn');
|
|
1552
|
-
var
|
|
1553
|
-
var progress = document.getElementById('login-progress');
|
|
1533
|
+
var waiting = document.getElementById('login-waiting');
|
|
1554
1534
|
var result = document.getElementById('login-result');
|
|
1555
1535
|
|
|
1556
1536
|
loginBtn.style.display = 'none';
|
|
1557
1537
|
reauthBtn.style.display = 'none';
|
|
1558
|
-
|
|
1559
|
-
progress.style.display = 'block';
|
|
1538
|
+
waiting.style.display = 'block';
|
|
1560
1539
|
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
1540
|
|
|
1565
1541
|
fetch(API + '/__dev/setup/login', {
|
|
1566
1542
|
method: 'POST',
|
|
@@ -1568,129 +1544,45 @@ function generateDashboardHtml(options) {
|
|
|
1568
1544
|
body: JSON.stringify({ reauth: !!reauth })
|
|
1569
1545
|
})
|
|
1570
1546
|
.then(function(r) { return r.json(); })
|
|
1571
|
-
.then(function() {
|
|
1572
|
-
|
|
1547
|
+
.then(function(data) {
|
|
1548
|
+
if (!data.success) {
|
|
1549
|
+
waiting.style.display = 'none';
|
|
1550
|
+
result.textContent = data.message;
|
|
1551
|
+
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;';
|
|
1552
|
+
loginBtn.style.display = 'inline-block';
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
// Poll setup status until login is detected
|
|
1573
1556
|
startLoginPoll();
|
|
1574
1557
|
})
|
|
1575
1558
|
.catch(function(err) {
|
|
1576
|
-
|
|
1577
|
-
result.textContent = 'Failed
|
|
1559
|
+
waiting.style.display = 'none';
|
|
1560
|
+
result.textContent = 'Failed: ' + err.message;
|
|
1578
1561
|
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
1562
|
loginBtn.style.display = 'inline-block';
|
|
1580
|
-
cancelBtn.style.display = 'none';
|
|
1581
1563
|
});
|
|
1582
1564
|
};
|
|
1583
1565
|
|
|
1584
1566
|
function startLoginPoll() {
|
|
1585
1567
|
if (loginPollTimer) clearInterval(loginPollTimer);
|
|
1586
1568
|
loginPollTimer = setInterval(function() {
|
|
1587
|
-
fetch(API + '/__dev/setup/
|
|
1569
|
+
fetch(API + '/__dev/setup/status')
|
|
1588
1570
|
.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) {
|
|
1571
|
+
.then(function(status) {
|
|
1572
|
+
if (status.auth.authenticated) {
|
|
1616
1573
|
clearInterval(loginPollTimer);
|
|
1617
1574
|
loginPollTimer = null;
|
|
1618
|
-
document.getElementById('login-
|
|
1619
|
-
document.getElementById('cancel-login-btn').style.display = 'none';
|
|
1575
|
+
document.getElementById('login-waiting').style.display = 'none';
|
|
1620
1576
|
var result = document.getElementById('login-result');
|
|
1621
|
-
result.textContent = 'Login successful!';
|
|
1577
|
+
result.textContent = 'Login successful! Logged in as ' + status.auth.user;
|
|
1622
1578
|
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
|
-
// Refresh wizard after short delay
|
|
1624
1579
|
setTimeout(refreshSetupStatus, 1500);
|
|
1625
1580
|
}
|
|
1626
|
-
|
|
1627
|
-
if (state.error) {
|
|
1628
|
-
clearInterval(loginPollTimer);
|
|
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;
|
|
1641
|
-
}
|
|
1642
1581
|
})
|
|
1643
1582
|
.catch(function() {});
|
|
1644
|
-
},
|
|
1583
|
+
}, 3000);
|
|
1645
1584
|
}
|
|
1646
1585
|
|
|
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
1586
|
// \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
1587
|
function loadProjectList(currentProjectId) {
|
|
1696
1588
|
var select = document.getElementById('project-select');
|
|
@@ -2155,21 +2047,11 @@ function generateDashboardHtml(options) {
|
|
|
2155
2047
|
|
|
2156
2048
|
// src/dev/firebase-setup.ts
|
|
2157
2049
|
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 {
|
|
2050
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2051
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
2052
|
+
import { tmpdir, platform } from "os";
|
|
2161
2053
|
var FirebaseSetup = class {
|
|
2162
2054
|
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
2055
|
constructor(projectDir) {
|
|
2174
2056
|
this.projectDir = projectDir;
|
|
2175
2057
|
}
|
|
@@ -2249,149 +2131,89 @@ var FirebaseSetup = class {
|
|
|
2249
2131
|
return { success: false, message: `Failed to install Firebase CLI: ${msg}` };
|
|
2250
2132
|
}
|
|
2251
2133
|
}
|
|
2252
|
-
// ───
|
|
2253
|
-
/**
|
|
2254
|
-
* Pre-configure Firebase analytics preference to avoid the interactive
|
|
2255
|
-
* "Allow Firebase to collect..." prompt that breaks non-TTY spawns.
|
|
2256
|
-
*/
|
|
2257
|
-
ensureAnalyticsConfigured() {
|
|
2258
|
-
try {
|
|
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 ────────────────────────────────────────────────────
|
|
2134
|
+
// ─── Login via Terminal ───────────────────────────────────────────
|
|
2276
2135
|
/**
|
|
2277
|
-
*
|
|
2136
|
+
* Opens a new terminal window and runs `firebase login`.
|
|
2278
2137
|
*
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2138
|
+
* Firebase CLI requires a real TTY for interactive login.
|
|
2139
|
+
* Instead of trying to fake a TTY, we open an actual terminal.
|
|
2140
|
+
* The dashboard polls getStatus() to detect when login completes.
|
|
2141
|
+
*
|
|
2142
|
+
* macOS: Creates a .command file and opens it (Terminal.app)
|
|
2143
|
+
* Linux: Tries common terminal emulators
|
|
2144
|
+
* Windows: Opens a new cmd window
|
|
2284
2145
|
*/
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
this.loginSession = {
|
|
2289
|
-
active: true,
|
|
2290
|
-
authUrl: null,
|
|
2291
|
-
waitingForCode: false,
|
|
2292
|
-
completed: false,
|
|
2293
|
-
error: null,
|
|
2294
|
-
output: ""
|
|
2295
|
-
};
|
|
2146
|
+
openLoginTerminal(reauth = false) {
|
|
2147
|
+
const cmd = reauth ? "firebase login --reauth" : "firebase login";
|
|
2148
|
+
const os = platform();
|
|
2296
2149
|
try {
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2150
|
+
if (os === "darwin") {
|
|
2151
|
+
const scriptPath = join3(tmpdir(), "clawfire-firebase-login.command");
|
|
2152
|
+
writeFileSync2(scriptPath, [
|
|
2153
|
+
"#!/bin/bash",
|
|
2154
|
+
`cd "${this.projectDir}"`,
|
|
2155
|
+
cmd,
|
|
2156
|
+
'echo ""',
|
|
2157
|
+
'echo "Login complete! You can close this window."',
|
|
2158
|
+
'read -p "Press Enter to close..."'
|
|
2159
|
+
].join("\n"), { mode: 493 });
|
|
2160
|
+
const child = spawn("open", [scriptPath], { detached: true, stdio: "ignore" });
|
|
2161
|
+
child.unref();
|
|
2162
|
+
} else if (os === "win32") {
|
|
2163
|
+
const child = spawn("cmd", ["/c", "start", "cmd", "/k", cmd], {
|
|
2164
|
+
cwd: this.projectDir,
|
|
2165
|
+
detached: true,
|
|
2166
|
+
stdio: "ignore"
|
|
2167
|
+
});
|
|
2168
|
+
child.unref();
|
|
2169
|
+
} else {
|
|
2170
|
+
const scriptPath = join3(tmpdir(), "clawfire-firebase-login.sh");
|
|
2171
|
+
writeFileSync2(scriptPath, [
|
|
2172
|
+
"#!/bin/bash",
|
|
2173
|
+
`cd "${this.projectDir}"`,
|
|
2174
|
+
cmd,
|
|
2175
|
+
'echo ""',
|
|
2176
|
+
'echo "Login complete! You can close this window."',
|
|
2177
|
+
'read -p "Press Enter to close..."'
|
|
2178
|
+
].join("\n"), { mode: 493 });
|
|
2179
|
+
const terminals = [
|
|
2180
|
+
{ cmd: "x-terminal-emulator", args: ["-e", scriptPath] },
|
|
2181
|
+
{ cmd: "gnome-terminal", args: ["--", "bash", scriptPath] },
|
|
2182
|
+
{ cmd: "konsole", args: ["-e", "bash", scriptPath] },
|
|
2183
|
+
{ cmd: "xfce4-terminal", args: ["-e", scriptPath] },
|
|
2184
|
+
{ cmd: "xterm", args: ["-e", scriptPath] }
|
|
2185
|
+
];
|
|
2186
|
+
let opened = false;
|
|
2187
|
+
for (const t of terminals) {
|
|
2318
2188
|
try {
|
|
2319
|
-
|
|
2189
|
+
const child = spawn(t.cmd, t.args, { detached: true, stdio: "ignore" });
|
|
2190
|
+
child.unref();
|
|
2191
|
+
child.on("error", () => {
|
|
2192
|
+
});
|
|
2193
|
+
opened = true;
|
|
2194
|
+
break;
|
|
2320
2195
|
} catch {
|
|
2196
|
+
continue;
|
|
2321
2197
|
}
|
|
2322
2198
|
}
|
|
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();
|
|
2199
|
+
if (!opened) {
|
|
2200
|
+
return {
|
|
2201
|
+
success: false,
|
|
2202
|
+
message: `Could not find a terminal emulator. Please run "${cmd}" manually in your terminal.`
|
|
2203
|
+
};
|
|
2347
2204
|
}
|
|
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
2205
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2206
|
+
return {
|
|
2207
|
+
success: true,
|
|
2208
|
+
message: "Terminal window opened. Please complete the login in the new terminal."
|
|
2209
|
+
};
|
|
2210
|
+
} catch (err) {
|
|
2211
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2212
|
+
return {
|
|
2213
|
+
success: false,
|
|
2214
|
+
message: `Failed to open terminal: ${msg}. Please run "${cmd}" manually in your terminal.`
|
|
2215
|
+
};
|
|
2392
2216
|
}
|
|
2393
|
-
this.loginSession.active = false;
|
|
2394
|
-
this.loginSession.waitingForCode = false;
|
|
2395
2217
|
}
|
|
2396
2218
|
// ─── Project Listing ───────────────────────────────────────────────
|
|
2397
2219
|
async listProjects() {
|
|
@@ -2485,7 +2307,6 @@ var FirebaseSetup = class {
|
|
|
2485
2307
|
}
|
|
2486
2308
|
/** Cleanup resources */
|
|
2487
2309
|
destroy() {
|
|
2488
|
-
this.cleanupLogin();
|
|
2489
2310
|
}
|
|
2490
2311
|
};
|
|
2491
2312
|
|
|
@@ -3408,36 +3229,8 @@ ${liveReloadScript}
|
|
|
3408
3229
|
reauth = !!data.reauth;
|
|
3409
3230
|
} catch {
|
|
3410
3231
|
}
|
|
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
|
-
}
|
|
3232
|
+
const result = this.firebaseSetup.openLoginTerminal(reauth);
|
|
3233
|
+
sendJson(result);
|
|
3441
3234
|
});
|
|
3442
3235
|
return;
|
|
3443
3236
|
}
|