@unbound-app/debugger 0.4.0 → 0.7.0
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/index.mjs +46 -37
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { cli } from "cleye";
|
|
|
8
8
|
const HISTORY_PATH = join(__dirname, "..", "..", ".history.json");
|
|
9
9
|
const HISTORY_FILE = Bun.file(HISTORY_PATH);
|
|
10
10
|
let history = [];
|
|
11
|
+
/** @description Loads the persisted REPL history from disk, if present. */
|
|
11
12
|
async function loadHistory() {
|
|
12
13
|
try {
|
|
13
14
|
if (await HISTORY_FILE.exists()) history = await HISTORY_FILE.json();
|
|
@@ -15,10 +16,12 @@ async function loadHistory() {
|
|
|
15
16
|
terminal.red.bold(`Failed to load history.\n`);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
/** @description Appends an entry to the REPL history and persists it. */
|
|
18
20
|
function addHistoryItem(item) {
|
|
19
21
|
history.push(item);
|
|
20
22
|
persistHistory();
|
|
21
23
|
}
|
|
24
|
+
/** @description Writes the REPL history to disk. */
|
|
22
25
|
function persistHistory() {
|
|
23
26
|
HISTORY_FILE.write(JSON.stringify(history, null, 2));
|
|
24
27
|
}
|
|
@@ -37,6 +40,7 @@ const resultListeners = /* @__PURE__ */ new Set();
|
|
|
37
40
|
const controllerListeners = /* @__PURE__ */ new Set();
|
|
38
41
|
/** How long to wait for a device to answer an eval before failing it. */
|
|
39
42
|
const EVAL_TIMEOUT_MS = 1e4;
|
|
43
|
+
const PLUGIN_PUSH_TIMEOUT_MS = 6e4;
|
|
40
44
|
/**
|
|
41
45
|
* @description Registers the connected device, rejecting a second one so eval routing stays
|
|
42
46
|
* unambiguous.
|
|
@@ -123,7 +127,7 @@ function evaluateForController(socket, id, code) {
|
|
|
123
127
|
/**
|
|
124
128
|
* @description Forwards a controller's {@link PluginPushRequest} to the device and delivers the
|
|
125
129
|
* id-correlated {@link PluginPushResult} back over that same controller's socket. Replies with an
|
|
126
|
-
* error frame instead if no device is connected or it doesn't answer within {@link
|
|
130
|
+
* error frame instead if no device is connected or it doesn't answer within {@link PLUGIN_PUSH_TIMEOUT_MS}.
|
|
127
131
|
* @param socket The controller that issued the push.
|
|
128
132
|
* @param request The push to forward verbatim; its `id` correlates the result.
|
|
129
133
|
*/
|
|
@@ -144,9 +148,9 @@ function pushPluginForController(socket, request) {
|
|
|
144
148
|
type: "plugin-push-result",
|
|
145
149
|
id: request.id,
|
|
146
150
|
ok: false,
|
|
147
|
-
error: `Plugin push timed out after ${
|
|
151
|
+
error: `Plugin push timed out after ${PLUGIN_PUSH_TIMEOUT_MS}ms.`
|
|
148
152
|
});
|
|
149
|
-
},
|
|
153
|
+
}, PLUGIN_PUSH_TIMEOUT_MS);
|
|
150
154
|
pendingPushes.set(request.id, {
|
|
151
155
|
deliver,
|
|
152
156
|
timer,
|
|
@@ -217,29 +221,41 @@ function handleDeviceMessage$1(message) {
|
|
|
217
221
|
entry.deliver(message);
|
|
218
222
|
return;
|
|
219
223
|
}
|
|
220
|
-
if (message.type
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
+
if (message.type !== "log") return;
|
|
225
|
+
for (const socket of state$2.controllers) socket.send(JSON.stringify(message));
|
|
226
|
+
for (const listener of logListeners) listener(message);
|
|
224
227
|
}
|
|
225
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* @description Subscribes to device log lines.
|
|
230
|
+
* @param listener Called with each log line.
|
|
231
|
+
* @returns An unsubscribe function.
|
|
232
|
+
*/
|
|
226
233
|
function onLog(listener) {
|
|
227
234
|
logListeners.add(listener);
|
|
228
235
|
return () => void logListeners.delete(listener);
|
|
229
236
|
}
|
|
230
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* @description Subscribes to code a controller (e.g. the MCP) sends to evaluate.
|
|
239
|
+
* @param listener Called with each controller's code.
|
|
240
|
+
* @returns An unsubscribe function.
|
|
241
|
+
*/
|
|
231
242
|
function onControllerEval(listener) {
|
|
232
243
|
evalListeners.add(listener);
|
|
233
244
|
return () => void evalListeners.delete(listener);
|
|
234
245
|
}
|
|
235
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* @description Subscribes to the results of controller evals.
|
|
248
|
+
* @param listener Called with each controller eval's result.
|
|
249
|
+
* @returns An unsubscribe function.
|
|
250
|
+
*/
|
|
236
251
|
function onControllerResult(listener) {
|
|
237
252
|
resultListeners.add(listener);
|
|
238
253
|
return () => void resultListeners.delete(listener);
|
|
239
254
|
}
|
|
240
255
|
/**
|
|
241
|
-
* @description Subscribes to controller (MCP) connect/disconnect transitions
|
|
242
|
-
*
|
|
256
|
+
* @description Subscribes to controller (MCP) connect/disconnect transitions.
|
|
257
|
+
* @param listener Called with the transition and the resulting controller count.
|
|
258
|
+
* @returns An unsubscribe function.
|
|
243
259
|
*/
|
|
244
260
|
function onController(listener) {
|
|
245
261
|
controllerListeners.add(listener);
|
|
@@ -269,12 +285,14 @@ const state$1 = {
|
|
|
269
285
|
repl: null,
|
|
270
286
|
currentInput: ""
|
|
271
287
|
};
|
|
288
|
+
/** @description Exits the debugger on Ctrl+C. */
|
|
272
289
|
function setupReplListeners() {
|
|
273
|
-
terminal.on("key", (name
|
|
290
|
+
terminal.on("key", (name) => {
|
|
274
291
|
if (name !== "CTRL_C") return;
|
|
275
292
|
process.exit(0);
|
|
276
293
|
});
|
|
277
294
|
}
|
|
295
|
+
/** @description Draws a fresh prompt and evaluates the submitted line on the device. */
|
|
278
296
|
async function takeReplInput() {
|
|
279
297
|
abortCurrentReplInput();
|
|
280
298
|
terminal.bold.cyan("» ");
|
|
@@ -296,27 +314,17 @@ async function takeReplInput() {
|
|
|
296
314
|
});
|
|
297
315
|
await state$1.repl.promise;
|
|
298
316
|
}
|
|
317
|
+
/** @description Cancels the active prompt, optionally erasing it and the pending input. */
|
|
299
318
|
function abortCurrentReplInput({ deletePrompt = false, deleteCurrentInput = false } = {}) {
|
|
300
|
-
if (state$1.repl)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
319
|
+
if (!state$1.repl) return;
|
|
320
|
+
state$1.repl.abort();
|
|
321
|
+
state$1.repl = null;
|
|
322
|
+
if (deletePrompt) terminal.deleteLine(-1);
|
|
323
|
+
if (deleteCurrentInput) state$1.currentInput = "";
|
|
306
324
|
}
|
|
307
325
|
//#endregion
|
|
308
326
|
//#region src/lib/format-log.ts
|
|
309
327
|
/**
|
|
310
|
-
* Reformats a log line as Hermes hands it to `nativeLoggingHook`. Hermes inspects each `console.*`
|
|
311
|
-
* argument and joins them with `, ` — quoting string args (`'a', 'b', { x: 1 }`). That reads badly
|
|
312
|
-
* in the debugger terminal, so we split on the top-level commas Hermes inserted, unquote the plain
|
|
313
|
-
* string segments, and rejoin with spaces — leaving inspected objects/arrays untouched.
|
|
314
|
-
*
|
|
315
|
-
* This is best-effort: it only rewrites what parses cleanly as Hermes's join format. A segment it
|
|
316
|
-
* can't confidently classify is left exactly as received, so worst case a line is unchanged, never
|
|
317
|
-
* mangled.
|
|
318
|
-
*/
|
|
319
|
-
/**
|
|
320
328
|
* @description Cleans a Hermes-joined console line into space-separated, unquoted output.
|
|
321
329
|
* @param message The raw `nativeLoggingHook` message string.
|
|
322
330
|
* @returns The reformatted line, or the original if it doesn't look like a Hermes join.
|
|
@@ -379,9 +387,8 @@ function unquoteSegment(segment) {
|
|
|
379
387
|
//#endregion
|
|
380
388
|
//#region src/lib/banner.ts
|
|
381
389
|
/**
|
|
382
|
-
* Renders the debugger's startup banner: a rounded box
|
|
383
|
-
*
|
|
384
|
-
* on wide ones; when the terminal is too narrow for a box, it falls back to plain starred lines.
|
|
390
|
+
* Renders the debugger's startup banner: a pink rounded box sized to the terminal, capped on wide
|
|
391
|
+
* ones, falling back to plain starred lines when the terminal is too narrow for a box.
|
|
385
392
|
*/
|
|
386
393
|
/** Brand pink used for the banner frame and title. */
|
|
387
394
|
const PINK = "#ff5fbf";
|
|
@@ -449,7 +456,7 @@ function printFallback(title, lines) {
|
|
|
449
456
|
/** The usable terminal width: terminal-kit's when it's a real TTY, else stdout's, else a default. */
|
|
450
457
|
function terminalWidth() {
|
|
451
458
|
const fromKit = terminal.width;
|
|
452
|
-
if (
|
|
459
|
+
if (Number.isFinite(fromKit)) return fromKit;
|
|
453
460
|
const fromStdout = process.stdout.columns;
|
|
454
461
|
if (typeof fromStdout === "number" && fromStdout > 0) return fromStdout;
|
|
455
462
|
return 80;
|
|
@@ -493,9 +500,7 @@ const SYSTEM_COLORS = {
|
|
|
493
500
|
gray: "#9ca3af"
|
|
494
501
|
};
|
|
495
502
|
/**
|
|
496
|
-
* @description Prints a bold, glyph-prefixed banner line for a bridge lifecycle event
|
|
497
|
-
* the given hex colour. Uses terminal-kit's runtime `colorRgbHex`, which `@types/terminal-kit`
|
|
498
|
-
* doesn't declare, so the chain is reached through a narrow cast.
|
|
503
|
+
* @description Prints a bold, glyph-prefixed banner line for a bridge lifecycle event.
|
|
499
504
|
* @param color The banner colour, keyed into {@link SYSTEM_COLORS}.
|
|
500
505
|
* @param text The line to print (the glyph is prepended and a trailing newline added).
|
|
501
506
|
*/
|
|
@@ -594,6 +599,10 @@ function handleControllerMessage(ws, raw) {
|
|
|
594
599
|
}
|
|
595
600
|
if (message?.type === "plugin-push") pushPluginForController(ws, message);
|
|
596
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* @description Finds the machine's first external IPv4 address, for devices on the LAN to dial.
|
|
604
|
+
* @returns The address, or `undefined` if the machine has none.
|
|
605
|
+
*/
|
|
597
606
|
function getLocalAddress() {
|
|
598
607
|
const interfaces = os.networkInterfaces();
|
|
599
608
|
for (const ifaces of Object.values(interfaces)) {
|
|
@@ -604,6 +613,6 @@ function getLocalAddress() {
|
|
|
604
613
|
//#endregion
|
|
605
614
|
//#region src/index.ts
|
|
606
615
|
await loadHistory();
|
|
607
|
-
|
|
616
|
+
setupWebSocket();
|
|
608
617
|
//#endregion
|
|
609
618
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unbound-app/debugger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A REPL debugger for @unbound-app/client runtime instances.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mario P.",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"build": "tsdown"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@unbound-app/debugger-protocol": "0.
|
|
27
|
+
"@unbound-app/debugger-protocol": "0.7.0",
|
|
28
28
|
"cleye": "^1.3.4",
|
|
29
29
|
"terminal-kit": "^3.1.2"
|
|
30
30
|
},
|