aboutsummaryrefslogtreecommitdiff
path: root/src/js/internal/debugger.ts
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-08-16 19:40:20 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-16 19:40:20 -0700
commit0486cea35a80be97ba43f41a29ce55f0d3a8eb01 (patch)
tree2a6335ff465826b3e20959c5751c89ba793becca /src/js/internal/debugger.ts
parent2634c64aa32fec00073bd0a776e5ac67ad6aa6e5 (diff)
downloadbun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.gz
bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.zst
bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.zip
`bun --inspect` (#4158)
* Let the debugger to pause/resume the event loop * Add initial support for Debug Adapter Protocol * Add progress * Update Worker.cpp * Fix require("console") #3820 (#4073) * Fix #3820 * Add Module (#4074) * Set exports to {} in user-constructed CommonJSModuleRecords (#4076) * feat(bun/test): Implement "toSatisfy" & "toIncludeRepeated" (fwup) (#3651) * Fix merge issues * oop * make codegen * Fix build issues --------- Co-authored-by: dave caruso <me@paperdave.net> * worker tests (#4058) Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * feat(bun:test) add support for test.each() and describe.each() (#4047) * rename callback to func * update testscope to handle function arguments * works * big cleanup * works in debug, not release * fix memory issue & update tests * catch & str test * write types for each() & switch tests to ts * rm & typo * move some code around & support describe * review changes * Fix one of the astro segfaults, also fix bun init version (#4079) * 4->16 * add assertions * fix version stuff * Remove unintentional logs from #4043 * Run prettier * Update main-worker-file.js * Update SIMDUTF (#4078) * Fix constructing buffer from a UTF16 string with the Latin1 encoding. (#4086) Close: #3914 * Add support for `bun --revision` (#4027) Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local> * Updates * Update __global.zig * remove non-node node-fallbacks (#4082) * remove non-node node-fallbacks. * organize the imports * Fix test * Sync bun-polyfills branch (#4081) * bun-polyfills: initial impl. & baseline refactor * move @types/ws dep from root to /test/ * bun-types: remove ReadableStream.forEach method (this does not exist, probably added by mistake) * bun-polyfills: remove extraneous stream utils * bun-polyfills: add types syncing file * bun-polyfills: re-arrange global polyfills * bun-polyfills: fix FileBlob streams types again * bun-polyfills: sync all of @types/node * bun-polyfills: typeguard all current polyfills * bun-polyfills: fix import paths * bun-polyfills: switch to wasm impl. of farmhash * bun-polyfills: support default import of bun obj * bun-polyfills: transpiler placeholder file * bun-polyfills: loaderless import.meta polyfill * bun-polyfills: refactor import.meta polyfill * bun-polyfills: repl entrypoint & todo list index * bun-types: Add null to return type of Bun.which * bun-types: match Bun.sha with Bun.hash.SHA512_256 * bun-polyfills: new "repl" package.json script * bun-polyfills: full refactor of toplevel hashes * bun-polyfills: these are fixed * bun-types: NODE_ENV is optional * bun-polyfills: fix Bun.env types * bun-types+polyfills: fix HeapSnapshot.version type * bun-polyfills: fix some web streams type conflicts * bun-polyfills: update internal FileBlob.slice * bun-polyfills: fix subproc stdin conversions * bun-polyfills: better internal fileblob types * bun-polyfills: try to sync global performance type * bun-polyfills: working zig wasm polyfills setup * bun-polyfills: update scripts * bun-polyfills: fix wasm file location resolution * bun-polyfills: goodbye farmhash (replaced by zig) * bun-polyfills: move all Bun.hash polyfills to zig * bun-polyfills: reimpl. seeding of seeded hashes * bun-polyfills: impl. undocumented murmur32v2 * bun-polyfills: switch zighash from jsdoc to .d.ts * bun-types: partial fix of Hash types * bun-polyfills: documented Hash.murmur32v2 * bun-polyfills: misc updates * bun-polyfills: enable sourcemaps * bun-polyfills: handle empty inputs to hash funcs * bun-types: narrow down hash func types * bun-polyfills: remove unnecessary bigint casts * bun-polyfills: impl. Bun.isMainThread * bun-polyfills: impl. Bun.sleep and fix sleepSync * bun-polyfills: impl. indexOfLine * bun-polyfills: impl. Bun.peek.status * bun-types: fix hashing test --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * Add remix guide * Fix title * add util.formatWithOptions (#4090) * Add formatWithOptions * tests and tweaks * adjust --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * bun test: format description of test.each (#4092) * bun test: format description * add tests for tests * only --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * Fixes #4062 (#4106) * Fixes #4062 * Update encoding.zig * Use faster C++ impl * Update wtf-bindings.cpp * undo * Fixup --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * zig fmt * Update remix guide * fs.zig: create temp files with 0o700, not 0o007 (#4107) * Handle thundering herd of setInterval (#4109) Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * Fix memory leak in base64url (#4111) Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> * run files without extensions (#4113) * run script without extension * process stdio write fix * don't check for trailing slash, var stream * More lazily initialize these static strings * Remove assertion * wip * Add --inspect flag * Deprecate loading `node_modules.bun` * realpath * regenerate schema * More * more * Update cli.zig * Debugger JS loads * have fun * Most of the code * Its starting to work. * more progress * win some, lose some * Update dap.ts * Async Context Tracking * untested websocket * Emit Console messages * Error handling * errors * Make profiling work better * [clap] CLI arguments with optional values ignore positional params In `bun --inspect foo.js`, `foo.js` should not be the value of `--inspect`. It is a positional parameter. `--inspect=foo` * Support multiple simultaneous clients, automatically unpause on disconnect * regenerate * Update Makefile * Update WebKit * Update cli.zig * Update InternalModuleRegistry+createInternalModuleById.h * BaseURL * Update WebKit * Add web-inspector-bun * Update build.ts * formatting, mostly * Update debugger.ts * Update InternalModuleRegistryConstants.h * wrap * Update test * Update test --------- Co-authored-by: Ashcon Partovi <ashcon@partovi.net> Co-authored-by: Dylan Conway <dylan.conway567@gmail.com> Co-authored-by: dave caruso <me@paperdave.net> Co-authored-by: Tiramify (A.K. Daniel) <94789999+TiranexDev@users.noreply.github.com> Co-authored-by: Jacques <25390037+jecquas@users.noreply.github.com> Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: Ai Hoshino <ambiguous404@gmail.com> Co-authored-by: Yash Sharma <yashosharma@gmail.com> Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local> Co-authored-by: Colin McDonnell <colinmcd94@gmail.com> Co-authored-by: jhmaster <32803471+jhmaster2000@users.noreply.github.com> Co-authored-by: Adhityaa Chandrasekar <github@adtac.in> Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
Diffstat (limited to 'src/js/internal/debugger.ts')
-rw-r--r--src/js/internal/debugger.ts280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts
new file mode 100644
index 000000000..2fd875d10
--- /dev/null
+++ b/src/js/internal/debugger.ts
@@ -0,0 +1,280 @@
+import type * as BunType from "bun";
+
+// We want to avoid dealing with creating a prototype for the inspector class
+let sendFn_, disconnectFn_;
+
+var debuggerCounter = 1;
+class DebuggerWithMessageQueue {
+ debugger?: Debugger = undefined;
+ messageQueue: string[] = [];
+ count: number = debuggerCounter++;
+
+ send(msg: string) {
+ sendFn_.call(this.debugger, msg);
+ }
+
+ disconnect() {
+ disconnectFn_.call(this.debugger);
+ this.messageQueue.length = 0;
+ }
+}
+
+let defaultPort = 6499;
+
+let generatedPath: string = "";
+function generatePath() {
+ if (!generatedPath) {
+ generatedPath = "/" + Math.random().toString(36).slice(2);
+ }
+
+ return generatedPath;
+}
+
+function terminalLink(url) {
+ if (Bun.enableANSIColors) {
+ // bold + hyperlink + reset
+ return "\x1b[1m\x1b]8;;" + url + "\x1b\\" + url + "\x1b]8;;\x1b\\" + "\x1b[22m";
+ }
+
+ return url;
+}
+
+function dim(text) {
+ if (Bun.enableANSIColors) {
+ return "\x1b[2m" + text + "\x1b[22m";
+ }
+
+ return text;
+}
+
+class WebSocketListener {
+ server: BunType.Server;
+ url: string = "";
+ createInspectorConnection;
+ scriptExecutionContextId: number = 0;
+ activeConnections: Set<BunType.ServerWebSocket<DebuggerWithMessageQueue>> = new Set();
+
+ constructor(scriptExecutionContextId: number = 0, url: string, createInspectorConnection) {
+ this.scriptExecutionContextId = scriptExecutionContextId;
+ this.createInspectorConnection = createInspectorConnection;
+ this.server = this.start(url);
+ }
+
+ start(url: string): BunType.Server {
+ let defaultHostname = "localhost";
+ let usingDefaultPort = false;
+ if (/^[0-9]*$/.test(url)) {
+ url = "ws://" + defaultHostname + ":" + url + generatePath();
+ } else if (!url || url.startsWith("/")) {
+ url = "ws://" + defaultHostname + ":" + defaultPort + generatePath();
+ usingDefaultPort = true;
+ } else if (url.includes(":") && !url.includes("://")) {
+ try {
+ const insertSlash = !url.includes("/");
+ url = new URL("ws://" + url).href;
+ if (insertSlash) {
+ url += generatePath().slice(1);
+ }
+ } catch (e) {
+ console.error("[Inspector]", "Failed to parse url", '"' + url + '"');
+ process.exit(1);
+ }
+ }
+
+ try {
+ var { hostname, port, pathname } = new URL(url);
+ this.url = pathname.toLowerCase();
+ } catch (e) {
+ console.error("[Inspector]", "Failed to parse url", '"' + url + '"');
+ process.exit(1);
+ }
+
+ const serveOptions: BunType.WebSocketServeOptions<DebuggerWithMessageQueue> = {
+ hostname,
+ development: false,
+
+ // ts-ignore
+ reusePort: false,
+
+ websocket: {
+ idleTimeout: 0,
+ open: socket => {
+ var connection = new DebuggerWithMessageQueue();
+ socket.data = connection;
+ this.activeConnections.add(socket);
+ connection.debugger = this.createInspectorConnection(this.scriptExecutionContextId, (...msgs: string[]) => {
+ if (socket.readyState > 1) {
+ connection.disconnect();
+ return;
+ }
+
+ if (connection.messageQueue.length > 0) {
+ connection.messageQueue.push(...msgs);
+ return;
+ }
+
+ for (let i = 0; i < msgs.length; i++) {
+ if (!socket.sendText(msgs[i])) {
+ if (socket.readyState < 2) {
+ connection.messageQueue.push(...msgs.slice(i));
+ }
+ return;
+ }
+ }
+ });
+
+ console.log(
+ "[Inspector]",
+ "Connection #" + connection.count + " opened",
+ "(" +
+ new Intl.DateTimeFormat(undefined, {
+ "timeStyle": "long",
+ "dateStyle": "short",
+ }).format(new Date()) +
+ ")",
+ );
+ },
+ drain: socket => {
+ const queue = socket.data.messageQueue;
+ for (let i = 0; i < queue.length; i++) {
+ if (!socket.sendText(queue[i])) {
+ socket.data.messageQueue = queue.slice(i);
+ return;
+ }
+ }
+ queue.length = 0;
+ },
+ message: (socket, message) => {
+ if (typeof message !== "string") {
+ console.warn("[Inspector]", "Received non-string message");
+ return;
+ }
+ socket.data.send(message as string);
+ },
+ close: socket => {
+ socket.data.disconnect();
+ console.log(
+ "[Inspector]",
+ "Connection #" + socket.data.count + " closed",
+ "(" +
+ new Intl.DateTimeFormat(undefined, {
+ "timeStyle": "long",
+ "dateStyle": "short",
+ }).format(new Date()) +
+ ")",
+ );
+ this.activeConnections.delete(socket);
+ },
+ },
+ fetch: (req, server) => {
+ let { pathname } = new URL(req.url);
+ pathname = pathname.toLowerCase();
+
+ if (pathname === "/json/version") {
+ return Response.json({
+ "Browser": navigator.userAgent,
+ "WebKit-Version": process.versions.webkit,
+ "Bun-Version": Bun.version,
+ "Bun-Revision": Bun.revision,
+ });
+ }
+
+ if (pathname === this.url) {
+ if (server.upgrade(req)) {
+ return new Response();
+ }
+
+ return new Response("WebSocket expected", {
+ status: 400,
+ });
+ }
+
+ return new Response("Not found", {
+ status: 404,
+ });
+ },
+ };
+
+ if (port === "") {
+ port = defaultPort + "";
+ }
+
+ let portNumber = Number(port);
+ var server, lastError;
+
+ if (usingDefaultPort) {
+ for (let tries = 0; tries < 10 && !server; tries++) {
+ try {
+ lastError = undefined;
+ server = Bun.serve<DebuggerWithMessageQueue>({
+ ...serveOptions,
+ port: portNumber++,
+ });
+ } catch (e) {
+ lastError = e;
+ }
+ }
+ } else {
+ try {
+ server = Bun.serve<DebuggerWithMessageQueue>({
+ ...serveOptions,
+ port: portNumber,
+ });
+ } catch (e) {
+ lastError = e;
+ }
+ }
+
+ if (!server) {
+ console.error("[Inspector]", "Failed to start server");
+ if (lastError) console.error(lastError);
+ process.exit(1);
+ }
+
+ let textToWrite = "";
+ function writeToConsole(text) {
+ textToWrite += text;
+ }
+ function flushToConsole() {
+ console.write(textToWrite);
+ }
+
+ // yellow foreground
+ writeToConsole(dim(`------------------ Bun Inspector ------------------` + "\n"));
+ // reset background
+ writeToConsole("\x1b[49m");
+
+ writeToConsole(
+ "Listening at:\n " +
+ `ws://${hostname}:${server.port}${this.url}` +
+ "\n\n" +
+ "Inspect in browser:\n " +
+ terminalLink(new URL(`https://debug.bun.sh#${server.hostname}:${server.port}${this.url}`).href) +
+ "\n",
+ );
+ writeToConsole(dim(`------------------ Bun Inspector ------------------` + "\n"));
+ flushToConsole();
+
+ return server;
+ }
+}
+
+interface Debugger {
+ send(msg: string): void;
+ disconnect(): void;
+}
+
+var listener: WebSocketListener;
+
+export default function start(debuggerId, hostOrPort, createInspectorConnection, sendFn, disconnectFn) {
+ try {
+ sendFn_ = sendFn;
+ disconnectFn_ = disconnectFn;
+ globalThis.listener = listener ||= new WebSocketListener(debuggerId, hostOrPort, createInspectorConnection);
+ } catch (e) {
+ console.error("Bun Inspector threw an exception\n", e);
+ process.exit(1);
+ }
+
+ return `http://${listener.server.hostname}:${listener.server.port}${listener.url}`;
+}