sapper-iq 1.4.0 → 1.4.1
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/package.json +1 -1
- package/sapper-ui.mjs +88 -25
package/package.json
CHANGED
package/sapper-ui.mjs
CHANGED
|
@@ -3126,41 +3126,98 @@ function spawnSapper(cols, rows) {
|
|
|
3126
3126
|
});
|
|
3127
3127
|
}
|
|
3128
3128
|
|
|
3129
|
+
// ─── Persistent PTY (survives browser refresh) ───────────────────
|
|
3130
|
+
// The pty process lives at module scope so it outlives any WS connection.
|
|
3131
|
+
// All output is stored in a ring buffer; new clients replay it on connect.
|
|
3132
|
+
|
|
3133
|
+
const PTY_SCROLLBACK_MAX = 512 * 1024; // 512 KB replay buffer
|
|
3134
|
+
let sharedPty = null;
|
|
3135
|
+
let ptyScrollback = ''; // raw bytes (utf-8) for replay
|
|
3136
|
+
let ptyCols = 220, ptyRows = 50; // last known size
|
|
3137
|
+
|
|
3138
|
+
function appendPtyScrollback(chunk) {
|
|
3139
|
+
ptyScrollback += chunk;
|
|
3140
|
+
if (ptyScrollback.length > PTY_SCROLLBACK_MAX) {
|
|
3141
|
+
// Drop oldest half to stay near cap without constant slicing
|
|
3142
|
+
ptyScrollback = ptyScrollback.slice(ptyScrollback.length - Math.floor(PTY_SCROLLBACK_MAX * 0.6));
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
function ensurePty(cols, rows) {
|
|
3147
|
+
if (sharedPty) return;
|
|
3148
|
+
ptyCols = cols || 220; ptyRows = rows || 50;
|
|
3149
|
+
try {
|
|
3150
|
+
sharedPty = spawnSapper(ptyCols, ptyRows);
|
|
3151
|
+
} catch (e) {
|
|
3152
|
+
console.error('[ui] spawn failed:', e.message);
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
dbg('pty pid=' + sharedPty.pid + ' ' + ptyCols + 'x' + ptyRows);
|
|
3156
|
+
sharedPty.onData((d) => {
|
|
3157
|
+
appendPtyScrollback(d);
|
|
3158
|
+
// Broadcast to every connected pty client
|
|
3159
|
+
for (const ws of ptyClients) {
|
|
3160
|
+
if (ws.readyState === ws.OPEN) {
|
|
3161
|
+
try { ws.send(Buffer.from(d, 'utf8')); } catch {}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
});
|
|
3165
|
+
sharedPty.onExit(({ exitCode, signal }) => {
|
|
3166
|
+
dbg('pty exit code=' + exitCode);
|
|
3167
|
+
sharedPty = null;
|
|
3168
|
+
// Notify all clients so they can show "exited" badge
|
|
3169
|
+
const msg = JSON.stringify({ type: 'exit', code: exitCode, signal });
|
|
3170
|
+
for (const ws of ptyClients) {
|
|
3171
|
+
if (ws.readyState === ws.OPEN) { try { ws.send(msg); } catch {} }
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const ptyClients = new Set(); // all currently-connected pty websockets
|
|
3177
|
+
|
|
3129
3178
|
wssPty.on('connection', (ws) => {
|
|
3130
3179
|
dbg('pty client connected');
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
function start(cols, rows) {
|
|
3134
|
-
if (pty) { try { pty.kill(); } catch {} }
|
|
3135
|
-
try { pty = spawnSapper(cols, rows); }
|
|
3136
|
-
catch (e) {
|
|
3137
|
-
console.error('[ui] spawn failed:', e.message);
|
|
3138
|
-
try { ws.send(Buffer.from('\x1b[31mFailed to spawn sapper: ' + e.message + '\x1b[0m\r\n', 'utf8')); } catch {}
|
|
3139
|
-
return;
|
|
3140
|
-
}
|
|
3141
|
-
dbg('pty pid=' + pty.pid + ' ' + cols + 'x' + rows);
|
|
3142
|
-
pty.onData((d) => { if (ws.readyState === ws.OPEN) ws.send(Buffer.from(d, 'utf8')); });
|
|
3143
|
-
pty.onExit(({ exitCode, signal }) => {
|
|
3144
|
-
dbg('pty exit code=' + exitCode);
|
|
3145
|
-
if (ws.readyState === ws.OPEN) { try { ws.send(JSON.stringify({ type: 'exit', code: exitCode, signal })); } catch {} }
|
|
3146
|
-
});
|
|
3147
|
-
try { ws.send(JSON.stringify({ type: 'cwd', path: workingDir })); } catch {}
|
|
3148
|
-
}
|
|
3180
|
+
ptyClients.add(ws);
|
|
3149
3181
|
|
|
3150
3182
|
ws.on('message', (raw, isBinary) => {
|
|
3151
3183
|
const str = raw.toString('utf8');
|
|
3152
3184
|
if (!isBinary && str.startsWith('{')) {
|
|
3153
3185
|
try {
|
|
3154
3186
|
const m = JSON.parse(str);
|
|
3155
|
-
if (m.type === 'init') {
|
|
3156
|
-
|
|
3157
|
-
|
|
3187
|
+
if (m.type === 'init') {
|
|
3188
|
+
// Spawn pty if not running yet
|
|
3189
|
+
ensurePty(m.cols, m.rows);
|
|
3190
|
+
// Replay scrollback so the refreshed browser sees prior output
|
|
3191
|
+
if (ptyScrollback.length > 0) {
|
|
3192
|
+
try { ws.send(Buffer.from(ptyScrollback, 'utf8')); } catch {}
|
|
3193
|
+
}
|
|
3194
|
+
// Always send current cwd
|
|
3195
|
+
try { ws.send(JSON.stringify({ type: 'cwd', path: workingDir })); } catch {}
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
if (m.type === 'resize' && sharedPty) {
|
|
3199
|
+
ptyCols = m.cols || ptyCols; ptyRows = m.rows || ptyRows;
|
|
3200
|
+
try { sharedPty.resize(ptyCols, ptyRows); } catch {}
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
if (m.type === 'restart') {
|
|
3204
|
+
// Kill current pty and start fresh; clear scrollback
|
|
3205
|
+
if (sharedPty) { try { sharedPty.kill(); } catch {} sharedPty = null; }
|
|
3206
|
+
ptyScrollback = '';
|
|
3207
|
+
ensurePty(ptyCols, ptyRows);
|
|
3208
|
+
try { ws.send(JSON.stringify({ type: 'cwd', path: workingDir })); } catch {}
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
3158
3211
|
} catch {}
|
|
3159
3212
|
}
|
|
3160
|
-
if (
|
|
3213
|
+
if (sharedPty) sharedPty.write(str);
|
|
3161
3214
|
});
|
|
3162
3215
|
|
|
3163
|
-
ws.on('close', () => {
|
|
3216
|
+
ws.on('close', () => {
|
|
3217
|
+
ptyClients.delete(ws);
|
|
3218
|
+
// Do NOT kill the pty — keep it alive for the next reconnect.
|
|
3219
|
+
dbg('pty client disconnected (' + ptyClients.size + ' remaining)');
|
|
3220
|
+
});
|
|
3164
3221
|
});
|
|
3165
3222
|
|
|
3166
3223
|
// ── FS watcher: broadcast to all /events clients ─────────────────
|
|
@@ -3437,5 +3494,11 @@ server.on('listening', () => {
|
|
|
3437
3494
|
|
|
3438
3495
|
tryListen(PORT);
|
|
3439
3496
|
|
|
3440
|
-
|
|
3441
|
-
|
|
3497
|
+
function shutdown() {
|
|
3498
|
+
console.log('\nShutting down…');
|
|
3499
|
+
try { watcher && watcher.close(); } catch {}
|
|
3500
|
+
if (sharedPty) { try { sharedPty.kill(); } catch {} }
|
|
3501
|
+
process.exit(0);
|
|
3502
|
+
}
|
|
3503
|
+
process.on('SIGINT', shutdown);
|
|
3504
|
+
process.on('SIGTERM', shutdown);
|