diff options
author | 2023-08-29 23:44:39 -0700 | |
---|---|---|
committer | 2023-08-29 23:44:39 -0700 | |
commit | f2553d24543d72a777ba60213473332809866cb2 (patch) | |
tree | 61faac4292dbdf1b4d0543e33d3f5d2c792825c5 /src | |
parent | c028b206bce3f9b5c3cba7899c6bf34856efe43f (diff) | |
download | bun-f2553d24543d72a777ba60213473332809866cb2.tar.gz bun-f2553d24543d72a777ba60213473332809866cb2.tar.zst bun-f2553d24543d72a777ba60213473332809866cb2.zip |
More support for DAP (#4380)
* Fix reconnect with --watch
* Support setVariable
* Support setExpression
* Support watch variables
* Conditional and hit breakpoints
* Support exceptionInfo
* Support goto and gotoTargets
* Support completions
* Support both a URL and UNIX inspector at the same time
* Fix url
* WIP, add timeouts to figure out issue
* Fix messages being dropped from debugger.ts
* Progress
* Fix breakpoints and ref-event-loop
* More fixes
* Fix exit
* Make hovers better
* Fix --hot
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/BunDebugger.cpp | 11 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 47 | ||||
-rw-r--r-- | src/cli.zig | 9 | ||||
-rw-r--r-- | src/js/internal/debugger.ts | 580 | ||||
-rw-r--r-- | src/js/out/InternalModuleRegistryConstants.h | 6 |
5 files changed, 337 insertions, 316 deletions
diff --git a/src/bun.js/bindings/BunDebugger.cpp b/src/bun.js/bindings/BunDebugger.cpp index 440a5125b..f4a5f535a 100644 --- a/src/bun.js/bindings/BunDebugger.cpp +++ b/src/bun.js/bindings/BunDebugger.cpp @@ -109,7 +109,7 @@ public: globalObject->setInspectable(true); auto& inspector = globalObject->inspectorDebuggable(); inspector.setInspectable(true); - globalObject->inspectorController().connectFrontend(*connection, true, waitingForConnection); + globalObject->inspectorController().connectFrontend(*connection, true, false); // waitingForConnection Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger()); if (debugger) { @@ -482,7 +482,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateConnection, (JSGlobalObject * globalObj return JSValue::encode(JSBunInspectorConnection::create(vm, JSBunInspectorConnection::createStructure(vm, globalObject, globalObject->objectPrototype()), connection)); } -extern "C" BunString Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGlobalObject, ScriptExecutionContextIdentifier scriptId, BunString* portOrPathString) +extern "C" void Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGlobalObject, ScriptExecutionContextIdentifier scriptId, BunString* portOrPathString) { if (!debuggerScriptExecutionContext) debuggerScriptExecutionContext = debuggerGlobalObject->scriptExecutionContext(); @@ -498,12 +498,7 @@ extern "C" BunString Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGloba arguments.append(JSFunction::create(vm, debuggerGlobalObject, 1, String("send"_s), jsFunctionSend, ImplementationVisibility::Public)); arguments.append(JSFunction::create(vm, debuggerGlobalObject, 0, String("disconnect"_s), jsFunctionDisconnect, ImplementationVisibility::Public)); - JSValue serverURLValue = JSC::call(debuggerGlobalObject, debuggerDefaultFn, arguments, "Bun__initJSDebuggerThread - debuggerDefaultFn"_s); - - if (serverURLValue.isUndefinedOrNull()) - return BunStringEmpty; - - return Bun::toStringRef(debuggerGlobalObject, serverURLValue); + JSC::call(debuggerGlobalObject, debuggerDefaultFn, arguments, "Bun__initJSDebuggerThread - debuggerDefaultFn"_s); } enum class AsyncCallTypeUint8 : uint8_t { diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 90a3bbc66..32cf04346 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -502,6 +502,7 @@ pub const VirtualMachine = struct { worker: ?*JSC.WebWorker = null, debugger: ?Debugger = null, + has_started_debugger: bool = false, pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void; @@ -794,7 +795,8 @@ pub const VirtualMachine = struct { pub var has_created_debugger: bool = false; pub const Debugger = struct { - path_or_port: []const u8 = "", + path_or_port: ?[]const u8 = null, + unix: []const u8 = "", script_execution_context_id: u32 = 0, next_debugger_id: u64 = 1, poll_ref: JSC.PollRef = .{}, @@ -805,8 +807,7 @@ pub const VirtualMachine = struct { extern "C" fn Bun__createJSDebugger(*JSC.JSGlobalObject) u32; extern "C" fn Bun__ensureDebugger(u32, bool) void; - extern "C" fn Bun__startJSDebuggerThread(*JSC.JSGlobalObject, u32, *bun.String) bun.String; - var has_started_debugger_thread: bool = false; + extern "C" fn Bun__startJSDebuggerThread(*JSC.JSGlobalObject, u32, *bun.String) void; var futex_atomic: std.atomic.Atomic(u32) = undefined; pub fn create(this: *VirtualMachine, globalObject: *JSGlobalObject) !void { @@ -816,8 +817,8 @@ pub const VirtualMachine = struct { has_created_debugger = true; var debugger = &this.debugger.?; debugger.script_execution_context_id = Bun__createJSDebugger(globalObject); - if (!has_started_debugger_thread) { - has_started_debugger_thread = true; + if (!this.has_started_debugger) { + this.has_started_debugger = true; futex_atomic = std.atomic.Atomic(u32).init(0); var thread = try std.Thread.spawn(.{}, startJSDebuggerThread, .{this}); thread.detach(); @@ -865,8 +866,6 @@ pub const VirtualMachine = struct { vm.global.vm().holdAPILock(other_vm, @ptrCast(&start)); } - pub export var Bun__debugger_server_url: bun.String = undefined; - pub export fn Debugger__didConnect() void { var this = VirtualMachine.get(); std.debug.assert(this.debugger.?.wait_for_connection); @@ -878,9 +877,17 @@ pub const VirtualMachine = struct { JSC.markBinding(@src()); var this = VirtualMachine.get(); - var str = bun.String.create(other_vm.debugger.?.path_or_port); - Bun__debugger_server_url = Bun__startJSDebuggerThread(this.global, other_vm.debugger.?.script_execution_context_id, &str); - Bun__debugger_server_url.toThreadSafe(); + var debugger = other_vm.debugger.?; + + if (debugger.unix.len > 0) { + var url = bun.String.create(debugger.unix); + Bun__startJSDebuggerThread(this.global, debugger.script_execution_context_id, &url); + } + + if (debugger.path_or_port) |path_or_port| { + var url = bun.String.create(path_or_port); + Bun__startJSDebuggerThread(this.global, debugger.script_execution_context_id, &url); + } this.global.handleRejectedPromises(); @@ -1189,13 +1196,27 @@ pub const VirtualMachine = struct { } fn configureDebugger(this: *VirtualMachine, debugger: bun.CLI.Command.Debugger) void { + var unix = bun.getenvZ("BUN_INSPECT") orelse ""; + var set_breakpoint_on_first_line = unix.len > 0 and strings.endsWith(unix, "?break=1"); + var wait_for_connection = set_breakpoint_on_first_line or (unix.len > 0 and strings.endsWith(unix, "?wait=1")); + switch (debugger) { - .unspecified => {}, + .unspecified => { + if (unix.len > 0) { + this.debugger = Debugger{ + .path_or_port = null, + .unix = unix, + .wait_for_connection = wait_for_connection, + .set_breakpoint_on_first_line = set_breakpoint_on_first_line, + }; + } + }, .enable => { this.debugger = Debugger{ .path_or_port = debugger.enable.path_or_port, - .wait_for_connection = debugger.enable.wait_for_connection, - .set_breakpoint_on_first_line = debugger.enable.set_breakpoint_on_first_line, + .unix = unix, + .wait_for_connection = wait_for_connection or debugger.enable.wait_for_connection, + .set_breakpoint_on_first_line = set_breakpoint_on_first_line or debugger.enable.set_breakpoint_on_first_line, }; }, } diff --git a/src/cli.zig b/src/cli.zig index 68ae7e4c2..c68297baa 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -543,15 +543,6 @@ pub const Arguments = struct { .wait_for_connection = true, .set_breakpoint_on_first_line = true, } }; - } else if (bun.getenvZ("BUN_INSPECT")) |inspect_value| { - ctx.runtime_options.debugger = if (inspect_value.len == 0 or inspect_value[0] == '0') - Command.Debugger{ .unspecified = {} } - else - Command.Debugger{ .enable = .{ - .path_or_port = inspect_value[1..], - .wait_for_connection = inspect_value[0] == '1' or inspect_value[0] == '2', - .set_breakpoint_on_first_line = inspect_value[0] == '2', - } }; } } diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 2e76b2c7c..cd6f8f516 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -1,309 +1,322 @@ -import type * as BunType from "bun"; - -// We want to avoid dealing with creating a prototype for the inspector class -let sendFn_, disconnectFn_; -const colors = Bun.enableANSIColors && process.env.NO_COLOR !== "1"; - -var debuggerCounter = 1; -class DebuggerWithMessageQueue { - debugger?: Debugger = undefined; - messageQueue: string[] = []; - count: number = debuggerCounter++; +import type { Server as WebSocketServer, WebSocketHandler, ServerWebSocket, SocketHandler, Socket } from "bun"; + +export default function ( + executionContextId: string, + url: string, + createBackend: ( + executionContextId: string, + refEventLoop: boolean, + receive: (...messages: string[]) => void, + ) => unknown, + send: (message: string) => void, + close: () => void, +): void { + let debug: Debugger | undefined; + try { + debug = new Debugger(executionContextId, url, createBackend, send, close); + } catch (error) { + exit("Failed to start inspector:\n", error); + } - send(msg: string) { - sendFn_.call(this.debugger, msg); + const { protocol, href, host, pathname } = debug.url; + if (!protocol.includes("unix")) { + console.log(dim("--------------------- Bun Inspector ---------------------"), reset()); + console.log(`Listening:\n ${dim(href)}`); + if (protocol.includes("ws")) { + console.log(`Inspect in browser:\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}`); + } + console.log(dim("--------------------- Bun Inspector ---------------------"), reset()); } - disconnect() { - disconnectFn_.call(this.debugger); - this.messageQueue.length = 0; + const unix = process.env["BUN_INSPECT_NOTIFY"]; + if (unix) { + const { protocol, pathname } = parseUrl(unix); + if (protocol === "unix:") { + notify(pathname); + } } } -let defaultPort = 6499; +class Debugger { + #url: URL; + #createBackend: (refEventLoop: boolean, receive: (...messages: string[]) => void) => Writer; + + constructor( + executionContextId: string, + url: string, + createBackend: ( + executionContextId: string, + refEventLoop: boolean, + receive: (...messages: string[]) => void, + ) => unknown, + send: (message: string) => void, + close: () => void, + ) { + this.#url = parseUrl(url); + this.#createBackend = (refEventLoop, receive) => { + const backend = createBackend(executionContextId, refEventLoop, receive); + return { + write: message => { + send.call(backend, message); + return true; + }, + close: () => close.call(backend), + }; + }; + this.#listen(); + } -let generatedPath: string = ""; -function generatePath() { - if (!generatedPath) { - generatedPath = "/" + Math.random().toString(36).slice(2); + get url(): URL { + return this.#url; } - return generatedPath; -} + #listen(): void { + const { protocol, hostname, port, pathname } = this.#url; + + if (protocol === "ws:" || protocol === "ws+tcp:") { + const server = Bun.serve({ + hostname, + port, + fetch: this.#fetch.bind(this), + websocket: this.#websocket, + }); + this.#url.hostname = server.hostname; + this.#url.port = `${server.port}`; + return; + } -function terminalLink(url) { - if (colors) { - // bold + hyperlink + reset - return "\x1b[1m\x1b]8;;" + url + "\x1b\\" + url + "\x1b]8;;\x1b\\" + "\x1b[22m"; - } + if (protocol === "ws+unix:") { + Bun.serve({ + unix: pathname, + fetch: this.#fetch.bind(this), + websocket: this.#websocket, + }); + return; + } - return url; -} + throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'unix:')`); + } -function dim(text) { - if (colors) { - return "\x1b[2m" + text + "\x1b[22m"; + get #websocket(): WebSocketHandler<Connection> { + return { + idleTimeout: 0, + closeOnBackpressureLimit: false, + open: ws => this.#open(ws, webSocketWriter(ws)), + message: (ws, message) => { + if (typeof message === "string") { + this.#message(ws, message); + } else { + this.#error(ws, new Error(`Unexpected binary message: ${message.toString()}`)); + } + }, + drain: ws => this.#drain(ws), + close: ws => this.#close(ws), + }; } - return text; -} + #fetch(request: Request, server: WebSocketServer): Response | undefined { + const { method, url, headers } = request; + const { pathname } = new URL(url); -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); - } + if (method !== "GET") { + return new Response(null, { + status: 405, // Method Not Allowed + }); + } - start(url: string): BunType.Server { - let defaultHostname = "localhost"; - let usingDefaultPort = false; - let isUnix = false; - - if (url.startsWith("ws+unix://")) { - isUnix = true; - url = url.slice(10); - } else 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); - } + switch (pathname) { + case "/json/version": + return Response.json(versionInfo()); + case "/json": + case "/json/list": + // TODO? } - if (!isUnix) { - 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); - } + if (!this.#url.protocol.includes("unix") && this.#url.pathname !== pathname) { + return new Response(null, { + status: 404, // Not Found + }); } - const serveOptions: BunType.WebSocketServeOptions<DebuggerWithMessageQueue> = { - ...(isUnix ? { unix: url } : { hostname }), - development: false, - - // @ts-ignore - reusePort: false, - - websocket: { - idleTimeout: 0, - open: socket => { - var connection = new DebuggerWithMessageQueue(); - // @ts-expect-error - const shouldRefEventLoop = !!socket.data?.shouldRefEventLoop; - - socket.data = connection; - this.activeConnections.add(socket); - connection.debugger = this.createInspectorConnection( - this.scriptExecutionContextId, - shouldRefEventLoop, - (...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; - } - } - }, - ); - - if (!isUnix) { - 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(); - if (!isUnix) { - console.log( - "[Inspector]", - "Connection #" + socket.data.count + " closed", - "(" + - new Intl.DateTimeFormat(undefined, { - "timeStyle": "long", - "dateStyle": "short", - }).format(new Date()) + - ")", - ); - } - this.activeConnections.delete(socket); + const data: Connection = { + refEventLoop: headers.get("Ref-Event-Loop") === "0", + }; + + if (!server.upgrade(request, { data })) { + return new Response(null, { + status: 426, // Upgrade Required + headers: { + "Upgrade": "websocket", }, - }, - 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 (!this.url || pathname === this.url) { - const refHeader = req.headers.get("Ref-Event-Loop"); - if ( - server.upgrade(req, { - data: { - shouldRefEventLoop: !!refHeader && refHeader !== "0", - }, - }) - ) { - return new Response(); - } + get #socket(): SocketHandler<Connection> { + return { + open: socket => this.#open(socket, socketWriter(socket)), + data: (socket, message) => this.#message(socket, message.toString()), + drain: socket => this.#drain(socket), + close: socket => this.#close(socket), + error: (socket, error) => this.#error(socket, error), + connectError: (_, error) => exit("Failed to start inspector:\n", error), + }; + } - return new Response("WebSocket expected", { - status: 400, - }); - } + #open(connection: ConnectionOwner, writer: Writer): void { + const { data } = connection; + const { refEventLoop } = data; - return new Response("Not found", { - status: 404, - }); - }, - }; + const client = bufferedWriter(writer); + const backend = this.#createBackend(refEventLoop, (...messages: string[]) => { + for (const message of messages) { + client.write(message); + } + }); - if (port === "") { - port = defaultPort + ""; - } + data.client = client; + data.backend = backend; + } - 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++, - }); - if (isUnix) { - notify(); - } - } catch (e) { - lastError = e; - } + #message(connection: ConnectionOwner, message: string): void { + const { data } = connection; + const { backend } = data; + backend?.write(message); + } + + #drain(connection: ConnectionOwner): void { + const { data } = connection; + const { client } = data; + client?.drain?.(); + } + + #close(connection: ConnectionOwner): void { + const { data } = connection; + const { backend } = data; + backend?.close(); + } + + #error(connection: ConnectionOwner, error: Error): void { + const { data } = connection; + const { backend } = data; + console.error(error); + backend?.close(); + } +} + +function versionInfo(): unknown { + return { + "Protocol-Version": "1.3", + "Browser": "Bun", + // @ts-ignore: Missing types for `navigator` + "User-Agent": navigator.userAgent, + "WebKit-Version": process.versions.webkit, + "Bun-Version": Bun.version, + "Bun-Revision": Bun.revision, + }; +} + +function webSocketWriter(ws: ServerWebSocket<unknown>): Writer { + return { + write: message => !!ws.sendText(message), + close: () => ws.close(), + }; +} + +function socketWriter(socket: Socket<unknown>): Writer { + return { + write: message => !!socket.write(message), + close: () => socket.end(), + }; +} + +function bufferedWriter(writer: Writer): Writer { + let draining = false; + let pendingMessages: string[] = []; + + return { + write: message => { + if (draining || !writer.write(message)) { + pendingMessages.push(message); } - } else { + return true; + }, + drain: () => { + draining = true; try { - server = Bun.serve<DebuggerWithMessageQueue>({ - ...serveOptions, - port: portNumber, - }); - if (isUnix) { - notify(); + for (let i = 0; i < pendingMessages.length; i++) { + if (!writer.write(pendingMessages[i])) { + pendingMessages = pendingMessages.slice(i); + return; + } } - } catch (e) { - lastError = e; + } finally { + draining = false; } - } + }, + close: () => { + writer.close(); + pendingMessages.length = 0; + }, + }; +} - if (!server) { - console.error("[Inspector]", "Failed to start server"); - if (lastError) console.error(lastError); - process.exit(1); - } +const defaultHostname = "localhost"; +const defaultPort = 6499; - let textToWrite = ""; - function writeToConsole(text) { - textToWrite += text; - } - function flushToConsole() { - console.write(textToWrite); +function parseUrl(url: string): URL { + try { + if (!url) { + return new URL(randomId(), `ws://${defaultHostname}:${defaultPort}/`); + } else if (url.startsWith("/")) { + return new URL(url, `ws://${defaultHostname}:${defaultPort}/`); + } else if (/^[a-z+]+:\/\//i.test(url)) { + return new URL(url); + } else if (/^\d+$/.test(url)) { + return new URL(randomId(), `ws://${defaultHostname}:${url}/`); + } else if (!url.includes("/") && url.includes(":")) { + return new URL(randomId(), `ws://${url}/`); + } else if (!url.includes(":")) { + const [hostname, pathname] = url.split("/", 2); + return new URL(`ws://${hostname}:${defaultPort}/${pathname}`); + } else { + return new URL(randomId(), `ws://${url}`); } + } catch { + throw new TypeError(`Invalid hostname or URL: '${url}'`); + } +} - if (!this.url) { - return server; - } +function randomId() { + return Math.random().toString(36).slice(2); +} - // yellow foreground - writeToConsole(dim(`------------------ Bun Inspector ------------------` + "\n")); - if (colors) { - // reset background - writeToConsole("\x1b[49m"); - } +const { enableANSIColors } = Bun; - 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; +function dim(string: string): string { + if (enableANSIColors) { + return `\x1b[2m${string}\x1b[22m`; } + return string; } -function notify(): void { - const unix = process.env["BUN_INSPECT_NOTIFY"]; - if (!unix || !unix.startsWith("unix://")) { - return; +function link(url: string): string { + if (enableANSIColors) { + return `\x1b[1m\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\\x1b[22m`; + } + return url; +} + +function reset(): string { + if (enableANSIColors) { + return "\x1b[49m"; } + return ""; +} + +function notify(unix: string): void { Bun.connect({ - unix: unix.slice(7), + unix, socket: { open: socket => { socket.end("1"); @@ -311,26 +324,27 @@ function notify(): void { data: () => {}, // required or it errors }, }).finally(() => { - // Do nothing + // Best-effort }); } -interface Debugger { - send(msg: string): void; - disconnect(): void; +function exit(...args: unknown[]): never { + console.error(...args); + process.exit(1); } -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}`; -} +type ConnectionOwner = { + data: Connection; +}; + +type Connection = { + refEventLoop: boolean; + client?: Writer; + backend?: Writer; +}; + +type Writer = { + write: (message: string) => boolean; + drain?: () => void; + close: () => void; +}; diff --git a/src/js/out/InternalModuleRegistryConstants.h b/src/js/out/InternalModuleRegistryConstants.h index 31d51538f..66a8f169d 100644 --- a/src/js/out/InternalModuleRegistryConstants.h +++ b/src/js/out/InternalModuleRegistryConstants.h @@ -14,7 +14,7 @@ static constexpr ASCIILiteral BunSqliteCode = "(function (){\"use strict\";// sr // // -static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar generatePath = function() {\n if (!generatedPath)\n generatedPath = \"/\" + Math.random().toString(36).slice(2);\n return generatedPath;\n}, terminalLink = function(url) {\n if (colors)\n return \"\\x1B[1m\\x1B]8;;\" + url + \"\\x1B\\\\\" + url + \"\\x1B]8;;\\x1B\\\\\" + \"\\x1B[22m\";\n return url;\n}, dim = function(text) {\n if (colors)\n return \"\\x1B[2m\" + text + \"\\x1B[22m\";\n return text;\n}, notify = function() {\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (!unix || !unix.startsWith(\"unix://\"))\n return;\n Bun.connect({\n unix: unix.slice(7),\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, $, sendFn_, disconnectFn_, colors = Bun.enableANSIColors && process.env.NO_COLOR !== \"1\", debuggerCounter = 1;\n\nclass DebuggerWithMessageQueue {\n debugger = void 0;\n messageQueue = [];\n count = debuggerCounter++;\n send(msg) {\n sendFn_.call(this.debugger, msg);\n }\n disconnect() {\n disconnectFn_.call(this.debugger), this.messageQueue.length = 0;\n }\n}\nvar defaultPort = 6499, generatedPath = \"\";\n\nclass WebSocketListener {\n server;\n url = \"\";\n createInspectorConnection;\n scriptExecutionContextId = 0;\n activeConnections = new Set;\n constructor(scriptExecutionContextId = 0, url, createInspectorConnection) {\n this.scriptExecutionContextId = scriptExecutionContextId, this.createInspectorConnection = createInspectorConnection, this.server = this.start(url);\n }\n start(url) {\n let defaultHostname = \"localhost\", usingDefaultPort = !1, isUnix = !1;\n if (url.startsWith(\"ws+unix://\"))\n isUnix = !0, url = url.slice(10);\n else if (/^[0-9]*$/.test(url))\n url = \"ws://\" + defaultHostname + \":\" + url + generatePath();\n else if (!url || url.startsWith(\"/\"))\n url = \"ws://\" + defaultHostname + \":\" + defaultPort + generatePath(), usingDefaultPort = !0;\n else if (url.includes(\":\") && !url.includes(\"://\"))\n try {\n const insertSlash = !url.includes(\"/\");\n if (url = new URL(\"ws://\" + url).href, insertSlash)\n url += generatePath().slice(1);\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n if (!isUnix)\n try {\n var { hostname, port, pathname } = new URL(url);\n this.url = pathname.toLowerCase();\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n const serveOptions = {\n ...isUnix \? { unix: url } : { hostname },\n development: !1,\n reusePort: !1,\n websocket: {\n idleTimeout: 0,\n open: (socket) => {\n var connection = new DebuggerWithMessageQueue;\n const shouldRefEventLoop = !!socket.data\?.shouldRefEventLoop;\n if (socket.data = connection, this.activeConnections.add(socket), connection.debugger = this.createInspectorConnection(this.scriptExecutionContextId, shouldRefEventLoop, (...msgs) => {\n if (socket.readyState > 1) {\n connection.disconnect();\n return;\n }\n if (connection.messageQueue.length > 0) {\n connection.messageQueue.push(...msgs);\n return;\n }\n for (let i = 0;i < msgs.length; i++)\n if (!socket.sendText(msgs[i])) {\n if (socket.readyState < 2)\n connection.messageQueue.push(...msgs.slice(i));\n return;\n }\n }), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + connection.count + \" opened\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n },\n drain: (socket) => {\n const queue = socket.data.messageQueue;\n for (let i = 0;i < queue.length; i++)\n if (!socket.sendText(queue[i])) {\n socket.data.messageQueue = queue.slice(i);\n return;\n }\n queue.length = 0;\n },\n message: (socket, message) => {\n if (typeof message !== \"string\") {\n console.warn(\"[Inspector]\", \"Received non-string message\");\n return;\n }\n socket.data.send(message);\n },\n close: (socket) => {\n if (socket.data.disconnect(), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + socket.data.count + \" closed\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n this.activeConnections.delete(socket);\n }\n },\n fetch: (req, server2) => {\n let { pathname: pathname2 } = new URL(req.url);\n if (pathname2 = pathname2.toLowerCase(), pathname2 === \"/json/version\")\n return Response.json({\n Browser: navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n });\n if (!this.url || pathname2 === this.url) {\n const refHeader = req.headers.get(\"Ref-Event-Loop\");\n if (server2.upgrade(req, {\n data: {\n shouldRefEventLoop: !!refHeader && refHeader !== \"0\"\n }\n }))\n return new Response;\n return new Response(\"WebSocket expected\", {\n status: 400\n });\n }\n return new Response(\"Not found\", {\n status: 404\n });\n }\n };\n if (port === \"\")\n port = defaultPort + \"\";\n let portNumber = Number(port);\n var server, lastError;\n if (usingDefaultPort)\n for (let tries = 0;tries < 10 && !server; tries++)\n try {\n if (lastError = void 0, server = Bun.serve({\n ...serveOptions,\n port: portNumber++\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n else\n try {\n if (server = Bun.serve({\n ...serveOptions,\n port: portNumber\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n if (!server) {\n if (console.error(\"[Inspector]\", \"Failed to start server\"), lastError)\n console.error(lastError);\n process.exit(1);\n }\n let textToWrite = \"\";\n function writeToConsole(text) {\n textToWrite += text;\n }\n function flushToConsole() {\n console.write(textToWrite);\n }\n if (!this.url)\n return server;\n if (writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), colors)\n writeToConsole(\"\\x1B[49m\");\n return writeToConsole(\"Listening at:\\n \" + `ws://${hostname}:${server.port}${this.url}` + \"\\n\\nInspect in browser:\\n \" + terminalLink(new URL(`https://debug.bun.sh#${server.hostname}:${server.port}${this.url}`).href) + \"\\n\"), writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), flushToConsole(), server;\n }\n}\nvar listener;\n$ = function start(debuggerId, hostOrPort, createInspectorConnection, sendFn, disconnectFn) {\n try {\n sendFn_ = sendFn, disconnectFn_ = disconnectFn, globalThis.listener = listener ||= new WebSocketListener(debuggerId, hostOrPort, createInspectorConnection);\n } catch (e) {\n console.error(\"Bun Inspector threw an exception\\n\", e), process.exit(1);\n }\n return `http://${listener.server.hostname}:${listener.server.port}${listener.url}`;\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar versionInfo = function() {\n return {\n \"Protocol-Version\": \"1.3\",\n Browser: \"Bun\",\n \"User-Agent\": navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n };\n}, webSocketWriter = function(ws) {\n return {\n write: (message) => !!ws.sendText(message),\n close: () => ws.close()\n };\n}, socketWriter = function(socket) {\n return {\n write: (message) => !!socket.write(message),\n close: () => socket.end()\n };\n}, bufferedWriter = function(writer) {\n let draining = !1, pendingMessages = [];\n return {\n write: (message) => {\n if (draining || !writer.write(message))\n pendingMessages.push(message);\n return !0;\n },\n drain: () => {\n draining = !0;\n try {\n for (let i = 0;i < pendingMessages.length; i++)\n if (!writer.write(pendingMessages[i])) {\n pendingMessages = pendingMessages.slice(i);\n return;\n }\n } finally {\n draining = !1;\n }\n },\n close: () => {\n writer.close(), pendingMessages.length = 0;\n }\n };\n}, parseUrl = function(url) {\n try {\n if (!url)\n return new URL(randomId(), `ws://${defaultHostname}:${defaultPort}/`);\n else if (url.startsWith(\"/\"))\n return new URL(url, `ws://${defaultHostname}:${defaultPort}/`);\n else if (/^[a-z+]+:\\/\\//i.test(url))\n return new URL(url);\n else if (/^\\d+$/.test(url))\n return new URL(randomId(), `ws://${defaultHostname}:${url}/`);\n else if (!url.includes(\"/\") && url.includes(\":\"))\n return new URL(randomId(), `ws://${url}/`);\n else if (!url.includes(\":\")) {\n const [hostname, pathname] = url.split(\"/\", 2);\n return new URL(`ws://${hostname}:${defaultPort}/${pathname}`);\n } else\n return new URL(randomId(), `ws://${url}`);\n } catch {\n @throwTypeError(`Invalid hostname or URL: '${url}'`);\n }\n}, randomId = function() {\n return Math.random().toString(36).slice(2);\n}, dim = function(string) {\n if (enableANSIColors)\n return `\\x1B[2m${string}\\x1B[22m`;\n return string;\n}, link = function(url) {\n if (enableANSIColors)\n return `\\x1B[1m\\x1B]8;;${url}\\x1B\\\\${url}\\x1B]8;;\\x1B\\\\\\x1B[22m`;\n return url;\n}, reset = function() {\n if (enableANSIColors)\n return \"\\x1B[49m\";\n return \"\";\n}, notify = function(unix) {\n Bun.connect({\n unix,\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, exit = function(...args) {\n console.error(...args), process.exit(1);\n}, $;\n$ = function(executionContextId, url, createBackend, send, close) {\n let debug;\n try {\n debug = new Debugger(executionContextId, url, createBackend, send, close);\n } catch (error) {\n exit(\"Failed to start inspector:\\n\", error);\n }\n const { protocol, href, host, pathname } = debug.url;\n if (!protocol.includes(\"unix\")) {\n if (console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset()), console.log(`Listening:\\n ${dim(href)}`), protocol.includes(\"ws\"))\n console.log(`Inspect in browser:\\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}`);\n console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset());\n }\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (unix) {\n const { protocol: protocol2, pathname: pathname2 } = parseUrl(unix);\n if (protocol2 === \"unix:\")\n notify(pathname2);\n }\n};\n\nclass Debugger {\n #url;\n #createBackend;\n constructor(executionContextId, url, createBackend, send, close) {\n this.#url = parseUrl(url), this.#createBackend = (refEventLoop, receive) => {\n const backend = createBackend(executionContextId, refEventLoop, receive);\n return {\n write: (message) => {\n return send.call(backend, message), !0;\n },\n close: () => close.call(backend)\n };\n }, this.#listen();\n }\n get url() {\n return this.#url;\n }\n #listen() {\n const { protocol, hostname, port, pathname } = this.#url;\n if (protocol === \"ws:\" || protocol === \"ws+tcp:\") {\n const server = Bun.serve({\n hostname,\n port,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n this.#url.hostname = server.hostname, this.#url.port = `${server.port}`;\n return;\n }\n if (protocol === \"ws+unix:\") {\n Bun.serve({\n unix: pathname,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n return;\n }\n @throwTypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'unix:')`);\n }\n get #websocket() {\n return {\n idleTimeout: 0,\n closeOnBackpressureLimit: !1,\n open: (ws) => this.#open(ws, webSocketWriter(ws)),\n message: (ws, message) => {\n if (typeof message === \"string\")\n this.#message(ws, message);\n else\n this.#error(ws, new Error(`Unexpected binary message: ${message.toString()}`));\n },\n drain: (ws) => this.#drain(ws),\n close: (ws) => this.#close(ws)\n };\n }\n #fetch(request, server) {\n const { method, url, headers } = request, { pathname } = new URL(url);\n if (method !== \"GET\")\n return new Response(null, {\n status: 405\n });\n switch (pathname) {\n case \"/json/version\":\n return Response.json(versionInfo());\n case \"/json\":\n case \"/json/list\":\n }\n if (!this.#url.protocol.includes(\"unix\") && this.#url.pathname !== pathname)\n return new Response(null, {\n status: 404\n });\n const data = {\n refEventLoop: headers.get(\"Ref-Event-Loop\") === \"0\"\n };\n if (!server.upgrade(request, { data }))\n return new Response(null, {\n status: 426,\n headers: {\n Upgrade: \"websocket\"\n }\n });\n }\n get #socket() {\n return {\n open: (socket) => this.#open(socket, socketWriter(socket)),\n data: (socket, message) => this.#message(socket, message.toString()),\n drain: (socket) => this.#drain(socket),\n close: (socket) => this.#close(socket),\n error: (socket, error) => this.#error(socket, error),\n connectError: (_, error) => exit(\"Failed to start inspector:\\n\", error)\n };\n }\n #open(connection, writer) {\n const { data } = connection, { refEventLoop } = data, client = bufferedWriter(writer), backend = this.#createBackend(refEventLoop, (...messages) => {\n for (let message of messages)\n client.write(message);\n });\n data.client = client, data.backend = backend;\n }\n #message(connection, message) {\n const { data } = connection, { backend } = data;\n backend\?.write(message);\n }\n #drain(connection) {\n const { data } = connection, { client } = data;\n client\?.drain\?.();\n }\n #close(connection) {\n const { data } = connection, { backend } = data;\n backend\?.close();\n }\n #error(connection, error) {\n const { data } = connection, { backend } = data;\n console.error(error), backend\?.close();\n }\n}\nvar defaultHostname = \"localhost\", defaultPort = 6499, { enableANSIColors } = Bun;\nreturn $})\n"_s; // // @@ -247,7 +247,7 @@ static constexpr ASCIILiteral BunSqliteCode = "(function (){\"use strict\";// sr // // -static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar generatePath = function() {\n if (!generatedPath)\n generatedPath = \"/\" + Math.random().toString(36).slice(2);\n return generatedPath;\n}, terminalLink = function(url) {\n if (colors)\n return \"\\x1B[1m\\x1B]8;;\" + url + \"\\x1B\\\\\" + url + \"\\x1B]8;;\\x1B\\\\\" + \"\\x1B[22m\";\n return url;\n}, dim = function(text) {\n if (colors)\n return \"\\x1B[2m\" + text + \"\\x1B[22m\";\n return text;\n}, notify = function() {\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (!unix || !unix.startsWith(\"unix://\"))\n return;\n Bun.connect({\n unix: unix.slice(7),\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, $, sendFn_, disconnectFn_, colors = Bun.enableANSIColors && process.env.NO_COLOR !== \"1\", debuggerCounter = 1;\n\nclass DebuggerWithMessageQueue {\n debugger = void 0;\n messageQueue = [];\n count = debuggerCounter++;\n send(msg) {\n sendFn_.call(this.debugger, msg);\n }\n disconnect() {\n disconnectFn_.call(this.debugger), this.messageQueue.length = 0;\n }\n}\nvar defaultPort = 6499, generatedPath = \"\";\n\nclass WebSocketListener {\n server;\n url = \"\";\n createInspectorConnection;\n scriptExecutionContextId = 0;\n activeConnections = new Set;\n constructor(scriptExecutionContextId = 0, url, createInspectorConnection) {\n this.scriptExecutionContextId = scriptExecutionContextId, this.createInspectorConnection = createInspectorConnection, this.server = this.start(url);\n }\n start(url) {\n let defaultHostname = \"localhost\", usingDefaultPort = !1, isUnix = !1;\n if (url.startsWith(\"ws+unix://\"))\n isUnix = !0, url = url.slice(10);\n else if (/^[0-9]*$/.test(url))\n url = \"ws://\" + defaultHostname + \":\" + url + generatePath();\n else if (!url || url.startsWith(\"/\"))\n url = \"ws://\" + defaultHostname + \":\" + defaultPort + generatePath(), usingDefaultPort = !0;\n else if (url.includes(\":\") && !url.includes(\"://\"))\n try {\n const insertSlash = !url.includes(\"/\");\n if (url = new URL(\"ws://\" + url).href, insertSlash)\n url += generatePath().slice(1);\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n if (!isUnix)\n try {\n var { hostname, port, pathname } = new URL(url);\n this.url = pathname.toLowerCase();\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n const serveOptions = {\n ...isUnix \? { unix: url } : { hostname },\n development: !1,\n reusePort: !1,\n websocket: {\n idleTimeout: 0,\n open: (socket) => {\n var connection = new DebuggerWithMessageQueue;\n const shouldRefEventLoop = !!socket.data\?.shouldRefEventLoop;\n if (socket.data = connection, this.activeConnections.add(socket), connection.debugger = this.createInspectorConnection(this.scriptExecutionContextId, shouldRefEventLoop, (...msgs) => {\n if (socket.readyState > 1) {\n connection.disconnect();\n return;\n }\n if (connection.messageQueue.length > 0) {\n connection.messageQueue.push(...msgs);\n return;\n }\n for (let i = 0;i < msgs.length; i++)\n if (!socket.sendText(msgs[i])) {\n if (socket.readyState < 2)\n connection.messageQueue.push(...msgs.slice(i));\n return;\n }\n }), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + connection.count + \" opened\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n },\n drain: (socket) => {\n const queue = socket.data.messageQueue;\n for (let i = 0;i < queue.length; i++)\n if (!socket.sendText(queue[i])) {\n socket.data.messageQueue = queue.slice(i);\n return;\n }\n queue.length = 0;\n },\n message: (socket, message) => {\n if (typeof message !== \"string\") {\n console.warn(\"[Inspector]\", \"Received non-string message\");\n return;\n }\n socket.data.send(message);\n },\n close: (socket) => {\n if (socket.data.disconnect(), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + socket.data.count + \" closed\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n this.activeConnections.delete(socket);\n }\n },\n fetch: (req, server2) => {\n let { pathname: pathname2 } = new URL(req.url);\n if (pathname2 = pathname2.toLowerCase(), pathname2 === \"/json/version\")\n return Response.json({\n Browser: navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n });\n if (!this.url || pathname2 === this.url) {\n const refHeader = req.headers.get(\"Ref-Event-Loop\");\n if (server2.upgrade(req, {\n data: {\n shouldRefEventLoop: !!refHeader && refHeader !== \"0\"\n }\n }))\n return new Response;\n return new Response(\"WebSocket expected\", {\n status: 400\n });\n }\n return new Response(\"Not found\", {\n status: 404\n });\n }\n };\n if (port === \"\")\n port = defaultPort + \"\";\n let portNumber = Number(port);\n var server, lastError;\n if (usingDefaultPort)\n for (let tries = 0;tries < 10 && !server; tries++)\n try {\n if (lastError = void 0, server = Bun.serve({\n ...serveOptions,\n port: portNumber++\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n else\n try {\n if (server = Bun.serve({\n ...serveOptions,\n port: portNumber\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n if (!server) {\n if (console.error(\"[Inspector]\", \"Failed to start server\"), lastError)\n console.error(lastError);\n process.exit(1);\n }\n let textToWrite = \"\";\n function writeToConsole(text) {\n textToWrite += text;\n }\n function flushToConsole() {\n console.write(textToWrite);\n }\n if (!this.url)\n return server;\n if (writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), colors)\n writeToConsole(\"\\x1B[49m\");\n return writeToConsole(\"Listening at:\\n \" + `ws://${hostname}:${server.port}${this.url}` + \"\\n\\nInspect in browser:\\n \" + terminalLink(new URL(`https://debug.bun.sh#${server.hostname}:${server.port}${this.url}`).href) + \"\\n\"), writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), flushToConsole(), server;\n }\n}\nvar listener;\n$ = function start(debuggerId, hostOrPort, createInspectorConnection, sendFn, disconnectFn) {\n try {\n sendFn_ = sendFn, disconnectFn_ = disconnectFn, globalThis.listener = listener ||= new WebSocketListener(debuggerId, hostOrPort, createInspectorConnection);\n } catch (e) {\n console.error(\"Bun Inspector threw an exception\\n\", e), process.exit(1);\n }\n return `http://${listener.server.hostname}:${listener.server.port}${listener.url}`;\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar versionInfo = function() {\n return {\n \"Protocol-Version\": \"1.3\",\n Browser: \"Bun\",\n \"User-Agent\": navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n };\n}, webSocketWriter = function(ws) {\n return {\n write: (message) => !!ws.sendText(message),\n close: () => ws.close()\n };\n}, socketWriter = function(socket) {\n return {\n write: (message) => !!socket.write(message),\n close: () => socket.end()\n };\n}, bufferedWriter = function(writer) {\n let draining = !1, pendingMessages = [];\n return {\n write: (message) => {\n if (draining || !writer.write(message))\n pendingMessages.push(message);\n return !0;\n },\n drain: () => {\n draining = !0;\n try {\n for (let i = 0;i < pendingMessages.length; i++)\n if (!writer.write(pendingMessages[i])) {\n pendingMessages = pendingMessages.slice(i);\n return;\n }\n } finally {\n draining = !1;\n }\n },\n close: () => {\n writer.close(), pendingMessages.length = 0;\n }\n };\n}, parseUrl = function(url) {\n try {\n if (!url)\n return new URL(randomId(), `ws://${defaultHostname}:${defaultPort}/`);\n else if (url.startsWith(\"/\"))\n return new URL(url, `ws://${defaultHostname}:${defaultPort}/`);\n else if (/^[a-z+]+:\\/\\//i.test(url))\n return new URL(url);\n else if (/^\\d+$/.test(url))\n return new URL(randomId(), `ws://${defaultHostname}:${url}/`);\n else if (!url.includes(\"/\") && url.includes(\":\"))\n return new URL(randomId(), `ws://${url}/`);\n else if (!url.includes(\":\")) {\n const [hostname, pathname] = url.split(\"/\", 2);\n return new URL(`ws://${hostname}:${defaultPort}/${pathname}`);\n } else\n return new URL(randomId(), `ws://${url}`);\n } catch {\n @throwTypeError(`Invalid hostname or URL: '${url}'`);\n }\n}, randomId = function() {\n return Math.random().toString(36).slice(2);\n}, dim = function(string) {\n if (enableANSIColors)\n return `\\x1B[2m${string}\\x1B[22m`;\n return string;\n}, link = function(url) {\n if (enableANSIColors)\n return `\\x1B[1m\\x1B]8;;${url}\\x1B\\\\${url}\\x1B]8;;\\x1B\\\\\\x1B[22m`;\n return url;\n}, reset = function() {\n if (enableANSIColors)\n return \"\\x1B[49m\";\n return \"\";\n}, notify = function(unix) {\n Bun.connect({\n unix,\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, exit = function(...args) {\n console.error(...args), process.exit(1);\n}, $;\n$ = function(executionContextId, url, createBackend, send, close) {\n let debug;\n try {\n debug = new Debugger(executionContextId, url, createBackend, send, close);\n } catch (error) {\n exit(\"Failed to start inspector:\\n\", error);\n }\n const { protocol, href, host, pathname } = debug.url;\n if (!protocol.includes(\"unix\")) {\n if (console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset()), console.log(`Listening:\\n ${dim(href)}`), protocol.includes(\"ws\"))\n console.log(`Inspect in browser:\\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}`);\n console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset());\n }\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (unix) {\n const { protocol: protocol2, pathname: pathname2 } = parseUrl(unix);\n if (protocol2 === \"unix:\")\n notify(pathname2);\n }\n};\n\nclass Debugger {\n #url;\n #createBackend;\n constructor(executionContextId, url, createBackend, send, close) {\n this.#url = parseUrl(url), this.#createBackend = (refEventLoop, receive) => {\n const backend = createBackend(executionContextId, refEventLoop, receive);\n return {\n write: (message) => {\n return send.call(backend, message), !0;\n },\n close: () => close.call(backend)\n };\n }, this.#listen();\n }\n get url() {\n return this.#url;\n }\n #listen() {\n const { protocol, hostname, port, pathname } = this.#url;\n if (protocol === \"ws:\" || protocol === \"ws+tcp:\") {\n const server = Bun.serve({\n hostname,\n port,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n this.#url.hostname = server.hostname, this.#url.port = `${server.port}`;\n return;\n }\n if (protocol === \"ws+unix:\") {\n Bun.serve({\n unix: pathname,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n return;\n }\n @throwTypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'unix:')`);\n }\n get #websocket() {\n return {\n idleTimeout: 0,\n closeOnBackpressureLimit: !1,\n open: (ws) => this.#open(ws, webSocketWriter(ws)),\n message: (ws, message) => {\n if (typeof message === \"string\")\n this.#message(ws, message);\n else\n this.#error(ws, new Error(`Unexpected binary message: ${message.toString()}`));\n },\n drain: (ws) => this.#drain(ws),\n close: (ws) => this.#close(ws)\n };\n }\n #fetch(request, server) {\n const { method, url, headers } = request, { pathname } = new URL(url);\n if (method !== \"GET\")\n return new Response(null, {\n status: 405\n });\n switch (pathname) {\n case \"/json/version\":\n return Response.json(versionInfo());\n case \"/json\":\n case \"/json/list\":\n }\n if (!this.#url.protocol.includes(\"unix\") && this.#url.pathname !== pathname)\n return new Response(null, {\n status: 404\n });\n const data = {\n refEventLoop: headers.get(\"Ref-Event-Loop\") === \"0\"\n };\n if (!server.upgrade(request, { data }))\n return new Response(null, {\n status: 426,\n headers: {\n Upgrade: \"websocket\"\n }\n });\n }\n get #socket() {\n return {\n open: (socket) => this.#open(socket, socketWriter(socket)),\n data: (socket, message) => this.#message(socket, message.toString()),\n drain: (socket) => this.#drain(socket),\n close: (socket) => this.#close(socket),\n error: (socket, error) => this.#error(socket, error),\n connectError: (_, error) => exit(\"Failed to start inspector:\\n\", error)\n };\n }\n #open(connection, writer) {\n const { data } = connection, { refEventLoop } = data, client = bufferedWriter(writer), backend = this.#createBackend(refEventLoop, (...messages) => {\n for (let message of messages)\n client.write(message);\n });\n data.client = client, data.backend = backend;\n }\n #message(connection, message) {\n const { data } = connection, { backend } = data;\n backend\?.write(message);\n }\n #drain(connection) {\n const { data } = connection, { client } = data;\n client\?.drain\?.();\n }\n #close(connection) {\n const { data } = connection, { backend } = data;\n backend\?.close();\n }\n #error(connection, error) {\n const { data } = connection, { backend } = data;\n console.error(error), backend\?.close();\n }\n}\nvar defaultHostname = \"localhost\", defaultPort = 6499, { enableANSIColors } = Bun;\nreturn $})\n"_s; // // @@ -481,7 +481,7 @@ static constexpr ASCIILiteral BunSqliteCode = "(function (){\"use strict\";// sr // // -static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar generatePath = function() {\n if (!generatedPath)\n generatedPath = \"/\" + Math.random().toString(36).slice(2);\n return generatedPath;\n}, terminalLink = function(url) {\n if (colors)\n return \"\\x1B[1m\\x1B]8;;\" + url + \"\\x1B\\\\\" + url + \"\\x1B]8;;\\x1B\\\\\" + \"\\x1B[22m\";\n return url;\n}, dim = function(text) {\n if (colors)\n return \"\\x1B[2m\" + text + \"\\x1B[22m\";\n return text;\n}, notify = function() {\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (!unix || !unix.startsWith(\"unix://\"))\n return;\n Bun.connect({\n unix: unix.slice(7),\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, $, sendFn_, disconnectFn_, colors = Bun.enableANSIColors && process.env.NO_COLOR !== \"1\", debuggerCounter = 1;\n\nclass DebuggerWithMessageQueue {\n debugger = void 0;\n messageQueue = [];\n count = debuggerCounter++;\n send(msg) {\n sendFn_.call(this.debugger, msg);\n }\n disconnect() {\n disconnectFn_.call(this.debugger), this.messageQueue.length = 0;\n }\n}\nvar defaultPort = 6499, generatedPath = \"\";\n\nclass WebSocketListener {\n server;\n url = \"\";\n createInspectorConnection;\n scriptExecutionContextId = 0;\n activeConnections = new Set;\n constructor(scriptExecutionContextId = 0, url, createInspectorConnection) {\n this.scriptExecutionContextId = scriptExecutionContextId, this.createInspectorConnection = createInspectorConnection, this.server = this.start(url);\n }\n start(url) {\n let defaultHostname = \"localhost\", usingDefaultPort = !1, isUnix = !1;\n if (url.startsWith(\"ws+unix://\"))\n isUnix = !0, url = url.slice(10);\n else if (/^[0-9]*$/.test(url))\n url = \"ws://\" + defaultHostname + \":\" + url + generatePath();\n else if (!url || url.startsWith(\"/\"))\n url = \"ws://\" + defaultHostname + \":\" + defaultPort + generatePath(), usingDefaultPort = !0;\n else if (url.includes(\":\") && !url.includes(\"://\"))\n try {\n const insertSlash = !url.includes(\"/\");\n if (url = new URL(\"ws://\" + url).href, insertSlash)\n url += generatePath().slice(1);\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n if (!isUnix)\n try {\n var { hostname, port, pathname } = new URL(url);\n this.url = pathname.toLowerCase();\n } catch (e) {\n console.error(\"[Inspector]\", \"Failed to parse url\", '\"' + url + '\"'), process.exit(1);\n }\n const serveOptions = {\n ...isUnix \? { unix: url } : { hostname },\n development: !1,\n reusePort: !1,\n websocket: {\n idleTimeout: 0,\n open: (socket) => {\n var connection = new DebuggerWithMessageQueue;\n const shouldRefEventLoop = !!socket.data\?.shouldRefEventLoop;\n if (socket.data = connection, this.activeConnections.add(socket), connection.debugger = this.createInspectorConnection(this.scriptExecutionContextId, shouldRefEventLoop, (...msgs) => {\n if (socket.readyState > 1) {\n connection.disconnect();\n return;\n }\n if (connection.messageQueue.length > 0) {\n connection.messageQueue.push(...msgs);\n return;\n }\n for (let i = 0;i < msgs.length; i++)\n if (!socket.sendText(msgs[i])) {\n if (socket.readyState < 2)\n connection.messageQueue.push(...msgs.slice(i));\n return;\n }\n }), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + connection.count + \" opened\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n },\n drain: (socket) => {\n const queue = socket.data.messageQueue;\n for (let i = 0;i < queue.length; i++)\n if (!socket.sendText(queue[i])) {\n socket.data.messageQueue = queue.slice(i);\n return;\n }\n queue.length = 0;\n },\n message: (socket, message) => {\n if (typeof message !== \"string\") {\n console.warn(\"[Inspector]\", \"Received non-string message\");\n return;\n }\n socket.data.send(message);\n },\n close: (socket) => {\n if (socket.data.disconnect(), !isUnix)\n console.log(\"[Inspector]\", \"Connection #\" + socket.data.count + \" closed\", \"(\" + new Intl.DateTimeFormat(void 0, {\n timeStyle: \"long\",\n dateStyle: \"short\"\n }).format(new Date) + \")\");\n this.activeConnections.delete(socket);\n }\n },\n fetch: (req, server2) => {\n let { pathname: pathname2 } = new URL(req.url);\n if (pathname2 = pathname2.toLowerCase(), pathname2 === \"/json/version\")\n return Response.json({\n Browser: navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n });\n if (!this.url || pathname2 === this.url) {\n const refHeader = req.headers.get(\"Ref-Event-Loop\");\n if (server2.upgrade(req, {\n data: {\n shouldRefEventLoop: !!refHeader && refHeader !== \"0\"\n }\n }))\n return new Response;\n return new Response(\"WebSocket expected\", {\n status: 400\n });\n }\n return new Response(\"Not found\", {\n status: 404\n });\n }\n };\n if (port === \"\")\n port = defaultPort + \"\";\n let portNumber = Number(port);\n var server, lastError;\n if (usingDefaultPort)\n for (let tries = 0;tries < 10 && !server; tries++)\n try {\n if (lastError = void 0, server = Bun.serve({\n ...serveOptions,\n port: portNumber++\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n else\n try {\n if (server = Bun.serve({\n ...serveOptions,\n port: portNumber\n }), isUnix)\n notify();\n } catch (e) {\n lastError = e;\n }\n if (!server) {\n if (console.error(\"[Inspector]\", \"Failed to start server\"), lastError)\n console.error(lastError);\n process.exit(1);\n }\n let textToWrite = \"\";\n function writeToConsole(text) {\n textToWrite += text;\n }\n function flushToConsole() {\n console.write(textToWrite);\n }\n if (!this.url)\n return server;\n if (writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), colors)\n writeToConsole(\"\\x1B[49m\");\n return writeToConsole(\"Listening at:\\n \" + `ws://${hostname}:${server.port}${this.url}` + \"\\n\\nInspect in browser:\\n \" + terminalLink(new URL(`https://debug.bun.sh#${server.hostname}:${server.port}${this.url}`).href) + \"\\n\"), writeToConsole(dim(\"------------------ Bun Inspector ------------------\\n\")), flushToConsole(), server;\n }\n}\nvar listener;\n$ = function start(debuggerId, hostOrPort, createInspectorConnection, sendFn, disconnectFn) {\n try {\n sendFn_ = sendFn, disconnectFn_ = disconnectFn, globalThis.listener = listener ||= new WebSocketListener(debuggerId, hostOrPort, createInspectorConnection);\n } catch (e) {\n console.error(\"Bun Inspector threw an exception\\n\", e), process.exit(1);\n }\n return `http://${listener.server.hostname}:${listener.server.port}${listener.url}`;\n};\nreturn $})\n"_s; +static constexpr ASCIILiteral InternalDebuggerCode = "(function (){\"use strict\";// src/js/out/tmp/internal/debugger.ts\nvar versionInfo = function() {\n return {\n \"Protocol-Version\": \"1.3\",\n Browser: \"Bun\",\n \"User-Agent\": navigator.userAgent,\n \"WebKit-Version\": process.versions.webkit,\n \"Bun-Version\": Bun.version,\n \"Bun-Revision\": Bun.revision\n };\n}, webSocketWriter = function(ws) {\n return {\n write: (message) => !!ws.sendText(message),\n close: () => ws.close()\n };\n}, socketWriter = function(socket) {\n return {\n write: (message) => !!socket.write(message),\n close: () => socket.end()\n };\n}, bufferedWriter = function(writer) {\n let draining = !1, pendingMessages = [];\n return {\n write: (message) => {\n if (draining || !writer.write(message))\n pendingMessages.push(message);\n return !0;\n },\n drain: () => {\n draining = !0;\n try {\n for (let i = 0;i < pendingMessages.length; i++)\n if (!writer.write(pendingMessages[i])) {\n pendingMessages = pendingMessages.slice(i);\n return;\n }\n } finally {\n draining = !1;\n }\n },\n close: () => {\n writer.close(), pendingMessages.length = 0;\n }\n };\n}, parseUrl = function(url) {\n try {\n if (!url)\n return new URL(randomId(), `ws://${defaultHostname}:${defaultPort}/`);\n else if (url.startsWith(\"/\"))\n return new URL(url, `ws://${defaultHostname}:${defaultPort}/`);\n else if (/^[a-z+]+:\\/\\//i.test(url))\n return new URL(url);\n else if (/^\\d+$/.test(url))\n return new URL(randomId(), `ws://${defaultHostname}:${url}/`);\n else if (!url.includes(\"/\") && url.includes(\":\"))\n return new URL(randomId(), `ws://${url}/`);\n else if (!url.includes(\":\")) {\n const [hostname, pathname] = url.split(\"/\", 2);\n return new URL(`ws://${hostname}:${defaultPort}/${pathname}`);\n } else\n return new URL(randomId(), `ws://${url}`);\n } catch {\n @throwTypeError(`Invalid hostname or URL: '${url}'`);\n }\n}, randomId = function() {\n return Math.random().toString(36).slice(2);\n}, dim = function(string) {\n if (enableANSIColors)\n return `\\x1B[2m${string}\\x1B[22m`;\n return string;\n}, link = function(url) {\n if (enableANSIColors)\n return `\\x1B[1m\\x1B]8;;${url}\\x1B\\\\${url}\\x1B]8;;\\x1B\\\\\\x1B[22m`;\n return url;\n}, reset = function() {\n if (enableANSIColors)\n return \"\\x1B[49m\";\n return \"\";\n}, notify = function(unix) {\n Bun.connect({\n unix,\n socket: {\n open: (socket) => {\n socket.end(\"1\");\n },\n data: () => {\n }\n }\n }).finally(() => {\n });\n}, exit = function(...args) {\n console.error(...args), process.exit(1);\n}, $;\n$ = function(executionContextId, url, createBackend, send, close) {\n let debug;\n try {\n debug = new Debugger(executionContextId, url, createBackend, send, close);\n } catch (error) {\n exit(\"Failed to start inspector:\\n\", error);\n }\n const { protocol, href, host, pathname } = debug.url;\n if (!protocol.includes(\"unix\")) {\n if (console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset()), console.log(`Listening:\\n ${dim(href)}`), protocol.includes(\"ws\"))\n console.log(`Inspect in browser:\\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}`);\n console.log(dim(\"--------------------- Bun Inspector ---------------------\"), reset());\n }\n const unix = process.env.BUN_INSPECT_NOTIFY;\n if (unix) {\n const { protocol: protocol2, pathname: pathname2 } = parseUrl(unix);\n if (protocol2 === \"unix:\")\n notify(pathname2);\n }\n};\n\nclass Debugger {\n #url;\n #createBackend;\n constructor(executionContextId, url, createBackend, send, close) {\n this.#url = parseUrl(url), this.#createBackend = (refEventLoop, receive) => {\n const backend = createBackend(executionContextId, refEventLoop, receive);\n return {\n write: (message) => {\n return send.call(backend, message), !0;\n },\n close: () => close.call(backend)\n };\n }, this.#listen();\n }\n get url() {\n return this.#url;\n }\n #listen() {\n const { protocol, hostname, port, pathname } = this.#url;\n if (protocol === \"ws:\" || protocol === \"ws+tcp:\") {\n const server = Bun.serve({\n hostname,\n port,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n this.#url.hostname = server.hostname, this.#url.port = `${server.port}`;\n return;\n }\n if (protocol === \"ws+unix:\") {\n Bun.serve({\n unix: pathname,\n fetch: this.#fetch.bind(this),\n websocket: this.#websocket\n });\n return;\n }\n @throwTypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'unix:')`);\n }\n get #websocket() {\n return {\n idleTimeout: 0,\n closeOnBackpressureLimit: !1,\n open: (ws) => this.#open(ws, webSocketWriter(ws)),\n message: (ws, message) => {\n if (typeof message === \"string\")\n this.#message(ws, message);\n else\n this.#error(ws, new Error(`Unexpected binary message: ${message.toString()}`));\n },\n drain: (ws) => this.#drain(ws),\n close: (ws) => this.#close(ws)\n };\n }\n #fetch(request, server) {\n const { method, url, headers } = request, { pathname } = new URL(url);\n if (method !== \"GET\")\n return new Response(null, {\n status: 405\n });\n switch (pathname) {\n case \"/json/version\":\n return Response.json(versionInfo());\n case \"/json\":\n case \"/json/list\":\n }\n if (!this.#url.protocol.includes(\"unix\") && this.#url.pathname !== pathname)\n return new Response(null, {\n status: 404\n });\n const data = {\n refEventLoop: headers.get(\"Ref-Event-Loop\") === \"0\"\n };\n if (!server.upgrade(request, { data }))\n return new Response(null, {\n status: 426,\n headers: {\n Upgrade: \"websocket\"\n }\n });\n }\n get #socket() {\n return {\n open: (socket) => this.#open(socket, socketWriter(socket)),\n data: (socket, message) => this.#message(socket, message.toString()),\n drain: (socket) => this.#drain(socket),\n close: (socket) => this.#close(socket),\n error: (socket, error) => this.#error(socket, error),\n connectError: (_, error) => exit(\"Failed to start inspector:\\n\", error)\n };\n }\n #open(connection, writer) {\n const { data } = connection, { refEventLoop } = data, client = bufferedWriter(writer), backend = this.#createBackend(refEventLoop, (...messages) => {\n for (let message of messages)\n client.write(message);\n });\n data.client = client, data.backend = backend;\n }\n #message(connection, message) {\n const { data } = connection, { backend } = data;\n backend\?.write(message);\n }\n #drain(connection) {\n const { data } = connection, { client } = data;\n client\?.drain\?.();\n }\n #close(connection) {\n const { data } = connection, { backend } = data;\n backend\?.close();\n }\n #error(connection, error) {\n const { data } = connection, { backend } = data;\n console.error(error), backend\?.close();\n }\n}\nvar defaultHostname = \"localhost\", defaultPort = 6499, { enableANSIColors } = Bun;\nreturn $})\n"_s; // // |