diff options
author | 2023-08-16 19:40:20 -0700 | |
---|---|---|
committer | 2023-08-16 19:40:20 -0700 | |
commit | 0486cea35a80be97ba43f41a29ce55f0d3a8eb01 (patch) | |
tree | 2a6335ff465826b3e20959c5751c89ba793becca /src/js/internal/debugger.ts | |
parent | 2634c64aa32fec00073bd0a776e5ac67ad6aa6e5 (diff) | |
download | bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.gz bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.tar.zst bun-0486cea35a80be97ba43f41a29ce55f0d3a8eb01.zip |
`bun --inspect` (#4158)
* Let the debugger to pause/resume the event loop
* Add initial support for Debug Adapter Protocol
* Add progress
* Update Worker.cpp
* Fix require("console") #3820 (#4073)
* Fix #3820
* Add Module (#4074)
* Set exports to {} in user-constructed CommonJSModuleRecords (#4076)
* feat(bun/test): Implement "toSatisfy" & "toIncludeRepeated" (fwup) (#3651)
* Fix merge issues
* oop
* make codegen
* Fix build issues
---------
Co-authored-by: dave caruso <me@paperdave.net>
* worker tests (#4058)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* feat(bun:test) add support for test.each() and describe.each() (#4047)
* rename callback to func
* update testscope to handle function arguments
* works
* big cleanup
* works in debug, not release
* fix memory issue & update tests
* catch & str test
* write types for each() & switch tests to ts
* rm & typo
* move some code around & support describe
* review changes
* Fix one of the astro segfaults, also fix bun init version (#4079)
* 4->16
* add assertions
* fix version stuff
* Remove unintentional logs from #4043
* Run prettier
* Update main-worker-file.js
* Update SIMDUTF (#4078)
* Fix constructing buffer from a UTF16 string with the Latin1 encoding. (#4086)
Close: #3914
* Add support for `bun --revision` (#4027)
Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local>
* Updates
* Update __global.zig
* remove non-node node-fallbacks (#4082)
* remove non-node node-fallbacks.
* organize the imports
* Fix test
* Sync bun-polyfills branch (#4081)
* bun-polyfills: initial impl. & baseline refactor
* move @types/ws dep from root to /test/
* bun-types: remove ReadableStream.forEach method
(this does not exist, probably added by mistake)
* bun-polyfills: remove extraneous stream utils
* bun-polyfills: add types syncing file
* bun-polyfills: re-arrange global polyfills
* bun-polyfills: fix FileBlob streams types again
* bun-polyfills: sync all of @types/node
* bun-polyfills: typeguard all current polyfills
* bun-polyfills: fix import paths
* bun-polyfills: switch to wasm impl. of farmhash
* bun-polyfills: support default import of bun obj
* bun-polyfills: transpiler placeholder file
* bun-polyfills: loaderless import.meta polyfill
* bun-polyfills: refactor import.meta polyfill
* bun-polyfills: repl entrypoint & todo list index
* bun-types: Add null to return type of Bun.which
* bun-types: match Bun.sha with Bun.hash.SHA512_256
* bun-polyfills: new "repl" package.json script
* bun-polyfills: full refactor of toplevel hashes
* bun-polyfills: these are fixed
* bun-types: NODE_ENV is optional
* bun-polyfills: fix Bun.env types
* bun-types+polyfills: fix HeapSnapshot.version type
* bun-polyfills: fix some web streams type conflicts
* bun-polyfills: update internal FileBlob.slice
* bun-polyfills: fix subproc stdin conversions
* bun-polyfills: better internal fileblob types
* bun-polyfills: try to sync global performance type
* bun-polyfills: working zig wasm polyfills setup
* bun-polyfills: update scripts
* bun-polyfills: fix wasm file location resolution
* bun-polyfills: goodbye farmhash (replaced by zig)
* bun-polyfills: move all Bun.hash polyfills to zig
* bun-polyfills: reimpl. seeding of seeded hashes
* bun-polyfills: impl. undocumented murmur32v2
* bun-polyfills: switch zighash from jsdoc to .d.ts
* bun-types: partial fix of Hash types
* bun-polyfills: documented Hash.murmur32v2
* bun-polyfills: misc updates
* bun-polyfills: enable sourcemaps
* bun-polyfills: handle empty inputs to hash funcs
* bun-types: narrow down hash func types
* bun-polyfills: remove unnecessary bigint casts
* bun-polyfills: impl. Bun.isMainThread
* bun-polyfills: impl. Bun.sleep and fix sleepSync
* bun-polyfills: impl. indexOfLine
* bun-polyfills: impl. Bun.peek.status
* bun-types: fix hashing test
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* Add remix guide
* Fix title
* add util.formatWithOptions (#4090)
* Add formatWithOptions
* tests and tweaks
* adjust
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* bun test: format description of test.each (#4092)
* bun test: format description
* add tests for tests
* only
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
* Fixes #4062 (#4106)
* Fixes #4062
* Update encoding.zig
* Use faster C++ impl
* Update wtf-bindings.cpp
* undo
* Fixup
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
* zig fmt
* Update remix guide
* fs.zig: create temp files with 0o700, not 0o007 (#4107)
* Handle thundering herd of setInterval (#4109)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
* Fix memory leak in base64url (#4111)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
* run files without extensions (#4113)
* run script without extension
* process stdio write fix
* don't check for trailing slash, var stream
* More lazily initialize these static strings
* Remove assertion
* wip
* Add --inspect flag
* Deprecate loading `node_modules.bun`
* realpath
* regenerate schema
* More
* more
* Update cli.zig
* Debugger JS loads
* have fun
* Most of the code
* Its starting to work.
* more progress
* win some, lose some
* Update dap.ts
* Async Context Tracking
* untested websocket
* Emit Console messages
* Error handling
* errors
* Make profiling work better
* [clap] CLI arguments with optional values ignore positional params
In `bun --inspect foo.js`, `foo.js` should not be the value of `--inspect`. It is a positional parameter. `--inspect=foo`
* Support multiple simultaneous clients, automatically unpause on disconnect
* regenerate
* Update Makefile
* Update WebKit
* Update cli.zig
* Update InternalModuleRegistry+createInternalModuleById.h
* BaseURL
* Update WebKit
* Add web-inspector-bun
* Update build.ts
* formatting, mostly
* Update debugger.ts
* Update InternalModuleRegistryConstants.h
* wrap
* Update test
* Update test
---------
Co-authored-by: Ashcon Partovi <ashcon@partovi.net>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: dave caruso <me@paperdave.net>
Co-authored-by: Tiramify (A.K. Daniel) <94789999+TiranexDev@users.noreply.github.com>
Co-authored-by: Jacques <25390037+jecquas@users.noreply.github.com>
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Ai Hoshino <ambiguous404@gmail.com>
Co-authored-by: Yash Sharma <yashosharma@gmail.com>
Co-authored-by: Yash Sharma <yashsharma@Yashs-MacBook-Air.local>
Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
Co-authored-by: jhmaster <32803471+jhmaster2000@users.noreply.github.com>
Co-authored-by: Adhityaa Chandrasekar <github@adtac.in>
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
Diffstat (limited to 'src/js/internal/debugger.ts')
-rw-r--r-- | src/js/internal/debugger.ts | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts new file mode 100644 index 000000000..2fd875d10 --- /dev/null +++ b/src/js/internal/debugger.ts @@ -0,0 +1,280 @@ +import type * as BunType from "bun"; + +// We want to avoid dealing with creating a prototype for the inspector class +let sendFn_, disconnectFn_; + +var debuggerCounter = 1; +class DebuggerWithMessageQueue { + debugger?: Debugger = undefined; + messageQueue: string[] = []; + count: number = debuggerCounter++; + + send(msg: string) { + sendFn_.call(this.debugger, msg); + } + + disconnect() { + disconnectFn_.call(this.debugger); + this.messageQueue.length = 0; + } +} + +let defaultPort = 6499; + +let generatedPath: string = ""; +function generatePath() { + if (!generatedPath) { + generatedPath = "/" + Math.random().toString(36).slice(2); + } + + return generatedPath; +} + +function terminalLink(url) { + if (Bun.enableANSIColors) { + // bold + hyperlink + reset + return "\x1b[1m\x1b]8;;" + url + "\x1b\\" + url + "\x1b]8;;\x1b\\" + "\x1b[22m"; + } + + return url; +} + +function dim(text) { + if (Bun.enableANSIColors) { + return "\x1b[2m" + text + "\x1b[22m"; + } + + return text; +} + +class WebSocketListener { + server: BunType.Server; + url: string = ""; + createInspectorConnection; + scriptExecutionContextId: number = 0; + activeConnections: Set<BunType.ServerWebSocket<DebuggerWithMessageQueue>> = new Set(); + + constructor(scriptExecutionContextId: number = 0, url: string, createInspectorConnection) { + this.scriptExecutionContextId = scriptExecutionContextId; + this.createInspectorConnection = createInspectorConnection; + this.server = this.start(url); + } + + start(url: string): BunType.Server { + let defaultHostname = "localhost"; + let usingDefaultPort = false; + if (/^[0-9]*$/.test(url)) { + url = "ws://" + defaultHostname + ":" + url + generatePath(); + } else if (!url || url.startsWith("/")) { + url = "ws://" + defaultHostname + ":" + defaultPort + generatePath(); + usingDefaultPort = true; + } else if (url.includes(":") && !url.includes("://")) { + try { + const insertSlash = !url.includes("/"); + url = new URL("ws://" + url).href; + if (insertSlash) { + url += generatePath().slice(1); + } + } catch (e) { + console.error("[Inspector]", "Failed to parse url", '"' + url + '"'); + process.exit(1); + } + } + + try { + var { hostname, port, pathname } = new URL(url); + this.url = pathname.toLowerCase(); + } catch (e) { + console.error("[Inspector]", "Failed to parse url", '"' + url + '"'); + process.exit(1); + } + + const serveOptions: BunType.WebSocketServeOptions<DebuggerWithMessageQueue> = { + hostname, + development: false, + + // ts-ignore + reusePort: false, + + websocket: { + idleTimeout: 0, + open: socket => { + var connection = new DebuggerWithMessageQueue(); + socket.data = connection; + this.activeConnections.add(socket); + connection.debugger = this.createInspectorConnection(this.scriptExecutionContextId, (...msgs: string[]) => { + if (socket.readyState > 1) { + connection.disconnect(); + return; + } + + if (connection.messageQueue.length > 0) { + connection.messageQueue.push(...msgs); + return; + } + + for (let i = 0; i < msgs.length; i++) { + if (!socket.sendText(msgs[i])) { + if (socket.readyState < 2) { + connection.messageQueue.push(...msgs.slice(i)); + } + return; + } + } + }); + + console.log( + "[Inspector]", + "Connection #" + connection.count + " opened", + "(" + + new Intl.DateTimeFormat(undefined, { + "timeStyle": "long", + "dateStyle": "short", + }).format(new Date()) + + ")", + ); + }, + drain: socket => { + const queue = socket.data.messageQueue; + for (let i = 0; i < queue.length; i++) { + if (!socket.sendText(queue[i])) { + socket.data.messageQueue = queue.slice(i); + return; + } + } + queue.length = 0; + }, + message: (socket, message) => { + if (typeof message !== "string") { + console.warn("[Inspector]", "Received non-string message"); + return; + } + socket.data.send(message as string); + }, + close: socket => { + socket.data.disconnect(); + console.log( + "[Inspector]", + "Connection #" + socket.data.count + " closed", + "(" + + new Intl.DateTimeFormat(undefined, { + "timeStyle": "long", + "dateStyle": "short", + }).format(new Date()) + + ")", + ); + this.activeConnections.delete(socket); + }, + }, + fetch: (req, server) => { + let { pathname } = new URL(req.url); + pathname = pathname.toLowerCase(); + + if (pathname === "/json/version") { + return Response.json({ + "Browser": navigator.userAgent, + "WebKit-Version": process.versions.webkit, + "Bun-Version": Bun.version, + "Bun-Revision": Bun.revision, + }); + } + + if (pathname === this.url) { + if (server.upgrade(req)) { + return new Response(); + } + + return new Response("WebSocket expected", { + status: 400, + }); + } + + return new Response("Not found", { + status: 404, + }); + }, + }; + + if (port === "") { + port = defaultPort + ""; + } + + let portNumber = Number(port); + var server, lastError; + + if (usingDefaultPort) { + for (let tries = 0; tries < 10 && !server; tries++) { + try { + lastError = undefined; + server = Bun.serve<DebuggerWithMessageQueue>({ + ...serveOptions, + port: portNumber++, + }); + } catch (e) { + lastError = e; + } + } + } else { + try { + server = Bun.serve<DebuggerWithMessageQueue>({ + ...serveOptions, + port: portNumber, + }); + } catch (e) { + lastError = e; + } + } + + if (!server) { + console.error("[Inspector]", "Failed to start server"); + if (lastError) console.error(lastError); + process.exit(1); + } + + let textToWrite = ""; + function writeToConsole(text) { + textToWrite += text; + } + function flushToConsole() { + console.write(textToWrite); + } + + // yellow foreground + writeToConsole(dim(`------------------ Bun Inspector ------------------` + "\n")); + // reset background + writeToConsole("\x1b[49m"); + + writeToConsole( + "Listening at:\n " + + `ws://${hostname}:${server.port}${this.url}` + + "\n\n" + + "Inspect in browser:\n " + + terminalLink(new URL(`https://debug.bun.sh#${server.hostname}:${server.port}${this.url}`).href) + + "\n", + ); + writeToConsole(dim(`------------------ Bun Inspector ------------------` + "\n")); + flushToConsole(); + + return server; + } +} + +interface Debugger { + send(msg: string): void; + disconnect(): void; +} + +var listener: WebSocketListener; + +export default function start(debuggerId, hostOrPort, createInspectorConnection, sendFn, disconnectFn) { + try { + sendFn_ = sendFn; + disconnectFn_ = disconnectFn; + globalThis.listener = listener ||= new WebSocketListener(debuggerId, hostOrPort, createInspectorConnection); + } catch (e) { + console.error("Bun Inspector threw an exception\n", e); + process.exit(1); + } + + return `http://${listener.server.hostname}:${listener.server.port}${listener.url}`; +} |