From 49090cf06758103ea2476edcf30a8b400c1b3155 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Tue, 17 Sep 2024 19:48:32 -0700 Subject: [PATCH 01/18] debugger using websockets --- .../src/debugger/adapter.ts | 147 ++++++------------ .../src/debugger/signal.ts | 50 +++--- packages/bun-vscode/scripts/build.mjs | 2 +- packages/bun-vscode/src/features/debug.ts | 10 +- src/js/internal/debugger.ts | 74 +++------ 5 files changed, 97 insertions(+), 186 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index fca2d9677fdf0f..93817257b53e06 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -2,12 +2,19 @@ import type { InspectorEventMap } from "../../../bun-inspector-protocol/src/insp import type { JSC } from "../../../bun-inspector-protocol/src/protocol"; import type { DAP } from "../protocol"; // @ts-ignore -import type { ChildProcess } from "node:child_process"; -import { spawn } from "node:child_process"; +import { spawn, ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { WebSocketInspector, remoteObjectToString } from "../../../bun-inspector-protocol/index"; -import { UnixSignal, randomUnixPath } from "./signal"; +import { WebSocketSignal } from "./signal"; import { Location, SourceMap } from "./sourcemap"; +import { createServer, AddressInfo } from "node:net"; + +export function getAvailablePort(): number { + const server = createServer().listen(0); + const port = (server.address() as AddressInfo).port; + server.close(); + return port; +} const capabilities: DAP.Capabilities = { supportsConfigurationDoneRequest: true, @@ -215,6 +222,7 @@ export class DebugAdapter extends EventEmitter implements #variables: Map; #initialized?: InitializeRequest; #options?: DebuggerOptions; + #signal?: WebSocketSignal; constructor(url?: string | URL) { super(); @@ -466,10 +474,6 @@ export class DebugAdapter extends EventEmitter implements throw new Error("No program specified. Did you set the 'program' property in your launch.json?"); } - if (!isJavaScript(program)) { - throw new Error("Program must be a JavaScript or TypeScript file."); - } - const processArgs = [...runtimeArgs, program, ...args]; if (isTestJavaScript(program) && !runtimeArgs.includes("test")) { @@ -480,108 +484,59 @@ export class DebugAdapter extends EventEmitter implements processArgs.unshift(watchMode === "hot" ? "--hot" : "--watch"); } - const processEnv = strictEnv - ? { - ...env, - } - : { - ...process.env, - ...env, - }; - - const url = `ws+unix://${randomUnixPath()}`; - const signal = new UnixSignal(); + const processEnv: Record = strictEnv ? { ...env } : { ...process.env, ...env }; - signal.on("Signal.received", () => { - this.#attach({ url }); - }); + // Create WebSocketSignal + const signalUrl = `ws://localhost:${getAvailablePort()}`; + this.#signal = new WebSocketSignal(signalUrl); - this.once("Adapter.terminated", () => { - signal.close(); - }); + // Set BUN_INSPECT_NOTIFY to signal's URL + processEnv["BUN_INSPECT_NOTIFY"] = this.#signal.url; - const query = stopOnEntry ? "break=1" : "wait=1"; - processEnv["BUN_INSPECT"] = `${url}?${query}`; - processEnv["BUN_INSPECT_NOTIFY"] = signal.url; + // Set BUN_INSPECT to the debugger's inspector URL with ?wait=1 + processEnv["BUN_INSPECT"] = `${this.#inspector.url}?wait=1`; - // This is probably not correct, but it's the best we can do for now. - processEnv["FORCE_COLOR"] = "1"; - processEnv["BUN_QUIET_DEBUG_LOGS"] = "1"; - processEnv["BUN_DEBUG_QUIET_LOGS"] = "1"; - - const started = await this.#spawn({ - command: runtime, - args: processArgs, - env: processEnv, + // Spawn the process + const child = spawn(runtime, processArgs, { cwd, - isDebugee: true, + env: processEnv, + stdio: ["ignore", "pipe", "pipe"], }); - if (!started) { - throw new Error("Program could not be started."); - } - } - - async #spawn(options: { - command: string; - args?: string[]; - cwd?: string; - env?: Record; - isDebugee?: boolean; - }): Promise { - const { command, args = [], cwd, env, isDebugee } = options; - const request = { command, args, cwd, env }; - this.emit("Process.requested", request); + this.#process = child; - let subprocess: ChildProcess; - try { - subprocess = spawn(command, args, { - ...request, - stdio: ["ignore", "pipe", "pipe"], - }); - } catch (cause) { - this.emit("Process.exited", new Error("Failed to spawn process", { cause }), null); - return false; - } - - subprocess.on("spawn", () => { - this.emit("Process.spawned", subprocess); - - if (isDebugee) { - this.#process = subprocess; - this.#emit("process", { - name: `${command} ${args.join(" ")}`, - systemProcessId: subprocess.pid, - isLocalProcess: true, - startMethod: "launch", - }); - } + // Attach event listeners for the process + child.stderr?.setEncoding("utf-8"); + child.stderr?.on("data", data => this.emit("Process.stderr", data)); + child.on("exit", (code, signal) => { + this.emit("Process.exited", code ?? signal ?? null, signal ?? null); }); - subprocess.on("exit", (code, signal) => { - this.emit("Process.exited", code, signal); - - if (isDebugee) { - this.#process = undefined; - this.#emit("exited", { - exitCode: code ?? -1, - }); - this.#emit("terminated"); - } - }); + this.emit("Process.spawned", child); - subprocess.stdout?.on("data", data => { - this.emit("Process.stdout", data.toString()); + // Wait for the signal from the debuggee + await this.#signal.ready; + await new Promise((resolve, reject) => { + this.#signal!.once("Signal.received", () => { + resolve(); + }); + this.#signal!.once("Signal.error", error => { + reject(error); + }); }); - subprocess.stderr?.on("data", data => { - this.emit("Process.stderr", data.toString()); - }); + // Connect the debugger to the debuggee + const connected = await this.#inspector.start(); + if (!connected) { + this.terminate(); + } - return new Promise(resolve => { - subprocess.on("spawn", () => resolve(true)); - subprocess.on("exit", () => resolve(false)); - subprocess.on("error", () => resolve(false)); + // Additional setup if needed + this.emit("Adapter.process", { + name: `${runtime} ${processArgs.join(" ")}`, + systemProcessId: child.pid, + isLocalProcess: true, + startMethod: "launch", }); } @@ -2107,7 +2062,7 @@ export class DebugAdapter extends EventEmitter implements close(): void { this.#process?.kill(); - // this.#signal?.close(); + this.#signal?.close(); this.#inspector.close(); this.#reset(); } diff --git a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index 6cd6e5ca3bf64c..e378c7db8ab65e 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -1,12 +1,9 @@ import { EventEmitter } from "node:events"; -import type { Server } from "node:net"; -import { createServer } from "node:net"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { WebSocketServer } from "ws"; const isDebug = process.env.NODE_ENV === "development"; -export type UnixSignalEventMap = { +export type WebSocketSignalEventMap = { "Signal.listening": [string]; "Signal.error": [Error]; "Signal.received": [string]; @@ -14,22 +11,24 @@ export type UnixSignalEventMap = { }; /** - * Starts a server that listens for signals on a UNIX domain socket. + * Starts a server that listens for signals over a WebSocket. */ -export class UnixSignal extends EventEmitter { - #path: string; - #server: Server; +export class WebSocketSignal extends EventEmitter { + #url: string; + #server: WebSocketServer; #ready: Promise; - constructor(path?: string | URL) { + constructor(url: string) { super(); - this.#path = path ? parseUnixPath(path) : randomUnixPath(); - this.#server = createServer(); - this.#server.on("listening", () => this.emit("Signal.listening", this.#path)); + this.#url = url; + const port = getPortFromUrl(url); + this.#server = new WebSocketServer({ port }); + + this.#server.on("listening", () => this.emit("Signal.listening", this.#url)); this.#server.on("error", error => this.emit("Signal.error", error)); this.#server.on("close", () => this.emit("Signal.closed")); this.#server.on("connection", socket => { - socket.on("data", data => { + socket.on("message", data => { this.emit("Signal.received", data.toString()); }); }); @@ -37,22 +36,20 @@ export class UnixSignal extends EventEmitter { this.#server.on("listening", resolve); this.#server.on("error", reject); }); - this.#server.listen(this.#path); } - emit(event: E, ...args: UnixSignalEventMap[E]): boolean { + emit(event: E, ...args: WebSocketSignalEventMap[E]): boolean { if (isDebug) { console.log(event, ...args); } - return super.emit(event, ...args); } /** - * The path to the UNIX domain socket. + * The WebSocket URL. */ get url(): string { - return `unix://${this.#path}`; + return this.#url; } /** @@ -70,18 +67,11 @@ export class UnixSignal extends EventEmitter { } } -export function randomUnixPath(): string { - return join(tmpdir(), `${Math.random().toString(36).slice(2)}.sock`); -} - -function parseUnixPath(path: string | URL): string { - if (typeof path === "string" && path.startsWith("/")) { - return path; - } +function getPortFromUrl(url: string): number { try { - const { pathname } = new URL(path); - return pathname; + const parsedUrl = new URL(url); + return parseInt(parsedUrl.port, 10) || 0; } catch { - throw new Error(`Invalid UNIX path: ${path}`); + throw new Error(`Invalid WebSocket URL: ${url}`); } } diff --git a/packages/bun-vscode/scripts/build.mjs b/packages/bun-vscode/scripts/build.mjs index c2281c467a0486..19aaa6b7092608 100644 --- a/packages/bun-vscode/scripts/build.mjs +++ b/packages/bun-vscode/scripts/build.mjs @@ -9,7 +9,7 @@ buildSync({ entryPoints: ["src/extension.ts", "src/web-extension.ts"], outdir: "dist", bundle: true, - external: ["vscode"], + external: ["vscode", "ws"], platform: "node", format: "cjs", // The following settings are required to allow for extension debugging diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index 88434622a1f8be..dd2fba4fb9ae1f 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -1,8 +1,7 @@ import { DebugSession } from "@vscode/debugadapter"; -import { tmpdir } from "node:os"; import * as vscode from "vscode"; import type { DAP } from "../../../bun-debug-adapter-protocol"; -import { DebugAdapter, UnixSignal } from "../../../bun-debug-adapter-protocol"; +import { DebugAdapter, getAvailablePort, WebSocketSignal } from "../../../bun-debug-adapter-protocol"; export const DEBUG_CONFIGURATION: vscode.DebugConfiguration = { type: "bun", @@ -176,7 +175,7 @@ class FileDebugSession extends DebugSession { constructor(sessionId?: string) { super(); const uniqueId = sessionId ?? Math.random().toString(36).slice(2); - const url = `ws+unix://${tmpdir()}/${uniqueId}.sock`; + const url = `ws://localhost:${getAvailablePort()}`; this.adapter = new DebugAdapter(url); this.adapter.on("Adapter.response", response => this.sendResponse(response)); @@ -204,11 +203,12 @@ class FileDebugSession extends DebugSession { } class TerminalDebugSession extends FileDebugSession { - readonly signal: UnixSignal; + readonly signal: WebSocketSignal; constructor() { super(); - this.signal = new UnixSignal(); + const signalUrl = `ws://localhost:${getAvailablePort()}`; + this.signal = new WebSocketSignal(signalUrl); this.signal.on("Signal.received", () => { vscode.debug.startDebugging(undefined, { ...ATTACH_CONFIGURATION, diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 6c4b8376dcb191..4f37be6fbd1d6c 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -28,11 +28,11 @@ export default function ( Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n"); } - const unix = process.env["BUN_INSPECT_NOTIFY"]; - if (unix) { - const { protocol, pathname } = parseUrl(unix); - if (protocol === "unix:") { - notify(pathname); + const notifyUrl = process.env["BUN_INSPECT_NOTIFY"]; + if (notifyUrl) { + const { protocol } = new URL(notifyUrl); + if (protocol === "ws:" || protocol === "wss:") { + notifyWebSocket(notifyUrl); } } } @@ -73,7 +73,7 @@ class Debugger { #listen(): void { const { protocol, hostname, port, pathname } = this.#url; - if (protocol === "ws:" || protocol === "ws+tcp:") { + if (protocol === "ws:" || protocol === "wss:") { const server = Bun.serve({ hostname, port, @@ -85,16 +85,7 @@ class Debugger { return; } - if (protocol === "ws+unix:") { - Bun.serve({ - unix: pathname, - fetch: this.#fetch.bind(this), - websocket: this.#websocket, - }); - return; - } - - throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'unix:')`); + throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:' or 'wss:')`); } get #websocket(): WebSocketHandler { @@ -264,34 +255,11 @@ const defaultHostname = "localhost"; const defaultPort = 6499; function parseUrl(input: string): URL { - if (input.startsWith("ws://") || input.startsWith("ws+unix://") || input.startsWith("unix://")) { + try { return new URL(input); + } catch { + throw new Error(`Invalid URL: ${input}`); } - const url = new URL(`ws://${defaultHostname}:${defaultPort}/${randomId()}`); - for (const part of input.split(/(\[[a-z0-9:]+\])|:/).filter(Boolean)) { - if (/^\d+$/.test(part)) { - url.port = part; - continue; - } - if (part.startsWith("[")) { - url.hostname = part; - continue; - } - if (part.startsWith("/")) { - url.pathname = part; - continue; - } - const [hostname, ...pathnames] = part.split("/"); - if (/^\d+$/.test(hostname)) { - url.port = hostname; - } else { - url.hostname = hostname; - } - if (pathnames.length) { - url.pathname = `/${pathnames.join("/")}`; - } - } - return url; } function randomId() { @@ -321,18 +289,16 @@ function reset(): string { return ""; } -function notify(unix: string): void { - Bun.connect({ - unix, - socket: { - open: socket => { - socket.end("1"); - }, - data: () => {}, // required or it errors - }, - }).finally(() => { - // Best-effort - }); +function notifyWebSocket(url: string): void { + const ws = new WebSocket(url); + ws.onopen = () => { + ws.send("1"); + ws.close(); + }; + ws.onerror = error => { + // Handle error if needed + console.error("WebSocket error:", error); + }; } function exit(...args: unknown[]): never { From 773a3ee0adb8594b13c0d720f00b712f5a5b27aa Mon Sep 17 00:00:00 2001 From: snoglobe Date: Thu, 19 Sep 2024 14:17:47 -0700 Subject: [PATCH 02/18] unix sockets on unix; websockets on windows --- src/js/internal/debugger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 4f37be6fbd1d6c..b770e71fe46f00 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -73,7 +73,7 @@ class Debugger { #listen(): void { const { protocol, hostname, port, pathname } = this.#url; - if (protocol === "ws:" || protocol === "wss:") { + if (protocol === "ws:" || protocol === "wss:" || protocol === "ws+tcp:") { const server = Bun.serve({ hostname, port, From bc666970122eb45c7d090b462828547b7bb3f8f6 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Thu, 19 Sep 2024 14:18:06 -0700 Subject: [PATCH 03/18] unix sockets on unix; websockets on windows --- .../src/debugger/adapter.ts | 194 ++++++++++++++---- .../src/debugger/signal.ts | 84 ++++++++ packages/bun-vscode/example/hello.js | 8 + packages/bun-vscode/src/features/debug.ts | 16 +- src/js/internal/debugger.ts | 74 ++++++- 5 files changed, 320 insertions(+), 56 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index 93817257b53e06..43e64236a5cd8f 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -5,7 +5,7 @@ import type { DAP } from "../protocol"; import { spawn, ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { WebSocketInspector, remoteObjectToString } from "../../../bun-inspector-protocol/index"; -import { WebSocketSignal } from "./signal"; +import { randomUnixPath, UnixSignal, WebSocketSignal } from "./signal"; import { Location, SourceMap } from "./sourcemap"; import { createServer, AddressInfo } from "node:net"; @@ -474,6 +474,10 @@ export class DebugAdapter extends EventEmitter implements throw new Error("No program specified. Did you set the 'program' property in your launch.json?"); } + if (!isJavaScript(program)) { + throw new Error("Program must be a JavaScript or TypeScript file."); + } + const processArgs = [...runtimeArgs, program, ...args]; if (isTestJavaScript(program) && !runtimeArgs.includes("test")) { @@ -484,59 +488,165 @@ export class DebugAdapter extends EventEmitter implements processArgs.unshift(watchMode === "hot" ? "--hot" : "--watch"); } - const processEnv: Record = strictEnv ? { ...env } : { ...process.env, ...env }; + const processEnv = strictEnv + ? { + ...env, + } + : { + ...process.env, + ...env, + }; - // Create WebSocketSignal - const signalUrl = `ws://localhost:${getAvailablePort()}`; - this.#signal = new WebSocketSignal(signalUrl); + if (process.platform !== "win32") { + // we're on unix + const url = `ws+unix://${randomUnixPath()}`; + const signal = new UnixSignal(); - // Set BUN_INSPECT_NOTIFY to signal's URL - processEnv["BUN_INSPECT_NOTIFY"] = this.#signal.url; + signal.on("Signal.received", () => { + this.#attach({ url }); + }); - // Set BUN_INSPECT to the debugger's inspector URL with ?wait=1 - processEnv["BUN_INSPECT"] = `${this.#inspector.url}?wait=1`; + this.once("Adapter.terminated", () => { + signal.close(); + }); - // Spawn the process - const child = spawn(runtime, processArgs, { - cwd, - env: processEnv, - stdio: ["ignore", "pipe", "pipe"], - }); + const query = stopOnEntry ? "break=1" : "wait=1"; + processEnv["BUN_INSPECT"] = `${url}?${query}`; + processEnv["BUN_INSPECT_NOTIFY"] = signal.url; + + // This is probably not correct, but it's the best we can do for now. + processEnv["FORCE_COLOR"] = "1"; + processEnv["BUN_QUIET_DEBUG_LOGS"] = "1"; + processEnv["BUN_DEBUG_QUIET_LOGS"] = "1"; + + const started = await this.#spawn({ + command: runtime, + args: processArgs, + env: processEnv, + cwd, + isDebugee: true, + }); + + if (!started) { + throw new Error("Program could not be started."); + } + } else { + // we're on windows + // Create WebSocketSignal + const signalUrl = `ws://localhost:${getAvailablePort()}`; + this.#signal = new WebSocketSignal(signalUrl); + + // Set BUN_INSPECT_NOTIFY to signal's URL + processEnv["BUN_INSPECT_NOTIFY"] = this.#signal.url; + + // Set BUN_INSPECT to the debugger's inspector URL with ?wait=1 + processEnv["BUN_INSPECT"] = `${this.#inspector.url}?wait=1`; + + // Spawn the process + const child = spawn(runtime, processArgs, { + cwd, + env: processEnv, + stdio: ["ignore", "pipe", "pipe"], + }); - this.#process = child; + this.#process = child; - // Attach event listeners for the process - child.stderr?.setEncoding("utf-8"); - child.stderr?.on("data", data => this.emit("Process.stderr", data)); - child.on("exit", (code, signal) => { - this.emit("Process.exited", code ?? signal ?? null, signal ?? null); - }); + // Attach event listeners for the process + child.stderr?.setEncoding("utf-8"); + child.stderr?.on("data", data => this.emit("Process.stderr", data)); + child.on("exit", (code, signal) => { + this.emit("Process.exited", code ?? signal ?? null, signal ?? null); + }); - this.emit("Process.spawned", child); + this.emit("Process.spawned", child); - // Wait for the signal from the debuggee - await this.#signal.ready; - await new Promise((resolve, reject) => { - this.#signal!.once("Signal.received", () => { - resolve(); + // Wait for the signal from the debuggee + await this.#signal.ready; + await new Promise((resolve, reject) => { + this.#signal!.once("Signal.received", () => { + resolve(); + }); + this.#signal!.once("Signal.error", error => { + reject(error); + }); }); - this.#signal!.once("Signal.error", error => { - reject(error); + + // Connect the debugger to the debuggee + const connected = await this.#inspector.start(); + if (!connected) { + this.terminate(); + } + + // Additional setup if needed + this.emit("Adapter.process", { + name: `${runtime} ${processArgs.join(" ")}`, + systemProcessId: child.pid, + isLocalProcess: true, + startMethod: "launch", }); - }); + } + } - // Connect the debugger to the debuggee - const connected = await this.#inspector.start(); - if (!connected) { - this.terminate(); + async #spawn(options: { + command: string; + args?: string[]; + cwd?: string; + env?: Record; + isDebugee?: boolean; + }): Promise { + const { command, args = [], cwd, env, isDebugee } = options; + const request = { command, args, cwd, env }; + this.emit("Process.requested", request); + + let subprocess: ChildProcess; + try { + subprocess = spawn(command, args, { + ...request, + stdio: ["ignore", "pipe", "pipe"], + }); + } catch (cause) { + this.emit("Process.exited", new Error("Failed to spawn process", { cause }), null); + return false; } - // Additional setup if needed - this.emit("Adapter.process", { - name: `${runtime} ${processArgs.join(" ")}`, - systemProcessId: child.pid, - isLocalProcess: true, - startMethod: "launch", + subprocess.on("spawn", () => { + this.emit("Process.spawned", subprocess); + + if (isDebugee) { + this.#process = subprocess; + this.#emit("process", { + name: `${command} ${args.join(" ")}`, + systemProcessId: subprocess.pid, + isLocalProcess: true, + startMethod: "launch", + }); + } + }); + + subprocess.on("exit", (code, signal) => { + this.emit("Process.exited", code, signal); + + if (isDebugee) { + this.#process = undefined; + this.#emit("exited", { + exitCode: code ?? -1, + }); + this.#emit("terminated"); + } + }); + + subprocess.stdout?.on("data", data => { + this.emit("Process.stdout", data.toString()); + }); + + subprocess.stderr?.on("data", data => { + this.emit("Process.stderr", data.toString()); + }); + + return new Promise(resolve => { + subprocess.on("spawn", () => resolve(true)); + subprocess.on("exit", () => resolve(false)); + subprocess.on("error", () => resolve(false)); }); } @@ -2062,7 +2172,7 @@ export class DebugAdapter extends EventEmitter implements close(): void { this.#process?.kill(); - this.#signal?.close(); + if (process.platform === "win32") this.#signal?.close(); this.#inspector.close(); this.#reset(); } diff --git a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index e378c7db8ab65e..4484e59dc5134c 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -1,8 +1,92 @@ import { EventEmitter } from "node:events"; import { WebSocketServer } from "ws"; +import type { Server } from "node:net"; +import { createServer } from "node:net"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; const isDebug = process.env.NODE_ENV === "development"; +export type UnixSignalEventMap = { + "Signal.listening": [string]; + "Signal.error": [Error]; + "Signal.received": [string]; + "Signal.closed": []; +}; + +/** + * Starts a server that listens for signals on a UNIX domain socket. + */ +export class UnixSignal extends EventEmitter { + #path: string; + #server: Server; + #ready: Promise; + + constructor(path?: string | URL) { + super(); + this.#path = path ? parseUnixPath(path) : randomUnixPath(); + this.#server = createServer(); + this.#server.on("listening", () => this.emit("Signal.listening", this.#path)); + this.#server.on("error", error => this.emit("Signal.error", error)); + this.#server.on("close", () => this.emit("Signal.closed")); + this.#server.on("connection", socket => { + socket.on("data", data => { + this.emit("Signal.received", data.toString()); + }); + }); + this.#ready = new Promise((resolve, reject) => { + this.#server.on("listening", resolve); + this.#server.on("error", reject); + }); + this.#server.listen(this.#path); + } + + emit(event: E, ...args: UnixSignalEventMap[E]): boolean { + if (isDebug) { + console.log(event, ...args); + } + + return super.emit(event, ...args); + } + + /** + * The path to the UNIX domain socket. + */ + get url(): string { + return `unix://${this.#path}`; + } + + /** + * Resolves when the server is listening or rejects if an error occurs. + */ + get ready(): Promise { + return this.#ready; + } + + /** + * Closes the server. + */ + close(): void { + this.#server.close(); + } +} + +export function randomUnixPath(): string { + return join(tmpdir(), `${Math.random().toString(36).slice(2)}.sock`); +} + +function parseUnixPath(path: string | URL): string { + if (typeof path === "string" && path.startsWith("/")) { + return path; + } + try { + const { pathname } = new URL(path); + return pathname; + } catch { + throw new Error(`Invalid UNIX path: ${path}`); + } +} + export type WebSocketSignalEventMap = { "Signal.listening": [string]; "Signal.error": [Error]; diff --git a/packages/bun-vscode/example/hello.js b/packages/bun-vscode/example/hello.js index 895e5479044908..a39a741f8fb726 100644 --- a/packages/bun-vscode/example/hello.js +++ b/packages/bun-vscode/example/hello.js @@ -1,4 +1,6 @@ +// @bun console.log("HELLO"); +console.log(process.argv0); console.log("HELLO 2"); console.log("HELLO 3"); a(); @@ -6,3 +8,9 @@ a(); function a() { console.log("HELLO 4"); } + +let i = 0; +while (true) { + console.log(i); + i++; +} \ No newline at end of file diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index dd2fba4fb9ae1f..ce1f7c223f34d7 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -1,7 +1,8 @@ import { DebugSession } from "@vscode/debugadapter"; +import { tmpdir } from "node:os"; import * as vscode from "vscode"; import type { DAP } from "../../../bun-debug-adapter-protocol"; -import { DebugAdapter, getAvailablePort, WebSocketSignal } from "../../../bun-debug-adapter-protocol"; +import { DebugAdapter, getAvailablePort, WebSocketSignal, UnixSignal } from "../../../bun-debug-adapter-protocol"; export const DEBUG_CONFIGURATION: vscode.DebugConfiguration = { type: "bun", @@ -175,7 +176,8 @@ class FileDebugSession extends DebugSession { constructor(sessionId?: string) { super(); const uniqueId = sessionId ?? Math.random().toString(36).slice(2); - const url = `ws://localhost:${getAvailablePort()}`; + const url = + process.platform === "win32" ? `ws://localhost:${getAvailablePort()}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; this.adapter = new DebugAdapter(url); this.adapter.on("Adapter.response", response => this.sendResponse(response)); @@ -203,12 +205,16 @@ class FileDebugSession extends DebugSession { } class TerminalDebugSession extends FileDebugSession { - readonly signal: WebSocketSignal; + readonly signal: WebSocketSignal | UnixSignal; constructor() { super(); - const signalUrl = `ws://localhost:${getAvailablePort()}`; - this.signal = new WebSocketSignal(signalUrl); + if (process.platform === "win32") { + const signalUrl = `ws://localhost:${getAvailablePort()}`; + this.signal = new WebSocketSignal(signalUrl); + } else { + this.signal = new UnixSignal(); + } this.signal.on("Signal.received", () => { vscode.debug.startDebugging(undefined, { ...ATTACH_CONFIGURATION, diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index b770e71fe46f00..c2476abf6647f4 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -28,11 +28,21 @@ export default function ( Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n"); } - const notifyUrl = process.env["BUN_INSPECT_NOTIFY"]; - if (notifyUrl) { - const { protocol } = new URL(notifyUrl); - if (protocol === "ws:" || protocol === "wss:") { - notifyWebSocket(notifyUrl); + if (process.platform === "win32") { + const notifyUrl = process.env["BUN_INSPECT_NOTIFY"]; + if (notifyUrl) { + const { protocol } = new URL(notifyUrl); + if (protocol === "ws:" || protocol === "wss:") { + notifyWebSocket(notifyUrl); + } + } + } else { + const unix = process.env["BUN_INSPECT_NOTIFY"]; + if (unix) { + const { protocol, pathname } = parseUrl(unix); + if (protocol === "unix:") { + notify(pathname); + } } } } @@ -85,7 +95,16 @@ class Debugger { return; } - throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:' or 'wss:')`); + if (protocol === "ws+unix:") { + Bun.serve({ + unix: pathname, + fetch: this.#fetch.bind(this), + websocket: this.#websocket, + }); + return; + } + + throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'wss:')`); } get #websocket(): WebSocketHandler { @@ -255,11 +274,34 @@ const defaultHostname = "localhost"; const defaultPort = 6499; function parseUrl(input: string): URL { - try { + if (input.startsWith("ws://") || input.startsWith("ws+unix://") || input.startsWith("unix://")) { return new URL(input); - } catch { - throw new Error(`Invalid URL: ${input}`); } + const url = new URL(`ws://${defaultHostname}:${defaultPort}/${randomId()}`); + for (const part of input.split(/(\[[a-z0-9:]+\])|:/).filter(Boolean)) { + if (/^\d+$/.test(part)) { + url.port = part; + continue; + } + if (part.startsWith("[")) { + url.hostname = part; + continue; + } + if (part.startsWith("/")) { + url.pathname = part; + continue; + } + const [hostname, ...pathnames] = part.split("/"); + if (/^\d+$/.test(hostname)) { + url.port = hostname; + } else { + url.hostname = hostname; + } + if (pathnames.length) { + url.pathname = `/${pathnames.join("/")}`; + } + } + return url; } function randomId() { @@ -301,6 +343,20 @@ function notifyWebSocket(url: string): void { }; } +function notify(unix: string): void { + Bun.connect({ + unix, + socket: { + open: socket => { + socket.end("1"); + }, + data: () => {}, // required or it errors + }, + }).finally(() => { + // Best-effort + }); +} + function exit(...args: unknown[]): never { console.error(...args); process.exit(1); From aea25a4486b3d4f50b4a17d8e307ccf6f8ba08a4 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Sep 2024 21:42:20 -0700 Subject: [PATCH 04/18] Don't use a WebSocket for this --- src/js/internal/debugger.ts | 53 ++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index c2476abf6647f4..52b2d2859fd910 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -1,4 +1,12 @@ -import type { ServerWebSocket, Socket, SocketHandler, WebSocketHandler, Server as WebSocketServer } from "bun"; +import type { + ServerWebSocket, + Socket, + SocketHandler, + TCPSocketConnectOptions, + UnixSocketOptions, + WebSocketHandler, + Server as WebSocketServer, +} from "bun"; export default function ( executionContextId: string, @@ -28,21 +36,18 @@ export default function ( Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n"); } - if (process.platform === "win32") { - const notifyUrl = process.env["BUN_INSPECT_NOTIFY"]; - if (notifyUrl) { - const { protocol } = new URL(notifyUrl); - if (protocol === "ws:" || protocol === "wss:") { - notifyWebSocket(notifyUrl); - } - } - } else { - const unix = process.env["BUN_INSPECT_NOTIFY"]; - if (unix) { - const { protocol, pathname } = parseUrl(unix); - if (protocol === "unix:") { - notify(pathname); - } + const notifyUrl = process.env["BUN_INSPECT_NOTIFY"] || ""; + if (notifyUrl) { + const { hostname, port, pathname, protocol } = parseUrl(notifyUrl); + if (protocol.startsWith("unix")) { + notify({ + unix: pathname, + }); + } else { + notify({ + hostname, + port: port ? Number(port) : undefined, + }); } } } @@ -331,21 +336,9 @@ function reset(): string { return ""; } -function notifyWebSocket(url: string): void { - const ws = new WebSocket(url); - ws.onopen = () => { - ws.send("1"); - ws.close(); - }; - ws.onerror = error => { - // Handle error if needed - console.error("WebSocket error:", error); - }; -} - -function notify(unix: string): void { +function notify(options): void { Bun.connect({ - unix, + ...options, socket: { open: socket => { socket.end("1"); From e291637ad3b1a783d26682c8d84578c156324bd9 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Sep 2024 21:45:00 -0700 Subject: [PATCH 05/18] Update debugger.ts --- src/js/internal/debugger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 52b2d2859fd910..4837e416a7de73 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -38,7 +38,7 @@ export default function ( const notifyUrl = process.env["BUN_INSPECT_NOTIFY"] || ""; if (notifyUrl) { - const { hostname, port, pathname, protocol } = parseUrl(notifyUrl); + const { hostname, port, pathname, protocol } = new URL(notifyUrl); if (protocol.startsWith("unix")) { notify({ unix: pathname, @@ -46,7 +46,7 @@ export default function ( } else { notify({ hostname, - port: port ? Number(port) : undefined, + port: port && port !== "0" ? Number(port) : undefined, }); } } From 5e6b0006d2b58787a9bb43d78d494d5428b96eef Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 14:38:04 -0700 Subject: [PATCH 06/18] - fixed getAvailablePort - removed wss --- .../src/debugger/adapter.ts | 18 ++++--- packages/bun-vscode/src/features/debug.ts | 51 ++++++++++--------- src/js/internal/debugger.ts | 4 +- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index 43e64236a5cd8f..f8ff76b470b864 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -9,11 +9,17 @@ import { randomUnixPath, UnixSignal, WebSocketSignal } from "./signal"; import { Location, SourceMap } from "./sourcemap"; import { createServer, AddressInfo } from "node:net"; -export function getAvailablePort(): number { - const server = createServer().listen(0); - const port = (server.address() as AddressInfo).port; - server.close(); - return port; +export async function getAvailablePort(): Promise { + const server = createServer(); + server.listen(0); + return new Promise((resolve, reject) => { + server.on("listening", () => { + const { port } = server.address() as AddressInfo; + server.close(() => { + resolve(port); + }); + }); + }); } const capabilities: DAP.Capabilities = { @@ -533,7 +539,7 @@ export class DebugAdapter extends EventEmitter implements } else { // we're on windows // Create WebSocketSignal - const signalUrl = `ws://localhost:${getAvailablePort()}`; + const signalUrl = `ws://localhost:${await getAvailablePort()}`; this.#signal = new WebSocketSignal(signalUrl); // Set BUN_INSPECT_NOTIFY to signal's URL diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index ce1f7c223f34d7..9de62a42384ea4 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -171,22 +171,24 @@ class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory } class FileDebugSession extends DebugSession { - readonly adapter: DebugAdapter; + adapter: DebugAdapter; constructor(sessionId?: string) { super(); const uniqueId = sessionId ?? Math.random().toString(36).slice(2); - const url = - process.platform === "win32" ? `ws://localhost:${getAvailablePort()}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; - - this.adapter = new DebugAdapter(url); - this.adapter.on("Adapter.response", response => this.sendResponse(response)); - this.adapter.on("Adapter.event", event => this.sendEvent(event)); - this.adapter.on("Adapter.reverseRequest", ({ command, arguments: args }) => - this.sendRequest(command, args, 5000, () => {}), - ); - - adapters.set(url, this); + // the signal is only first used well after this resolves + getAvailablePort().then(port => { + const url = process.platform === "win32" ? `ws://localhost:${port}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; + + this.adapter = new DebugAdapter(url); + this.adapter.on("Adapter.response", response => this.sendResponse(response)); + this.adapter.on("Adapter.event", event => this.sendEvent(event)); + this.adapter.on("Adapter.reverseRequest", ({ command, arguments: args }) => + this.sendRequest(command, args, 5000, () => {}), + ); + + adapters.set(url, this); + }); } handleMessage(message: DAP.Event | DAP.Request | DAP.Response): void { @@ -205,20 +207,23 @@ class FileDebugSession extends DebugSession { } class TerminalDebugSession extends FileDebugSession { - readonly signal: WebSocketSignal | UnixSignal; + signal: WebSocketSignal | UnixSignal; constructor() { super(); - if (process.platform === "win32") { - const signalUrl = `ws://localhost:${getAvailablePort()}`; - this.signal = new WebSocketSignal(signalUrl); - } else { - this.signal = new UnixSignal(); - } - this.signal.on("Signal.received", () => { - vscode.debug.startDebugging(undefined, { - ...ATTACH_CONFIGURATION, - url: this.adapter.url, + // the signal is only first used well after this resolves + getAvailablePort().then(port => { + if (process.platform === "win32") { + const signalUrl = `ws://localhost:${port}`; + this.signal = new WebSocketSignal(signalUrl); + } else { + this.signal = new UnixSignal(); + } + this.signal.on("Signal.received", () => { + vscode.debug.startDebugging(undefined, { + ...ATTACH_CONFIGURATION, + url: this.adapter.url, + }); }); }); } diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 4837e416a7de73..237b052983c26c 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -88,7 +88,7 @@ class Debugger { #listen(): void { const { protocol, hostname, port, pathname } = this.#url; - if (protocol === "ws:" || protocol === "wss:" || protocol === "ws+tcp:") { + if (protocol === "ws:" || protocol === "ws+tcp:") { const server = Bun.serve({ hostname, port, @@ -109,7 +109,7 @@ class Debugger { return; } - throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:', 'ws+unix:', or 'wss:')`); + throw new TypeError(`Unsupported protocol: '${protocol}' (expected 'ws:' or 'ws+unix:')`); } get #websocket(): WebSocketHandler { From 3d45d68632c912cb6ed445c3ad85533f479d36df Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 14:40:22 -0700 Subject: [PATCH 07/18] ws not external --- packages/bun-vscode/scripts/build.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-vscode/scripts/build.mjs b/packages/bun-vscode/scripts/build.mjs index 19aaa6b7092608..c2281c467a0486 100644 --- a/packages/bun-vscode/scripts/build.mjs +++ b/packages/bun-vscode/scripts/build.mjs @@ -9,7 +9,7 @@ buildSync({ entryPoints: ["src/extension.ts", "src/web-extension.ts"], outdir: "dist", bundle: true, - external: ["vscode", "ws"], + external: ["vscode"], platform: "node", format: "cjs", // The following settings are required to allow for extension debugging From 802b4082c8e4b8dafeb47e062bebf7b738bd4334 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 20 Sep 2024 22:12:45 -0700 Subject: [PATCH 08/18] TRUE windows debug support --- .../src/debugger/adapter.ts | 100 +++++++++--------- .../src/debugger/signal.ts | 75 ++++++------- packages/bun-vscode/bun.lockb | Bin 53586 -> 54384 bytes packages/bun-vscode/example/hello.js | 21 ++-- packages/bun-vscode/package.json | 2 +- packages/bun-vscode/scripts/build.mjs | 10 +- packages/bun-vscode/scripts/test.mjs | 16 +-- packages/bun-vscode/src/features/debug.ts | 11 +- 8 files changed, 114 insertions(+), 121 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index f8ff76b470b864..ad34d35760baee 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -5,9 +5,10 @@ import type { DAP } from "../protocol"; import { spawn, ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { WebSocketInspector, remoteObjectToString } from "../../../bun-inspector-protocol/index"; -import { randomUnixPath, UnixSignal, WebSocketSignal } from "./signal"; +import { randomUnixPath, TCPSocketSignal, UnixSignal } from "./signal"; import { Location, SourceMap } from "./sourcemap"; import { createServer, AddressInfo } from "node:net"; +import * as path from "node:path"; export async function getAvailablePort(): Promise { const server = createServer(); @@ -228,7 +229,6 @@ export class DebugAdapter extends EventEmitter implements #variables: Map; #initialized?: InitializeRequest; #options?: DebuggerOptions; - #signal?: WebSocketSignal; constructor(url?: string | URL) { super(); @@ -538,58 +538,38 @@ export class DebugAdapter extends EventEmitter implements } } else { // we're on windows - // Create WebSocketSignal - const signalUrl = `ws://localhost:${await getAvailablePort()}`; - this.#signal = new WebSocketSignal(signalUrl); + // Create TCPSocketSignal + const url = `ws://127.0.0.1:${await getAvailablePort()}/${getRandomId()}`; + const signal = new TCPSocketSignal(await getAvailablePort()); - // Set BUN_INSPECT_NOTIFY to signal's URL - processEnv["BUN_INSPECT_NOTIFY"] = this.#signal.url; - - // Set BUN_INSPECT to the debugger's inspector URL with ?wait=1 - processEnv["BUN_INSPECT"] = `${this.#inspector.url}?wait=1`; - - // Spawn the process - const child = spawn(runtime, processArgs, { - cwd, - env: processEnv, - stdio: ["ignore", "pipe", "pipe"], + signal.on("Signal.received", async () => { + this.#attach({ url }); }); - this.#process = child; - - // Attach event listeners for the process - child.stderr?.setEncoding("utf-8"); - child.stderr?.on("data", data => this.emit("Process.stderr", data)); - child.on("exit", (code, signal) => { - this.emit("Process.exited", code ?? signal ?? null, signal ?? null); + this.once("Adapter.terminated", () => { + signal.close(); }); - this.emit("Process.spawned", child); + const query = stopOnEntry ? "break=1" : "wait=1"; + processEnv["BUN_INSPECT"] = `${url}?${query}`; + processEnv["BUN_INSPECT_NOTIFY"] = `tcp://127.0.0.1:${signal.port}`; - // Wait for the signal from the debuggee - await this.#signal.ready; - await new Promise((resolve, reject) => { - this.#signal!.once("Signal.received", () => { - resolve(); - }); - this.#signal!.once("Signal.error", error => { - reject(error); - }); + // This is probably not correct, but it's the best we can do for now. + processEnv["FORCE_COLOR"] = "1"; + processEnv["BUN_QUIET_DEBUG_LOGS"] = "1"; + processEnv["BUN_DEBUG_QUIET_LOGS"] = "1"; + + const started = await this.#spawn({ + command: runtime, + args: processArgs, + env: processEnv, + cwd, + isDebugee: true, }); - // Connect the debugger to the debuggee - const connected = await this.#inspector.start(); - if (!connected) { - this.terminate(); + if (!started) { + throw new Error("Program could not be started."); } - - // Additional setup if needed - this.emit("Adapter.process", { - name: `${runtime} ${processArgs.join(" ")}`, - systemProcessId: child.pid, - isLocalProcess: true, - startMethod: "launch", - }); } } @@ -755,6 +735,9 @@ export class DebugAdapter extends EventEmitter implements async breakpointLocations(request: DAP.BreakpointLocationsRequest): Promise { const { line, endLine, column, endColumn, source: source0 } = request; + if(process.platform === "win32") { + source0.path = source0.path ? normalizeWindowsPath(source0.path) : source0.path; + } const source = await this.#getSource(sourceToId(source0)); const { locations } = await this.send("Debugger.getBreakpointLocations", { @@ -859,6 +842,9 @@ export class DebugAdapter extends EventEmitter implements } async #setBreakpointsByUrl(url: string, requests: DAP.SourceBreakpoint[], unsetOld?: boolean): Promise { + if(process.platform === "win32") { + url = url ? normalizeWindowsPath(url) : url; + } const source = this.#getSourceIfPresent(url); // If the source is not loaded, set a placeholder breakpoint at the start of the file. @@ -1232,6 +1218,9 @@ export class DebugAdapter extends EventEmitter implements async gotoTargets(request: DAP.GotoTargetsRequest): Promise { const { source: source0 } = request; + if(process.platform === "win32") { + source0.path = source0.path ? normalizeWindowsPath(source0.path) : source0.path; + } const source = await this.#getSource(sourceToId(source0)); const { breakpoints } = await this.breakpointLocations(request); @@ -1398,7 +1387,7 @@ export class DebugAdapter extends EventEmitter implements // 1. If it has a `path`, the client retrieves the source from the file system. // 2. If it has a `sourceReference`, the client sends a `source` request. // Moreover, the code is usually shown in a read-only editor. - const isUserCode = url.startsWith("/"); + const isUserCode = path.isAbsolute(url); const sourceMap = SourceMap(sourceMapURL); const name = sourceName(url); const presentationHint = sourcePresentationHint(url); @@ -1717,12 +1706,11 @@ export class DebugAdapter extends EventEmitter implements // If the source does not have a path or is a builtin module, // it cannot be retrieved from the file system. - if (typeof sourceId === "number" || !sourceId.startsWith("/")) { + if (typeof sourceId === "number" || !path.isAbsolute(sourceId)) { throw new Error(`Source not found: ${sourceId}`); } // If the source is not present, it may not have been loaded yet. - // In that case, wait for it to be loaded. let resolves = this.#pendingSources.get(sourceId); if (!resolves) { this.#pendingSources.set(sourceId, (resolves = [])); @@ -2178,7 +2166,6 @@ export class DebugAdapter extends EventEmitter implements close(): void { this.#process?.kill(); - if (process.platform === "win32") this.#signal?.close(); this.#inspector.close(); this.#reset(); } @@ -2220,10 +2207,10 @@ function titleize(name: string): string { } function sourcePresentationHint(url?: string): DAP.Source["presentationHint"] { - if (!url || !url.startsWith("/")) { + if (!url || path.isAbsolute(url)) { return "deemphasize"; } - if (url.includes("/node_modules/")) { + if (url.includes("/node_modules/") || url.includes("\\node_modules\\")) { return "normal"; } return "emphasize"; @@ -2234,6 +2221,9 @@ function sourceName(url?: string): string { return "unknown.js"; } if (isJavaScript(url)) { + if(process.platform === "win32") { + return url.split("\\").pop() || url; + } return url.split("/").pop() || url; } return `${url}.js`; @@ -2638,3 +2628,11 @@ let sequence = 1; function nextId(): number { return sequence++; } + +export function getRandomId() { + return Math.random().toString(36).slice(2); +} + +export function normalizeWindowsPath(path: string): string { + return (path.charAt(0).toUpperCase() + path.slice(1)).replaceAll("\\\\", "\\") +} \ No newline at end of file diff --git a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index 4484e59dc5134c..fc8fa0bbf74baf 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -1,6 +1,5 @@ import { EventEmitter } from "node:events"; -import { WebSocketServer } from "ws"; -import type { Server } from "node:net"; +import type { Server, Socket } from "node:net"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -87,42 +86,47 @@ function parseUnixPath(path: string | URL): string { } } -export type WebSocketSignalEventMap = { - "Signal.listening": [string]; - "Signal.error": [Error]; - "Signal.received": [string]; - "Signal.closed": []; -}; +export type TCPSocketSignalEventMap = { + 'Signal.listening': []; + 'Signal.error': [Error]; + 'Signal.closed': []; + 'Signal.received': [string]; +} -/** - * Starts a server that listens for signals over a WebSocket. - */ -export class WebSocketSignal extends EventEmitter { - #url: string; - #server: WebSocketServer; +export class TCPSocketSignal extends EventEmitter { + #port: number; + #server: ReturnType; #ready: Promise; - constructor(url: string) { + constructor(port: number) { super(); - this.#url = url; - const port = getPortFromUrl(url); - this.#server = new WebSocketServer({ port }); + this.#port = port; - this.#server.on("listening", () => this.emit("Signal.listening", this.#url)); - this.#server.on("error", error => this.emit("Signal.error", error)); - this.#server.on("close", () => this.emit("Signal.closed")); - this.#server.on("connection", socket => { - socket.on("message", data => { - this.emit("Signal.received", data.toString()); + this.#server = createServer((socket: Socket) => { + socket.on('data', (data) => { + console.error('received', data); + this.emit('Signal.received', data.toString()); + }); + + socket.on('error', (error) => { + this.emit('Signal.error', error); + }); + + socket.on('close', () => { + this.emit('Signal.closed'); }); }); + this.#ready = new Promise((resolve, reject) => { - this.#server.on("listening", resolve); - this.#server.on("error", reject); + this.#server.listen(this.#port, () => { + this.emit('Signal.listening'); + resolve(); + }); + this.#server.on('error', reject); }); } - emit(event: E, ...args: WebSocketSignalEventMap[E]): boolean { + emit(event: E, ...args: TCPSocketSignalEventMap[E]): boolean { if (isDebug) { console.log(event, ...args); } @@ -130,10 +134,10 @@ export class WebSocketSignal extends EventEmitter { } /** - * The WebSocket URL. + * The TCP port. */ - get url(): string { - return this.#url; + get port(): number { + return this.#port; } /** @@ -149,13 +153,4 @@ export class WebSocketSignal extends EventEmitter { close(): void { this.#server.close(); } -} - -function getPortFromUrl(url: string): number { - try { - const parsedUrl = new URL(url); - return parseInt(parsedUrl.port, 10) || 0; - } catch { - throw new Error(`Invalid WebSocket URL: ${url}`); - } -} +} \ No newline at end of file diff --git a/packages/bun-vscode/bun.lockb b/packages/bun-vscode/bun.lockb index 1f071eb387a7b9d896dca15ad3ce7ddb4576c2c5..9433b0c6deda1a39e15fafd3d7500d880660af40 100755 GIT binary patch delta 8765 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 O1eQ?_CN|^1PyY!_NDY<% delta 8252 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 { - const url = process.platform === "win32" ? `ws://localhost:${port}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; + const url = process.platform === "win32" ? `ws://127.0.0.1:${port}/${getRandomId()}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; this.adapter = new DebugAdapter(url); this.adapter.on("Adapter.response", response => this.sendResponse(response)); @@ -207,15 +207,14 @@ class FileDebugSession extends DebugSession { } class TerminalDebugSession extends FileDebugSession { - signal: WebSocketSignal | UnixSignal; + signal: TCPSocketSignal | UnixSignal; constructor() { super(); // the signal is only first used well after this resolves getAvailablePort().then(port => { if (process.platform === "win32") { - const signalUrl = `ws://localhost:${port}`; - this.signal = new WebSocketSignal(signalUrl); + this.signal = new TCPSocketSignal(port); } else { this.signal = new UnixSignal(); } From 5d0275e797b054ec25b6bc793cab4e424222e835 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 23:20:16 -0700 Subject: [PATCH 09/18] fix debugger.ts --- src/js/internal/debugger.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 4542ac1ea0edbf..5e725413be99a9 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -38,16 +38,19 @@ export default function ( const notifyUrl = process.env["BUN_INSPECT_NOTIFY"] || ""; if (notifyUrl) { - const { hostname, port, pathname, protocol } = new URL(notifyUrl); - if (protocol.startsWith("unix")) { + if (notifyUrl.startsWith("unix://")) { + const path = require("node:path"); notify({ - unix: pathname, + // This is actually a filesystem path, not a URL. + unix: path.resolve(notifyUrl.substring("unix://".length)), }); } else { + const { hostname, port } = new URL(notifyUrl); notify({ hostname, port: port && port !== "0" ? Number(port) : undefined, }); + return; } } } From 6a0ad7f5101a2e31d7164afcd294a96499fec88f Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 23:21:09 -0700 Subject: [PATCH 10/18] comments for 127.0.0.1 --- .../src/debugger/adapter.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index ad34d35760baee..39f52a7b9a6ebd 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -539,7 +539,7 @@ export class DebugAdapter extends EventEmitter implements } else { // we're on windows // Create TCPSocketSignal - const url = `ws://127.0.0.1:${await getAvailablePort()}/${getRandomId()}`; + const url = `ws://127.0.0.1:${await getAvailablePort()}/${getRandomId()}`; // 127.0.0.1 so it resolves correctly on windows const signal = new TCPSocketSignal(await getAvailablePort()); signal.on("Signal.received", async () => { @@ -552,7 +552,7 @@ export class DebugAdapter extends EventEmitter implements const query = stopOnEntry ? "break=1" : "wait=1"; processEnv["BUN_INSPECT"] = `${url}?${query}`; - processEnv["BUN_INSPECT_NOTIFY"] = `tcp://127.0.0.1:${signal.port}`; + processEnv["BUN_INSPECT_NOTIFY"] = `tcp://127.0.0.1:${signal.port}`; // 127.0.0.1 so it resolves correctly on windows // This is probably not correct, but it's the best we can do for now. processEnv["FORCE_COLOR"] = "1"; @@ -735,7 +735,7 @@ export class DebugAdapter extends EventEmitter implements async breakpointLocations(request: DAP.BreakpointLocationsRequest): Promise { const { line, endLine, column, endColumn, source: source0 } = request; - if(process.platform === "win32") { + if (process.platform === "win32") { source0.path = source0.path ? normalizeWindowsPath(source0.path) : source0.path; } const source = await this.#getSource(sourceToId(source0)); @@ -842,7 +842,7 @@ export class DebugAdapter extends EventEmitter implements } async #setBreakpointsByUrl(url: string, requests: DAP.SourceBreakpoint[], unsetOld?: boolean): Promise { - if(process.platform === "win32") { + if (process.platform === "win32") { url = url ? normalizeWindowsPath(url) : url; } const source = this.#getSourceIfPresent(url); @@ -1218,7 +1218,7 @@ export class DebugAdapter extends EventEmitter implements async gotoTargets(request: DAP.GotoTargetsRequest): Promise { const { source: source0 } = request; - if(process.platform === "win32") { + if (process.platform === "win32") { source0.path = source0.path ? normalizeWindowsPath(source0.path) : source0.path; } const source = await this.#getSource(sourceToId(source0)); @@ -2221,7 +2221,7 @@ function sourceName(url?: string): string { return "unknown.js"; } if (isJavaScript(url)) { - if(process.platform === "win32") { + if (process.platform === "win32") { return url.split("\\").pop() || url; } return url.split("/").pop() || url; @@ -2634,5 +2634,5 @@ export function getRandomId() { } export function normalizeWindowsPath(path: string): string { - return (path.charAt(0).toUpperCase() + path.slice(1)).replaceAll("\\\\", "\\") -} \ No newline at end of file + return (path.charAt(0).toUpperCase() + path.slice(1)).replaceAll("\\\\", "\\"); +} From 32c5479bcd6446b80fc05615e7311c381fb58d0f Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 23:22:12 -0700 Subject: [PATCH 11/18] remove extraneous return --- src/js/internal/debugger.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 5e725413be99a9..cf79349cd381ea 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -50,7 +50,6 @@ export default function ( hostname, port: port && port !== "0" ? Number(port) : undefined, }); - return; } } } From 693dea4b611c8d19570d2d0fc09e07237566c2e1 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 23:32:39 -0700 Subject: [PATCH 12/18] path fuckery --- .../src/debugger/adapter.ts | 12 +++-- packages/bun-vscode/src/features/debug.ts | 52 ++++++++++--------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index 39f52a7b9a6ebd..ad64cb9fb7774b 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -2207,7 +2207,7 @@ function titleize(name: string): string { } function sourcePresentationHint(url?: string): DAP.Source["presentationHint"] { - if (!url || path.isAbsolute(url)) { + if (!url || !path.isAbsolute(url)) { return "deemphasize"; } if (url.includes("/node_modules/") || url.includes("\\node_modules\\")) { @@ -2222,7 +2222,7 @@ function sourceName(url?: string): string { } if (isJavaScript(url)) { if (process.platform === "win32") { - return url.split("\\").pop() || url; + url = url.replaceAll("\\", "/"); } return url.split("/").pop() || url; } @@ -2633,6 +2633,10 @@ export function getRandomId() { return Math.random().toString(36).slice(2); } -export function normalizeWindowsPath(path: string): string { - return (path.charAt(0).toUpperCase() + path.slice(1)).replaceAll("\\\\", "\\"); +export function normalizeWindowsPath(winPath: string): string { + winPath = path.normalize(winPath); + if (winPath[1] === ":" && (winPath[2] === "\\" || winPath[2] === "/")) { + return (winPath.charAt(0).toUpperCase() + winPath.slice(1)).replaceAll("\\\\", "\\"); + } + return winPath; } diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index e58019ca0c25f3..0e5b0035841a47 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -176,19 +176,23 @@ class FileDebugSession extends DebugSession { constructor(sessionId?: string) { super(); const uniqueId = sessionId ?? Math.random().toString(36).slice(2); - // the signal is only first used well after this resolves - getAvailablePort().then(port => { - const url = process.platform === "win32" ? `ws://127.0.0.1:${port}/${getRandomId()}` : `ws+unix://${tmpdir()}/${uniqueId}.sock`; - - this.adapter = new DebugAdapter(url); - this.adapter.on("Adapter.response", response => this.sendResponse(response)); - this.adapter.on("Adapter.event", event => this.sendEvent(event)); - this.adapter.on("Adapter.reverseRequest", ({ command, arguments: args }) => - this.sendRequest(command, args, 5000, () => {}), - ); - - adapters.set(url, this); - }); + let url; + if (process.platform === "win32") { + getAvailablePort().then(port => { + // the signal is only first used well after this resolves + url = `ws://127.0.0.1:${port}/${getRandomId()}`; + }); + } else { + url = `ws+unix://${tmpdir()}/${uniqueId}.sock`; + } + this.adapter = new DebugAdapter(url); + this.adapter.on("Adapter.response", response => this.sendResponse(response)); + this.adapter.on("Adapter.event", event => this.sendEvent(event)); + this.adapter.on("Adapter.reverseRequest", ({ command, arguments: args }) => + this.sendRequest(command, args, 5000, () => {}), + ); + + adapters.set(url, this); } handleMessage(message: DAP.Event | DAP.Request | DAP.Response): void { @@ -211,18 +215,18 @@ class TerminalDebugSession extends FileDebugSession { constructor() { super(); - // the signal is only first used well after this resolves - getAvailablePort().then(port => { - if (process.platform === "win32") { + if (process.platform === "win32") { + getAvailablePort().then(port => { + // the signal is only first used well after this resolves this.signal = new TCPSocketSignal(port); - } else { - this.signal = new UnixSignal(); - } - this.signal.on("Signal.received", () => { - vscode.debug.startDebugging(undefined, { - ...ATTACH_CONFIGURATION, - url: this.adapter.url, - }); + }); + } else { + this.signal = new UnixSignal(); + } + this.signal.on("Signal.received", () => { + vscode.debug.startDebugging(undefined, { + ...ATTACH_CONFIGURATION, + url: this.adapter.url, }); }); } From 2626b89b3bca53c7012179303f06d72f6f478e44 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Fri, 20 Sep 2024 23:48:36 -0700 Subject: [PATCH 13/18] promises fixed --- packages/bun-vscode/src/features/debug.ts | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index 0e5b0035841a47..36d4330b9d86fd 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -81,7 +81,7 @@ function debugFileCommand(resource?: vscode.Uri) { if (path) debugCommand(path); } -function injectDebugTerminal(terminal: vscode.Terminal): void { +async function injectDebugTerminal(terminal: vscode.Terminal): Promise { if (!getConfig("debugTerminal.enabled")) return; const { name, creationOptions } = terminal; @@ -97,14 +97,16 @@ function injectDebugTerminal(terminal: vscode.Terminal): void { const stopOnEntry = getConfig("debugTerminal.stopOnEntry") === true; const query = stopOnEntry ? "break=1" : "wait=1"; - const { adapter, signal } = new TerminalDebugSession(); + const debugSession = new TerminalDebugSession(); + await debugSession.initialize(); + const { adapter, signal } = debugSession; const debug = vscode.window.createTerminal({ ...creationOptions, name: "JavaScript Debug Terminal", env: { ...env, "BUN_INSPECT": `${adapter.url}?${query}`, - "BUN_INSPECT_NOTIFY": `${signal.url}`, + "BUN_INSPECT_NOTIFY": `${signal instanceof UnixSignal ? signal.url : `tcp://127.0.0.1:${signal.port}`}`, }, }); @@ -153,7 +155,9 @@ class DebugConfigurationProvider implements vscode.DebugConfigurationProvider { } class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory { - createDebugAdapterDescriptor(session: vscode.DebugSession): vscode.ProviderResult { + async createDebugAdapterDescriptor( + session: vscode.DebugSession, + ): vscode.ProviderResult { const { configuration } = session; const { request, url } = configuration; @@ -166,22 +170,25 @@ class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory } const adapter = new FileDebugSession(session.id); + await adapter.initialize(); return new vscode.DebugAdapterInlineImplementation(adapter); } } class FileDebugSession extends DebugSession { adapter: DebugAdapter; + sessionId?: string; constructor(sessionId?: string) { super(); - const uniqueId = sessionId ?? Math.random().toString(36).slice(2); + this.sessionId = sessionId; + } + + async initialize() { + const uniqueId = this.sessionId ?? Math.random().toString(36).slice(2); let url; if (process.platform === "win32") { - getAvailablePort().then(port => { - // the signal is only first used well after this resolves - url = `ws://127.0.0.1:${port}/${getRandomId()}`; - }); + url = `ws://127.0.0.1:${await getAvailablePort()}/${getRandomId()}`; } else { url = `ws+unix://${tmpdir()}/${uniqueId}.sock`; } @@ -215,11 +222,11 @@ class TerminalDebugSession extends FileDebugSession { constructor() { super(); + } + + async initialize() { if (process.platform === "win32") { - getAvailablePort().then(port => { - // the signal is only first used well after this resolves - this.signal = new TCPSocketSignal(port); - }); + this.signal = new TCPSocketSignal(await getAvailablePort()); } else { this.signal = new UnixSignal(); } From f83db90be9aae51f9086f753cd9455e111fe4333 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 21 Sep 2024 00:02:13 -0700 Subject: [PATCH 14/18] debug terminal fix --- packages/bun-vscode/example/hello.js | 9 --------- packages/bun-vscode/example/hello.ts | 9 +++++++++ packages/bun-vscode/src/features/debug.ts | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 packages/bun-vscode/example/hello.js create mode 100644 packages/bun-vscode/example/hello.ts diff --git a/packages/bun-vscode/example/hello.js b/packages/bun-vscode/example/hello.js deleted file mode 100644 index 4c0899d9fb26d8..00000000000000 --- a/packages/bun-vscode/example/hello.js +++ /dev/null @@ -1,9 +0,0 @@ - -// debugger; - -Bun.serve({ - fetch(req) { - console.log("test") - return new Response('Hello, world!'); - } -}); diff --git a/packages/bun-vscode/example/hello.ts b/packages/bun-vscode/example/hello.ts new file mode 100644 index 00000000000000..ccd2773908ef4c --- /dev/null +++ b/packages/bun-vscode/example/hello.ts @@ -0,0 +1,9 @@ +type OS = "Windows"; + +Bun.serve({ + fetch(req: Request) { + return new Response( + `Hello, ${"Windows" as OS}!` + ); + } +}); diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index 36d4330b9d86fd..ec1d87ec59131f 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -157,7 +157,7 @@ class DebugConfigurationProvider implements vscode.DebugConfigurationProvider { class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory { async createDebugAdapterDescriptor( session: vscode.DebugSession, - ): vscode.ProviderResult { + ): Promise> { const { configuration } = session; const { request, url } = configuration; @@ -225,6 +225,7 @@ class TerminalDebugSession extends FileDebugSession { } async initialize() { + await super.initialize(); if (process.platform === "win32") { this.signal = new TCPSocketSignal(await getAvailablePort()); } else { @@ -243,7 +244,7 @@ class TerminalDebugSession extends FileDebugSession { name: "Bun Terminal", env: { "BUN_INSPECT": `${this.adapter.url}?wait=1`, - "BUN_INSPECT_NOTIFY": `${this.signal.url}`, + "BUN_INSPECT_NOTIFY": `${this.signal instanceof UnixSignal ? this.signal.url : `tcp://127.0.0.1:${this.signal.port}`}`, }, isTransient: true, iconPath: new vscode.ThemeIcon("debug-console"), From e8b2fb52db5afdbd58e7b3803448daf99a0f97e8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sat, 21 Sep 2024 00:12:20 -0700 Subject: [PATCH 15/18] Update packages/bun-debug-adapter-protocol/src/debugger/signal.ts --- packages/bun-debug-adapter-protocol/src/debugger/signal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index fc8fa0bbf74baf..dd0196d8ef467f 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -104,7 +104,6 @@ export class TCPSocketSignal extends EventEmitter { this.#server = createServer((socket: Socket) => { socket.on('data', (data) => { - console.error('received', data); this.emit('Signal.received', data.toString()); }); From 27f7965665b7d75ca0b682f809682f1b38dc69c2 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Sat, 21 Sep 2024 00:15:02 -0700 Subject: [PATCH 16/18] signal.url --- .../src/debugger/signal.ts | 32 +++++++++++-------- packages/bun-vscode/src/features/debug.ts | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index dd0196d8ef467f..db2b029a972b8d 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -87,11 +87,11 @@ function parseUnixPath(path: string | URL): string { } export type TCPSocketSignalEventMap = { - 'Signal.listening': []; - 'Signal.error': [Error]; - 'Signal.closed': []; - 'Signal.received': [string]; -} + "Signal.listening": []; + "Signal.error": [Error]; + "Signal.closed": []; + "Signal.received": [string]; +}; export class TCPSocketSignal extends EventEmitter { #port: number; @@ -103,25 +103,25 @@ export class TCPSocketSignal extends EventEmitter { this.#port = port; this.#server = createServer((socket: Socket) => { - socket.on('data', (data) => { - this.emit('Signal.received', data.toString()); + socket.on("data", data => { + this.emit("Signal.received", data.toString()); }); - socket.on('error', (error) => { - this.emit('Signal.error', error); + socket.on("error", error => { + this.emit("Signal.error", error); }); - socket.on('close', () => { - this.emit('Signal.closed'); + socket.on("close", () => { + this.emit("Signal.closed"); }); }); this.#ready = new Promise((resolve, reject) => { this.#server.listen(this.#port, () => { - this.emit('Signal.listening'); + this.emit("Signal.listening"); resolve(); }); - this.#server.on('error', reject); + this.#server.on("error", reject); }); } @@ -139,6 +139,10 @@ export class TCPSocketSignal extends EventEmitter { return this.#port; } + get url(): string { + return `tcp://127.0.0.1:${this.#port}`; + } + /** * Resolves when the server is listening or rejects if an error occurs. */ @@ -152,4 +156,4 @@ export class TCPSocketSignal extends EventEmitter { close(): void { this.#server.close(); } -} \ No newline at end of file +} diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index ec1d87ec59131f..2e6ea8edc647a6 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -106,7 +106,7 @@ async function injectDebugTerminal(terminal: vscode.Terminal): Promise { env: { ...env, "BUN_INSPECT": `${adapter.url}?${query}`, - "BUN_INSPECT_NOTIFY": `${signal instanceof UnixSignal ? signal.url : `tcp://127.0.0.1:${signal.port}`}`, + "BUN_INSPECT_NOTIFY": signal.url, }, }); From 8beb0d36f6f6c02037ebcd84740b419f85116295 Mon Sep 17 00:00:00 2001 From: snoglobe Date: Sat, 21 Sep 2024 00:16:40 -0700 Subject: [PATCH 17/18] signal.url --- packages/bun-vscode/src/features/debug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-vscode/src/features/debug.ts b/packages/bun-vscode/src/features/debug.ts index 2e6ea8edc647a6..c6fc47f0c4d3d0 100644 --- a/packages/bun-vscode/src/features/debug.ts +++ b/packages/bun-vscode/src/features/debug.ts @@ -244,7 +244,7 @@ class TerminalDebugSession extends FileDebugSession { name: "Bun Terminal", env: { "BUN_INSPECT": `${this.adapter.url}?wait=1`, - "BUN_INSPECT_NOTIFY": `${this.signal instanceof UnixSignal ? this.signal.url : `tcp://127.0.0.1:${this.signal.port}`}`, + "BUN_INSPECT_NOTIFY": this.signal.url, }, isTransient: true, iconPath: new vscode.ThemeIcon("debug-console"), From ba54a1769d3085fc34a4009b6ada9800dc393a8c Mon Sep 17 00:00:00 2001 From: snoglobe Date: Sat, 21 Sep 2024 00:17:55 -0700 Subject: [PATCH 18/18] signal.url --- packages/bun-debug-adapter-protocol/src/debugger/adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index ad64cb9fb7774b..ddf5a9ff253f99 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -552,7 +552,7 @@ export class DebugAdapter extends EventEmitter implements const query = stopOnEntry ? "break=1" : "wait=1"; processEnv["BUN_INSPECT"] = `${url}?${query}`; - processEnv["BUN_INSPECT_NOTIFY"] = `tcp://127.0.0.1:${signal.port}`; // 127.0.0.1 so it resolves correctly on windows + processEnv["BUN_INSPECT_NOTIFY"] = signal.url; // 127.0.0.1 so it resolves correctly on windows // This is probably not correct, but it's the best we can do for now. processEnv["FORCE_COLOR"] = "1";