clawfire 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/{dev-server-TGEKP4YE.js → dev-server-MIUKPOMC.js} +253 -19
- package/dist/dev.cjs +252 -18
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.d.cts +3 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +253 -19
- package/dist/dev.js.map +1 -1
- package/package.json +1 -1
- package/templates/starter/app/pages/todos/index.html +1 -2
package/dist/dev.cjs
CHANGED
|
@@ -1494,6 +1494,34 @@ async function checkCli(projectDir) {
|
|
|
1494
1494
|
}
|
|
1495
1495
|
return result;
|
|
1496
1496
|
}
|
|
1497
|
+
async function fetchFirebaseSdkConfig(projectDir) {
|
|
1498
|
+
const output = await execWithTimeout(
|
|
1499
|
+
"firebase",
|
|
1500
|
+
["apps:sdkconfig", "web", "--json"],
|
|
1501
|
+
projectDir,
|
|
1502
|
+
15e3
|
|
1503
|
+
);
|
|
1504
|
+
const data = JSON.parse(output);
|
|
1505
|
+
if (data?.result?.sdkConfig) {
|
|
1506
|
+
return data.result.sdkConfig;
|
|
1507
|
+
}
|
|
1508
|
+
if (data?.result?.fileContents) {
|
|
1509
|
+
const contents = data.result.fileContents;
|
|
1510
|
+
const config = {};
|
|
1511
|
+
const extract = (key) => {
|
|
1512
|
+
const match = contents.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
1513
|
+
return match ? match[1] : void 0;
|
|
1514
|
+
};
|
|
1515
|
+
config.apiKey = extract("apiKey");
|
|
1516
|
+
config.authDomain = extract("authDomain");
|
|
1517
|
+
config.projectId = extract("projectId");
|
|
1518
|
+
config.storageBucket = extract("storageBucket");
|
|
1519
|
+
config.messagingSenderId = extract("messagingSenderId");
|
|
1520
|
+
config.appId = extract("appId");
|
|
1521
|
+
return config;
|
|
1522
|
+
}
|
|
1523
|
+
throw new Error("Could not parse Firebase SDK config from CLI output");
|
|
1524
|
+
}
|
|
1497
1525
|
function execWithTimeout(command, args, cwd, timeoutMs) {
|
|
1498
1526
|
return new Promise((resolve6, reject) => {
|
|
1499
1527
|
const proc = (0, import_node_child_process.execFile)(command, args, { cwd, timeout: timeoutMs }, (err, stdout) => {
|
|
@@ -1542,9 +1570,25 @@ function generateDashboardHtml(options) {
|
|
|
1542
1570
|
|
|
1543
1571
|
<!-- Section 2: Config Overview -->
|
|
1544
1572
|
<div style="margin-bottom:32px;">
|
|
1545
|
-
<
|
|
1573
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
1574
|
+
<h2 style="font-size:18px;font-weight:700;color:#f97316;">Config Overview</h2>
|
|
1575
|
+
<button id="autofill-btn" onclick="autoFillConfig()" style="padding:6px 14px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Auto-fill from Firebase</button>
|
|
1576
|
+
</div>
|
|
1577
|
+
<div id="autofill-status" style="display:none;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;"></div>
|
|
1546
1578
|
<div id="config-section" style="border-radius:8px;border:1px solid #2a2a2a;background:#141414;overflow:hidden;">
|
|
1547
|
-
<
|
|
1579
|
+
<table id="config-table" style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
1580
|
+
<thead>
|
|
1581
|
+
<tr style="border-bottom:1px solid #2a2a2a;">
|
|
1582
|
+
<th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;width:180px;">Key</th>
|
|
1583
|
+
<th style="padding:10px 16px;text-align:left;color:#a3a3a3;font-weight:500;">Value</th>
|
|
1584
|
+
<th style="padding:10px 16px;text-align:right;color:#a3a3a3;font-weight:500;width:80px;">Action</th>
|
|
1585
|
+
</tr>
|
|
1586
|
+
</thead>
|
|
1587
|
+
<tbody id="config-tbody"></tbody>
|
|
1588
|
+
</table>
|
|
1589
|
+
<div id="config-empty" style="display:none;padding:32px;text-align:center;color:#666;">
|
|
1590
|
+
No clawfire.config.ts found.
|
|
1591
|
+
</div>
|
|
1548
1592
|
</div>
|
|
1549
1593
|
</div>
|
|
1550
1594
|
|
|
@@ -1574,7 +1618,7 @@ function generateDashboardHtml(options) {
|
|
|
1574
1618
|
</div>
|
|
1575
1619
|
|
|
1576
1620
|
<!-- Env Modal -->
|
|
1577
|
-
<div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;
|
|
1621
|
+
<div id="env-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;align-items:center;justify-content:center;">
|
|
1578
1622
|
<div style="background:#1e1e1e;border:1px solid #2a2a2a;border-radius:12px;padding:24px;width:440px;max-width:90vw;">
|
|
1579
1623
|
<h3 id="modal-title" style="font-size:16px;font-weight:700;color:#e5e5e5;margin-bottom:16px;">Add Variable</h3>
|
|
1580
1624
|
<div style="margin-bottom:12px;">
|
|
@@ -1602,6 +1646,7 @@ function generateDashboardHtml(options) {
|
|
|
1602
1646
|
(function() {
|
|
1603
1647
|
var API = 'http://localhost:${apiPort}';
|
|
1604
1648
|
var envData = [];
|
|
1649
|
+
var configData = [];
|
|
1605
1650
|
var editingKey = null;
|
|
1606
1651
|
|
|
1607
1652
|
// \u2500\u2500\u2500 Load Dashboard Data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -1627,7 +1672,6 @@ function generateDashboardHtml(options) {
|
|
|
1627
1672
|
|
|
1628
1673
|
// \u2500\u2500\u2500 Firebase Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1629
1674
|
function renderFirebaseStatus(data) {
|
|
1630
|
-
// CLI Banner
|
|
1631
1675
|
var dot = document.getElementById('cli-dot');
|
|
1632
1676
|
var text = document.getElementById('cli-text');
|
|
1633
1677
|
var proj = document.getElementById('cli-project');
|
|
@@ -1669,7 +1713,6 @@ function generateDashboardHtml(options) {
|
|
|
1669
1713
|
grid.appendChild(card);
|
|
1670
1714
|
});
|
|
1671
1715
|
|
|
1672
|
-
// Config Warnings
|
|
1673
1716
|
if (data.configWarnings && data.configWarnings.length > 0) {
|
|
1674
1717
|
var warningCard = document.createElement('div');
|
|
1675
1718
|
warningCard.style.cssText = 'padding:16px;border-radius:8px;border:1px solid #eab308;background:#1a1a0a;grid-column:1/-1;';
|
|
@@ -1682,26 +1725,167 @@ function generateDashboardHtml(options) {
|
|
|
1682
1725
|
}
|
|
1683
1726
|
}
|
|
1684
1727
|
|
|
1685
|
-
// \u2500\u2500\u2500 Config Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1728
|
+
// \u2500\u2500\u2500 Config Overview (editable) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1686
1729
|
function renderConfig(data) {
|
|
1687
|
-
var
|
|
1688
|
-
|
|
1689
|
-
|
|
1730
|
+
var tbody = document.getElementById('config-tbody');
|
|
1731
|
+
var empty = document.getElementById('config-empty');
|
|
1732
|
+
var table = document.getElementById('config-table');
|
|
1733
|
+
|
|
1734
|
+
configData = (data && data.fields) ? data.fields : [];
|
|
1735
|
+
|
|
1736
|
+
if (configData.length === 0) {
|
|
1737
|
+
table.style.display = 'none';
|
|
1738
|
+
empty.style.display = 'block';
|
|
1690
1739
|
return;
|
|
1691
1740
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1741
|
+
|
|
1742
|
+
table.style.display = 'table';
|
|
1743
|
+
empty.style.display = 'none';
|
|
1744
|
+
tbody.innerHTML = '';
|
|
1745
|
+
|
|
1746
|
+
configData.forEach(function(field) {
|
|
1747
|
+
var tr = document.createElement('tr');
|
|
1748
|
+
tr.style.borderBottom = '1px solid #2a2a2a';
|
|
1749
|
+
tr.id = 'cfg-row-' + field.key;
|
|
1694
1750
|
var color = field.isPlaceholder ? '#eab308' : '#a3a3a3';
|
|
1695
1751
|
var badge = field.isPlaceholder ? ' <span style="background:#eab30822;color:#eab308;padding:1px 6px;border-radius:4px;font-size:10px;">PLACEHOLDER</span>' : '';
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1752
|
+
tr.innerHTML =
|
|
1753
|
+
'<td style="padding:10px 16px;color:#e5e5e5;font-family:monospace;white-space:nowrap;">' + escHtml(field.key) + '</td>' +
|
|
1754
|
+
'<td style="padding:10px 16px;font-family:monospace;">' +
|
|
1755
|
+
'<span id="cfg-val-' + field.key + '" style="color:' + color + ';">' + escHtml(field.value) + '</span>' +
|
|
1756
|
+
badge +
|
|
1757
|
+
'<input id="cfg-input-' + field.key + '" type="text" value="' + escHtml(field.value) + '" style="display:none;width:100%;padding:4px 8px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:4px;color:#e5e5e5;font-family:monospace;font-size:13px;outline:none;" />' +
|
|
1758
|
+
'</td>' +
|
|
1759
|
+
'<td style="padding:10px 16px;text-align:right;white-space:nowrap;">' +
|
|
1760
|
+
'<button id="cfg-edit-' + field.key + '" onclick="editConfigField(\\'' + escHtml(field.key) + '\\')" style="background:none;border:none;color:#3b82f6;cursor:pointer;font-size:12px;padding:4px 8px;">Edit</button>' +
|
|
1761
|
+
'<button id="cfg-save-' + field.key + '" onclick="saveConfigField(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#22c55e;cursor:pointer;font-size:12px;padding:4px 8px;">Save</button>' +
|
|
1762
|
+
'<button id="cfg-cancel-' + field.key + '" onclick="cancelConfigEdit(\\'' + escHtml(field.key) + '\\')" style="display:none;background:none;border:none;color:#a3a3a3;cursor:pointer;font-size:12px;padding:4px 8px;">Cancel</button>' +
|
|
1763
|
+
'</td>';
|
|
1764
|
+
tbody.appendChild(tr);
|
|
1701
1765
|
});
|
|
1702
|
-
el.innerHTML = html;
|
|
1703
1766
|
}
|
|
1704
1767
|
|
|
1768
|
+
window.editConfigField = function(key) {
|
|
1769
|
+
var valSpan = document.getElementById('cfg-val-' + key);
|
|
1770
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1771
|
+
var editBtn = document.getElementById('cfg-edit-' + key);
|
|
1772
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1773
|
+
var cancelBtn = document.getElementById('cfg-cancel-' + key);
|
|
1774
|
+
if (!valSpan || !input) return;
|
|
1775
|
+
// Hide all badges in this cell
|
|
1776
|
+
var badges = valSpan.parentNode.querySelectorAll('span[style*="PLACEHOLDER"]');
|
|
1777
|
+
for (var i = 0; i < badges.length; i++) badges[i].style.display = 'none';
|
|
1778
|
+
valSpan.style.display = 'none';
|
|
1779
|
+
input.style.display = 'block';
|
|
1780
|
+
input.focus();
|
|
1781
|
+
input.select();
|
|
1782
|
+
editBtn.style.display = 'none';
|
|
1783
|
+
saveBtn.style.display = 'inline';
|
|
1784
|
+
cancelBtn.style.display = 'inline';
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
window.cancelConfigEdit = function(key) {
|
|
1788
|
+
var valSpan = document.getElementById('cfg-val-' + key);
|
|
1789
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1790
|
+
var editBtn = document.getElementById('cfg-edit-' + key);
|
|
1791
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1792
|
+
var cancelBtn = document.getElementById('cfg-cancel-' + key);
|
|
1793
|
+
if (!valSpan || !input) return;
|
|
1794
|
+
var badges = valSpan.parentNode.querySelectorAll('span[style*="border-radius"]');
|
|
1795
|
+
for (var i = 0; i < badges.length; i++) badges[i].style.display = '';
|
|
1796
|
+
valSpan.style.display = '';
|
|
1797
|
+
input.style.display = 'none';
|
|
1798
|
+
input.value = valSpan.textContent;
|
|
1799
|
+
editBtn.style.display = 'inline';
|
|
1800
|
+
saveBtn.style.display = 'none';
|
|
1801
|
+
cancelBtn.style.display = 'none';
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
window.saveConfigField = function(key) {
|
|
1805
|
+
var input = document.getElementById('cfg-input-' + key);
|
|
1806
|
+
var saveBtn = document.getElementById('cfg-save-' + key);
|
|
1807
|
+
if (!input) return;
|
|
1808
|
+
var value = input.value;
|
|
1809
|
+
saveBtn.textContent = '...';
|
|
1810
|
+
fetch(API + '/__dev/config', {
|
|
1811
|
+
method: 'POST',
|
|
1812
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1813
|
+
body: JSON.stringify({ action: 'set', key: key, value: value })
|
|
1814
|
+
}).then(function(r) { return r.json(); })
|
|
1815
|
+
.then(function(data) {
|
|
1816
|
+
if (data.error) { alert('Error: ' + data.error); saveBtn.textContent = 'Save'; return; }
|
|
1817
|
+
// Reload config
|
|
1818
|
+
return fetch(API + '/__dev/config').then(function(r) { return r.json(); });
|
|
1819
|
+
})
|
|
1820
|
+
.then(function(data) { if (data) renderConfig(data); })
|
|
1821
|
+
.catch(function(err) { alert('Failed: ' + err.message); saveBtn.textContent = 'Save'; });
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
// \u2500\u2500\u2500 Auto-fill from Firebase \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1825
|
+
window.autoFillConfig = function() {
|
|
1826
|
+
var btn = document.getElementById('autofill-btn');
|
|
1827
|
+
var status = document.getElementById('autofill-status');
|
|
1828
|
+
btn.disabled = true;
|
|
1829
|
+
btn.textContent = 'Fetching...';
|
|
1830
|
+
status.style.display = 'none';
|
|
1831
|
+
|
|
1832
|
+
fetch(API + '/__dev/firebase-sdk-config')
|
|
1833
|
+
.then(function(r) { return r.json(); })
|
|
1834
|
+
.then(function(data) {
|
|
1835
|
+
if (data.error) {
|
|
1836
|
+
status.textContent = data.error;
|
|
1837
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1838
|
+
btn.disabled = false;
|
|
1839
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Map SDK config keys to clawfire.config.ts keys
|
|
1844
|
+
var fields = {};
|
|
1845
|
+
if (data.apiKey) fields.apiKey = data.apiKey;
|
|
1846
|
+
if (data.authDomain) fields.authDomain = data.authDomain;
|
|
1847
|
+
if (data.projectId) fields.projectId = data.projectId;
|
|
1848
|
+
if (data.storageBucket) fields.storageBucket = data.storageBucket;
|
|
1849
|
+
if (data.appId) fields.appId = data.appId;
|
|
1850
|
+
|
|
1851
|
+
var count = Object.keys(fields).length;
|
|
1852
|
+
if (count === 0) {
|
|
1853
|
+
status.textContent = 'No web app config found. Create a Web app in Firebase Console first.';
|
|
1854
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1a1a0a;border:1px solid #eab308;color:#eab308;';
|
|
1855
|
+
btn.disabled = false;
|
|
1856
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
// Save all fields at once
|
|
1861
|
+
return fetch(API + '/__dev/config', {
|
|
1862
|
+
method: 'POST',
|
|
1863
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1864
|
+
body: JSON.stringify({ action: 'set-multiple', fields: fields })
|
|
1865
|
+
}).then(function(r) { return r.json(); })
|
|
1866
|
+
.then(function(result) {
|
|
1867
|
+
if (result.error) {
|
|
1868
|
+
status.textContent = 'Error saving: ' + result.error;
|
|
1869
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1870
|
+
} else {
|
|
1871
|
+
status.textContent = count + ' fields updated from Firebase project!';
|
|
1872
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
|
|
1873
|
+
// Reload config view
|
|
1874
|
+
return fetch(API + '/__dev/config').then(function(r) { return r.json(); })
|
|
1875
|
+
.then(function(cfg) { renderConfig(cfg); });
|
|
1876
|
+
}
|
|
1877
|
+
});
|
|
1878
|
+
})
|
|
1879
|
+
.catch(function(err) {
|
|
1880
|
+
status.textContent = 'Failed: ' + err.message;
|
|
1881
|
+
status.style.cssText = 'display:block;padding:8px 12px;border-radius:6px;margin-bottom:12px;font-size:13px;background:#1c0808;border:1px solid #ef4444;color:#ef4444;';
|
|
1882
|
+
})
|
|
1883
|
+
.finally(function() {
|
|
1884
|
+
btn.disabled = false;
|
|
1885
|
+
btn.textContent = 'Auto-fill from Firebase';
|
|
1886
|
+
});
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1705
1889
|
// \u2500\u2500\u2500 Environment Variables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1706
1890
|
function renderEnvVars(data) {
|
|
1707
1891
|
envData = data.variables || [];
|
|
@@ -1740,7 +1924,7 @@ function generateDashboardHtml(options) {
|
|
|
1740
1924
|
});
|
|
1741
1925
|
}
|
|
1742
1926
|
|
|
1743
|
-
// \u2500\u2500\u2500 Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1927
|
+
// \u2500\u2500\u2500 Env Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1744
1928
|
window.showEnvModal = function(key) {
|
|
1745
1929
|
editingKey = key || null;
|
|
1746
1930
|
var modal = document.getElementById('env-modal');
|
|
@@ -2685,6 +2869,37 @@ ${liveReloadScript}
|
|
|
2685
2869
|
sendJson(this.readProjectConfig());
|
|
2686
2870
|
return;
|
|
2687
2871
|
}
|
|
2872
|
+
if (url.pathname === "/__dev/config" && req.method === "POST") {
|
|
2873
|
+
let body = "";
|
|
2874
|
+
req.on("data", (chunk) => {
|
|
2875
|
+
body += chunk;
|
|
2876
|
+
});
|
|
2877
|
+
req.on("end", () => {
|
|
2878
|
+
try {
|
|
2879
|
+
const data = JSON.parse(body);
|
|
2880
|
+
if (data.action === "set" && data.key && data.value !== void 0) {
|
|
2881
|
+
this.updateProjectConfig(data.key, String(data.value));
|
|
2882
|
+
clearFirebaseStatusCache();
|
|
2883
|
+
sendJson({ ok: true });
|
|
2884
|
+
} else if (data.action === "set-multiple" && data.fields) {
|
|
2885
|
+
for (const [key, value] of Object.entries(data.fields)) {
|
|
2886
|
+
this.updateProjectConfig(key, String(value));
|
|
2887
|
+
}
|
|
2888
|
+
clearFirebaseStatusCache();
|
|
2889
|
+
sendJson({ ok: true });
|
|
2890
|
+
} else {
|
|
2891
|
+
sendJson({ error: "Invalid action" }, 400);
|
|
2892
|
+
}
|
|
2893
|
+
} catch (err) {
|
|
2894
|
+
sendJson({ error: err instanceof Error ? err.message : "Failed" }, 400);
|
|
2895
|
+
}
|
|
2896
|
+
});
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
if (url.pathname === "/__dev/firebase-sdk-config" && req.method === "GET") {
|
|
2900
|
+
fetchFirebaseSdkConfig(this.options.projectDir).then((config) => sendJson(config)).catch((err) => sendJson({ error: err instanceof Error ? err.message : "Failed to fetch SDK config" }, 500));
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2688
2903
|
if (url.pathname === "/__dev/env" && req.method === "GET") {
|
|
2689
2904
|
try {
|
|
2690
2905
|
sendJson(this.envManager.read());
|
|
@@ -2740,6 +2955,25 @@ ${liveReloadScript}
|
|
|
2740
2955
|
}
|
|
2741
2956
|
return { fields };
|
|
2742
2957
|
}
|
|
2958
|
+
/** Update a single key's value in clawfire.config.ts */
|
|
2959
|
+
updateProjectConfig(key, value) {
|
|
2960
|
+
const configPath = (0, import_node_path4.resolve)(this.options.projectDir, "clawfire.config.ts");
|
|
2961
|
+
if (!(0, import_node_fs4.existsSync)(configPath)) {
|
|
2962
|
+
throw new Error("clawfire.config.ts not found");
|
|
2963
|
+
}
|
|
2964
|
+
let content = (0, import_node_fs4.readFileSync)(configPath, "utf-8");
|
|
2965
|
+
const pattern = new RegExp(
|
|
2966
|
+
`(${this.escapeRegex(key)}\\s*:\\s*)["'\`][^"'\`]*["'\`]`
|
|
2967
|
+
);
|
|
2968
|
+
if (!pattern.test(content)) {
|
|
2969
|
+
throw new Error(`Key "${key}" not found in config`);
|
|
2970
|
+
}
|
|
2971
|
+
content = content.replace(pattern, `$1"${value.replace(/"/g, '\\"')}"`);
|
|
2972
|
+
(0, import_node_fs4.writeFileSync)(configPath, content, "utf-8");
|
|
2973
|
+
}
|
|
2974
|
+
escapeRegex(s) {
|
|
2975
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2976
|
+
}
|
|
2743
2977
|
};
|
|
2744
2978
|
async function startDevServer(options) {
|
|
2745
2979
|
const server = new DevServer(options);
|