From 722e3fa48104c747d741aa5fc633661fff3dddb2 Mon Sep 17 00:00:00 2001 From: snwy Date: Sat, 21 Sep 2024 00:20:33 -0700 Subject: [PATCH] fix for windows debug support (#14048) Co-authored-by: Jarred Sumner --- .../src/debugger/adapter.ts | 139 +++++++++++++----- .../src/debugger/signal.ts | 74 +++++++++- packages/bun-vscode/bun.lockb | Bin 53586 -> 54384 bytes packages/bun-vscode/example/hello.js | 8 - packages/bun-vscode/example/hello.ts | 9 ++ 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 | 46 ++++-- src/js/internal/debugger.ts | 2 +- 10 files changed, 241 insertions(+), 65 deletions(-) delete mode 100644 packages/bun-vscode/example/hello.js create mode 100644 packages/bun-vscode/example/hello.ts diff --git a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts index fca2d9677f..ddf5a9ff25 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/adapter.ts @@ -2,12 +2,26 @@ 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 { 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(); + 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 = { supportsConfigurationDoneRequest: true, @@ -489,36 +503,73 @@ export class DebugAdapter extends EventEmitter implements ...env, }; - const url = `ws+unix://${randomUnixPath()}`; - const signal = new UnixSignal(); + if (process.platform !== "win32") { + // we're on unix + const url = `ws+unix://${randomUnixPath()}`; + const signal = new UnixSignal(); - signal.on("Signal.received", () => { - this.#attach({ url }); - }); + signal.on("Signal.received", () => { + this.#attach({ url }); + }); - this.once("Adapter.terminated", () => { - signal.close(); - }); + this.once("Adapter.terminated", () => { + signal.close(); + }); - const query = stopOnEntry ? "break=1" : "wait=1"; - processEnv["BUN_INSPECT"] = `${url}?${query}`; - processEnv["BUN_INSPECT_NOTIFY"] = signal.url; + 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"; + // 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, - }); + const started = await this.#spawn({ + command: runtime, + args: processArgs, + env: processEnv, + cwd, + isDebugee: true, + }); - if (!started) { - throw new Error("Program could not be started."); + if (!started) { + throw new Error("Program could not be started."); + } + } else { + // we're on windows + // Create TCPSocketSignal + 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 () => { + this.#attach({ url }); + }); + + this.once("Adapter.terminated", () => { + signal.close(); + }); + + const query = stopOnEntry ? "break=1" : "wait=1"; + processEnv["BUN_INSPECT"] = `${url}?${query}`; + 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"; + 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."); + } } } @@ -684,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", { @@ -788,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. @@ -1161,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); @@ -1327,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); @@ -1646,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 = [])); @@ -2107,7 +2166,6 @@ export class DebugAdapter extends EventEmitter implements close(): void { this.#process?.kill(); - // this.#signal?.close(); this.#inspector.close(); this.#reset(); } @@ -2149,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"; @@ -2163,6 +2221,9 @@ function sourceName(url?: string): string { return "unknown.js"; } if (isJavaScript(url)) { + if (process.platform === "win32") { + url = url.replaceAll("\\", "/"); + } return url.split("/").pop() || url; } return `${url}.js`; @@ -2567,3 +2628,15 @@ let sequence = 1; function nextId(): number { return sequence++; } + +export function getRandomId() { + return Math.random().toString(36).slice(2); +} + +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-debug-adapter-protocol/src/debugger/signal.ts b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts index 6cd6e5ca3b..db2b029a97 100644 --- a/packages/bun-debug-adapter-protocol/src/debugger/signal.ts +++ b/packages/bun-debug-adapter-protocol/src/debugger/signal.ts @@ -1,5 +1,5 @@ import { EventEmitter } from "node:events"; -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"; @@ -85,3 +85,75 @@ function parseUnixPath(path: string | URL): string { throw new Error(`Invalid UNIX path: ${path}`); } } + +export type TCPSocketSignalEventMap = { + "Signal.listening": []; + "Signal.error": [Error]; + "Signal.closed": []; + "Signal.received": [string]; +}; + +export class TCPSocketSignal extends EventEmitter { + #port: number; + #server: ReturnType; + #ready: Promise; + + constructor(port: number) { + super(); + this.#port = port; + + this.#server = createServer((socket: Socket) => { + socket.on("data", 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.listen(this.#port, () => { + this.emit("Signal.listening"); + resolve(); + }); + this.#server.on("error", reject); + }); + } + + emit(event: E, ...args: TCPSocketSignalEventMap[E]): boolean { + if (isDebug) { + console.log(event, ...args); + } + return super.emit(event, ...args); + } + + /** + * The TCP port. + */ + get port(): number { + 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. + */ + get ready(): Promise { + return this.#ready; + } + + /** + * Closes the server. + */ + close(): void { + this.#server.close(); + } +} diff --git a/packages/bun-vscode/bun.lockb b/packages/bun-vscode/bun.lockb index 1f071eb387a7b9d896dca15ad3ce7ddb4576c2c5..9433b0c6deda1a39e15fafd3d7500d880660af40 100755 GIT binary patch delta 8765 zcmeHMd013emVdVZ<)IaJsG>071}d@$La}HSC@%Ow+)Gr{0t+Z*QI-fq1Ure#q;csJ z6c?fxH%#arV>?RJq|MjeY1^b@G^v8 z>Z`wZ?(f`l?>*h@6h>X$nZr_ROk(hA-Q9YZjcDc16XwK1IYvY zyU^@zL56}~g=9z0LUQ|I$`MiX&%pNpu$99kX*lF*Nc?ZBch<(2A;efHIlQfq{*bpJ zAA)>Km+tBcSFO9gt{EKv+p6e{NOazVi43;Bp=v#+DGft%uhNQ|jn%c1vt-{ns z$YqcmlV6}_S6iwjm%v$H<*s&Dx$Ej7)Q0VpV+d~zLKDx@5dzwH@J$zC)$n=5%3Gt$C7H#r+r#eQ(rYghFeZf~rrOkP_jN!dsjdt2vhWVO^# zTN%H$#@$-I5?XlJmL9Gd)HhUDyD;{g`47r>YutaDX8;=+6Jj92BZa`8kqv~$0P(u68^X>Co_8pIC$s)sQT zz3F8cnXHd68j^Dz0m&YfR=BV(YNWR*&^x^KYP9Cpuu+;{{op+AnwqMHdUs{rMR4}F zSC^%Ab@3Z3ob}5`YyECQ@~9;>HI*gKI#+3hvvQpzNyo=%{gA-;vPx(99&ldcTOoP; z%VX7qTiS-lXajwPQTW5K2c0oj+mKj#**n2x8mGUQ*LL6Neg4gWq275d7j{m$_Poyp zZ{u~?#>3VZGKj9k#?kr+SqOSTwuuTFknKJWe71Axo}A@#5Ve_V?LE|HtF`gop4KYV zG~GGWG`p|wx7vL?t=mw`SNrzg*I9;0(p**NMonuyhnhYLYTC%thk9n)hMJ~(9W||o z1wm*%oT$xF$9onv&F*v5G&?);r0T>r>hrgYD>Q&-4P^vyy$R2?)Q9Ie8VInIsZm!lj*eX^~Q)5AHmY8B`nJ@F^Dq5Z9dDe&D!X4K$hsFzHqyEn+C$|vISdB=KWz8gR!IDJc-Yf zU`S(NmQO!wb7@O?in@40DAR0{%du2#>U1K=Djx@9hrB5V>^2z323JC?hOwcP8DSG; z)D&Ttk75Dygo3UFTjhTPV?#k(keT6OlxeZabFgDgg>o1N!mrR`_c;e0!U@e1pHfDo z-7qbjnj`t0^hMgmSsIA6%XhF}@teU|K~^!3nnu{=4(vr*4v~3rmj<9qGkZMXysigJ zPS6wd)V&tE?aQmas;*>Zk71W$+N+DrFaoS7MrLq$}YbG-E3{vP^-b;LYdJv zv6Pyk?ecl-V^h@5JkjT1oFTXumF|lzEL|m6BCWnPV4TYPYpR#}M%u+kG%(UG$Beii zR)AHkr>0SM`51JZL^v|aD!&b;CGiRtaP-5JIoc+>vB}+^LGA`i)+UE5{b1~v7a?ZZ zKT5MSsBdJ}ea23=f^k1AQoOA*V4Ptuep^1Dfz71L!CA63T61RzXGv_JzOi=s9&}}} z9jdO^(ve!`hEh(nRo(~2zG1Ogtn%w%?2V7=jd_%oCWE?=iorNDzMOISAQ<;ky%GJ? z7i*WpvAJ?uHSb+uY%zqkjLY)TMrF@`13f29{fzrYj?t{{=X()ljJL~Yq2{6B2;BNo zH&Q>JhQzV-)p(oSiY=M_LE;hVFR5vQ-4GT-FHEq>3t}W`A)2si(R7?L;_N<_SV>Z7 zYFw628ESc46F;JXxK{BAHBD?aj2%Z`P0Vb?w+?^tNd^F4Xlh}JH7_A#wfqfb>)&hw ze7Yqoz=34|9MlXz05btDGjSAB z`!5A};$^!2LCN|Rx_-q(PX&@YuF^S69(c9RS$dN(F_tc+_-1em7Px9r4$0*&lJr_i zR3P`}GXNKs25L_Xr$dRpt?a>iR--&9xyJ^8+v@=?EIDO~>ESW#4bC&#$ckBE?$##2q zF>u+ViT_fv;Scn7mP3L406TUN;PyiR7naPsn4qxajCBK?$z!@a3CWr0&BcRDFTkEY z5AXnI0WK`L<39ncKM!zuPzt)08s5t8Tm;za62OHe^UF+7STcVRVEdow@?}U*;hllO z?f;A2K}+N)eU$%yslUkoY2v?Zd-yAN2cDl(`+wdZ^bD?m)<8$edQ6{n?Utiaxtmbt&PU_nXt7eJFW%ap8{eDM?rU z9sTywWle< zi*BYlgf~sHI)o4Hz;g(_hv!hrNOuTd+JmP|xAF9&oD7E;M*Hy`PM_gvq`XWA-OV&o zZ>A#rX&}=f0%(4gLj+O}oXQqRe&N7m5mLgK9XqJO~XB+7oFKh#9n_0P)R5d|zb>GJh~0mS%~-w6`|1nLc})@@qrak z-aNzy);muTg){(mYChtduZa27GavCSKzs`nv5<-uAijl&4{R~X3lSe!)j~x$s1K}S z5#n2COA%iY;sYzEj3UGb)>WhkH{Ay7T!#3TDPkS%UxxUWBR;Sy%3F^3 zzXxw?e^RDm^O@Uoqkg9;2IJ z+e;B&sUn`B9i@nG4dMgaMj2}mA6VBKMeLy4V4W_+=TgLzwBLpJ%7)W(W%;6=^2!jO z&UVp2St(tQ4WSj~(W3ZlOZj&~oH^U#US$w(oV~SfmX}Z22plKyw|qE$z3`^onh>Ms zce01@XJOFU_twoa&=L1tvbUF<{YmZG2vj=cUD~(Wv0ZfiWC(rM9%q8$+wZi(ZG5uh zuR}qTb_R#WunOh-0BiY+GoFfe#wP4VjSCOREqv(O2XNujAnUH{vxWSC4(yDfpYDt{ z;ujtDaC8Oo65ZPwC-bQtKWs?}G-+3`JQ=lAAdL!k74l!LQZz6UZ~=V)AKZQnoCEmO zNWfD-2f$9T6P-W{uo>XMEcHSO-+{Ux*c`xB)AW z4wM0WLgX(4K5CuD3KRi+B5ji-i+ZF#0dfT3 zQ*|~c4P|x*UdpIRoCZz_r^b^C3+fR7zis|f6M()%zBAMOJ&sn6wGH4XI2w+EW9H;l z1Dv;Vfb+EmC;_qn_Kv+)fY|^?$JvPlI8#{DQVx&}*a6OaB47hDfdl}{qg9%ON;H6_ zF7YZ!0@#oZMgkLnSRe)%3yjj`7|788_ZbI_2jT#a4X?zB0Qcqg6kR4m^4iP*(s@m> zXK4UOH3gUo(umIrqwFFoUECLqloORsJ?~ya>1Xcs= zB(INgm|t2B6a%XOUND|$N>O(K+{Wv<65#c`7I5oehaUx+0bW$Rz#4&ifEOj}xXz1? z7aYT5$GTST=uvv4+5mkEu<3zz9-JL!ooA5Ey6$nv$AB%s_kbsWtpI0>*U1hY?U0@o zh>ZKIpWMa{Xi+BiP});5wEC%!DfeVRjKQF(CFve2epsj#bwB*>EB|NRA!6m^gro$l zouPtOpEQZXq;!}qp1Y_8w(D_`BLYu)3)o~N;FVvc)(&%;=kDrhOVSJdw@a4|wtH^3 zlBOM4b$*DyVX(!=K)>oRrFpKyUY340E9SN#d(gmhVdn0ea%%FBst*TSatsu?*JSbB zupRB$y!Lm#V{Z%^R8ZkwbE>K=O81ltndq2Ig*DOAuPxhK4Jy(R3b!YV2_5<4ot%iG1^gdIn=T7k#U$jMS zI<}Yw4LrAwzJa#53*xC)2U})P)jqTMini^W;pdN6GYkPl_|ZS_OOEv1-7Jf}lD1{{ zq8kS7W#KI(`_Y)rWP{C*mUbrN5cFiHSuCLAo#kRB8TTJd^W3PJKKeF6+12oKwP#8K zwgk_;;-#Z6#qFgJrl7^DEdY5Ky}jQQ>A9SX{PKw#E97sQVUP?1eCkIJqo4!Hk)Aul z7yafJkBUpY2ZN-9R5r*RMyn5$iAD6vffP9M^#QXuO`!)<3~9q?&cS3u>~PwAFd65T zGY8G0nXVm75nJiIgDDozrRBvNAC{b|`|&mmIVl1EBa#Q_ z=A|@Pr6geWVpWq@SBg4r2q|4rVi~Pd`Qa|J#dCFe@tbD@?URp{tGy<1ukF;|RUn8) zKAmC+3Z`EjHBt4`5e9Q8z4){#(sSvUyJy7Z2~#@v8pLMy3H?-qd_#5^4L_Wp=DA}O zo5sFU9CNBd^F0~Pdu|zxVRx7P;rs8ls4Yne*fS@ZXy;*5s^{AA&X1;chn!nHPB*~& z^;}HOXqtB5;*}XjwIzcm|1}jKG1DIpM~L5&{}Hprb7eX7(4JREKlb5KeGI$})l#TW zL#l}ykK~Kz=x0X?uv~(>Q!LtDrPTJbYagH6yefZ#-Vsyek+a*3jo?u{d#R(lK-{AD zyHn5)dP+Jv8E0G9(Q-X!mfmRXu5anTrWWTfz8j5!5Iy^Pk;F4*Toi3Mo`RpW3!h1` zybaqZEbzTcuVtRDE_esFy!v2!mAa3G(b;FLhGC=V&NC)mXuWB{u?V4#HmL4CW`2-_ z(I>~OIFOG%o-(N3aJ(D?@W8)6K0@3i;|X4IIVa3w4=u;@I5nI|iPSG6+e$W!nVLP! zKV8j)n(W~?p`A!pKT~D{t|TX(G-)E)plz3idlEIDOg5Ot(GO3W{2F_-efB~R&XU>K zbSdCeQbefGE^{AGPkmK1KJ@KJx>ex~T6XFS$~kYDu(2*NK5=7Re0sv9bmXJHyt$;I ztjtx*m#_`>W%23pk2ovcYn=72v-5hqVrkuV$HI+;wMyMQ=k(n-v`LTX(r-#b+GJaF zdUTz;*;P|E5jFHJudXS>?!x5_%X|CYyp^!Y74}xp=^F#mM|#KE{qWi{W{In|&RtV& zi=O=74}t5eM2hzF#Q}QUoW_1_-0iyja}NCJ{MYq<@2c}BEv&C~SC>B+z@$G3K>f{d O1eQ?_CN|^1PyY!_NDY<% delta 8252 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 { 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.url, }, }); @@ -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, + ): Promise> { const { configuration } = session; const { request, url } = configuration; @@ -166,18 +170,28 @@ class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory } const adapter = new FileDebugSession(session.id); + await adapter.initialize(); return new vscode.DebugAdapterInlineImplementation(adapter); } } class FileDebugSession extends DebugSession { - readonly adapter: DebugAdapter; + adapter: DebugAdapter; + sessionId?: string; constructor(sessionId?: string) { super(); - const uniqueId = sessionId ?? Math.random().toString(36).slice(2); - const url = `ws+unix://${tmpdir()}/${uniqueId}.sock`; + this.sessionId = sessionId; + } + async initialize() { + const uniqueId = this.sessionId ?? Math.random().toString(36).slice(2); + let url; + if (process.platform === "win32") { + url = `ws://127.0.0.1:${await getAvailablePort()}/${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)); @@ -204,11 +218,19 @@ class FileDebugSession extends DebugSession { } class TerminalDebugSession extends FileDebugSession { - readonly signal: UnixSignal; + signal: TCPSocketSignal | UnixSignal; constructor() { super(); - this.signal = new UnixSignal(); + } + + async initialize() { + await super.initialize(); + if (process.platform === "win32") { + this.signal = new TCPSocketSignal(await getAvailablePort()); + } else { + this.signal = new UnixSignal(); + } this.signal.on("Signal.received", () => { vscode.debug.startDebugging(undefined, { ...ATTACH_CONFIGURATION, @@ -222,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.url, }, isTransient: true, iconPath: new vscode.ThemeIcon("debug-console"), diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 2ac52a7c7a..cf79349cd3 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -111,7 +111,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 {