stashes 0.1.37 → 0.1.39
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 +323 -304
- package/dist/mcp.js +323 -304
- package/dist/web/assets/index-BorzN7F8.js +96 -0
- package/dist/web/assets/index-D1l83rdq.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-D_C2e-QW.js +0 -96
- package/dist/web/assets/index-Ya7csjt0.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1362,7 +1362,291 @@ async function cleanup(projectPath) {
|
|
|
1362
1362
|
import { readFileSync as readFileSync4, existsSync as existsSync8 } from "fs";
|
|
1363
1363
|
import { join as join8 } from "path";
|
|
1364
1364
|
|
|
1365
|
+
// ../server/dist/services/app-proxy.js
|
|
1366
|
+
import { spawn as spawn5 } from "child_process";
|
|
1367
|
+
function startAppProxy(userDevPort, proxyPort, injectOverlay) {
|
|
1368
|
+
const overlayScript = injectOverlay("", userDevPort, proxyPort);
|
|
1369
|
+
const overlayEscaped = JSON.stringify(overlayScript);
|
|
1370
|
+
const proxyScript = `
|
|
1371
|
+
const http = require('http');
|
|
1372
|
+
const net = require('net');
|
|
1373
|
+
const zlib = require('zlib');
|
|
1374
|
+
const UPSTREAM = ${userDevPort};
|
|
1375
|
+
const OVERLAY = ${overlayEscaped};
|
|
1376
|
+
|
|
1377
|
+
const server = http.createServer((clientReq, clientRes) => {
|
|
1378
|
+
const opts = {
|
|
1379
|
+
hostname: 'localhost',
|
|
1380
|
+
port: UPSTREAM,
|
|
1381
|
+
path: clientReq.url,
|
|
1382
|
+
method: clientReq.method,
|
|
1383
|
+
headers: clientReq.headers,
|
|
1384
|
+
};
|
|
1385
|
+
const proxyReq = http.request(opts, (proxyRes) => {
|
|
1386
|
+
const ct = proxyRes.headers['content-type'] || '';
|
|
1387
|
+
if (ct.includes('text/html')) {
|
|
1388
|
+
// Buffer HTML to inject overlay
|
|
1389
|
+
const chunks = [];
|
|
1390
|
+
proxyRes.on('data', c => chunks.push(c));
|
|
1391
|
+
proxyRes.on('end', () => {
|
|
1392
|
+
let html = Buffer.concat(chunks);
|
|
1393
|
+
const enc = proxyRes.headers['content-encoding'];
|
|
1394
|
+
// Decompress if needed
|
|
1395
|
+
if (enc === 'gzip') {
|
|
1396
|
+
try { html = zlib.gunzipSync(html); } catch {}
|
|
1397
|
+
} else if (enc === 'br') {
|
|
1398
|
+
try { html = zlib.brotliDecompressSync(html); } catch {}
|
|
1399
|
+
} else if (enc === 'deflate') {
|
|
1400
|
+
try { html = zlib.inflateSync(html); } catch {}
|
|
1401
|
+
}
|
|
1402
|
+
const hdrs = { ...proxyRes.headers };
|
|
1403
|
+
delete hdrs['content-length'];
|
|
1404
|
+
delete hdrs['content-encoding'];
|
|
1405
|
+
delete hdrs['transfer-encoding'];
|
|
1406
|
+
clientRes.writeHead(proxyRes.statusCode, hdrs);
|
|
1407
|
+
clientRes.write(html);
|
|
1408
|
+
clientRes.end(OVERLAY);
|
|
1409
|
+
});
|
|
1410
|
+
} else {
|
|
1411
|
+
// Non-HTML: stream through unchanged
|
|
1412
|
+
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
1413
|
+
proxyRes.pipe(clientRes);
|
|
1414
|
+
}
|
|
1415
|
+
proxyRes.on('error', () => clientRes.end());
|
|
1416
|
+
});
|
|
1417
|
+
proxyReq.on('error', () => { try { clientRes.writeHead(502); clientRes.end(); } catch {} });
|
|
1418
|
+
clientReq.pipe(proxyReq);
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
// WebSocket upgrades: raw TCP pipe
|
|
1422
|
+
server.on('upgrade', (req, socket, head) => {
|
|
1423
|
+
const upstream = net.createConnection(UPSTREAM, 'localhost', () => {
|
|
1424
|
+
const lines = [req.method + ' ' + req.url + ' HTTP/1.1'];
|
|
1425
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
1426
|
+
lines.push(k + ': ' + (Array.isArray(v) ? v.join(', ') : v));
|
|
1427
|
+
}
|
|
1428
|
+
upstream.write(lines.join('\\r\\n') + '\\r\\n\\r\\n');
|
|
1429
|
+
if (head.length) upstream.write(head);
|
|
1430
|
+
socket.pipe(upstream);
|
|
1431
|
+
upstream.pipe(socket);
|
|
1432
|
+
});
|
|
1433
|
+
upstream.on('error', () => socket.destroy());
|
|
1434
|
+
socket.on('error', () => upstream.destroy());
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
server.listen(${proxyPort}, () => {
|
|
1438
|
+
if (process.send) process.send('ready');
|
|
1439
|
+
});
|
|
1440
|
+
`;
|
|
1441
|
+
const child = spawn5("node", ["-e", proxyScript], {
|
|
1442
|
+
stdio: ["ignore", "inherit", "inherit", "ipc"]
|
|
1443
|
+
});
|
|
1444
|
+
child.on("error", (err) => {
|
|
1445
|
+
logger.error("proxy", `Failed to start proxy: ${err.message}`);
|
|
1446
|
+
});
|
|
1447
|
+
child.on("message", (msg) => {
|
|
1448
|
+
if (msg === "ready") {
|
|
1449
|
+
logger.info("proxy", `App proxy at http://localhost:${proxyPort} \u2192 http://localhost:${userDevPort}`);
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
return child;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// ../server/dist/services/overlay-script.js
|
|
1456
|
+
function injectOverlayScript(html, _upstreamPort, _proxyPort) {
|
|
1457
|
+
const overlayScript = `
|
|
1458
|
+
<script data-stashes-overlay>
|
|
1459
|
+
(function() {
|
|
1460
|
+
var highlightOverlay = null;
|
|
1461
|
+
var pickerEnabled = false;
|
|
1462
|
+
var precisionMode = false;
|
|
1463
|
+
|
|
1464
|
+
function createOverlay() {
|
|
1465
|
+
var overlay = document.createElement('div');
|
|
1466
|
+
overlay.id = 'stashes-highlight';
|
|
1467
|
+
overlay.style.cssText = 'position:fixed;pointer-events:none;border:2px solid #6366f1;background:rgba(99,102,241,0.1);z-index:99999;transition:all 0.1s ease;display:none;border-radius:4px;';
|
|
1468
|
+
var tooltip = document.createElement('div');
|
|
1469
|
+
tooltip.id = 'stashes-tooltip';
|
|
1470
|
+
tooltip.style.cssText = 'position:fixed;background:#1e1b4b;color:#e0e7ff;padding:4px 10px;border-radius:6px;font-size:11px;font-family:ui-monospace,monospace;z-index:100000;pointer-events:none;display:none;white-space:nowrap;box-shadow:0 4px 12px rgba(0,0,0,0.3);max-width:400px;overflow:hidden;text-overflow:ellipsis;';
|
|
1471
|
+
document.body.appendChild(overlay);
|
|
1472
|
+
document.body.appendChild(tooltip);
|
|
1473
|
+
return overlay;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
var SEMANTIC_TAGS = ['header','nav','main','section','article','aside','footer','form','dialog'];
|
|
1477
|
+
var LEAF_TAGS = ['h1','h2','h3','h4','h5','h6','p','button','a','input','textarea','select','img','svg','video','label','li','td','th','figcaption','blockquote','pre','code','span'];
|
|
1478
|
+
|
|
1479
|
+
function findTarget(el, precise) {
|
|
1480
|
+
if (precise) return el;
|
|
1481
|
+
// If the element itself is a meaningful leaf, select it directly
|
|
1482
|
+
var elTag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
1483
|
+
if (LEAF_TAGS.indexOf(elTag) !== -1) return el;
|
|
1484
|
+
var current = el;
|
|
1485
|
+
var best = el;
|
|
1486
|
+
while (current && current !== document.body) {
|
|
1487
|
+
var tag = current.tagName.toLowerCase();
|
|
1488
|
+
if (LEAF_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
1489
|
+
if (SEMANTIC_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
1490
|
+
if (current.id) { best = current; break; }
|
|
1491
|
+
if (current.getAttribute('role')) { best = current; break; }
|
|
1492
|
+
if (current.getAttribute('data-testid')) { best = current; break; }
|
|
1493
|
+
if (current.children && current.children.length > 1 && current.getBoundingClientRect().height > 50) {
|
|
1494
|
+
best = current;
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
current = current.parentElement;
|
|
1498
|
+
}
|
|
1499
|
+
return best;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function describeElement(el) {
|
|
1503
|
+
var tag = el.tagName.toLowerCase();
|
|
1504
|
+
var id = el.id ? '#' + el.id : '';
|
|
1505
|
+
var role = el.getAttribute('role') || '';
|
|
1506
|
+
var testId = el.getAttribute('data-testid') || '';
|
|
1507
|
+
var cls = (el.className && typeof el.className === 'string') ? el.className.trim().split(/[ ]+/).slice(0, 2).join('.') : '';
|
|
1508
|
+
var text = (el.textContent || '').trim().substring(0, 40);
|
|
1509
|
+
var label = tag;
|
|
1510
|
+
if (SEMANTIC_TAGS.indexOf(tag) !== -1) label = tag.toUpperCase();
|
|
1511
|
+
if (id) label += ' ' + id;
|
|
1512
|
+
else if (cls) label += '.' + cls;
|
|
1513
|
+
if (role) label += ' [' + role + ']';
|
|
1514
|
+
if (testId) label += ' [' + testId + ']';
|
|
1515
|
+
if (!id && !cls && text) label += ' "' + text.substring(0, 25) + '"';
|
|
1516
|
+
return label;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function generateSelector(el) {
|
|
1520
|
+
if (el.id) return '#' + el.id;
|
|
1521
|
+
var parts = [];
|
|
1522
|
+
var current = el;
|
|
1523
|
+
var depth = 0;
|
|
1524
|
+
while (current && current !== document.body && depth < 5) {
|
|
1525
|
+
var sel = current.tagName.toLowerCase();
|
|
1526
|
+
if (current.id) { parts.unshift('#' + current.id); break; }
|
|
1527
|
+
if (current.className && typeof current.className === 'string') {
|
|
1528
|
+
var c = current.className.trim().split(/[ ]+/).slice(0, 2).join('.');
|
|
1529
|
+
if (c) sel += '.' + c;
|
|
1530
|
+
}
|
|
1531
|
+
parts.unshift(sel);
|
|
1532
|
+
current = current.parentElement;
|
|
1533
|
+
depth++;
|
|
1534
|
+
}
|
|
1535
|
+
return parts.join(' > ');
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function onMouseMove(e) {
|
|
1539
|
+
if (!pickerEnabled) return;
|
|
1540
|
+
if (!highlightOverlay) highlightOverlay = createOverlay();
|
|
1541
|
+
precisionMode = e.shiftKey;
|
|
1542
|
+
var target = findTarget(e.target, precisionMode);
|
|
1543
|
+
var overlay = document.getElementById('stashes-highlight');
|
|
1544
|
+
var tooltip = document.getElementById('stashes-tooltip');
|
|
1545
|
+
if (target) {
|
|
1546
|
+
var rect = target.getBoundingClientRect();
|
|
1547
|
+
overlay.style.display = 'block';
|
|
1548
|
+
overlay.style.top = rect.top + 'px';
|
|
1549
|
+
overlay.style.left = rect.left + 'px';
|
|
1550
|
+
overlay.style.width = rect.width + 'px';
|
|
1551
|
+
overlay.style.height = rect.height + 'px';
|
|
1552
|
+
overlay.style.borderColor = precisionMode ? '#f59e0b' : '#6366f1';
|
|
1553
|
+
tooltip.style.display = 'block';
|
|
1554
|
+
tooltip.style.top = Math.max(0, rect.top - 30) + 'px';
|
|
1555
|
+
tooltip.style.left = Math.max(0, rect.left) + 'px';
|
|
1556
|
+
tooltip.textContent = (precisionMode ? '[precise] ' : '') + describeElement(target);
|
|
1557
|
+
} else {
|
|
1558
|
+
overlay.style.display = 'none';
|
|
1559
|
+
tooltip.style.display = 'none';
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function onClick(e) {
|
|
1564
|
+
if (!pickerEnabled) return;
|
|
1565
|
+
e.preventDefault();
|
|
1566
|
+
e.stopPropagation();
|
|
1567
|
+
var target = findTarget(e.target, e.shiftKey);
|
|
1568
|
+
if (target) {
|
|
1569
|
+
var desc = describeElement(target);
|
|
1570
|
+
var selector = generateSelector(target);
|
|
1571
|
+
var tag = target.tagName.toLowerCase();
|
|
1572
|
+
var outerSnippet = target.outerHTML.substring(0, 500);
|
|
1573
|
+
window.parent.postMessage({
|
|
1574
|
+
type: 'stashes:component_selected',
|
|
1575
|
+
component: {
|
|
1576
|
+
name: desc,
|
|
1577
|
+
filePath: 'auto-detect',
|
|
1578
|
+
domSelector: selector,
|
|
1579
|
+
htmlSnippet: outerSnippet,
|
|
1580
|
+
tag: tag
|
|
1581
|
+
}
|
|
1582
|
+
}, '*');
|
|
1583
|
+
var overlay = document.getElementById('stashes-highlight');
|
|
1584
|
+
if (overlay) {
|
|
1585
|
+
overlay.style.borderColor = '#22c55e';
|
|
1586
|
+
overlay.style.background = 'rgba(34,197,94,0.1)';
|
|
1587
|
+
setTimeout(function() {
|
|
1588
|
+
overlay.style.borderColor = '#6366f1';
|
|
1589
|
+
overlay.style.background = 'rgba(99,102,241,0.1)';
|
|
1590
|
+
}, 500);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
window.addEventListener('message', function(e) {
|
|
1596
|
+
if (!e.data || !e.data.type) return;
|
|
1597
|
+
if (e.data.type === 'stashes:toggle_picker') {
|
|
1598
|
+
pickerEnabled = e.data.enabled;
|
|
1599
|
+
if (!pickerEnabled) {
|
|
1600
|
+
var ov = document.getElementById('stashes-highlight');
|
|
1601
|
+
var tp = document.getElementById('stashes-tooltip');
|
|
1602
|
+
if (ov) ov.style.display = 'none';
|
|
1603
|
+
if (tp) tp.style.display = 'none';
|
|
1604
|
+
}
|
|
1605
|
+
} else if (e.data.type === 'stashes:navigate_back') {
|
|
1606
|
+
history.back();
|
|
1607
|
+
} else if (e.data.type === 'stashes:navigate_forward') {
|
|
1608
|
+
history.forward();
|
|
1609
|
+
} else if (e.data.type === 'stashes:refresh') {
|
|
1610
|
+
location.reload();
|
|
1611
|
+
} else if (e.data.type === 'stashes:navigate_to') {
|
|
1612
|
+
window.location.href = e.data.url;
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
// Report current URL to parent for status bar display
|
|
1617
|
+
function reportUrl() {
|
|
1618
|
+
window.parent.postMessage({
|
|
1619
|
+
type: 'stashes:url_change',
|
|
1620
|
+
url: window.location.pathname + window.location.search + window.location.hash
|
|
1621
|
+
}, '*');
|
|
1622
|
+
}
|
|
1623
|
+
reportUrl();
|
|
1624
|
+
var origPush = history.pushState;
|
|
1625
|
+
history.pushState = function() {
|
|
1626
|
+
origPush.apply(this, arguments);
|
|
1627
|
+
setTimeout(reportUrl, 0);
|
|
1628
|
+
};
|
|
1629
|
+
var origReplace = history.replaceState;
|
|
1630
|
+
history.replaceState = function() {
|
|
1631
|
+
origReplace.apply(this, arguments);
|
|
1632
|
+
setTimeout(reportUrl, 0);
|
|
1633
|
+
};
|
|
1634
|
+
window.addEventListener('popstate', reportUrl);
|
|
1635
|
+
|
|
1636
|
+
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
1637
|
+
document.addEventListener('click', onClick, true);
|
|
1638
|
+
})();
|
|
1639
|
+
</script>`;
|
|
1640
|
+
if (html.includes("</body>")) {
|
|
1641
|
+
return html.replace("</body>", () => overlayScript + `
|
|
1642
|
+
</body>`);
|
|
1643
|
+
}
|
|
1644
|
+
return html + overlayScript;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1365
1647
|
// ../server/dist/services/preview-pool.js
|
|
1648
|
+
var DEV_PORT_OFFSET = 1000;
|
|
1649
|
+
|
|
1366
1650
|
class PreviewPool {
|
|
1367
1651
|
entries = new Map;
|
|
1368
1652
|
usedPorts = new Set;
|
|
@@ -1388,7 +1672,8 @@ class PreviewPool {
|
|
|
1388
1672
|
if (this.entries.size >= this.maxSize) {
|
|
1389
1673
|
this.evictOldest();
|
|
1390
1674
|
}
|
|
1391
|
-
const
|
|
1675
|
+
const proxyPort = this.allocatePort();
|
|
1676
|
+
const devPort = proxyPort + DEV_PORT_OFFSET;
|
|
1392
1677
|
const worktreePath = await this.worktreeManager.createPreviewForPool(stashId);
|
|
1393
1678
|
const process2 = Bun.spawn({
|
|
1394
1679
|
cmd: ["npm", "run", "dev"],
|
|
@@ -1396,20 +1681,22 @@ class PreviewPool {
|
|
|
1396
1681
|
stdin: "ignore",
|
|
1397
1682
|
stdout: "pipe",
|
|
1398
1683
|
stderr: "pipe",
|
|
1399
|
-
env: { ...Bun.env, PORT: String(
|
|
1684
|
+
env: { ...Bun.env, PORT: String(devPort), BROWSER: "none" }
|
|
1400
1685
|
});
|
|
1686
|
+
const proxyProcess = startAppProxy(devPort, proxyPort, injectOverlayScript);
|
|
1401
1687
|
const entry = {
|
|
1402
1688
|
stashId,
|
|
1403
|
-
port,
|
|
1689
|
+
port: proxyPort,
|
|
1404
1690
|
process: process2,
|
|
1691
|
+
proxyProcess,
|
|
1405
1692
|
worktreePath,
|
|
1406
1693
|
lastHeartbeat: Date.now()
|
|
1407
1694
|
};
|
|
1408
1695
|
this.entries.set(stashId, entry);
|
|
1409
|
-
this.usedPorts.add(
|
|
1410
|
-
logger.info("pool", `cold start: ${stashId}
|
|
1411
|
-
await this.waitForPort(
|
|
1412
|
-
return
|
|
1696
|
+
this.usedPorts.add(proxyPort);
|
|
1697
|
+
logger.info("pool", `cold start: ${stashId} dev=:${devPort} proxy=:${proxyPort}`, { poolSize: this.entries.size });
|
|
1698
|
+
await this.waitForPort(proxyPort, 60000);
|
|
1699
|
+
return proxyPort;
|
|
1413
1700
|
}
|
|
1414
1701
|
heartbeat(stashId) {
|
|
1415
1702
|
const entry = this.entries.get(stashId);
|
|
@@ -1527,6 +1814,9 @@ class PreviewPool {
|
|
|
1527
1814
|
try {
|
|
1528
1815
|
entry.process.kill();
|
|
1529
1816
|
} catch {}
|
|
1817
|
+
try {
|
|
1818
|
+
entry.proxyProcess.kill();
|
|
1819
|
+
} catch {}
|
|
1530
1820
|
}
|
|
1531
1821
|
async waitForPort(port, timeout) {
|
|
1532
1822
|
const start = Date.now();
|
|
@@ -2031,124 +2321,34 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
2031
2321
|
}
|
|
2032
2322
|
}
|
|
2033
2323
|
await stashService.message(event.projectId, event.chatId, event.message, event.referenceStashIds, event.componentContext);
|
|
2034
|
-
break;
|
|
2035
|
-
}
|
|
2036
|
-
case "interact":
|
|
2037
|
-
await stashService.switchPreview(event.stashId, event.sortedStashIds);
|
|
2038
|
-
break;
|
|
2039
|
-
case "preview_heartbeat":
|
|
2040
|
-
stashService.previewHeartbeat(event.stashId);
|
|
2041
|
-
break;
|
|
2042
|
-
case "apply_stash":
|
|
2043
|
-
await stashService.applyStash(event.stashId);
|
|
2044
|
-
break;
|
|
2045
|
-
case "delete_stash":
|
|
2046
|
-
await stashService.deleteStash(event.stashId);
|
|
2047
|
-
break;
|
|
2048
|
-
}
|
|
2049
|
-
} catch (err) {
|
|
2050
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2051
|
-
logger.error("ws", `handler failed for ${event.type}`, { error: errorMsg });
|
|
2052
|
-
if ("stashId" in event && event.stashId) {
|
|
2053
|
-
broadcast({ type: "stash:error", stashId: event.stashId, error: errorMsg });
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
},
|
|
2057
|
-
close(ws) {
|
|
2058
|
-
clients.delete(ws);
|
|
2059
|
-
logger.info("ws", "client disconnected", { remaining: clients.size });
|
|
2060
|
-
}
|
|
2061
|
-
};
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
// ../server/dist/services/app-proxy.js
|
|
2065
|
-
import { spawn as spawn5 } from "child_process";
|
|
2066
|
-
function startAppProxy(userDevPort, proxyPort, injectOverlay) {
|
|
2067
|
-
const overlayScript = injectOverlay("", userDevPort, proxyPort);
|
|
2068
|
-
const overlayEscaped = JSON.stringify(overlayScript);
|
|
2069
|
-
const proxyScript = `
|
|
2070
|
-
const http = require('http');
|
|
2071
|
-
const net = require('net');
|
|
2072
|
-
const zlib = require('zlib');
|
|
2073
|
-
const UPSTREAM = ${userDevPort};
|
|
2074
|
-
const OVERLAY = ${overlayEscaped};
|
|
2075
|
-
|
|
2076
|
-
const server = http.createServer((clientReq, clientRes) => {
|
|
2077
|
-
const opts = {
|
|
2078
|
-
hostname: 'localhost',
|
|
2079
|
-
port: UPSTREAM,
|
|
2080
|
-
path: clientReq.url,
|
|
2081
|
-
method: clientReq.method,
|
|
2082
|
-
headers: clientReq.headers,
|
|
2083
|
-
};
|
|
2084
|
-
const proxyReq = http.request(opts, (proxyRes) => {
|
|
2085
|
-
const ct = proxyRes.headers['content-type'] || '';
|
|
2086
|
-
if (ct.includes('text/html')) {
|
|
2087
|
-
// Buffer HTML to inject overlay
|
|
2088
|
-
const chunks = [];
|
|
2089
|
-
proxyRes.on('data', c => chunks.push(c));
|
|
2090
|
-
proxyRes.on('end', () => {
|
|
2091
|
-
let html = Buffer.concat(chunks);
|
|
2092
|
-
const enc = proxyRes.headers['content-encoding'];
|
|
2093
|
-
// Decompress if needed
|
|
2094
|
-
if (enc === 'gzip') {
|
|
2095
|
-
try { html = zlib.gunzipSync(html); } catch {}
|
|
2096
|
-
} else if (enc === 'br') {
|
|
2097
|
-
try { html = zlib.brotliDecompressSync(html); } catch {}
|
|
2098
|
-
} else if (enc === 'deflate') {
|
|
2099
|
-
try { html = zlib.inflateSync(html); } catch {}
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
case "interact":
|
|
2327
|
+
await stashService.switchPreview(event.stashId, event.sortedStashIds);
|
|
2328
|
+
break;
|
|
2329
|
+
case "preview_heartbeat":
|
|
2330
|
+
stashService.previewHeartbeat(event.stashId);
|
|
2331
|
+
break;
|
|
2332
|
+
case "apply_stash":
|
|
2333
|
+
await stashService.applyStash(event.stashId);
|
|
2334
|
+
break;
|
|
2335
|
+
case "delete_stash":
|
|
2336
|
+
await stashService.deleteStash(event.stashId);
|
|
2337
|
+
break;
|
|
2100
2338
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
proxyRes.pipe(clientRes);
|
|
2113
|
-
}
|
|
2114
|
-
proxyRes.on('error', () => clientRes.end());
|
|
2115
|
-
});
|
|
2116
|
-
proxyReq.on('error', () => { try { clientRes.writeHead(502); clientRes.end(); } catch {} });
|
|
2117
|
-
clientReq.pipe(proxyReq);
|
|
2118
|
-
});
|
|
2119
|
-
|
|
2120
|
-
// WebSocket upgrades: raw TCP pipe
|
|
2121
|
-
server.on('upgrade', (req, socket, head) => {
|
|
2122
|
-
const upstream = net.createConnection(UPSTREAM, 'localhost', () => {
|
|
2123
|
-
const lines = [req.method + ' ' + req.url + ' HTTP/1.1'];
|
|
2124
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
2125
|
-
lines.push(k + ': ' + (Array.isArray(v) ? v.join(', ') : v));
|
|
2126
|
-
}
|
|
2127
|
-
upstream.write(lines.join('\\r\\n') + '\\r\\n\\r\\n');
|
|
2128
|
-
if (head.length) upstream.write(head);
|
|
2129
|
-
socket.pipe(upstream);
|
|
2130
|
-
upstream.pipe(socket);
|
|
2131
|
-
});
|
|
2132
|
-
upstream.on('error', () => socket.destroy());
|
|
2133
|
-
socket.on('error', () => upstream.destroy());
|
|
2134
|
-
});
|
|
2135
|
-
|
|
2136
|
-
server.listen(${proxyPort}, () => {
|
|
2137
|
-
if (process.send) process.send('ready');
|
|
2138
|
-
});
|
|
2139
|
-
`;
|
|
2140
|
-
const child = spawn5("node", ["-e", proxyScript], {
|
|
2141
|
-
stdio: ["ignore", "inherit", "inherit", "ipc"]
|
|
2142
|
-
});
|
|
2143
|
-
child.on("error", (err) => {
|
|
2144
|
-
logger.error("proxy", `Failed to start proxy: ${err.message}`);
|
|
2145
|
-
});
|
|
2146
|
-
child.on("message", (msg) => {
|
|
2147
|
-
if (msg === "ready") {
|
|
2148
|
-
logger.info("proxy", `App proxy at http://localhost:${proxyPort} \u2192 http://localhost:${userDevPort}`);
|
|
2339
|
+
} catch (err) {
|
|
2340
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2341
|
+
logger.error("ws", `handler failed for ${event.type}`, { error: errorMsg });
|
|
2342
|
+
if ("stashId" in event && event.stashId) {
|
|
2343
|
+
broadcast({ type: "stash:error", stashId: event.stashId, error: errorMsg });
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
close(ws) {
|
|
2348
|
+
clients.delete(ws);
|
|
2349
|
+
logger.info("ws", "client disconnected", { remaining: clients.size });
|
|
2149
2350
|
}
|
|
2150
|
-
}
|
|
2151
|
-
return child;
|
|
2351
|
+
};
|
|
2152
2352
|
}
|
|
2153
2353
|
|
|
2154
2354
|
// ../server/dist/index.js
|
|
@@ -2226,187 +2426,6 @@ function startServer(projectPath, userDevPort, port = STASHES_PORT) {
|
|
|
2226
2426
|
logger.info("server", `Project: ${projectPath}`);
|
|
2227
2427
|
return server;
|
|
2228
2428
|
}
|
|
2229
|
-
function injectOverlayScript(html, _upstreamPort, _proxyPort) {
|
|
2230
|
-
const overlayScript = `
|
|
2231
|
-
<script data-stashes-overlay>
|
|
2232
|
-
(function() {
|
|
2233
|
-
var highlightOverlay = null;
|
|
2234
|
-
var pickerEnabled = false;
|
|
2235
|
-
var precisionMode = false;
|
|
2236
|
-
|
|
2237
|
-
function createOverlay() {
|
|
2238
|
-
var overlay = document.createElement('div');
|
|
2239
|
-
overlay.id = 'stashes-highlight';
|
|
2240
|
-
overlay.style.cssText = 'position:fixed;pointer-events:none;border:2px solid #6366f1;background:rgba(99,102,241,0.1);z-index:99999;transition:all 0.1s ease;display:none;border-radius:4px;';
|
|
2241
|
-
var tooltip = document.createElement('div');
|
|
2242
|
-
tooltip.id = 'stashes-tooltip';
|
|
2243
|
-
tooltip.style.cssText = 'position:fixed;background:#1e1b4b;color:#e0e7ff;padding:4px 10px;border-radius:6px;font-size:11px;font-family:ui-monospace,monospace;z-index:100000;pointer-events:none;display:none;white-space:nowrap;box-shadow:0 4px 12px rgba(0,0,0,0.3);max-width:400px;overflow:hidden;text-overflow:ellipsis;';
|
|
2244
|
-
document.body.appendChild(overlay);
|
|
2245
|
-
document.body.appendChild(tooltip);
|
|
2246
|
-
return overlay;
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
var SEMANTIC_TAGS = ['header','nav','main','section','article','aside','footer','form','dialog'];
|
|
2250
|
-
var LEAF_TAGS = ['h1','h2','h3','h4','h5','h6','p','button','a','input','textarea','select','img','svg','video','label','li','td','th','figcaption','blockquote','pre','code','span'];
|
|
2251
|
-
|
|
2252
|
-
function findTarget(el, precise) {
|
|
2253
|
-
if (precise) return el;
|
|
2254
|
-
// If the element itself is a meaningful leaf, select it directly
|
|
2255
|
-
var elTag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
2256
|
-
if (LEAF_TAGS.indexOf(elTag) !== -1) return el;
|
|
2257
|
-
var current = el;
|
|
2258
|
-
var best = el;
|
|
2259
|
-
while (current && current !== document.body) {
|
|
2260
|
-
var tag = current.tagName.toLowerCase();
|
|
2261
|
-
if (LEAF_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
2262
|
-
if (SEMANTIC_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
2263
|
-
if (current.id) { best = current; break; }
|
|
2264
|
-
if (current.getAttribute('role')) { best = current; break; }
|
|
2265
|
-
if (current.getAttribute('data-testid')) { best = current; break; }
|
|
2266
|
-
if (current.children && current.children.length > 1 && current.getBoundingClientRect().height > 50) {
|
|
2267
|
-
best = current;
|
|
2268
|
-
break;
|
|
2269
|
-
}
|
|
2270
|
-
current = current.parentElement;
|
|
2271
|
-
}
|
|
2272
|
-
return best;
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
function describeElement(el) {
|
|
2276
|
-
var tag = el.tagName.toLowerCase();
|
|
2277
|
-
var id = el.id ? '#' + el.id : '';
|
|
2278
|
-
var role = el.getAttribute('role') || '';
|
|
2279
|
-
var testId = el.getAttribute('data-testid') || '';
|
|
2280
|
-
var cls = (el.className && typeof el.className === 'string') ? el.className.trim().split(/[ ]+/).slice(0, 2).join('.') : '';
|
|
2281
|
-
var text = (el.textContent || '').trim().substring(0, 40);
|
|
2282
|
-
var label = tag;
|
|
2283
|
-
if (SEMANTIC_TAGS.indexOf(tag) !== -1) label = tag.toUpperCase();
|
|
2284
|
-
if (id) label += ' ' + id;
|
|
2285
|
-
else if (cls) label += '.' + cls;
|
|
2286
|
-
if (role) label += ' [' + role + ']';
|
|
2287
|
-
if (testId) label += ' [' + testId + ']';
|
|
2288
|
-
if (!id && !cls && text) label += ' "' + text.substring(0, 25) + '"';
|
|
2289
|
-
return label;
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
function generateSelector(el) {
|
|
2293
|
-
if (el.id) return '#' + el.id;
|
|
2294
|
-
var parts = [];
|
|
2295
|
-
var current = el;
|
|
2296
|
-
var depth = 0;
|
|
2297
|
-
while (current && current !== document.body && depth < 5) {
|
|
2298
|
-
var sel = current.tagName.toLowerCase();
|
|
2299
|
-
if (current.id) { parts.unshift('#' + current.id); break; }
|
|
2300
|
-
if (current.className && typeof current.className === 'string') {
|
|
2301
|
-
var c = current.className.trim().split(/[ ]+/).slice(0, 2).join('.');
|
|
2302
|
-
if (c) sel += '.' + c;
|
|
2303
|
-
}
|
|
2304
|
-
parts.unshift(sel);
|
|
2305
|
-
current = current.parentElement;
|
|
2306
|
-
depth++;
|
|
2307
|
-
}
|
|
2308
|
-
return parts.join(' > ');
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
function onMouseMove(e) {
|
|
2312
|
-
if (!pickerEnabled) return;
|
|
2313
|
-
if (!highlightOverlay) highlightOverlay = createOverlay();
|
|
2314
|
-
precisionMode = e.shiftKey;
|
|
2315
|
-
var target = findTarget(e.target, precisionMode);
|
|
2316
|
-
var overlay = document.getElementById('stashes-highlight');
|
|
2317
|
-
var tooltip = document.getElementById('stashes-tooltip');
|
|
2318
|
-
if (target) {
|
|
2319
|
-
var rect = target.getBoundingClientRect();
|
|
2320
|
-
overlay.style.display = 'block';
|
|
2321
|
-
overlay.style.top = rect.top + 'px';
|
|
2322
|
-
overlay.style.left = rect.left + 'px';
|
|
2323
|
-
overlay.style.width = rect.width + 'px';
|
|
2324
|
-
overlay.style.height = rect.height + 'px';
|
|
2325
|
-
overlay.style.borderColor = precisionMode ? '#f59e0b' : '#6366f1';
|
|
2326
|
-
tooltip.style.display = 'block';
|
|
2327
|
-
tooltip.style.top = Math.max(0, rect.top - 30) + 'px';
|
|
2328
|
-
tooltip.style.left = Math.max(0, rect.left) + 'px';
|
|
2329
|
-
tooltip.textContent = (precisionMode ? '[precise] ' : '') + describeElement(target);
|
|
2330
|
-
} else {
|
|
2331
|
-
overlay.style.display = 'none';
|
|
2332
|
-
tooltip.style.display = 'none';
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
function onClick(e) {
|
|
2337
|
-
if (!pickerEnabled) return;
|
|
2338
|
-
e.preventDefault();
|
|
2339
|
-
e.stopPropagation();
|
|
2340
|
-
var target = findTarget(e.target, e.shiftKey);
|
|
2341
|
-
if (target) {
|
|
2342
|
-
var desc = describeElement(target);
|
|
2343
|
-
var selector = generateSelector(target);
|
|
2344
|
-
var tag = target.tagName.toLowerCase();
|
|
2345
|
-
var outerSnippet = target.outerHTML.substring(0, 500);
|
|
2346
|
-
window.parent.postMessage({
|
|
2347
|
-
type: 'stashes:component_selected',
|
|
2348
|
-
component: {
|
|
2349
|
-
name: desc,
|
|
2350
|
-
filePath: 'auto-detect',
|
|
2351
|
-
domSelector: selector,
|
|
2352
|
-
htmlSnippet: outerSnippet,
|
|
2353
|
-
tag: tag
|
|
2354
|
-
}
|
|
2355
|
-
}, '*');
|
|
2356
|
-
var overlay = document.getElementById('stashes-highlight');
|
|
2357
|
-
if (overlay) {
|
|
2358
|
-
overlay.style.borderColor = '#22c55e';
|
|
2359
|
-
overlay.style.background = 'rgba(34,197,94,0.1)';
|
|
2360
|
-
setTimeout(function() {
|
|
2361
|
-
overlay.style.borderColor = '#6366f1';
|
|
2362
|
-
overlay.style.background = 'rgba(99,102,241,0.1)';
|
|
2363
|
-
}, 500);
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
window.addEventListener('message', function(e) {
|
|
2369
|
-
if (e.data && e.data.type === 'stashes:toggle_picker') {
|
|
2370
|
-
pickerEnabled = e.data.enabled;
|
|
2371
|
-
if (!pickerEnabled) {
|
|
2372
|
-
var ov = document.getElementById('stashes-highlight');
|
|
2373
|
-
var tp = document.getElementById('stashes-tooltip');
|
|
2374
|
-
if (ov) ov.style.display = 'none';
|
|
2375
|
-
if (tp) tp.style.display = 'none';
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
});
|
|
2379
|
-
|
|
2380
|
-
// Report current URL to parent for status bar display
|
|
2381
|
-
function reportUrl() {
|
|
2382
|
-
window.parent.postMessage({
|
|
2383
|
-
type: 'stashes:url_change',
|
|
2384
|
-
url: window.location.pathname + window.location.search + window.location.hash
|
|
2385
|
-
}, '*');
|
|
2386
|
-
}
|
|
2387
|
-
reportUrl();
|
|
2388
|
-
var origPush = history.pushState;
|
|
2389
|
-
history.pushState = function() {
|
|
2390
|
-
origPush.apply(this, arguments);
|
|
2391
|
-
setTimeout(reportUrl, 0);
|
|
2392
|
-
};
|
|
2393
|
-
var origReplace = history.replaceState;
|
|
2394
|
-
history.replaceState = function() {
|
|
2395
|
-
origReplace.apply(this, arguments);
|
|
2396
|
-
setTimeout(reportUrl, 0);
|
|
2397
|
-
};
|
|
2398
|
-
window.addEventListener('popstate', reportUrl);
|
|
2399
|
-
|
|
2400
|
-
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
2401
|
-
document.addEventListener('click', onClick, true);
|
|
2402
|
-
})();
|
|
2403
|
-
</script>`;
|
|
2404
|
-
if (html.includes("</body>")) {
|
|
2405
|
-
return html.replace("</body>", () => overlayScript + `
|
|
2406
|
-
</body>`);
|
|
2407
|
-
}
|
|
2408
|
-
return html + overlayScript;
|
|
2409
|
-
}
|
|
2410
2429
|
|
|
2411
2430
|
// ../server/dist/services/detector.js
|
|
2412
2431
|
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|