mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
More improvements to debugger support (#4345)
* More fixes for dap * More changes * More changes 2 * More fixes * Fix debugger.ts * Bun Terminal
This commit is contained in:
Binary file not shown.
@@ -1,143 +0,0 @@
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`remoteObjectToString 1`] = `"undefined"`;
|
||||
|
||||
exports[`remoteObjectToString 2`] = `"null"`;
|
||||
|
||||
exports[`remoteObjectToString 3`] = `"true"`;
|
||||
|
||||
exports[`remoteObjectToString 4`] = `"false"`;
|
||||
|
||||
exports[`remoteObjectToString 5`] = `"0"`;
|
||||
|
||||
exports[`remoteObjectToString 6`] = `"1"`;
|
||||
|
||||
exports[`remoteObjectToString 7`] = `"3.141592653589793"`;
|
||||
|
||||
exports[`remoteObjectToString 8`] = `"-2.718281828459045"`;
|
||||
|
||||
exports[`remoteObjectToString 9`] = `"NaN"`;
|
||||
|
||||
exports[`remoteObjectToString 10`] = `"Infinity"`;
|
||||
|
||||
exports[`remoteObjectToString 11`] = `"-Infinity"`;
|
||||
|
||||
exports[`remoteObjectToString 12`] = `"0n"`;
|
||||
|
||||
exports[`remoteObjectToString 13`] = `"1n"`;
|
||||
|
||||
exports[`remoteObjectToString 14`] = `"10000000000000n"`;
|
||||
|
||||
exports[`remoteObjectToString 15`] = `"-10000000000000n"`;
|
||||
|
||||
exports[`remoteObjectToString 16`] = `""""`;
|
||||
|
||||
exports[`remoteObjectToString 17`] = `"" ""`;
|
||||
|
||||
exports[`remoteObjectToString 18`] = `""Hello""`;
|
||||
|
||||
exports[`remoteObjectToString 19`] = `""Hello World""`;
|
||||
|
||||
exports[`remoteObjectToString 20`] = `"Array(0)"`;
|
||||
|
||||
exports[`remoteObjectToString 21`] = `"Array(3) [1, 2, 3]"`;
|
||||
|
||||
exports[`remoteObjectToString 22`] = `"Array(4) ["a", 1, null, undefined]"`;
|
||||
|
||||
exports[`remoteObjectToString 23`] = `"Array(2) [1, Array]"`;
|
||||
|
||||
exports[`remoteObjectToString 24`] = `"Array(1) [Array]"`;
|
||||
|
||||
exports[`remoteObjectToString 25`] = `"{}"`;
|
||||
|
||||
exports[`remoteObjectToString 26`] = `"{a: 1}"`;
|
||||
|
||||
exports[`remoteObjectToString 27`] = `"{a: 1, b: 2, c: 3}"`;
|
||||
|
||||
exports[`remoteObjectToString 28`] = `"{a: Object}"`;
|
||||
|
||||
exports[`remoteObjectToString 29`] = `
|
||||
"ƒ() {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`remoteObjectToString 30`] = `
|
||||
"ƒ namedFunction() {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`remoteObjectToString 31`] = `
|
||||
"class {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`remoteObjectToString 32`] = `
|
||||
"class namedClass {
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`remoteObjectToString 33`] = `
|
||||
"class namedClass {
|
||||
a() {
|
||||
}
|
||||
b = 1;
|
||||
c = [
|
||||
null,
|
||||
undefined,
|
||||
"a",
|
||||
{
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3
|
||||
}
|
||||
];
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`remoteObjectToString 34`] = `"Wed Dec 31 1969 16:00:00 GMT-0800 (Pacific Standard Time)"`;
|
||||
|
||||
exports[`remoteObjectToString 35`] = `"Invalid Date"`;
|
||||
|
||||
exports[`remoteObjectToString 36`] = `"/(?:)/"`;
|
||||
|
||||
exports[`remoteObjectToString 37`] = `"/abc/"`;
|
||||
|
||||
exports[`remoteObjectToString 38`] = `"/abc/g"`;
|
||||
|
||||
exports[`remoteObjectToString 39`] = `"/abc/"`;
|
||||
|
||||
exports[`remoteObjectToString 40`] = `"Set(0)"`;
|
||||
|
||||
exports[`remoteObjectToString 41`] = `"Set(3) [1, 2, 3]"`;
|
||||
|
||||
exports[`remoteObjectToString 42`] = `"WeakSet(0)"`;
|
||||
|
||||
exports[`remoteObjectToString 43`] = `"WeakSet(3) [{a: 1}, {b: 2}, {c: 3}]"`;
|
||||
|
||||
exports[`remoteObjectToString 44`] = `"Map(0)"`;
|
||||
|
||||
exports[`remoteObjectToString 45`] = `"Map(3) {"a" => 1, "b" => 2, "c" => 3}"`;
|
||||
|
||||
exports[`remoteObjectToString 46`] = `"WeakMap(0)"`;
|
||||
|
||||
exports[`remoteObjectToString 47`] = `"WeakMap(3) {{a: 1} => 1, {b: 2} => 2, {c: 3} => 3}"`;
|
||||
|
||||
exports[`remoteObjectToString 48`] = `"Symbol()"`;
|
||||
|
||||
exports[`remoteObjectToString 49`] = `"Symbol(namedSymbol)"`;
|
||||
|
||||
exports[`remoteObjectToString 50`] = `"Error"`;
|
||||
|
||||
exports[`remoteObjectToString 51`] = `"TypeError: This is a TypeError"`;
|
||||
|
||||
exports[`remoteObjectToString 52`] = `"Headers {append: ƒ, delete: ƒ, get: ƒ, getAll: ƒ, has: ƒ, …}"`;
|
||||
|
||||
exports[`remoteObjectToString 53`] = `"Headers {a: "1", append: ƒ, b: "2", delete: ƒ, get: ƒ, …}"`;
|
||||
|
||||
exports[`remoteObjectToString 54`] = `"Request {arrayBuffer: ƒ, blob: ƒ, body: null, bodyUsed: false, cache: "default", …}"`;
|
||||
|
||||
exports[`remoteObjectToString 55`] = `"Request {arrayBuffer: ƒ, blob: ƒ, body: ReadableStream, bodyUsed: false, cache: "default", …}"`;
|
||||
|
||||
exports[`remoteObjectToString 56`] = `"Response {arrayBuffer: ƒ, blob: ƒ, body: null, bodyUsed: false, clone: ƒ, …}"`;
|
||||
|
||||
exports[`remoteObjectToString 57`] = `"Response {arrayBuffer: ƒ, blob: ƒ, body: ReadableStream, bodyUsed: false, clone: ƒ, …}"`;
|
||||
@@ -1,99 +0,0 @@
|
||||
console.log(
|
||||
undefined,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
1,
|
||||
Math.PI,
|
||||
-Math.E,
|
||||
NaN,
|
||||
Infinity,
|
||||
-Infinity,
|
||||
BigInt(0),
|
||||
BigInt(1),
|
||||
BigInt("10000000000000"),
|
||||
BigInt("-10000000000000"),
|
||||
"",
|
||||
" ",
|
||||
"Hello",
|
||||
"Hello World",
|
||||
[],
|
||||
[1, 2, 3],
|
||||
["a", 1, null, undefined],
|
||||
[1, [2, [3, [4, [5, [6, [7, [8, [9, [10]]]]]]]]]],
|
||||
[[[[[]]]]],
|
||||
{},
|
||||
{ a: 1 },
|
||||
{ a: 1, b: 2, c: 3 },
|
||||
{ a: { b: { c: { d: { e: { f: { g: { h: { i: { j: 10 } } } } } } } } } },
|
||||
function () {},
|
||||
function namedFunction() {},
|
||||
class {},
|
||||
class namedClass {},
|
||||
class namedClass {
|
||||
a() {}
|
||||
b = 1;
|
||||
c = [
|
||||
null,
|
||||
undefined,
|
||||
"a",
|
||||
{
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
},
|
||||
];
|
||||
},
|
||||
new Date(0),
|
||||
new Date(NaN),
|
||||
new RegExp(),
|
||||
new RegExp("abc"),
|
||||
new RegExp("abc", "g"),
|
||||
/abc/,
|
||||
new Set(),
|
||||
new Set([1, 2, 3]),
|
||||
new WeakSet(),
|
||||
new WeakSet([{ a: 1 }, { b: 2 }, { c: 3 }]),
|
||||
new Map(),
|
||||
new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
new WeakMap(),
|
||||
new WeakMap([
|
||||
[{ a: 1 }, 1],
|
||||
[{ b: 2 }, 2],
|
||||
[{ c: 3 }, 3],
|
||||
]),
|
||||
Symbol(),
|
||||
Symbol("namedSymbol"),
|
||||
new Error(),
|
||||
new TypeError("This is a TypeError"),
|
||||
//"a".repeat(10000),
|
||||
//["a"].fill("a", 0, 10000),
|
||||
new Headers(),
|
||||
new Headers({
|
||||
a: "1",
|
||||
b: "2",
|
||||
}),
|
||||
new Request("https://example.com/"),
|
||||
new Request("https://example.com/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
a: "1",
|
||||
b: "2",
|
||||
},
|
||||
body: '{"example":true}',
|
||||
}),
|
||||
new Response(),
|
||||
new Response('{"example":true}', {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
a: "1",
|
||||
b: "2",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -1,62 +0,0 @@
|
||||
import { beforeAll, afterAll, test, expect } from "bun:test";
|
||||
import type { JSC } from "../../bun-inspector-protocol";
|
||||
import { WebSocketInspector } from "../../bun-inspector-protocol";
|
||||
import type { PipedSubprocess } from "bun";
|
||||
import { spawn } from "bun";
|
||||
import { remoteObjectToString } from "./preview";
|
||||
|
||||
let subprocess: PipedSubprocess | undefined;
|
||||
let objects: JSC.Runtime.RemoteObject[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
subprocess = spawn({
|
||||
cwd: import.meta.dir,
|
||||
cmd: [process.argv0, "--inspect-wait=0", "fixtures/preview.js"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
});
|
||||
const decoder = new TextDecoder();
|
||||
let url: URL;
|
||||
for await (const chunk of subprocess!.stdout) {
|
||||
const text = decoder.decode(chunk);
|
||||
if (text.includes("ws://")) {
|
||||
url = new URL(/(ws:\/\/.*)/.exec(text)![0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
objects = await new Promise((resolve, reject) => {
|
||||
const inspector = new WebSocketInspector({
|
||||
url,
|
||||
listener: {
|
||||
["Inspector.connected"]: () => {
|
||||
inspector.send("Inspector.enable");
|
||||
inspector.send("Runtime.enable");
|
||||
inspector.send("Console.enable");
|
||||
inspector.send("Debugger.enable");
|
||||
inspector.send("Debugger.resume");
|
||||
inspector.send("Inspector.initialized");
|
||||
},
|
||||
["Inspector.disconnected"]: error => {
|
||||
reject(error);
|
||||
},
|
||||
["Console.messageAdded"]: ({ message }) => {
|
||||
const { parameters } = message;
|
||||
resolve(parameters!);
|
||||
inspector.close();
|
||||
},
|
||||
},
|
||||
});
|
||||
inspector.start();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
subprocess?.kill();
|
||||
});
|
||||
|
||||
test("remoteObjectToString", () => {
|
||||
for (const object of objects) {
|
||||
expect(remoteObjectToString(object)).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import type { JSC } from "../../bun-inspector-protocol";
|
||||
|
||||
export function remoteObjectToString(remoteObject: JSC.Runtime.RemoteObject): string {
|
||||
const { type, subtype, value, description, className, preview } = remoteObject;
|
||||
switch (type) {
|
||||
case "undefined":
|
||||
return "undefined";
|
||||
case "boolean":
|
||||
case "number":
|
||||
return description ?? JSON.stringify(value);
|
||||
case "string":
|
||||
return JSON.stringify(value ?? description);
|
||||
case "symbol":
|
||||
case "bigint":
|
||||
return description!;
|
||||
case "function":
|
||||
return description!.replace("function", "ƒ") || "ƒ";
|
||||
}
|
||||
switch (subtype) {
|
||||
case "null":
|
||||
return "null";
|
||||
case "regexp":
|
||||
case "date":
|
||||
case "error":
|
||||
return description!;
|
||||
}
|
||||
if (preview) {
|
||||
return objectPreviewToString(preview);
|
||||
}
|
||||
if (className) {
|
||||
return className;
|
||||
}
|
||||
return description || "Object";
|
||||
}
|
||||
|
||||
export function objectPreviewToString(objectPreview: JSC.Runtime.ObjectPreview): string {
|
||||
const { type, subtype, entries, properties, overflow, description, size } = objectPreview;
|
||||
if (type !== "object") {
|
||||
return remoteObjectToString(objectPreview);
|
||||
}
|
||||
let items: string[];
|
||||
if (entries) {
|
||||
items = entries.map(entryPreviewToString).sort();
|
||||
} else if (properties) {
|
||||
if (isIndexed(subtype)) {
|
||||
items = properties.map(indexedPropertyPreviewToString).sort();
|
||||
} else {
|
||||
items = properties.map(namedPropertyPreviewToString).sort();
|
||||
}
|
||||
} else {
|
||||
items = ["…"];
|
||||
}
|
||||
if (overflow) {
|
||||
items.push("…");
|
||||
}
|
||||
let label: string;
|
||||
if (description === "Object") {
|
||||
label = "";
|
||||
} else if (size === undefined) {
|
||||
label = description!;
|
||||
} else {
|
||||
label = `${description}(${size})`;
|
||||
}
|
||||
if (!items.length) {
|
||||
return label || "{}";
|
||||
}
|
||||
if (label) {
|
||||
label += " ";
|
||||
}
|
||||
if (isIndexed(subtype)) {
|
||||
return `${label}[${items.join(", ")}]`;
|
||||
}
|
||||
return `${label}{${items.join(", ")}}`;
|
||||
}
|
||||
|
||||
function propertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
|
||||
const { type, value, ...preview } = propertyPreview;
|
||||
if (type === "accessor") {
|
||||
return "ƒ";
|
||||
}
|
||||
return remoteObjectToString({ ...preview, type, description: value });
|
||||
}
|
||||
|
||||
function entryPreviewToString(entryPreview: JSC.Runtime.EntryPreview): string {
|
||||
const { key, value } = entryPreview;
|
||||
if (key) {
|
||||
return `${objectPreviewToString(key)} => ${objectPreviewToString(value)}`;
|
||||
}
|
||||
return objectPreviewToString(value);
|
||||
}
|
||||
|
||||
function namedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
|
||||
const { name, valuePreview } = propertyPreview;
|
||||
if (valuePreview) {
|
||||
return `${name}: ${objectPreviewToString(valuePreview)}`;
|
||||
}
|
||||
return `${name}: ${propertyPreviewToString(propertyPreview)}`;
|
||||
}
|
||||
|
||||
function indexedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
|
||||
const { valuePreview } = propertyPreview;
|
||||
if (valuePreview) {
|
||||
return objectPreviewToString(valuePreview);
|
||||
}
|
||||
return propertyPreviewToString(propertyPreview);
|
||||
}
|
||||
|
||||
function isIndexed(type?: JSC.Runtime.RemoteObject["subtype"]): boolean {
|
||||
return type === "array" || type === "set" || type === "weakset";
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export type * from "./protocol";
|
||||
export * from "./debugger/adapter";
|
||||
export type * from "./src/protocol";
|
||||
export * from "./src/debugger/adapter";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "bun-debug-adapter-protocol",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.4",
|
||||
"ws": "^8.13.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Protocol, Type } from "../protocol/schema.d.ts";
|
||||
import type { Protocol, Type } from "../src/protocol/schema";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { DAP } from "..";
|
||||
// @ts-ignore: FIXME - there is something wrong with the types
|
||||
import type { JSC, InspectorListener } from "../../bun-inspector-protocol";
|
||||
import { WebSocketInspector } from "../../bun-inspector-protocol";
|
||||
import type { DAP } from "../protocol";
|
||||
// @ts-ignore
|
||||
import type { JSC, InspectorListener, WebSocketInspectorOptions } from "../../../bun-inspector-protocol";
|
||||
import { UnixWebSocketInspector, remoteObjectToString } from "../../../bun-inspector-protocol/index";
|
||||
import type { ChildProcess } from "node:child_process";
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import capabilities from "./capabilities";
|
||||
import { Location, SourceMap } from "./sourcemap";
|
||||
import { remoteObjectToString } from "./preview";
|
||||
import { compare, parse } from "semver";
|
||||
|
||||
type InitializeRequest = DAP.InitializeRequest & {
|
||||
@@ -21,6 +20,7 @@ type LaunchRequest = DAP.LaunchRequest & {
|
||||
env?: Record<string, string>;
|
||||
inheritEnv?: boolean;
|
||||
watch?: boolean | "hot";
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
type AttachRequest = DAP.AttachRequest & {
|
||||
@@ -71,24 +71,31 @@ type Variable = DAP.Variable & {
|
||||
};
|
||||
|
||||
type IDebugAdapter = {
|
||||
[E in keyof DAP.EventMap]?: (event: DAP.EventMap[E]) => void;
|
||||
[E in keyof DAP.EventMap]?: (event: DAP.EventMap[E]) => void | Promise<void>;
|
||||
} & {
|
||||
[R in keyof DAP.RequestMap]?: (
|
||||
request: DAP.RequestMap[R],
|
||||
) => void | DAP.ResponseMap[R] | Promise<void | DAP.ResponseMap[R]>;
|
||||
) => void | DAP.ResponseMap[R] | Promise<DAP.ResponseMap[R]> | Promise<void>;
|
||||
};
|
||||
|
||||
export type DebugAdapterOptions = {
|
||||
sendToAdapter(message: DAP.Request | DAP.Response | DAP.Event): Promise<void>;
|
||||
export type DebugAdapterOptions = WebSocketInspectorOptions & {
|
||||
url: string | URL;
|
||||
send(message: DAP.Request | DAP.Response | DAP.Event): Promise<void>;
|
||||
stdout?(message: string): void;
|
||||
stderr?(message: string): void;
|
||||
};
|
||||
|
||||
// This adapter only support single-threaded debugging,
|
||||
// which means that there is only one thread at a time.
|
||||
const threadId = 1;
|
||||
|
||||
// @ts-ignore
|
||||
export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
#sendToAdapter: DebugAdapterOptions["sendToAdapter"];
|
||||
#inspector: WebSocketInspector;
|
||||
#url: URL;
|
||||
#sendToAdapter: DebugAdapterOptions["send"];
|
||||
#stdout?: DebugAdapterOptions["stdout"];
|
||||
#stderr?: DebugAdapterOptions["stderr"];
|
||||
#inspector: UnixWebSocketInspector;
|
||||
#sourceId: number;
|
||||
#pendingSources: Map<string, ((source: Source) => void)[]>;
|
||||
#sources: Map<string | number, Source>;
|
||||
@@ -102,12 +109,14 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
#initialized?: InitializeRequest;
|
||||
#launched?: LaunchRequest;
|
||||
#connected?: boolean;
|
||||
#terminated?: boolean;
|
||||
#url?: URL;
|
||||
|
||||
constructor({ sendToAdapter }: DebugAdapterOptions) {
|
||||
this.#inspector = new WebSocketInspector({ listener: this });
|
||||
this.#sendToAdapter = sendToAdapter;
|
||||
constructor({ url, send, stdout, stderr, ...options }: DebugAdapterOptions) {
|
||||
this.#url = new URL(url);
|
||||
// @ts-ignore
|
||||
this.#inspector = new UnixWebSocketInspector({ ...options, url, listener: this });
|
||||
this.#stdout = stdout;
|
||||
this.#stderr = stderr;
|
||||
this.#sendToAdapter = send;
|
||||
this.#sourceId = 1;
|
||||
this.#pendingSources = new Map();
|
||||
this.#sources = new Map();
|
||||
@@ -119,6 +128,10 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
this.#variables = [{ name: "", value: "", type: undefined, variablesReference: 0 }];
|
||||
}
|
||||
|
||||
get inspector(): UnixWebSocketInspector {
|
||||
return this.#inspector;
|
||||
}
|
||||
|
||||
async accept(message: DAP.Request | DAP.Response | DAP.Event): Promise<void> {
|
||||
const { type } = message;
|
||||
|
||||
@@ -140,7 +153,6 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
}
|
||||
response = await this[command as keyof this](args);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message } = unknownToError(error);
|
||||
return this.#sendToAdapter({
|
||||
type: "response",
|
||||
@@ -238,7 +250,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
throw new Error("Another program is already running. Did you terminate the last session?");
|
||||
}
|
||||
|
||||
const { program, runtime = "bun", args = [], cwd, env = {}, inheritEnv = true, watch = true } = request;
|
||||
const { program, runtime = "bun", args = [], cwd, env = {}, inheritEnv = true, watch = false } = request;
|
||||
if (!program) {
|
||||
throw new Error("No program specified. Did you set the 'program' property in your launch.json?");
|
||||
}
|
||||
@@ -247,16 +259,39 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
throw new Error("Program must be a JavaScript or TypeScript file.");
|
||||
}
|
||||
|
||||
const argz = ["--inspect-wait=0", ...args];
|
||||
if (watch) {
|
||||
argz.push(watch === "hot" ? "--hot" : "--watch");
|
||||
const finalArgs = [...args];
|
||||
const isTest = isTestJavaScript(program);
|
||||
if (isTest) {
|
||||
finalArgs.unshift("test");
|
||||
}
|
||||
console.log(argz);
|
||||
|
||||
const subprocess = spawn(runtime, [...argz, program], {
|
||||
stdio: ["ignore", "pipe", "pipe", "pipe"],
|
||||
if (watch) {
|
||||
finalArgs.push(watch === "hot" ? "--hot" : "--watch");
|
||||
}
|
||||
|
||||
const finalEnv = inheritEnv
|
||||
? {
|
||||
...process.env,
|
||||
...env,
|
||||
}
|
||||
: {
|
||||
...env,
|
||||
};
|
||||
|
||||
finalEnv["BUN_INSPECT"] = `1${this.#url}`;
|
||||
finalEnv["BUN_INSPECT_NOTIFY"] = `unix://${this.#inspector.unix}`;
|
||||
|
||||
if (isTest) {
|
||||
finalEnv["FORCE_COLOR"] = "1";
|
||||
} else {
|
||||
// https://github.com/microsoft/vscode/issues/571
|
||||
finalEnv["NO_COLOR"] = "1";
|
||||
}
|
||||
|
||||
const subprocess = spawn(runtime, [...finalArgs, program], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
cwd,
|
||||
env: inheritEnv ? { ...process.env, ...env } : env,
|
||||
env: finalEnv,
|
||||
});
|
||||
|
||||
subprocess.on("spawn", () => {
|
||||
@@ -269,31 +304,40 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
});
|
||||
});
|
||||
|
||||
subprocess.on("exit", code => {
|
||||
subprocess.on("exit", (code, signal) => {
|
||||
this.#emit("exited", {
|
||||
exitCode: code ?? -1,
|
||||
});
|
||||
this.#process = undefined;
|
||||
});
|
||||
|
||||
const stdout: string[] = [];
|
||||
subprocess.stdout!.on("data", data => {
|
||||
if (!this.#url) {
|
||||
const text = data.toString();
|
||||
stdout.push(text);
|
||||
const url = (this.#url = parseUrlMaybe(text));
|
||||
this.#inspector.start(url);
|
||||
} else if (stdout.length) {
|
||||
stdout.length = 0;
|
||||
const text = data.toString();
|
||||
this.#stdout?.(text);
|
||||
|
||||
if (isTest) {
|
||||
this.#emit("output", {
|
||||
category: "stdout",
|
||||
output: text,
|
||||
source: {
|
||||
path: program,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const stderr: string[] = [];
|
||||
subprocess.stderr!.on("data", data => {
|
||||
if (!this.#url) {
|
||||
stderr.push(data.toString());
|
||||
} else if (stderr.length) {
|
||||
stderr.length = 0;
|
||||
const text = data.toString();
|
||||
this.#stderr?.(text);
|
||||
|
||||
if (isTest) {
|
||||
this.#emit("output", {
|
||||
category: "stdout", // Not stderr, since VSCode will highlight it as red.
|
||||
output: text,
|
||||
source: {
|
||||
path: program,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -317,11 +361,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
throw new Error(`Program exited with code ${reason} before the debugger could attached.`);
|
||||
}
|
||||
|
||||
for (let retries = 0; !this.#url && retries < 10; retries++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100 * retries));
|
||||
}
|
||||
|
||||
if (this.#url) {
|
||||
if (await this.#start()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -334,42 +374,68 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
|
||||
const { stdout: version } = spawnSync(runtime, ["--version"], { stdio: "pipe", encoding: "utf-8" });
|
||||
|
||||
if (parse(version, true) && compare("0.8.0", version, true)) {
|
||||
throw new Error(
|
||||
`Bun v${version.trim()} does not have debugger support. Please upgrade to v0.8 or later by running: \`bun upgrade\``,
|
||||
);
|
||||
}
|
||||
|
||||
for (const message of stderr) {
|
||||
this.#emit("output", {
|
||||
category: "stderr",
|
||||
output: message,
|
||||
source: {
|
||||
path: program,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const message of stdout) {
|
||||
this.#emit("output", {
|
||||
category: "stdout",
|
||||
output: message,
|
||||
source: {
|
||||
path: program,
|
||||
},
|
||||
});
|
||||
const minVersion = "0.8.2";
|
||||
if (parse(version, true) && compare(minVersion, version, true)) {
|
||||
throw new Error(`This extension requires Bun v${minVersion} or later. Please upgrade by running: bun upgrade`);
|
||||
}
|
||||
|
||||
throw new Error("Program started, but the debugger could not be attached.");
|
||||
}
|
||||
|
||||
attach(request: AttachRequest): void {
|
||||
async #start(url?: string | URL): Promise<boolean> {
|
||||
if (url) {
|
||||
this.#url = new URL(url);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const ok = await this.#inspector.start(url);
|
||||
if (ok) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100 * i));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async attach(request: DAP.AttachRequest): Promise<void> {
|
||||
try {
|
||||
await this.#attach(request);
|
||||
} catch (error) {
|
||||
// Some clients, like VSCode, will show a system-level popup when a `launch` request fails.
|
||||
// Instead, we want to show the error as a sidebar notification.
|
||||
const { message } = unknownToError(error);
|
||||
this.#emit("output", {
|
||||
category: "stderr",
|
||||
output: `Failed to start debugger.\n${message}`,
|
||||
});
|
||||
this.#emit("terminated");
|
||||
}
|
||||
}
|
||||
|
||||
async #attach(request: AttachRequest): Promise<void> {
|
||||
const { url } = request;
|
||||
this.#inspector.start(parseUrl(url));
|
||||
|
||||
if (this.#url.href === url) {
|
||||
this.#emit("output", {
|
||||
category: "debug console",
|
||||
output: "Debugger attached.\n",
|
||||
});
|
||||
|
||||
this.configurationDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.#start(url)) {
|
||||
this.configurationDone();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("Failed to attach to program.");
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
this.#terminated = true;
|
||||
this.#process?.kill();
|
||||
}
|
||||
|
||||
@@ -468,20 +534,20 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
if (!numberIsValid(line)) {
|
||||
return 0;
|
||||
}
|
||||
if (this.#initialized?.linesStartAt1) {
|
||||
return line - 1;
|
||||
if (!this.#initialized?.linesStartAt1) {
|
||||
return line;
|
||||
}
|
||||
return line;
|
||||
return line - 1;
|
||||
}
|
||||
|
||||
#columnTo0BasedColumn(column?: number): number {
|
||||
if (!numberIsValid(column)) {
|
||||
return 0;
|
||||
}
|
||||
if (this.#initialized?.columnsStartAt1) {
|
||||
return column - 1;
|
||||
if (!this.#initialized?.columnsStartAt1) {
|
||||
return column;
|
||||
}
|
||||
return column;
|
||||
return column - 1;
|
||||
}
|
||||
|
||||
#originalLocation(
|
||||
@@ -505,17 +571,17 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
}
|
||||
|
||||
#lineFrom0BasedLine(line?: number): number {
|
||||
if (this.#initialized?.linesStartAt1) {
|
||||
return numberIsValid(line) ? line + 1 : 1;
|
||||
if (!this.#initialized?.linesStartAt1) {
|
||||
return numberIsValid(line) ? line : 0;
|
||||
}
|
||||
return numberIsValid(line) ? line : 0;
|
||||
return numberIsValid(line) ? line + 1 : 1;
|
||||
}
|
||||
|
||||
#columnFrom0BasedColumn(column?: number): number {
|
||||
if (this.#initialized?.columnsStartAt1) {
|
||||
return numberIsValid(column) ? column + 1 : 1;
|
||||
if (!this.#initialized?.columnsStartAt1) {
|
||||
return numberIsValid(column) ? column : 0;
|
||||
}
|
||||
return numberIsValid(column) ? column : 0;
|
||||
return numberIsValid(column) ? column + 1 : 1;
|
||||
}
|
||||
|
||||
async setBreakpoints(request: DAP.SetBreakpointsRequest): Promise<DAP.SetBreakpointsResponse> {
|
||||
@@ -788,9 +854,12 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
this.#emit("initialized");
|
||||
}
|
||||
|
||||
["Inspector.disconnected"](error?: Error): void {
|
||||
if (this.#connected && this.#process?.exitCode === null) {
|
||||
this.#url = undefined;
|
||||
async ["Inspector.disconnected"](error?: Error): Promise<void> {
|
||||
if (this.#connected && this.#process?.exitCode === null && (await this.#start())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -799,14 +868,6 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
output: "Debugger detached.\n",
|
||||
});
|
||||
|
||||
if (error && !this.#terminated) {
|
||||
const { message } = error;
|
||||
this.#emit("output", {
|
||||
category: "stderr",
|
||||
output: `${message}\n`,
|
||||
});
|
||||
}
|
||||
|
||||
this.#emit("terminated");
|
||||
this.#reset();
|
||||
}
|
||||
@@ -942,8 +1003,7 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
const variables = parameters.map((parameter, i) => {
|
||||
const variable = this.#addVariable(parameter, { name: `${i}` });
|
||||
|
||||
const { value } = variable;
|
||||
output += value + " ";
|
||||
output += remoteObjectToString(parameter, true) + " ";
|
||||
|
||||
return variable;
|
||||
});
|
||||
@@ -1371,7 +1431,6 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.#terminated = true;
|
||||
this.#process?.kill();
|
||||
this.#inspector.close();
|
||||
this.#reset();
|
||||
@@ -1389,8 +1448,6 @@ export class DebugAdapter implements IDebugAdapter, InspectorListener {
|
||||
this.#launched = undefined;
|
||||
this.#initialized = undefined;
|
||||
this.#connected = undefined;
|
||||
this.#terminated = undefined;
|
||||
this.#url = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1598,6 +1655,10 @@ function isJavaScript(path: string): boolean {
|
||||
return /\.(c|m)?(j|t)sx?$/.test(path);
|
||||
}
|
||||
|
||||
function isTestJavaScript(path: string): boolean {
|
||||
return /\.(test|spec)\.(c|m)?(j|t)sx?$/.test(path);
|
||||
}
|
||||
|
||||
function parseUrl(hostname?: string, port?: number): URL {
|
||||
hostname ||= "localhost";
|
||||
port ||= 6499;
|
||||
@@ -1681,8 +1742,6 @@ function consoleLevelToAnsiColor(level: JSC.Console.ConsoleMessage["level"]): st
|
||||
return "\u001b[33m";
|
||||
case "error":
|
||||
return "\u001b[31m";
|
||||
case "debug":
|
||||
return "\u001b[36m";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DAP } from "..";
|
||||
import type { DAP } from "../protocol";
|
||||
|
||||
const capabilities: DAP.Capabilities = {
|
||||
/**
|
||||
@@ -52,6 +52,7 @@ class ActualSourceMap implements SourceMap {
|
||||
|
||||
generatedLocation(request: LocationRequest): Location {
|
||||
const { line, column, url } = request;
|
||||
|
||||
let lineRange: LineRange;
|
||||
try {
|
||||
const source = this.#getSource(url);
|
||||
@@ -68,6 +69,7 @@ class ActualSourceMap implements SourceMap {
|
||||
message: unknownToError(error),
|
||||
};
|
||||
}
|
||||
|
||||
if (!locationIsValid(lineRange)) {
|
||||
return {
|
||||
line: lineToLine(line),
|
||||
@@ -75,6 +77,7 @@ class ActualSourceMap implements SourceMap {
|
||||
verified: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { line: gline, column: gcolumn } = lineRange;
|
||||
return {
|
||||
line: lineToLine(gline),
|
||||
@@ -85,6 +88,7 @@ class ActualSourceMap implements SourceMap {
|
||||
|
||||
originalLocation(request: LocationRequest): Location {
|
||||
const { line, column } = request;
|
||||
|
||||
let mappedPosition: MappedPosition;
|
||||
try {
|
||||
mappedPosition = this.#sourceMap.originalPositionFor({
|
||||
@@ -99,6 +103,7 @@ class ActualSourceMap implements SourceMap {
|
||||
message: unknownToError(error),
|
||||
};
|
||||
}
|
||||
|
||||
if (!locationIsValid(mappedPosition)) {
|
||||
return {
|
||||
line: lineToLine(line),
|
||||
@@ -106,6 +111,7 @@ class ActualSourceMap implements SourceMap {
|
||||
verified: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { line: oline, column: ocolumn } = mappedPosition;
|
||||
return {
|
||||
line: lineTo0BasedLine(oline),
|
||||
3761
packages/bun-debug-adapter-protocol/src/protocol/protocol.json
Normal file
3761
packages/bun-debug-adapter-protocol/src/protocol/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
"allowJs": true,
|
||||
"types": ["bun-types"],
|
||||
"outDir": "dist",
|
||||
},
|
||||
"include": [".", "../bun-types/index.d.ts", "../bun-inspector-protocol/index"]
|
||||
"include": ["src", "scripts", "../bun-types/index.d.ts", "../bun-inspector-protocol/src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user