diff options
-rw-r--r-- | packages/bun-error/bun-error.css | 11 | ||||
-rw-r--r-- | packages/bun-error/index.tsx | 50 | ||||
-rw-r--r-- | src/__global.zig | 24 | ||||
-rw-r--r-- | src/fallback-backend.html | 26 | ||||
-rw-r--r-- | src/http.zig | 167 | ||||
-rw-r--r-- | src/javascript/jsc/api/server.zig | 164 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 475 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 13 | ||||
-rw-r--r-- | src/javascript/jsc/rare_data.zig | 3 | ||||
-rw-r--r-- | src/js_printer.zig | 1 | ||||
-rw-r--r-- | src/jsc.zig | 1 | ||||
-rw-r--r-- | src/logger.zig | 6 | ||||
-rw-r--r-- | src/open.zig | 101 | ||||
-rw-r--r-- | src/runtime.zig | 23 |
14 files changed, 894 insertions, 171 deletions
diff --git a/packages/bun-error/bun-error.css b/packages/bun-error/bun-error.css index c35dc3843..79a2ffef6 100644 --- a/packages/bun-error/bun-error.css +++ b/packages/bun-error/bun-error.css @@ -33,6 +33,17 @@ Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } +.BunErrorRoot--FullPage #BunErrorOverlay-container { + position: static; + top: unset; + right: unset; + margin: 60px auto; +} + +.BunError-error-message--quoted { + color: rgb(25, 46, 9); +} + :host a { color: inherit; } diff --git a/packages/bun-error/index.tsx b/packages/bun-error/index.tsx index 832343504..90eb0f004 100644 --- a/packages/bun-error/index.tsx +++ b/packages/bun-error/index.tsx @@ -237,6 +237,16 @@ const srcFileURL = ( return new URL("/" + filename, globalThis.location.href).href; } + if (!filename.startsWith("/") && thisCwd) { + var orig = filename; + filename = thisCwd; + if (thisCwd.endsWith("/")) { + filename += orig; + } else { + filename += "/" + orig; + } + } + var base = `/src:${filename}`; base = appendLineColumnIfNeeded(base, line, column); @@ -1072,8 +1082,8 @@ const ResolveError = ({ message }: { message: Message }) => { <div className={`BunError-error-message`}> Can't import{" "} - <span className="BunError-error-message--mono"> - {message.on.resolve} + <span className="BunError-error-message--mono BunError-error-message--quoted"> + "{message.on.resolve}" </span> </div> @@ -1189,16 +1199,34 @@ function renderWithFunc(func) { reactRoot = document.createElement("div"); reactRoot.id = BUN_ERROR_CONTAINER_ID; - reactRoot.style.visibility = "hidden"; - const link = document.createElement("link"); - link.rel = "stylesheet"; - link.href = new URL("/bun:erro.css", document.baseURI).href; - link.onload = () => { - reactRoot.style.visibility = "visible"; - }; + const fallbackStyleSheet = document.querySelector( + "style[data-has-bun-fallback-style]" + ); + if (!fallbackStyleSheet) { + reactRoot.style.visibility = "hidden"; + } const shadowRoot = root.attachShadow({ mode: "closed" }); - shadowRoot.appendChild(link); + if (!fallbackStyleSheet) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = new URL("/bun:erro.css", document.baseURI).href; + link.onload = () => { + reactRoot.style.visibility = "visible"; + }; + shadowRoot.appendChild(link); + } else { + fallbackStyleSheet.remove(); + shadowRoot.appendChild(fallbackStyleSheet); + reactRoot.classList.add("BunErrorRoot--FullPage"); + + const page = document.querySelector("style[data-bun-error-page-style]"); + if (page) { + page.remove(); + shadowRoot.appendChild(page); + } + } + shadowRoot.appendChild(reactRoot); document.body.appendChild(root); @@ -1222,6 +1250,8 @@ export function renderFallbackError(fallback: FallbackMessageContainer) { )); } +globalThis[Symbol.for("Bun__renderFallbackError")] = renderFallbackError; + import { parse as getStackTrace } from "./stack-trace-parser"; var runtimeErrorController: AbortController; var pending = []; diff --git a/src/__global.zig b/src/__global.zig index f9d51ffd7..39ac9496f 100644 --- a/src/__global.zig +++ b/src/__global.zig @@ -119,3 +119,27 @@ pub fn crash() noreturn { const Global = @This(); const string = @import("./global.zig").string; + +pub const BunInfo = struct { + bun_version: string, + platform: Analytics.GenerateHeader.GeneratePlatform.Platform = undefined, + framework: string = "", + framework_version: string = "", + + const Analytics = @import("./analytics/analytics_thread.zig"); + const JSON = @import("./json_parser.zig"); + const JSAst = @import("./js_ast.zig"); + pub fn generate(comptime Bundler: type, bundler: Bundler, allocator: std.mem.Allocator) !JSAst.Expr { + var info = BunInfo{ + .bun_version = Global.package_json_version, + .platform = Analytics.GenerateHeader.GeneratePlatform.forOS(), + }; + + if (bundler.options.framework) |framework| { + info.framework = framework.package; + info.framework_version = framework.version; + } + + return try JSON.toAST(allocator, BunInfo, info); + } +}; diff --git a/src/fallback-backend.html b/src/fallback-backend.html new file mode 100644 index 000000000..c7e4d6db7 --- /dev/null +++ b/src/fallback-backend.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + </head> + <body> + <script id="__bunfallback" type="binary/peechy"> + {[blob]s} + </script> + + <style data-has-bun-fallback-style> + {[bun_error_css]s} + </style> + + <style data-bun-error-page-style> + {[bun_error_page_css]s} + </style> + + <script type="module" async> + {[fallback]s}; + ;{[bun_error]s}; + ;globalThis[Symbol.for("Bun__renderFallbackError")](globalThis.__BUN_DATA__); + </script> + </body> +</html> diff --git a/src/http.zig b/src/http.zig index 585df3756..b3b3c0403 100644 --- a/src/http.zig +++ b/src/http.zig @@ -95,6 +95,7 @@ fn disableSIGPIPESoClosingTheTabDoesntCrash(conn: anytype) void { &std.mem.toBytes(@as(c_int, 1)), ) catch {}; } +var http_editor_context: EditorContext = EditorContext{}; pub const RequestContext = struct { request: Request, @@ -817,11 +818,11 @@ pub const RequestContext = struct { pub fn sendJSB(ctx: *RequestContext) !void { const node_modules_bundle = ctx.bundler.options.node_modules_bundle orelse unreachable; if (ctx.header("Open-In-Editor") != null) { - if (Server.editor == null) { - Server.detectEditor(ctx.bundler.env); + if (http_editor_context.editor == null) { + http_editor_context.detectEditor(ctx.bundler.env); } - if (Server.editor.? != .none) { + if (http_editor_context.editor.? != .none) { var buf: string = ""; if (node_modules_bundle.code_string == null) { @@ -830,8 +831,8 @@ pub const RequestContext = struct { buf = node_modules_bundle.code_string.?.str; } - Server.openInEditor( - Server.editor.?, + http_editor_context.openInEditor( + http_editor_context.editor.?, buf, std.fs.path.basename(ctx.url.path), ctx.bundler.fs.tmpdir(), @@ -839,7 +840,7 @@ pub const RequestContext = struct { ctx.header("Editor-Column") orelse "", ); - if (Server.editor.? != .none) { + if (http_editor_context.editor.? != .none) { try ctx.sendNoContent(); return; } @@ -2325,13 +2326,13 @@ pub const RequestContext = struct { } if (chunky.rctx.header("Open-In-Editor") != null) { - if (Server.editor == null) { - Server.detectEditor(chunky.rctx.bundler.env); + if (http_editor_context.editor == null) { + http_editor_context.detectEditor(chunky.rctx.bundler.env); } - if (Server.editor.? != .none) { - Server.openInEditor( - Server.editor.?, + if (http_editor_context.editor.? != .none) { + http_editor_context.openInEditor( + http_editor_context.editor.?, buf, std.fs.path.basename(chunky.rctx.url.path), chunky.rctx.bundler.fs.tmpdir(), @@ -2339,7 +2340,7 @@ pub const RequestContext = struct { chunky.rctx.header("Editor-Column") orelse "", ); - if (Server.editor.? != .none) { + if (http_editor_context.editor.? != .none) { try chunky.rctx.sendNoContent(); return; } @@ -2701,8 +2702,8 @@ pub const RequestContext = struct { } if (ctx.header("Open-In-Editor") != null) { - if (Server.editor == null) { - Server.detectEditor(ctx.bundler.env); + if (http_editor_context.editor == null) { + http_editor_context.detectEditor(ctx.bundler.env); } if (line.len == 0) { @@ -2717,10 +2718,10 @@ pub const RequestContext = struct { } } - if (Server.editor) |editor| { + if (http_editor_context.editor) |editor| { if (editor != .none) { - Server.openInEditor(editor, blob.ptr[0..blob.len], id, Fs.FileSystem.instance.tmpdir(), line, column); - if (Server.editor.? != .none) { + http_editor_context.openInEditor(editor, blob.ptr[0..blob.len], id, Fs.FileSystem.instance.tmpdir(), line, column); + if (http_editor_context.editor.? != .none) { defer ctx.done(); try ctx.writeStatus(200); ctx.appendHeader("Content-Type", MimeType.html.value); @@ -2866,23 +2867,6 @@ pub const RequestContext = struct { } fn sendBunInfoJSON(ctx: *RequestContext) anyerror!void { - const Info = struct { - bun_version: string, - platform: Analytics.GenerateHeader.GeneratePlatform.Platform = undefined, - framework: string = "", - framework_version: string = "", - }; - var info = Info{ - .bun_version = Global.package_json_version, - .platform = Analytics.GenerateHeader.GeneratePlatform.forOS(), - }; - - if (ctx.bundler.options.framework) |framework| { - info.framework = framework.package; - info.framework_version = framework.version; - } - - var expr = try JSON.toAST(ctx.allocator, Info, info); defer ctx.bundler.resetStore(); var buffer_writer = try JSPrinter.BufferWriter.init(default_allocator); @@ -2890,7 +2874,12 @@ pub const RequestContext = struct { var writer = JSPrinter.BufferPrinter.init(buffer_writer); defer writer.ctx.buffer.deinit(); var source = logger.Source.initEmptyFile("info.json"); - _ = try JSPrinter.printJSON(*JSPrinter.BufferPrinter, &writer, expr, &source); + _ = try JSPrinter.printJSON( + *JSPrinter.BufferPrinter, + &writer, + try Global.BunInfo.generate(*Bundler, ctx.bundler, ctx.allocator) , + &source, + ); const buffer = writer.ctx.written; ctx.appendHeader("Content-Type", MimeType.json.value); @@ -2955,20 +2944,20 @@ pub const RequestContext = struct { .pending => |resolve_result| { const path = resolve_result.pathConst() orelse return try ctx.sendNotFound(); if (ctx.header("Open-In-Editor") != null) { - if (Server.editor == null) - Server.detectEditor(ctx.bundler.env); + if (http_editor_context.editor == null) + http_editor_context.detectEditor(ctx.bundler.env); - if (Server.editor) |editor| { + if (http_editor_context.editor) |editor| { if (editor != .none) { - editor.open(Server.editor_path, path.text, line, column, bun.default_allocator) catch |err| { + editor.open(http_editor_context.path, path.text, line, column, bun.default_allocator) catch |err| { if (editor != .other) { Output.prettyErrorln("Error {s} opening in {s}", .{ @errorName(err), @tagName(editor) }); } - Server.editor = Editor.none; + http_editor_context.editor = Editor.none; }; - if (Server.editor.? != .none) { + if (http_editor_context.editor.? != .none) { defer ctx.done(); try ctx.writeStatus(200); ctx.appendHeader("Content-Type", MimeType.html.value); @@ -3209,6 +3198,7 @@ var serve_as_package_path = false; // - Parsing time // - IO read time const Editor = @import("./open.zig").Editor; +const EditorContext = @import("./open.zig").EditorContext; pub const Server = struct { log: logger.Log, @@ -3250,99 +3240,6 @@ pub const Server = struct { } } - pub var editor: ?Editor = null; - pub var editor_name: string = ""; - pub var editor_path: string = ""; - - pub fn openInEditor(editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) void { - _openInEditor(editor_, blob, id, tmpdir, line, column) catch |err| { - if (editor_ != .other) { - Output.prettyErrorln("Error {s} opening in {s}", .{ @errorName(err), @tagName(editor_) }); - } - - editor = Editor.none; - }; - } - - fn _openInEditor(editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) !void { - var basename_buf: [512]u8 = undefined; - var basename = std.fs.path.basename(id); - if (strings.endsWith(basename, ".bun") and basename.len < 499) { - std.mem.copy(u8, &basename_buf, basename); - basename_buf[basename.len..][0..3].* = ".js".*; - basename = basename_buf[0 .. basename.len + 3]; - } - - try tmpdir.writeFile(basename, blob); - - var opened = try tmpdir.openFile(basename, .{}); - defer opened.close(); - var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - try editor_.open( - Server.editor_path, - try std.os.getFdPath(opened.handle, &path_buf), - line, - column, - default_allocator, - ); - } - - pub fn detectEditor(env: *DotEnv.Loader) void { - var buf: [bun.MAX_PATH_BYTES]u8 = undefined; - - var out: string = ""; - // first: choose from user preference - if (Server.editor_name.len > 0) { - // /usr/bin/vim - if (std.fs.path.isAbsolute(Server.editor_name)) { - Server.editor = Editor.byName(std.fs.path.basename(Server.editor_name)) orelse Editor.other; - editor_path = Server.editor_name; - return; - } - - // "vscode" - if (Editor.byName(std.fs.path.basename(Server.editor_name))) |editor_| { - if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { - editor = editor_; - editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; - return; - } - - // not in path, try common ones - if (Editor.byFallbackPathForEditor(editor_, &out)) { - editor = editor_; - editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; - return; - } - } - } - - // EDITOR=code - if (Editor.detect(env)) |editor_| { - if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { - editor = editor_; - editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; - return; - } - - // not in path, try common ones - if (Editor.byFallbackPathForEditor(editor_, &out)) { - editor = editor_; - editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; - return; - } - } - - // Don't know, so we will just guess based on what exists - if (Editor.byFallback(env, &buf, Fs.FileSystem.instance.top_level_dir, &out)) |editor_| { - editor = editor_; - editor_path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; - return; - } - - Server.editor = Editor.none; - } - threadlocal var filechange_buf: [32]u8 = undefined; threadlocal var filechange_buf_hinted: [32]u8 = undefined; @@ -4090,7 +3987,7 @@ pub const Server = struct { return; } - Server.editor_name = debug.editor; + http_editor_context.name = debug.editor; server.bundler.options.macro_remap = debug.macros orelse .{}; diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig index 7a1cfad23..2beac4d23 100644 --- a/src/javascript/jsc/api/server.zig +++ b/src/javascript/jsc/api/server.zig @@ -78,6 +78,8 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine; const IOTask = JSC.IOTask; const is_bindgen = JSC.is_bindgen; const uws = @import("uws"); +const Fallback = Runtime.Fallback; +const MimeType = HTTP.MimeType; const Blob = JSC.WebCore.Blob; const SendfileContext = struct { fd: i32, @@ -154,6 +156,7 @@ pub fn NewServer(comptime ssl_enabled: bool) type { sendfile: SendfileContext = undefined, request_js_object: JSC.C.JSObjectRef = null, request_body_buf: std.ArrayListUnmanaged(u8) = .{}, + fallback_buf: std.ArrayListUnmanaged(u8) = .{}, pub threadlocal var pool: *RequestContextStackAllocator = undefined; @@ -194,7 +197,21 @@ pub fn NewServer(comptime ssl_enabled: bool) type { _: *JSC.JSGlobalObject, arguments: []const JSC.JSValue, ) void { - JSC.VirtualMachine.vm.defaultErrorHandler(arguments[0], null); + var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(bun.default_allocator); + JSC.VirtualMachine.vm.defaultErrorHandler(arguments[0], &exception_list); + if (!ctx.resp.hasResponded()) { + ctx.renderDefaultError( + JSC.VirtualMachine.vm.log, + error.PromiseRejection, + exception_list.toOwnedSlice(), + "", + .{}, + ); + JSC.VirtualMachine.vm.log.reset(); + return; + } else { + exception_list.deinit(); + } if (ctx.aborted) { ctx.finalize(); @@ -205,6 +222,64 @@ pub fn NewServer(comptime ssl_enabled: bool) type { ctx.finalize(); } + pub fn renderDefaultError( + this: *RequestContext, + log: *logger.Log, + err: anyerror, + exceptions: []Api.JsException, + comptime fmt: string, + args: anytype, + ) void { + this.resp.writeStatus("500 Internal Server Error"); + this.resp.writeHeader("content-type", MimeType.html.value); + + var allocator = bun.default_allocator; + + var fallback_container = allocator.create(Api.FallbackMessageContainer) catch unreachable; + defer allocator.destroy(fallback_container); + fallback_container.* = Api.FallbackMessageContainer{ + .message = std.fmt.allocPrint(allocator, fmt, args) catch unreachable, + .router = null, + .reason = .fetch_event_handler, + .cwd = VirtualMachine.vm.bundler.fs.top_level_dir, + .problems = Api.Problems{ + .code = @truncate(u16, @errorToInt(err)), + .name = @errorName(err), + .exceptions = exceptions, + .build = log.toAPI(allocator) catch unreachable, + }, + }; + + if (comptime fmt.len > 0) Output.prettyErrorln(fmt, args); + Output.flush(); + + var bb = std.ArrayList(u8).init(allocator); + var bb_writer = bb.writer(); + + Fallback.renderBackend( + allocator, + fallback_container, + @TypeOf(bb_writer), + bb_writer, + ) catch unreachable; + if (this.resp.tryEnd(bb.items, bb.items.len)) { + bb.clearAndFree(); + this.finalize(); + return; + } + + this.fallback_buf = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity }; + this.resp.onWritable(*RequestContext, onWritableFallback, this); + } + + pub fn onWritableFallback(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { + if (this.aborted) { + return false; + } + + return this.sendWritableBytes(this.fallback_buf.items, write_offset, resp); + } + pub fn create(this: *RequestContext, server: *ThisServer, req: *uws.Request, resp: *App.Response) void { this.* = .{ .resp = resp, @@ -257,6 +332,8 @@ pub fn NewServer(comptime ssl_enabled: bool) type { this.response_headers.?.deref(); this.response_headers = null; } + + this.fallback_buf.clearAndFree(bun.default_allocator); } pub fn finalize(this: *RequestContext) void { this.finalizeWithoutDeinit(); @@ -370,9 +447,12 @@ pub fn NewServer(comptime ssl_enabled: bool) type { return false; var bytes = this.blob.sharedView(); + return this.sendWritableBytes(bytes, write_offset, resp); + } - bytes = bytes[@minimum(bytes.len, @truncate(usize, write_offset))..]; - if (resp.tryEnd(bytes, this.blob.size)) { + pub fn sendWritableBytes(this: *RequestContext, bytes_: []const u8, write_offset: c_ulong, resp: *App.Response) bool { + var bytes = bytes_[@minimum(bytes_.len, @truncate(usize, write_offset))..]; + if (resp.tryEnd(bytes, bytes_.len)) { this.finalize(); return true; } else { @@ -583,7 +663,61 @@ pub fn NewServer(comptime ssl_enabled: bool) type { } }; + pub fn onBunInfoRequest(_: *ThisServer, req: *uws.Request, resp: *App.Response) void { + if (comptime JSC.is_bindgen) return undefined; + req.setYield(false); + var stack_fallback = std.heap.stackFallback(8096, bun.default_allocator); + var allocator = stack_fallback.get(); + + var buffer_writer = js_printer.BufferWriter.init(allocator) catch unreachable; + var writer = js_printer.BufferPrinter.init(buffer_writer); + defer writer.ctx.buffer.deinit(); + var source = logger.Source.initEmptyFile("info.json"); + _ = js_printer.printJSON( + *js_printer.BufferPrinter, + &writer, + bun.Global.BunInfo.generate(*Bundler, &JSC.VirtualMachine.vm.bundler, allocator) catch unreachable, + &source, + ) catch unreachable; + + resp.writeStatus("200 OK"); + resp.writeHeader("Content-Type", MimeType.json.value); + resp.writeHeader("Cache-Control", "public, max-age=3600"); + resp.writeHeaderInt("Age", 0); + const buffer = writer.ctx.written; + resp.end(buffer, false); + } + pub fn onSrcRequest(_: *ThisServer, req: *uws.Request, resp: *App.Response) void { + if (comptime JSC.is_bindgen) return undefined; + req.setYield(false); + if (req.header("open-in-editor") == null) { + resp.writeStatus("501 Not Implemented"); + resp.end("Viewing source without opening in editor is not implemented yet!", false); + return; + } + + var ctx = &JSC.VirtualMachine.vm.rareData().editor_context; + ctx.autoDetectEditor(JSC.VirtualMachine.vm.bundler.env); + var line: ?string = req.header("editor-line"); + var column: ?string = req.header("editor-column"); + + if (ctx.editor) |editor| { + resp.writeStatus("200 Opened"); + resp.end("Opened in editor", false); + var url = req.url()["/src:".len..]; + if (strings.indexOfChar(url, ':')) |colon| { + url = url[0..colon]; + } + editor.open(ctx.path, url, line, column, bun.default_allocator) catch Output.prettyErrorln("Failed to open editor", .{}); + } else { + resp.writeStatus("500 Missing Editor :("); + resp.end("Please set your editor in bunfig.toml", false); + } + } + pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void { + if (comptime JSC.is_bindgen) return undefined; + req.setYield(false); var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory"); ctx.create(this, req, resp); @@ -613,12 +747,30 @@ pub fn NewServer(comptime ssl_enabled: bool) type { return; } - if (ctx.response_jsvalue.isUndefinedOrNull() or ctx.response_jsvalue.isError() or ctx.response_jsvalue.isAggregateError(this.globalThis) or ctx.response_jsvalue.isException(this.globalThis.vm())) { + if (ctx.response_jsvalue.isUndefinedOrNull()) { req.setYield(true); ctx.finalize(); return; } + if (ctx.response_jsvalue.isError() or ctx.response_jsvalue.isAggregateError(this.globalThis) or ctx.response_jsvalue.isException(this.globalThis.vm())) { + var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(bun.default_allocator); + JSC.VirtualMachine.vm.defaultErrorHandler(ctx.response_jsvalue, &exception_list); + if (!ctx.resp.hasResponded()) { + ctx.renderDefaultError( + JSC.VirtualMachine.vm.log, + error.ExceptionOcurred, + exception_list.toOwnedSlice(), + "Unhandled exception in request handler", + .{}, + ); + JSC.VirtualMachine.vm.log.reset(); + return; + } else { + exception_list.deinit(); + } + } + JSC.C.JSValueProtect(this.globalThis.ref(), ctx.response_jsvalue.asObjectRef()); if (ctx.response_jsvalue.as(JSC.WebCore.Response)) |response| { @@ -650,6 +802,10 @@ pub fn NewServer(comptime ssl_enabled: bool) type { pub fn listen(this: *ThisServer) void { this.app = App.create(.{}); this.app.any("/*", *ThisServer, this, onRequest); + + this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest); + this.app.get("/src:/*", *ThisServer, this, onSrcRequest); + this.app.listenWithConfig(*ThisServer, this, onListen, .{ .port = this.default_server.getPort().?, .host = bun.default_allocator.dupeZ(u8, this.default_server.displayHostname()) catch unreachable, diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 13de8770d..1652df07b 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -943,9 +943,6 @@ pub fn NewClassWithInstanceType( const class_definition_ptr = &complete_definition; pub fn get() callconv(.C) [*c]js.JSClassRef { - if (comptime JSC.is_bindgen) - unreachable; - var lazy = lazy_ref; if (!lazy.loaded) { @@ -977,9 +974,6 @@ pub fn NewClassWithInstanceType( } pub fn make(ctx: js.JSContextRef, ptr: *ZigType) js.JSObjectRef { - if (comptime JSC.is_bindgen) - unreachable; - var real_ptr = JSPrivateDataPtr.init(ptr).ptr(); if (comptime Environment.allow_assert) { std.debug.assert(JSPrivateDataPtr.isValidPtr(real_ptr)); @@ -1030,9 +1024,6 @@ pub fn NewClassWithInstanceType( prop: js.JSStringRef, exception: js.ExceptionRef, ) callconv(.C) js.JSValueRef { - if (comptime JSC.is_bindgen) - unreachable; - var this: ObjectPtrType(ZigType) = if (comptime ZigType == void) void{} else GetJSPrivateData(ZigType, obj) orelse return js.JSValueMakeUndefined(ctx); const Field = @TypeOf(@field( @@ -1104,9 +1095,6 @@ pub fn NewClassWithInstanceType( value: js.JSValueRef, exception: js.ExceptionRef, ) callconv(.C) bool { - if (comptime JSC.is_bindgen) - unreachable; - var this = GetJSPrivateData(ZigType, obj) orelse return false; switch (comptime @typeInfo(@TypeOf(@field( @@ -1141,6 +1129,460 @@ pub fn NewClassWithInstanceType( return definition; } + const GetterNameFormatter = struct { + index: usize = 0, + + pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.writeAll(std.mem.span(class_name_str)); + try writer.writeAll("_get_"); + const property_name = property_names[this.index]; + try writer.writeAll(std.mem.span(property_name)); + } + }; + + const SetterNameFormatter = struct { + index: usize = 0, + + pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.writeAll(std.mem.span(class_name_str)); + try writer.writeAll("_set_"); + const property_name = property_names[this.index]; + try writer.writeAll(std.mem.span(property_name)); + } + }; + + const FunctionNameFormatter = struct { + index: usize = 0, + + pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.writeAll(std.mem.span(class_name_str)); + try writer.writeAll("_fn_"); + const property_name = function_names[this.index]; + try writer.writeAll(std.mem.span(property_name)); + } + }; + + const PropertyDeclaration = struct { + index: usize = 0, + pub fn format(this: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + const property = definition.staticValues[this.index]; + + if (property.getProperty != null) { + try writer.writeAll("static JSC_DECLARE_CUSTOM_GETTER("); + const getter_name = GetterNameFormatter{ .index = this.index }; + try getter_name.format(fmt, opts, writer); + try writer.writeAll(");\n"); + } + + if (property.setProperty != null) { + try writer.writeAll("static JSC_DECLARE_CUSTOM_SETTER("); + const getter_name = SetterNameFormatter{ .index = this.index }; + try getter_name.format(fmt, opts, writer); + try writer.writeAll(");\n"); + } + } + }; + + const FunctionDeclaration = struct { + index: usize = 0, + pub fn format(this: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + const function = definition.staticFunctions[this.index]; + + if (function.callAsFunction != null) { + try writer.writeAll("static JSC_DECLARE_HOST_FUNCTION("); + const getter_name = FunctionNameFormatter{ .index = this.index }; + try getter_name.format(fmt, opts, writer); + try writer.writeAll(");\n"); + } + } + }; + + const PropertyDefinition = struct { + index: usize = 0, + pub fn format(this: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + const property = definition.staticValues[this.index]; + + if (property.getProperty != null) { + try writer.writeAll("static JSC_DEFINE_CUSTOM_GETTER("); + const getter_name = GetterNameFormatter{ .index = this.index }; + try getter_name.format(fmt, opts, writer); + try writer.writeAll(", (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) {\n"); + try std.fmt.format( + writer, + \\ JSC::VM& vm = globalObject->vm(); + \\ Bun::{[name]s}* thisObject = JSC::jsDynamicCast<Bun::{[name]s}*>(vm, JSValue::decode(thisValue)); + \\ if (UNLIKELY(!thisObject)) {{ + \\ return JSValue::encode(JSC::jsUndefined()); + \\ }} + \\ + \\ auto clientData = Bun::clientData(vm); + \\ auto scope = DECLARE_THROW_SCOPE(vm); + \\ + , + .{ .name = std.mem.span(class_name_str) }, + ); + if (ZigType == void) { + try std.fmt.format( + writer, + \\ JSC::EncodedJSValue result = Zig__{[getter]any}(globalObject); + , + .{ .getter = getter_name }, + ); + } else { + try std.fmt.format( + writer, + \\ JSC::EncodedJSValue result = Zig__{[getter]any}(globalObject, thisObject->m_ptr); + , + .{ .getter = getter_name }, + ); + } + + try writer.writeAll( + \\ JSC::JSObject *obj = JSC::JSValue::decode(result).getObject(); + \\ + \\ if (UNLIKELY(obj != nullptr && obj->isErrorInstance())) { + \\ scope.throwException(globalObject, obj); + \\ return JSValue::encode(JSC::jsUndefined()); + \\ } + \\ + \\ scope.release(); + \\ + \\ return result; + ); + + try writer.writeAll("}\n"); + } + + if (property.setProperty != null) { + try writer.writeAll("JSC_DEFINE_CUSTOM_SETTER("); + const getter_name = SetterNameFormatter{ .index = this.index }; + try getter_name.format(fmt, opts, writer); + try writer.writeAll(", (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, JSC::PropertyName)) {\n"); + try std.fmt.format(writer, + \\ JSC::VM& vm = globalObject->vm(); + \\ Bun::{[name]s}* thisObject = JSC::jsDynamicCast<Bun::{[name]s}*>(vm, JSValue::decode(thisValue)); + \\ if (UNLIKELY(!thisObject)) {{ + \\ return false; + \\ }} + \\ + \\ auto clientData = Bun::clientData(vm); + \\ auto scope = DECLARE_THROW_SCOPE(vm); + \\ + \\ + , .{ .name = getter_name }); + try writer.writeAll("};\n"); + } + } + }; + + const PropertyDeclarationsFormatter = struct { + pub fn format(_: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + for (definition.staticValues[0 .. static_values_ptr.len - 1]) |_, i| { + const property = PropertyDeclaration{ .index = i }; + try property.format(fmt, opts, writer); + } + } + }; + + const PropertyDefinitionsFormatter = struct { + pub fn format(_: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + if (static_values_ptr.len > 1) { + for (definition.staticValues[0 .. static_values_ptr.len - 1]) |_, i| { + const property = PropertyDefinition{ .index = i }; + try property.format(fmt, opts, writer); + } + } + } + }; + + const FunctionDefinitionsFormatter = struct { + pub fn format(_: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = writer; + _ = opts; + // for (static_properties[0 .. static_properties.len - 1]) |_, i| { + // const property = FunctionDefinition{ .index = i }; + // try property.format(fmt, opts, writer); + // } + } + }; + + const FunctionDeclarationsFormatter = struct { + pub fn format(_: @This(), comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = writer; + const definition = getDefinition(); + if (static_functions__.len > 1) { + for (definition.staticFunctions[0 .. static_functions__.len - 1]) |_, i| { + const function = FunctionDeclaration{ .index = i }; + try function.format(fmt, opts, writer); + } + } + } + }; + + pub fn @"generateC++Header"(writer: anytype) !void { + const header_file = + \\// AUTO-GENERATED FILE + \\#pragma once + \\ + \\#include "BunBuiltinNames.h" + \\#include "BunClientData.h" + \\#include "root.h" + \\ + \\ + \\namespace Bun {{ + \\ + \\using namespace JSC; + \\using namespace Zig; + \\ + \\class {[name]s} : public JSNonFinalObject {{ + \\ using Base = JSNonFinalObject; + \\ + \\public: + \\ {[name]s}(JSC::VM& vm, Structure* structure) : Base(vm, structure) {{}} + \\ + \\ + \\ DECLARE_INFO; + \\ + \\ static constexpr unsigned StructureFlags = Base::StructureFlags; + \\ template<typename CellType, SubspaceAccess> static GCClient::IsoSubspace* subspaceFor(VM& vm) + \\ {{ + \\ return &vm.cellSpace(); + \\ }} + \\ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, + \\ JSC::JSValue prototype) + \\ {{ + \\ return JSC::Structure::create(vm, globalObject, prototype, + \\ JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + \\ }} + \\ + \\ static {[name]s}* create(JSC::VM& vm, JSC::Structure* structure) + \\ {{ + \\ {[name]s}* accessor = new (NotNull, JSC::allocateCell<{[name]s}>(vm)) {[name]s}(vm, structure); + \\ accessor->finishCreation(vm); + \\ return accessor; + \\ }} + \\ + \\ void finishCreation(JSC::VM& vm); + \\ + \\}}; + \\ + \\}} // namespace Bun + \\ + ; + _ = writer; + _ = header_file; + const Opts = struct { name: string }; + try writer.print(header_file, Opts{ + .name = std.mem.span(name), + }); + } + + const LookupTableFormatter = struct { + // example: + // + // /* Source for IntlLocalePrototype.lut.h + // @begin localePrototypeTable + // maximize intlLocalePrototypeFuncMaximize DontEnum|Function 0 + // minimize intlLocalePrototypeFuncMinimize DontEnum|Function 0 + // toString intlLocalePrototypeFuncToString DontEnum|Function 0 + // baseName intlLocalePrototypeGetterBaseName DontEnum|ReadOnly|CustomAccessor + // calendar intlLocalePrototypeGetterCalendar DontEnum|ReadOnly|CustomAccessor + // calendars intlLocalePrototypeGetterCalendars DontEnum|ReadOnly|CustomAccessor + // caseFirst intlLocalePrototypeGetterCaseFirst DontEnum|ReadOnly|CustomAccessor + // collation intlLocalePrototypeGetterCollation DontEnum|ReadOnly|CustomAccessor + // collations intlLocalePrototypeGetterCollations DontEnum|ReadOnly|CustomAccessor + // hourCycle intlLocalePrototypeGetterHourCycle DontEnum|ReadOnly|CustomAccessor + // hourCycles intlLocalePrototypeGetterHourCycles DontEnum|ReadOnly|CustomAccessor + // numeric intlLocalePrototypeGetterNumeric DontEnum|ReadOnly|CustomAccessor + // numberingSystem intlLocalePrototypeGetterNumberingSystem DontEnum|ReadOnly|CustomAccessor + // numberingSystems intlLocalePrototypeGetterNumberingSystems DontEnum|ReadOnly|CustomAccessor + // language intlLocalePrototypeGetterLanguage DontEnum|ReadOnly|CustomAccessor + // script intlLocalePrototypeGetterScript DontEnum|ReadOnly|CustomAccessor + // region intlLocalePrototypeGetterRegion DontEnum|ReadOnly|CustomAccessor + // timeZones intlLocalePrototypeGetterTimeZones DontEnum|ReadOnly|CustomAccessor + // textInfo intlLocalePrototypeGetterTextInfo DontEnum|ReadOnly|CustomAccessor + // weekInfo intlLocalePrototypeGetterWeekInfo DontEnum|ReadOnly|CustomAccessor + // @end + // */ + pub fn format(_: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + const definition = getDefinition(); + try writer.writeAll("/* Source for "); + try writer.writeAll(std.mem.span(definition.className)); + try writer.writeAll(".lut.h\n"); + try writer.writeAll("@begin "); + try writer.writeAll(std.mem.span(definition.className)); + try writer.writeAll("HashTableValues \n"); + var middle_padding: usize = 0; + if (property_names.len > 0) { + for (property_names) |prop| { + middle_padding = @maximum(prop.len, middle_padding); + } + } + if (function_names.len > 0) { + for (function_names[0..function_names.len]) |_name| { + middle_padding = @maximum(std.mem.span(_name).len, middle_padding); + } + } + + if (property_names.len > 0) { + comptime var i: usize = 0; + inline while (i < property_names.len) : (i += 1) { + try writer.writeAll(" "); + const name_ = property_names[i]; + try writer.writeAll(name_); + try writer.writeAll(" "); + var k: usize = 0; + while (k < middle_padding - name_.len) : (k += 1) { + try writer.writeAll(" "); + } + + try writer.print("{any} ", .{GetterNameFormatter{ .index = i }}); + + k = 0; + + while (k < middle_padding - name_.len) : (k += 1) { + try writer.writeAll(" "); + } + + try writer.writeAll("CustomAccessor"); + if (options.read_only or @hasField(@TypeOf(@field(properties, property_names[i])), "ro")) { + try writer.writeAll("|ReadOnly"); + } + + if (@hasField(@TypeOf(@field(properties, property_names[i])), "enumerable") and !@field(properties, property_names[i])) { + try writer.writeAll("|DontEnum"); + } + + try writer.writeAll("\n"); + } + } + if (function_names.len > 0) { + comptime var i: usize = 0; + inline while (i < function_names.len) : (i += 1) { + try writer.writeAll(" "); + const name_ = function_names[i]; + try writer.writeAll(name_); + try writer.writeAll(" "); + var k: usize = 0; + while (k < middle_padding - name_.len) : (k += 1) { + try writer.writeAll(" "); + } + + try writer.print("{any} ", .{FunctionNameFormatter{ .index = i }}); + k = 0; + + while (k < middle_padding - name_.len) : (k += 1) { + try writer.writeAll(" "); + } + var read_only_ = false; + if (options.read_only or @hasField(@TypeOf(comptime @field(staticFunctions, function_names[i])), "ro")) { + read_only_ = true; + try writer.writeAll("ReadOnly"); + } + + if (comptime std.meta.trait.isContainer( + @TypeOf(comptime @field(staticFunctions, function_names[i])), + ) and + @hasField(@TypeOf(comptime @field( + staticFunctions, + function_names[i], + )), "enumerable") and !@field(staticFunctions, function_names[i]).enumerable) { + if (read_only_) { + try writer.writeAll("|"); + } + try writer.writeAll("DontEnum"); + } + + try writer.writeAll("Function 1"); + + try writer.writeAll("\n"); + } + } + + try writer.writeAll("@end\n*/\n"); + } + }; + + pub fn @"generateC++Class"(writer: anytype) !void { + const implementation_file = + \\// AUTO-GENERATED FILE + \\ + \\#include "{[name]s}.generated.h" + \\#include "{[name]s}.lut.h" + \\ + \\namespace Bun {{ + \\ + \\{[lut]any} + \\ + \\using JSGlobalObject = JSC::JSGlobalObject; + \\using Exception = JSC::Exception; + \\using JSValue = JSC::JSValue; + \\using JSString = JSC::JSString; + \\using JSModuleLoader = JSC::JSModuleLoader; + \\using JSModuleRecord = JSC::JSModuleRecord; + \\using Identifier = JSC::Identifier; + \\using SourceOrigin = JSC::SourceOrigin; + \\using JSObject = JSC::JSObject; + \\using JSNonFinalObject = JSC::JSNonFinalObject; + \\namespace JSCastingHelpers = JSC::JSCastingHelpers; + \\ + \\#pragma mark - Function Declarations + \\ + \\{[function_declarations]any} + \\ + \\#pragma mark - Property Declarations + \\ + \\{[property_declarations]any} + \\ + \\#pragma mark - Function Definitions + \\ + \\{[function_definitions]any} + \\ + \\#pragma mark - Property Definitions + \\ + \\{[property_definitions]any} + \\ + \\const JSC::ClassInfo {[name]s}::s_info = {{ "{[name]s}", &Base::s_info, &{[name]s}HashTableValues, nullptr, CREATE_METHOD_TABLE([name]s) }}; + \\ + \\ void {[name]s}::finishCreation(JSC::VM& vm) {{ + \\ Base::finishCreation(vm); + \\ auto clientData = Bun::clientData(vm); + \\ JSC::JSGlobalObject *globalThis = globalObject(); + \\ + \\ + \\#pragma mark - Property Initializers + \\ + \\{[property_initializers]any} + \\ + \\#pragma mark - Function Initializers + \\ + \\{[function_initializers]any} + \\ + \\ }} + \\ + \\}} // namespace Bun + \\ + ; + + try writer.print(implementation_file, .{ + .name = std.mem.span(class_name_str), + .function_initializers = @as(string, ""), + .property_initializers = @as(string, ""), + .function_declarations = FunctionDeclarationsFormatter{}, + .property_declarations = FunctionDeclarationsFormatter{}, + .function_definitions = FunctionDefinitionsFormatter{}, + .property_definitions = PropertyDefinitionsFormatter{}, + .lut = LookupTableFormatter{}, + }); + } + // This should only be run at comptime pub fn typescriptModuleDeclaration() d.ts.module { comptime var class = options.ts.module; @@ -1325,8 +1767,6 @@ pub fn NewClassWithInstanceType( _: js.JSObjectRef, props: js.JSPropertyNameAccumulatorRef, ) callconv(.C) void { - if (comptime JSC.is_bindgen) - unreachable; if (comptime property_name_refs.len > 0) { comptime var i: usize = 0; if (!property_name_refs_set) { @@ -1480,7 +1920,7 @@ pub fn NewClassWithInstanceType( return comptime class; } - const static_properties = brk: { + const static_properties: [property_names.len + 1]js.JSStaticValue = brk: { var props: [property_names.len + 1]js.JSStaticValue = undefined; std.mem.set( js.JSStaticValue, @@ -1660,7 +2100,7 @@ pub fn NewClassWithInstanceType( } const base_def_ = generateDef(JSC.C.JSClassDefinition); - const static_functions__ = generateDef([function_name_literals.len + 1]js.JSStaticFunction); + const static_functions__: [function_name_literals.len + 1]js.JSStaticFunction = generateDef([function_name_literals.len + 1]js.JSStaticFunction); const static_functions_ptr = &static_functions__; const static_values_ptr = &static_properties; const class_name_str: stringZ = options.name; @@ -1709,9 +2149,6 @@ pub fn JSError( ctx: js.JSContextRef, exception: ExceptionValueRef, ) void { - if (comptime JSC.is_bindgen) - unreachable; - var error_args: [1]js.JSValueRef = undefined; @setCold(true); diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index eebeae8e9..ca8c72f75 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -161,8 +161,6 @@ pub fn ConcurrentPromiseTask(comptime Context: type) type { } pub fn runFromJS(this: This) void { - if (comptime JSC.is_bindgen) - unreachable; var promise_value = this.promise; var promise = promise_value.asInternalPromise() orelse { if (comptime @hasDecl(Context, "deinit")) { @@ -492,6 +490,17 @@ pub const VirtualMachine = struct { source_mappings: SavedSourceMap = undefined, response_objects_pool: ?*Response.Pool = null, + + rare_data: ?*JSC.RareData = null, + + pub inline fn rareData(this: *VirtualMachine) *JSC.RareData { + return this.rare_data orelse brk: { + this.rare_data = this.allocator.create(JSC.RareData) catch unreachable; + this.rare_data.?.* = .{}; + break :brk this.rare_data.?; + }; + } + pub inline fn eventLoop(this: *VirtualMachine) *EventLoop { return this.event_loop; } diff --git a/src/javascript/jsc/rare_data.zig b/src/javascript/jsc/rare_data.zig new file mode 100644 index 000000000..72bf45fbd --- /dev/null +++ b/src/javascript/jsc/rare_data.zig @@ -0,0 +1,3 @@ +const EditorContext = @import("../../open.zig").EditorContext; + +editor_context: EditorContext = EditorContext{}, diff --git a/src/js_printer.zig b/src/js_printer.zig index fbf20b07b..85df7fdc8 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -636,7 +636,6 @@ pub fn NewPrinter( } } - // fn printBunJestImportStatement(p: *Printer, import: S.Import) void { p.print("const "); diff --git a/src/jsc.zig b/src/jsc.zig index e159441bb..07e6ae5e6 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -4,6 +4,7 @@ pub const is_bindgen = @import("std").meta.globalOption("bindgen", bool) orelse pub usingnamespace @import("./javascript/jsc/bindings/exports.zig"); pub usingnamespace @import("./javascript/jsc/bindings/bindings.zig"); pub usingnamespace @import("./javascript/jsc/base.zig"); +pub const RareData = @import("./javascript/jsc/rare_data.zig"); pub usingnamespace @import("./javascript/jsc/javascript.zig"); pub const C = @import("./javascript/jsc/javascript_core_c_api.zig"); pub const WebCore = @import("./javascript/jsc/webcore.zig"); diff --git a/src/logger.zig b/src/logger.zig index b99d346dc..edc33db4d 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -491,6 +491,12 @@ pub const Log = struct { msgs: ArrayList(Msg), level: Level = if (Environment.isDebug) Level.info else Level.warn, + pub fn reset(this: *Log) void { + this.msgs.clearRetainingCapacity(); + this.warnings = 0; + this.errors = 0; + } + pub var default_log_level = if (Environment.isDebug) Level.info else Level.warn; pub fn hasAny(this: *const Log) bool { diff --git a/src/open.zig b/src/open.zig index 4afa5ae08..f129dfc54 100644 --- a/src/open.zig +++ b/src/open.zig @@ -324,3 +324,104 @@ pub const Editor = enum(u8) { child_process.deinit(); } }; + +pub const EditorContext = struct { + editor: ?Editor = null, + name: string = "", + path: string = "", + const Fs = @import("./fs.zig"); + + pub fn openInEditor(this: *EditorContext, editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) void { + _openInEditor(this.path, editor_, blob, id, tmpdir, line, column) catch |err| { + if (editor_ != .other) { + Output.prettyErrorln("Error {s} opening in {s}", .{ @errorName(err), @tagName(editor_) }); + } + + this.editor = Editor.none; + }; + } + + fn _openInEditor(path: string, editor_: Editor, blob: []const u8, id: string, tmpdir: std.fs.Dir, line: string, column: string) !void { + var basename_buf: [512]u8 = undefined; + var basename = std.fs.path.basename(id); + if (strings.endsWith(basename, ".bun") and basename.len < 499) { + std.mem.copy(u8, &basename_buf, basename); + basename_buf[basename.len..][0..3].* = ".js".*; + basename = basename_buf[0 .. basename.len + 3]; + } + + try tmpdir.writeFile(basename, blob); + + var opened = try tmpdir.openFile(basename, .{}); + defer opened.close(); + var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + try editor_.open( + path, + try std.os.getFdPath(opened.handle, &path_buf), + line, + column, + default_allocator, + ); + } + + pub fn autoDetectEditor(this: *EditorContext, env: *DotEnv.Loader) void { + if (this.editor == null) { + this.detectEditor(env); + } + } + pub fn detectEditor(this: *EditorContext, env: *DotEnv.Loader) void { + var buf: [bun.MAX_PATH_BYTES]u8 = undefined; + + var out: string = ""; + // first: choose from user preference + if (this.name.len > 0) { + // /usr/bin/vim + if (std.fs.path.isAbsolute(this.name)) { + this.editor = Editor.byName(std.fs.path.basename(this.name)) orelse Editor.other; + this.path = this.name; + return; + } + + // "vscode" + if (Editor.byName(std.fs.path.basename(this.name))) |editor_| { + if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { + this.editor = editor_; + this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; + return; + } + + // not in path, try common ones + if (Editor.byFallbackPathForEditor(editor_, &out)) { + this.editor = editor_; + this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; + return; + } + } + } + + // EDITOR=code + if (Editor.detect(env)) |editor_| { + if (Editor.byPATHForEditor(env, editor_, &buf, Fs.FileSystem.instance.top_level_dir, &out)) { + this.editor = editor_; + this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; + return; + } + + // not in path, try common ones + if (Editor.byFallbackPathForEditor(editor_, &out)) { + this.editor = editor_; + this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; + return; + } + } + + // Don't know, so we will just guess based on what exists + if (Editor.byFallback(env, &buf, Fs.FileSystem.instance.top_level_dir, &out)) |editor_| { + this.editor = editor_; + this.path = Fs.FileSystem.instance.dirname_store.append(string, out) catch unreachable; + return; + } + + this.editor = Editor.none; + } +}; diff --git a/src/runtime.zig b/src/runtime.zig index e33b31961..ecce90a74 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -70,6 +70,7 @@ pub const ErrorJS = struct { pub const Fallback = struct { pub const ProdSourceContent = @embedFile("./fallback.out.js"); pub const HTMLTemplate = @embedFile("./fallback.html"); + pub const HTMLBackendTemplate = @embedFile("./fallback-backend.html"); const Base64FallbackMessage = struct { msg: *const Api.FallbackMessageContainer, @@ -160,6 +161,28 @@ pub const Fallback = struct { .entry_point = entry_point, }); } + + pub fn renderBackend( + allocator: std.mem.Allocator, + msg: *const Api.FallbackMessageContainer, + comptime WriterType: type, + writer: WriterType, + ) !void { + const PrintArgs = struct { + blob: Base64FallbackMessage, + bun_error_css: string, + bun_error: string, + fallback: string, + bun_error_page_css: string, + }; + try writer.print(HTMLBackendTemplate, PrintArgs{ + .blob = Base64FallbackMessage{ .msg = msg, .allocator = allocator }, + .bun_error_css = ErrorCSS.sourceContent(), + .bun_error = ErrorJS.sourceContent(), + .bun_error_page_css = "", + .fallback = scriptContent(), + }); + } }; pub const Runtime = struct { |