|
1 | 1 | import assert from "assert"; |
2 | | -import childProcess from "child_process"; |
| 2 | +import childProcess, { spawn } from "child_process"; |
| 3 | +import { randomBytes } from "crypto"; |
3 | 4 | import { Abortable, once } from "events"; |
| 5 | +import path from "path"; |
4 | 6 | import rl from "readline"; |
5 | 7 | import { Readable } from "stream"; |
6 | 8 | import { $ as $colors, red } from "kleur/colors"; |
@@ -119,13 +121,40 @@ function getRuntimeArgs(options: RuntimeOptions) { |
119 | 121 | return args; |
120 | 122 | } |
121 | 123 |
|
| 124 | +/** |
| 125 | + * Copied from https://github.com/microsoft/vscode-js-debug/blob/0b5e0dade997b3c702a98e1f58989afcb30612d6/src/targets/node/bootloader/environment.ts#L129 |
| 126 | + * |
| 127 | + * This function returns the segment of process.env.VSCODE_INSPECTOR_OPTIONS that corresponds to the current process (rather than a parent process) |
| 128 | + */ |
| 129 | +function getInspectorOptions() { |
| 130 | + const value = process.env.VSCODE_INSPECTOR_OPTIONS; |
| 131 | + if (!value) { |
| 132 | + return undefined; |
| 133 | + } |
| 134 | + |
| 135 | + const ownOptions = value |
| 136 | + .split(":::") |
| 137 | + .reverse() |
| 138 | + .find((v) => !!v); |
| 139 | + if (!ownOptions) { |
| 140 | + return; |
| 141 | + } |
| 142 | + |
| 143 | + try { |
| 144 | + return JSON.parse(ownOptions); |
| 145 | + } catch { |
| 146 | + return undefined; |
| 147 | + } |
| 148 | +} |
| 149 | + |
122 | 150 | export class Runtime { |
123 | 151 | #process?: childProcess.ChildProcess; |
124 | 152 | #processExitPromise?: Promise<void>; |
125 | 153 |
|
126 | 154 | async updateConfig( |
127 | 155 | configBuffer: Buffer, |
128 | | - options: Abortable & RuntimeOptions |
| 156 | + options: Abortable & RuntimeOptions, |
| 157 | + workerNames: string[] |
129 | 158 | ): Promise<SocketPorts | undefined> { |
130 | 159 | // 1. Stop existing process (if any) and wait for exit |
131 | 160 | await this.dispose(); |
@@ -156,7 +185,46 @@ export class Runtime { |
156 | 185 | await once(runtimeProcess.stdin, "finish"); |
157 | 186 |
|
158 | 187 | // 4. Wait for sockets to start listening |
159 | | - return waitForPorts(controlPipe, options); |
| 188 | + const ports = await waitForPorts(controlPipe, options); |
| 189 | + if (ports?.has(kInspectorSocket) && process.env.VSCODE_INSPECTOR_OPTIONS) { |
| 190 | + // We have an inspector socket and we're in a VSCode Debug Terminal. |
| 191 | + // Let's startup a watchdog service to register ourselves as a debuggable target |
| 192 | + |
| 193 | + // First, we need to _find_ the watchdog script. It's located next to bootloader.js, which should be injected as a require hook |
| 194 | + const bootloaderPath = |
| 195 | + process.env.NODE_OPTIONS?.match(/--require "(.*?)"/)?.[1]; |
| 196 | + |
| 197 | + if (!bootloaderPath) { |
| 198 | + return ports; |
| 199 | + } |
| 200 | + const watchdogPath = path.resolve(bootloaderPath, "../watchdog.js"); |
| 201 | + |
| 202 | + const info = getInspectorOptions(); |
| 203 | + |
| 204 | + for (const name of workerNames) { |
| 205 | + // This is copied from https://github.com/microsoft/vscode-js-debug/blob/0b5e0dade997b3c702a98e1f58989afcb30612d6/src/targets/node/bootloader.ts#L284 |
| 206 | + // It spawns a detached "watchdog" process for each corresponding (user) Worker in workerd which will maintain the VSCode debug connection |
| 207 | + const p = spawn(process.execPath, [watchdogPath], { |
| 208 | + env: { |
| 209 | + NODE_INSPECTOR_INFO: JSON.stringify({ |
| 210 | + ipcAddress: info.inspectorIpc || "", |
| 211 | + pid: String(this.#process.pid), |
| 212 | + scriptName: name, |
| 213 | + inspectorURL: `ws://127.0.0.1:${ports?.get(kInspectorSocket)}/core:user:${name}`, |
| 214 | + waitForDebugger: true, |
| 215 | + ownId: randomBytes(12).toString("hex"), |
| 216 | + openerId: info.openerId, |
| 217 | + }), |
| 218 | + NODE_SKIP_PLATFORM_CHECK: process.env.NODE_SKIP_PLATFORM_CHECK, |
| 219 | + ELECTRON_RUN_AS_NODE: "1", |
| 220 | + }, |
| 221 | + stdio: "ignore", |
| 222 | + detached: true, |
| 223 | + }); |
| 224 | + p.unref(); |
| 225 | + } |
| 226 | + } |
| 227 | + return ports; |
160 | 228 | } |
161 | 229 |
|
162 | 230 | dispose(): Awaitable<void> { |
|
0 commit comments