Compare commits

..

1 Commits

Author SHA1 Message Date
Colin McDonnell
5fab11a527 Add tty types 2023-08-21 21:21:53 -07:00
228 changed files with 10744 additions and 53741 deletions

View File

@@ -14,7 +14,7 @@ body:
- type: input
attributes:
label: What version of Bun is running?
description: Copy the output of `bun --revision`
description: Copy the output of `bun -v`
- type: input
attributes:
label: What platform is your computer?

View File

@@ -36,7 +36,7 @@ jobs:
arch: aarch64
build_arch: arm64
runner: linux-arm64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-linux-arm64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-linux-arm64-lto.tar.gz"
webkit_basename: "bun-webkit-linux-arm64-lto"
build_machine_arch: aarch64

View File

@@ -46,7 +46,7 @@ jobs:
arch: x86_64
build_arch: amd64
runner: big-ubuntu
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-linux-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-linux-amd64-lto.tar.gz"
webkit_basename: "bun-webkit-linux-amd64-lto"
build_machine_arch: x86_64
- cpu: nehalem
@@ -54,7 +54,7 @@ jobs:
arch: x86_64
build_arch: amd64
runner: big-ubuntu
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-linux-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-linux-amd64-lto.tar.gz"
webkit_basename: "bun-webkit-linux-amd64-lto"
build_machine_arch: x86_64

View File

@@ -117,7 +117,7 @@ jobs:
# obj: bun-obj-darwin-x64-baseline
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: true
# compile_obj: false
# - cpu: haswell
@@ -126,7 +126,7 @@ jobs:
# obj: bun-obj-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: true
# compile_obj: false
# - cpu: nehalem
@@ -135,7 +135,7 @@ jobs:
# obj: bun-obj-darwin-x64-baseline
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: false
# compile_obj: true
# - cpu: haswell
@@ -144,7 +144,7 @@ jobs:
# obj: bun-obj-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: false
# compile_obj: true
- cpu: native
@@ -152,7 +152,7 @@ jobs:
tag: bun-darwin-aarch64
obj: bun-obj-darwin-aarch64
artifact: bun-obj-darwin-aarch64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-arm64-lto.tar.gz"
runner: macos-arm64
dependencies: true
compile_obj: true
@@ -257,7 +257,7 @@ jobs:
# package: bun-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# - cpu: haswell
# arch: x86_64
# tag: bun-darwin-x64
@@ -265,14 +265,14 @@ jobs:
# package: bun-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
- cpu: native
arch: aarch64
tag: bun-darwin-aarch64
obj: bun-obj-darwin-aarch64
package: bun-darwin-aarch64
artifact: bun-obj-darwin-aarch64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-arm64-lto.tar.gz"
runner: macos-arm64
steps:
- uses: actions/checkout@v3

View File

@@ -117,7 +117,7 @@ jobs:
obj: bun-obj-darwin-x64-baseline
runner: macos-11
artifact: bun-obj-darwin-x64-baseline
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
dependencies: true
compile_obj: false
# - cpu: haswell
@@ -126,7 +126,7 @@ jobs:
# obj: bun-obj-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: true
# compile_obj: false
- cpu: nehalem
@@ -135,7 +135,7 @@ jobs:
obj: bun-obj-darwin-x64-baseline
runner: macos-11
artifact: bun-obj-darwin-x64-baseline
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
dependencies: false
compile_obj: true
# - cpu: haswell
@@ -144,7 +144,7 @@ jobs:
# obj: bun-obj-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: false
# compile_obj: true
# - cpu: native
@@ -152,7 +152,7 @@ jobs:
# tag: bun-darwin-aarch64
# obj: bun-obj-darwin-aarch64
# artifact: bun-obj-darwin-aarch64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# runner: macos-arm64
# dependencies: true
# compile_obj: true
@@ -258,7 +258,7 @@ jobs:
package: bun-darwin-x64
runner: macos-11
artifact: bun-obj-darwin-x64-baseline
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# - cpu: haswell
# arch: x86_64
# tag: bun-darwin-x64
@@ -266,14 +266,14 @@ jobs:
# package: bun-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# - cpu: native
# arch: aarch64
# tag: bun-darwin-aarch64
# obj: bun-obj-darwin-aarch64
# package: bun-darwin-aarch64
# artifact: bun-obj-darwin-aarch64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# runner: macos-arm64
steps:
- uses: actions/checkout@v3

View File

@@ -117,7 +117,7 @@ jobs:
# obj: bun-obj-darwin-x64-baseline
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: true
# compile_obj: false
- cpu: haswell
@@ -126,7 +126,7 @@ jobs:
obj: bun-obj-darwin-x64
runner: macos-11
artifact: bun-obj-darwin-x64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
dependencies: true
compile_obj: false
# - cpu: nehalem
@@ -135,7 +135,7 @@ jobs:
# obj: bun-obj-darwin-x64-baseline
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# dependencies: false
# compile_obj: true
- cpu: haswell
@@ -144,7 +144,7 @@ jobs:
obj: bun-obj-darwin-x64
runner: macos-11
artifact: bun-obj-darwin-x64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
dependencies: false
compile_obj: true
# - cpu: native
@@ -152,7 +152,7 @@ jobs:
# tag: bun-darwin-aarch64
# obj: bun-obj-darwin-aarch64
# artifact: bun-obj-darwin-aarch64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-arm64-lto.tar.gz"
# runner: macos-arm64
# dependencies: true
# compile_obj: true
@@ -260,7 +260,7 @@ jobs:
# package: bun-darwin-x64
# runner: macos-11
# artifact: bun-obj-darwin-x64-baseline
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
- cpu: haswell
arch: x86_64
tag: bun-darwin-x64
@@ -268,14 +268,14 @@ jobs:
package: bun-darwin-x64
runner: macos-11
artifact: bun-obj-darwin-x64
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-amd64-lto.tar.gz"
# - cpu: native
# arch: aarch64
# tag: bun-darwin-aarch64
# obj: bun-obj-darwin-aarch64
# package: bun-darwin-aarch64
# artifact: bun-obj-darwin-aarch64
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-4/bun-webkit-macos-arm64-lto.tar.gz"
# runner: macos-arm64
steps:
- uses: actions/checkout@v3

View File

@@ -1,7 +1,7 @@
name: zig-fmt
env:
ZIG_VERSION: 0.12.0-dev.163+6780a6bbf
ZIG_VERSION: 0.11.0-dev.4006+bf827d0b5
on:
pull_request:

3
.gitignore vendored
View File

@@ -130,6 +130,3 @@ src/js/out/tmp
src/js/out/DebugPath.h
make-dev-stats.csv
.uuid
tsconfig.tsbuildinfo

View File

@@ -10,9 +10,9 @@ ARG ARCH=x86_64
ARG BUILD_MACHINE_ARCH=x86_64
ARG TRIPLET=${ARCH}-linux-gnu
ARG BUILDARCH=amd64
ARG WEBKIT_TAG=2023-aug3-5
ARG WEBKIT_TAG=2023-aug3-4
ARG ZIG_TAG=jul1
ARG ZIG_VERSION="0.12.0-dev.163+6780a6bbf"
ARG ZIG_VERSION="0.11.0-dev.4006+bf827d0b5"
ARG WEBKIT_BASENAME="bun-webkit-linux-$BUILDARCH"
ARG ZIG_FOLDERNAME=zig-linux-${BUILD_MACHINE_ARCH}-${ZIG_VERSION}
@@ -20,7 +20,7 @@ ARG ZIG_FILENAME=${ZIG_FOLDERNAME}.tar.xz
ARG WEBKIT_URL="https://github.com/oven-sh/WebKit/releases/download/$WEBKIT_TAG/${WEBKIT_BASENAME}.tar.gz"
ARG ZIG_URL="https://ziglang.org/builds/${ZIG_FILENAME}"
ARG GIT_SHA=""
ARG BUN_BASE_VERSION=0.8
ARG BUN_BASE_VERSION=0.7
FROM bitnami/minideb:bullseye as bun-base

View File

@@ -38,7 +38,7 @@ NATIVE_OR_OLD_MARCH = -march=nehalem
endif
MIN_MACOS_VERSION ?= $(DEFAULT_MIN_MACOS_VERSION)
BUN_BASE_VERSION = 0.8
BUN_BASE_VERSION = 0.7
CI ?= false

View File

@@ -123,6 +123,7 @@ bun upgrade --canary
- [HTMLRewriter](https://bun.sh/docs/api/html-rewriter)
- [Testing](https://bun.sh/docs/api/test)
- [Utils](https://bun.sh/docs/api/utils)
- [DNS](https://bun.sh/docs/api/dns)
- [Node-API](https://bun.sh/docs/api/node-api)
## Contributing

View File

@@ -1,5 +1,4 @@
const std = @import("std");
const pathRel = std.fs.path.relative;
const Wyhash = @import("./src/wyhash.zig").Wyhash;
var is_debug_build = false;
fn moduleSource(comptime out: []const u8) FileSource {
@@ -97,7 +96,6 @@ const BunBuildOptions = struct {
}
};
// relative to the prefix
var output_dir: []const u8 = "";
fn panicIfNotFound(comptime filepath: []const u8) []const u8 {
var file = std.fs.cwd().openFile(filepath, .{ .optimize = .read_only }) catch |err| {
@@ -174,12 +172,13 @@ pub fn build(b: *Build) !void {
var triplet = triplet_buf[0 .. osname.len + cpuArchName.len + 1];
if (b.option([]const u8, "output-dir", "target to install to") orelse std.os.getenv("OUTPUT_DIR")) |output_dir_| {
output_dir = try pathRel(b.allocator, b.install_prefix, output_dir_);
output_dir = b.pathFromRoot(output_dir_);
} else {
const output_dir_base = try std.fmt.bufPrint(&output_dir_buf, "{s}{s}", .{ bin_label, triplet });
output_dir = try pathRel(b.allocator, b.install_prefix, output_dir_base);
output_dir = b.pathFromRoot(output_dir_base);
}
std.fs.cwd().makePath(output_dir) catch {};
is_debug_build = optimize == OptimizeMode.Debug;
const bun_executable_name = if (optimize == std.builtin.OptimizeMode.Debug) "bun-debug" else "bun";
const root_src = if (target.getOsTag() == std.Target.Os.Tag.freestanding)
@@ -187,12 +186,12 @@ pub fn build(b: *Build) !void {
else
"root.zig";
const min_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
const min_version: std.SemanticVersion = if (target.getOsTag() != .freestanding and !target.isWindows())
target.getOsVersionMin().semver
else
.{ .major = 0, .minor = 0, .patch = 0 };
const max_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
const max_version: std.SemanticVersion = if (target.getOsTag() != .freestanding and !target.isWindows())
target.getOsVersionMax().semver
else
.{ .major = 0, .minor = 0, .patch = 0 };
@@ -203,7 +202,6 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative(root_src),
.target = target,
.optimize = optimize,
.main_pkg_path = .{ .cwd_relative = b.pathFromRoot(".") },
});
var default_build_options: BunBuildOptions = brk: {
@@ -241,6 +239,8 @@ pub fn build(b: *Build) !void {
};
{
obj.setMainPkgPath(b.pathFromRoot("."));
try addInternalPackages(
b,
obj,
@@ -271,15 +271,9 @@ pub fn build(b: *Build) !void {
std.io.getStdErr().writer().print("Output: {s}/{s}\n\n", .{ output_dir, bun_executable_name }) catch unreachable;
defer obj_step.dependOn(&obj.step);
var install = b.addInstallFileWithDir(
obj.getEmittedBin(),
.{ .custom = output_dir },
b.fmt("{s}.o", .{bun_executable_name}),
);
install.step.dependOn(&obj.step);
obj_step.dependOn(&install.step);
obj.emit_bin = .{
.emit_to = b.fmt("{s}/{s}.o", .{ output_dir, bun_executable_name }),
};
var actual_build_options = default_build_options;
if (b.option(bool, "generate-sizes", "Generate sizes of things") orelse false) {
actual_build_options.sizegen = true;
@@ -296,8 +290,7 @@ pub fn build(b: *Build) !void {
if (target.getCpuArch().isX86()) obj.disable_stack_probing = true;
if (b.option(bool, "for-editor", "Do not emit bin, just check for errors") orelse false) {
// obj.emit_bin = .no_emit;
obj.generated_bin = null;
obj.emit_bin = .no_emit;
}
if (target.getOsTag() == .linux) {
@@ -315,10 +308,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("src/bindgen.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
var headers_build_options = default_build_options;
headers_build_options.bindgen = true;
headers_obj.addOptions("build_options", default_build_options.step(b));
@@ -326,22 +318,21 @@ pub fn build(b: *Build) !void {
}
{
const wasm_step = b.step("bun-wasm", "Build WASM");
var wasm = b.addStaticLibrary(.{
const wasm = b.step("bun-wasm", "Build WASM");
var wasm_step = b.addStaticLibrary(.{
.name = "bun-wasm",
.root_source_file = FileSource.relative("root_wasm.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer wasm_step.dependOn(&wasm.step);
wasm.strip = false;
defer wasm.dependOn(&wasm_step.step);
wasm_step.strip = false;
// wasm_step.link_function_sections = true;
// wasm_step.link_emit_relocs = true;
// wasm_step.single_threaded = true;
try configureObjectStep(b, wasm, wasm_step, @TypeOf(target), target);
try configureObjectStep(b, wasm_step, @TypeOf(target), target, obj.main_pkg_path.?);
var build_opts = default_build_options;
wasm.addOptions("build_options", build_opts.step(b));
wasm_step.addOptions("build_options", build_opts.step(b));
}
{
@@ -351,10 +342,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("misctools/http_bench.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -365,10 +355,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("misctools/machbench.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -379,10 +368,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("misctools/fetch.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -393,10 +381,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("src/bench/string-handling.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -407,10 +394,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("src/sha.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -421,10 +407,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("src/sourcemap/vlq_bench.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -435,10 +420,9 @@ pub fn build(b: *Build) !void {
.root_source_file = FileSource.relative("misctools/tgz.zig"),
.target = target,
.optimize = optimize,
.main_pkg_path = obj.main_pkg_path,
});
defer headers_step.dependOn(&headers_obj.step);
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_obj.addOptions("build_options", default_build_options.step(b));
}
@@ -452,23 +436,16 @@ pub fn build(b: *Build) !void {
var headers_obj: *CompileStep = b.addTest(.{
.root_source_file = FileSource.relative(test_file orelse "src/main.zig"),
.target = target,
.main_pkg_path = obj.main_pkg_path,
});
headers_obj.filter = test_filter;
if (test_bin_) |test_bin| {
headers_obj.name = std.fs.path.basename(test_bin);
if (std.fs.path.dirname(test_bin)) |dir| {
var install = b.addInstallFileWithDir(
headers_obj.getEmittedBin(),
.{ .custom = try std.fs.path.relative(b.allocator, output_dir, dir) },
headers_obj.name,
);
install.step.dependOn(&headers_obj.step);
headers_step.dependOn(&install.step);
}
if (std.fs.path.dirname(test_bin)) |dir| headers_obj.emit_bin = .{
.emit_to = b.fmt("{s}/{s}", .{ dir, headers_obj.name }),
};
}
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
headers_step.dependOn(&headers_obj.step);
headers_obj.addOptions("build_options", default_build_options.step(b));
@@ -479,7 +456,9 @@ pub fn build(b: *Build) !void {
pub var original_make_fn: ?*const fn (step: *std.build.Step) anyerror!void = null;
pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, obj_step: *std.build.Step, comptime Target: type, target: Target) !void {
pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, comptime Target: type, target: Target, main_pkg_path: []const u8) !void {
obj.setMainPkgPath(main_pkg_path);
// obj.setTarget(target);
try addInternalPackages(b, obj, std.heap.page_allocator, b.zig_exe, target);
@@ -487,16 +466,11 @@ pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, obj_step: *
// obj.setBuildMode(optimize);
obj.bundle_compiler_rt = false;
if (obj.emit_directory == null) {
var install = b.addInstallFileWithDir(
obj.getEmittedBin(),
.{ .custom = output_dir },
b.fmt("{s}.o", .{obj.name}),
);
if (obj.emit_bin == .default)
obj.emit_bin = .{
.emit_to = b.fmt("{s}/{s}.o", .{ output_dir, obj.name }),
};
install.step.dependOn(&obj.step);
obj_step.dependOn(&install.step);
}
if (target.getOsTag() != .freestanding) obj.linkLibC();
if (target.getOsTag() != .freestanding) obj.bundle_compiler_rt = false;

BIN
bun.lockb

Binary file not shown.

View File

@@ -428,21 +428,6 @@ const str = Bun.inspect(arr);
// => "Uint8Array(3) [ 1, 2, 3 ]"
```
## `Bun.inspect.custom`
This is the symbol that Bun uses to implement `Bun.inspect`. You can override this to customize how your objects are printed. It is identical to `util.inspect.custom` in Node.js.
```ts
class Foo {
[Bun.inspect.custom]() {
return "foo";
}
}
const foo = new Foo();
console.log(foo); // => "foo"
```
## `Bun.nanoseconds()`
Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking.

View File

@@ -30,50 +30,14 @@ The runner recursively searches the working directory for files that match the f
- `*.spec.{js|jsx|ts|tsx}`
- `*_spec.{js|jsx|ts|tsx}`
You can filter the set of _test files_ to run by passing additional positional arguments to `bun test`. Any test file with a path that matches one of the filters will run. Commonly, these filters will be file or directory names; glob patterns are not yet supported.
You can filter the set of tests to run by passing additional positional arguments to `bun test`. Any file in the directory with an _absolute path_ that contains one of the filters will run. Commonly, these filters will be file or directory names; glob patterns are not yet supported.
```bash
$ bun test <filter> <filter> ...
```
To filter by _test name_, use the `-t`/`--test-name-pattern` flag.
```sh
# run all tests or test suites with "addition" in the name
$ bun test --test-name-pattern addition
```
The test runner runs all tests in a single process. It loads all `--preload` scripts (see [Lifecycle](/docs/test/lifecycle) for details), then runs all tests. If a test fails, the test runner will exit with a non-zero exit code.
## Timeouts
Use the `--timeout` flag to specify a _per-test_ timeout in milliseconds. If a test times out, it will be marked as failed. The default value is `5000`.
```bash
# default value is 5000
$ bun test --timeout 20
```
## Rerun tests
Use the `--rerun-each` flag to run each test multiple times. This is useful for detecting flaky or non-deterministic test failures.
```sh
$ bun test --rerun-each 100
```
## Bail out with `--bail`
Use the `--bail` flag to abort the test run early after a pre-determined number of test failures. By default Bun will run all tests and report all failures, but sometimes in CI environments it's preferable to terminate earlier to reduce CPU usage.
```sh
# bail after 1 failure
$ bun test --bail
# bail after 10 failure
$ bun test --bail 10
```
## Watch mode
Similar to `bun run`, you can pass the `--watch` flag to `bun test` to watch for changes and re-run tests.

View File

@@ -1,65 +0,0 @@
---
name: Build an app with Nuxt and Bun
---
Bun supports [Nuxt](https://nuxt.com) out of the box. Initialize a Nuxt app with official `nuxi` CLI.
```sh
$ bunx nuxi init my-nuxt-app
Nuxi 3.6.5
✨ Nuxt project is created with v3 template. Next steps:
cd my-nuxt-app
Install dependencies with npm install or yarn install or pnpm install
Start development server with npm run dev or yarn dev or pnpm run dev
```
---
Then move into the project directory and install dependencies.
```sh
$ cd my-app
$ bun install
bun install v0.8.0
+ @nuxt/devtools@0.8.0
+ @types/node@18.17.6
+ nuxt@3.6.5
Nuxi 3.6.5
✔ Types generated in .nuxt
776 packages installed [1.72s]
```
---
To start the dev server, run `bun run dev` from the project root. This will execute the `nuxt dev` command (as defined in the `"dev"` script in `package.json`).
{% callout %}
The `nuxt` CLI uses Node.js by default; passing the `--bun` flag forces the dev server to use the Bun runtime instead.
{% /callout %}
```
$ bun --bun run dev
$ nuxt dev
Nuxi 3.6.5
Nuxt 3.6.5 with Nitro 2.5.2
> Local: http://localhost:3000/
> Network: http://192.168.0.21:3000/
> Network: http://[fd8a:d31d:481c:4883:1c64:3d90:9f83:d8a2]:3000/
✔ Nuxt DevTools is enabled v0.8.0 (experimental)
Vite client warmed up in 547ms
✔ Nitro built in 244 ms
```
---
Once the dev server spins up, open [http://localhost:3000](http://localhost:3000) to see the app. The app will render Nuxt's built-in `WelcomePage` template component.
To start developing your app, replace `<WelcomePage />` in `app.vue` with your own UI.
{% image src="https://github.com/oven-sh/bun/assets/3084745/2c683ecc-3298-4bb0-b8c0-cf4cfaea1daa" caption="Demo Nuxt app running on localhost" /%}
---
Refer to the [Nuxt website](https://nuxt.com/docs) for complete documentation.

View File

@@ -1,82 +0,0 @@
---
name: Debugging Bun with the web debugger
---
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-vscode/types/jsc.d.ts). To enable debugging when running code with Bun, use the `--inspect` flag. For demonstration purposes, consider the following simple web server.
```ts#server.ts
Bun.serve({
fetch(req){
console.log(req.url);
return new Response("Hello, world!");
}
})
```
---
Let's run this file with the `--inspect` flag.
This automatically starts a WebSocket server on an available port that can be used to introspect the running Bun process. Various debugging tools can connect to this server to provide an interactive debugging experience.
Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a modified version of WebKit's [Web Inspector Interface](https://webkit.org/web-inspector/web-inspector-interface/), which will look familiar to Safari users.
```sh
$ bun --inspect server.ts
------------------ Bun Inspector ------------------
Listening at:
ws://localhost:6499/0tqxs9exrgrm
Inspect in browser:
https://debug.bun.sh/#localhost:6499/0tqxs9exrgrm
------------------ Bun Inspector ------------------
```
---
Open the provided `debug.bun.sh` URL in your browser to start a debugging session. From this interface, you'll be able to view the source code of the running file, view and set breakpoints, and execute code with the built-in console.
{% image src="https://github.com/oven-sh/bun/assets/3084745/e6a976a8-80cc-4394-8925-539025cc025d" alt="Screenshot of Bun debugger, Console tab" /%}
---
Let's set a breakpoint. Navigate to the Sources tab; you should see the code from earlier. Click on the line number `3` to set a breakpoint on our `console.log(req.url)` statement.
{% image src="https://github.com/oven-sh/bun/assets/3084745/3b69c7e9-25ff-4f9d-acc4-caa736862935" alt="screenshot of Bun debugger" /%}
---
Then visit [`http://localhost:3000`](http://localhost:3000) in your web browser. This will send an HTTP request to our `localhost` web server. It will seem like the page isn't loading. Why? Because the program has paused execution at the breakpoint we set earlier.
Note how the UI has changed.
{% image src="https://github.com/oven-sh/bun/assets/3084745/8b565e58-5445-4061-9bc4-f41090dfe769" alt="screenshot of Bun debugger" /%}
---
At this point there's a lot we can do to introspect the current execution environment. We can use the console at the bottom to run arbitrary code in the context of the program, with full access to the variables in scope at our breakpoint.
{% image src="https://github.com/oven-sh/bun/assets/3084745/f4312b76-48ba-4a7d-b3b6-6205968ac681" /%}
---
On the right side of the Sources pane, we can see all local variables currently in scope, and drill down to see their properties and methods. Here, we're inspecting the `req` variable.
{% image src="https://github.com/oven-sh/bun/assets/3084745/63d7f843-5180-489c-aa94-87c486e68646" /%}
---
In the upper left of the Sources pane, we can control the execution of the program.
{% image src="https://github.com/oven-sh/bun/assets/3084745/41b76deb-7371-4461-9d5d-81b5a6d2f7a4" /%}
---
Here's a cheat sheet explaining the functions of the control flow buttons.
- _Continue script execution_ — continue running the program until the next breakpoint or exception.
- _Step over_ — The program will continue to the next line.
- _Step into_ — If the current statement contains a function call, the debugger will "step into" the called function.
- _Step out_ — If the current statement is a function call, the debugger will finish executing the call, then "step out" of the function to the location where it was called.
{% image src="https://github-production-user-asset-6210df.s3.amazonaws.com/3084745/261510346-6a94441c-75d3-413a-99a7-efa62365f83d.png" /%}

View File

@@ -1,23 +0,0 @@
---
name: Detect when code is executed with Bun
---
The recommended way to conditionally detect when code is being executed with `bun` is to check for the existence of the `Bun` global.
This is similar to how you'd check for the existence of the `window` variable to detect when code is being executed in a browser.
```ts
if (typeof Bun !== "undefined") {
// this code will only run when the file is run with Bun
}
```
---
In TypeScript environments, the previous approach will result in a type error unless `bun-types` is globally installed. To avoid this, you can check `process.versions` instead.
```ts
if (process.versions.bun) {
// this code will only run when the file is run with Bun
}
```

View File

@@ -8,7 +8,7 @@ The `Bun.password.hash()` function provides a fast, built-in mechanism for secur
const password = "super-secure-pa$$word";
const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/...
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4
```
---

View File

@@ -132,9 +132,6 @@ export default {
page("runtime/configuration", "Configuration", {
description: `Bun's runtime is configurable with environment variables and the bunfig.toml config file.`,
}),
page("runtime/debugger", "Debugger", {
description: `Debug your code with Bun's web-based debugger or VS Code extension`,
}),
page("runtime/framework", "Framework API", {
disabled: true,
description:
@@ -191,13 +188,13 @@ export default {
page("cli/test", "`bun test`", {
description: "Bun's test runner uses Jest-compatible syntax but runs 100x faster.",
}),
page("test/hot", "Watch mode", {
description: "Reload your tests automatically on change.",
}),
page("test/writing", "Writing tests", {
description:
"Write your tests using Jest-like expect matchers, plus setup/teardown hooks, snapshot testing, and more",
}),
page("test/hot", "Watch mode", {
description: "Reload your tests automatically on change.",
}),
page("test/lifecycle", "Lifecycle hooks", {
description: "Add lifecycle hooks to your tests that run before/after each test or test run",
}),

View File

@@ -112,7 +112,7 @@ Zig can be installed either with our npm package [`@oven/zig`](https://www.npmjs
```bash
$ bun install -g @oven/zig
$ zigup 0.12.0-dev.163+6780a6bbf
$ zigup 0.11.0-dev.4006+bf827d0b5
```
{% callout %}

View File

@@ -1,90 +0,0 @@
---
name: Debugger
---
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-vscode/types/jsc.d.ts). For demonstration purposes, consider the following simple web server.
```ts#server.ts
Bun.serve({
fetch(req){
console.log(req.url);
return new Response("Hello, world!");
}
})
```
### `--inspect`
To enable debugging when running code with Bun, use the `--inspect` flag. This automatically starts a WebSocket server on an available port that can be used to introspect the running Bun process.
```sh
$ bun --inspect server.ts
------------------ Bun Inspector ------------------
Listening at:
ws://localhost:6499/0tqxs9exrgrm
Inspect in browser:
https://debug.bun.sh/#localhost:6499/0tqxs9exrgrm
------------------ Bun Inspector ------------------
```
### `--inspect-brk`
The `--inspect-brk` flag behaves identically to `--inspect`, except it automatically injects a breakpoint at the first line of the executed script. This is useful for debugging scripts that run quickly and exit immediately.
### `--inspect-wait`
The `--inspect-wait` flag behaves identically to `--inspect`, except the code will not execute until a debugger has attached to the running process.
### Setting a port or URL for the debugger
Regardless of which flag you use, you can optionally specify a port number, URL prefix, or both.
```sh
$ bun --inspect=4000 server.ts
$ bun --inspect=localhost:4000 server.ts
$ bun --inspect=localhost:4000/prefix server.ts
```
## Debuggers
Various debugging tools can connect to this server to provide an interactive debugging experience. Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a modified version of WebKit's [Web Inspector Interface](https://webkit.org/web-inspector/web-inspector-interface/), which will look familiar to Safari users.
### `debug.bun.sh`
Bun hosts a web-based debugger at [debug.bun.sh](https://debug.bun.sh). It is a modified version of WebKit's [Web Inspector Interface](https://webkit.org/web-inspector/web-inspector-interface/), which will look familiar to Safari users.
Open the provided `debug.bun.sh` URL in your browser to start a debugging session. From this interface, you'll be able to view the source code of the running file, view and set breakpoints, and execute code with the built-in console.
{% image src="https://github.com/oven-sh/bun/assets/3084745/e6a976a8-80cc-4394-8925-539025cc025d" alt="Screenshot of Bun debugger, Console tab" /%}
Let's set a breakpoint. Navigate to the Sources tab; you should see the code from earlier. Click on the line number `3` to set a breakpoint on our `console.log(req.url)` statement.
{% image src="https://github.com/oven-sh/bun/assets/3084745/3b69c7e9-25ff-4f9d-acc4-caa736862935" alt="screenshot of Bun debugger" /%}
Then visit [`http://localhost:3000`](http://localhost:3000) in your web browser. This will send an HTTP request to our `localhost` web server. It will seem like the page isn't loading. Why? Because the program has paused execution at the breakpoint we set earlier.
Note how the UI has changed.
{% image src="https://github.com/oven-sh/bun/assets/3084745/8b565e58-5445-4061-9bc4-f41090dfe769" alt="screenshot of Bun debugger" /%}
At this point there's a lot we can do to introspect the current execution environment. We can use the console at the bottom to run arbitrary code in the context of the program, with full access to the variables in scope at our breakpoint.
{% image src="https://github.com/oven-sh/bun/assets/3084745/f4312b76-48ba-4a7d-b3b6-6205968ac681" /%}
On the right side of the Sources pane, we can see all local variables currently in scope, and drill down to see their properties and methods. Here, we're inspecting the `req` variable.
{% image src="https://github.com/oven-sh/bun/assets/3084745/63d7f843-5180-489c-aa94-87c486e68646" /%}
In the upper left of the Sources pane, we can control the execution of the program.
{% image src="https://github.com/oven-sh/bun/assets/3084745/41b76deb-7371-4461-9d5d-81b5a6d2f7a4" /%}
Here's a cheat sheet explaining the functions of the control flow buttons.
- _Continue script execution_ — continue running the program until the next breakpoint or exception.
- _Step over_ — The program will continue to the next line.
- _Step into_ — If the current statement contains a function call, the debugger will "step into" the called function.
- _Step out_ — If the current statement is a function call, the debugger will finish executing the call, then "step out" of the function to the location where it was called.
{% image src="https://github-production-user-asset-6210df.s3.amazonaws.com/3084745/261510346-6a94441c-75d3-413a-99a7-efa62365f83d.png" /%}

View File

@@ -138,7 +138,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
### [`node:tty`](https://nodejs.org/api/tty.html)
🟢 Fully implemented.
🟡 Missing `tty.ReadStream` and `tty.WriteStream`.
### [`node:url`](https://nodejs.org/api/url.html)

View File

@@ -1,11 +1,12 @@
Bun's test runner now supports built-in _code coverage reporting_. This makes it easy to see how much of the codebase is covered by tests, and find areas that are not currently well-tested.
`bun:test` supports seeing which lines of code are covered by tests. To use this feature, pass `--coverage` to the CLI:
## Enabling coverage
```sh
bun test --coverage
```
`bun:test` supports seeing which lines of code are covered by tests. To use this feature, pass `--coverage` to the CLI. It will print out a coverage report to the console:
It will print out a coverage report to the console:
```js
$ bun test --coverage
-------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
@@ -25,45 +26,32 @@ All files | 38.89 | 42.11 |
-------------|---------|---------|-------------------
```
To always enable coverage reporting by default, add the following line to your `bunfig.toml`:
If coverage is below a threshold, `bun:test` will exit with a non-zero exit code to indicate the failure.
### Configuring coverage
`bunfig.toml` supports configuring coverage:
```toml
[test]
# always enable coverage
# Always enable coverage
coverage = true
```
By default coverage reports will _include_ test files and _exclude_ sourcemaps. This is usually what you want, but it can be configured otherwise in `bunfig.toml`.
```toml
[test]
coverageSkipTestFiles = true # default false
```
### Coverage thresholds
{% callout %}
**Note** — Support for coverage reporting was added in Bun v0.7.3.
{% /callout %}
It is possible to specify a coverage threshold in `bunfig.toml`. If your test suite does not meet or exceed this threshold, `bun test` will exit with a non-zero exit code to indicate the failure.
```toml
[test]
# to require 90% line-level and function-level coverage
coverageThreshold = 0.9
# to set different thresholds for lines and functions
# Anything less than 90% coverage will fail the test
# coverageThreshold = 0.9
coverageThreshold = { line = 0.9, function = 0.9 }
# Don't include .test.* files in coverage reports
coverageSkipTestFiles = true
# Disable sourcemap support in coverage reports
# By default, coverage reports will automatically use Bun's internal sourcemap.
# You probably don't want to configure this
# coverageIgnoreSourcemaps = false
```
### Sourcemaps
`coverageThreshold` can be either a number or an object with `line` and `function` keys. When a number, it is treated as both the line and function threshold.
Internally, Bun transpiles all files by default, so Bun automatically generates an internal [source map](https://web.dev/source-maps/) that maps lines of your original source code onto Bun's internal representation. If for any reason you want to disable this, set `test.coverageIgnoreSourcemaps` to `false`; this will rarely be desirable outside of advanced use cases.
```toml
[test]
coverageIgnoreSourcemaps = true # default false
```
Coverage support was added in Bun v0.7.3.

View File

@@ -187,17 +187,7 @@ pub fn main() anyerror!void {
var ctx = try default_allocator.create(HTTP.HTTPChannelContext);
ctx.* = .{
.channel = channel,
.http = try HTTP.AsyncHTTP.init(
default_allocator,
args.method,
args.url,
args.headers,
args.headers_buf,
response_body_string,
args.body,
0,
HTTP.FetchRedirect.follow,
),
.http = try HTTP.AsyncHTTP.init(default_allocator, args.method, args.url, args.headers, args.headers_buf, response_body_string, args.body, 0, HTTP.FetchRedirect.follow),
};
ctx.http.callback = HTTP.HTTPChannelContext.callback;
var batch = HTTPThread.Batch{};

View File

@@ -9,7 +9,6 @@
"prettier": "^2.4.1",
"react": "next",
"react-dom": "next",
"source-map-js": "^1.0.2",
"typescript": "^5.0.2"
},
"private": true,
@@ -18,7 +17,7 @@
"build-fallback": "esbuild --target=esnext --bundle src/fallback.ts --format=iife --platform=browser --minify > src/fallback.out.js",
"postinstall": "bash .scripts/postinstall.sh",
"typecheck": "tsc --noEmit && cd test && bun run typecheck",
"fmt": "prettier --write --cache './{src,test,bench,packages/{bun-inspector-*,bun-vscode,bun-debug-adapter-protocol}}/**/*.{mjs,ts,tsx,js,jsx}'",
"fmt": "prettier --write --cache './{src,test,bench}/**/*.{mjs,ts,tsx,js,jsx}'",
"lint": "eslint './**/*.d.ts' --cache",
"lint:fix": "eslint './**/*.d.ts' --cache --fix"
},
@@ -26,7 +25,7 @@
"@types/react": "^18.0.25",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"bun-webkit": "0.0.1-48c1316e907ca597e27e5a7624160dc18a4df8ec"
"bun-webkit": "0.0.1-fd79ce3120a692f4aed314c3da3dd452b4aa865f"
},
"version": "0.0.0",
"prettier": "./.prettierrc.cjs"

View File

@@ -1,2 +0,0 @@
protocol/*/protocol.json linguist-generated=true
protocol/*/index.d.ts linguist-generated=true

View File

@@ -1 +0,0 @@
protocol/*.json

View File

@@ -1,3 +0,0 @@
# bun-debug-adapter-protocol
https://microsoft.github.io/debug-adapter-protocol/overview

View File

@@ -1,2 +0,0 @@
export type * from "./src/protocol";
export * from "./src/debugger/adapter";

View File

@@ -1,8 +0,0 @@
{
"name": "bun-debug-adapter-protocol",
"version": "0.0.1",
"dependencies": {
"semver": "^7.5.4",
"source-map-js": "^1.0.2"
}
}

View File

@@ -1,176 +0,0 @@
import type { Protocol, Type } from "../src/protocol/schema";
import { writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
run().catch(console.error);
async function run() {
const cwd = new URL("../protocol/", import.meta.url);
const runner = "Bun" in globalThis ? "bunx" : "npx";
const write = (name: string, data: string) => {
const path = new URL(name, cwd);
writeFileSync(path, data);
spawnSync(runner, ["prettier", "--write", path.pathname], { cwd, stdio: "ignore" });
};
const schema: Protocol = await download(
"https://microsoft.github.io/debug-adapter-protocol/debugAdapterProtocol.json",
);
write("protocol.json", JSON.stringify(schema));
const types = formatProtocol(schema);
write("index.d.ts", `// GENERATED - DO NOT EDIT\n${types}`);
}
function formatProtocol(protocol: Protocol, extraTs?: string): string {
const { definitions } = protocol;
const requestMap = new Map();
const responseMap = new Map();
const eventMap = new Map();
let body = `export namespace DAP {`;
loop: for (const [key, definition] of Object.entries(definitions)) {
if (/[a-z]+Request$/i.test(key)) {
continue;
}
if (/[a-z]+Arguments$/i.test(key)) {
const name = key.replace(/(Request)?Arguments$/, "");
const requestName = `${name}Request`;
requestMap.set(toMethod(name), requestName);
body += formatType(definition, requestName);
continue;
}
if ("allOf" in definition) {
const { allOf } = definition;
for (const type of allOf) {
if (type.type !== "object") {
continue;
}
const { description, properties = {} } = type;
if (/[a-z]+Event$/i.test(key)) {
const { event, body: type = {} } = properties;
if (!event || !("enum" in event)) {
continue;
}
const [eventKey] = event.enum ?? [];
eventMap.set(eventKey, key);
const eventType: Type = {
type: "object",
description,
...type,
};
body += formatType(eventType, key);
continue loop;
}
if (/[a-z]+Response$/i.test(key)) {
const { body: type = {} } = properties;
const bodyType: Type = {
type: "object",
description,
...type,
};
const name = key.replace(/Response$/, "");
responseMap.set(toMethod(name), key);
body += formatType(bodyType, key);
continue loop;
}
}
}
body += formatType(definition, key);
}
for (const [key, name] of responseMap) {
if (requestMap.has(key)) {
continue;
}
const requestName = `${name.replace(/Response$/, "")}Request`;
requestMap.set(key, requestName);
body += formatType({ type: "object", properties: {} }, requestName);
}
body += formatMapType("RequestMap", requestMap);
body += formatMapType("ResponseMap", responseMap);
body += formatMapType("EventMap", eventMap);
if (extraTs) {
body += extraTs;
}
return body + "};";
}
function formatMapType(key: string, typeMap: Map<string, string>): string {
const type: Type = {
type: "object",
required: [...typeMap.keys()],
properties: Object.fromEntries([...typeMap.entries()].map(([key, value]) => [key, { $ref: value }])),
};
return formatType(type, key);
}
function formatType(type: Type, key?: string): string {
const { description, type: kind } = type;
let body = "";
if (key) {
if (description) {
body += `\n${toComment(description)}\n`;
}
body += `export type ${key}=`;
}
if (kind === "boolean") {
body += "boolean";
} else if (kind === "number" || kind === "integer") {
body += "number";
} else if (kind === "string") {
const { enum: choices } = type;
if (choices) {
body += choices.map(value => `"${value}"`).join("|");
} else {
body += "string";
}
} else if (kind === "array") {
const { items } = type;
const itemType = items ? formatType(items) : "unknown";
body += `${itemType}[]`;
} else if (kind === "object") {
const { properties, required } = type;
if (!properties || Object.keys(properties).length === 0) {
body += "{}";
} else {
body += "{";
for (const [key, { description, ...type }] of Object.entries(properties)) {
if (description) {
body += `\n${toComment(description)}`;
}
const delimit = required?.includes(key) ? ":" : "?:";
body += `\n${key}${delimit}${formatType(type)};`;
}
body += "}";
}
} else if ("$ref" in type) {
const { $ref: ref } = type;
body += ref.split("/").pop() || "unknown";
} else if ("allOf" in type) {
const { allOf } = type;
body += allOf.map(type => formatType(type)).join("&");
} else {
body += "unknown";
}
if (key) {
body += ";";
}
return body;
}
function toMethod(name: string): string {
return `${name.substring(0, 1).toLowerCase()}${name.substring(1)}`;
}
function toComment(description?: string): string {
if (!description) {
return "";
}
const lines = ["/**", ...description.split("\n").map(line => ` * ${line.trim()}`), "*/"];
return lines.join("\n");
}
async function download<T>(url: string | URL): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download ${url}: ${response.statusText}`);
}
return response.json();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,271 +0,0 @@
import type { DAP } from "../protocol";
const capabilities: DAP.Capabilities = {
/**
* The debug adapter supports the `configurationDone` request.
* @see configurationDone
*/
supportsConfigurationDoneRequest: true,
/**
* The debug adapter supports function breakpoints using the `setFunctionBreakpoints` request.
* @see setFunctionBreakpoints
*/
supportsFunctionBreakpoints: true,
/**
* The debug adapter supports conditional breakpoints.
* @see setBreakpoints
* @see setInstructionBreakpoints
* @see setFunctionBreakpoints
* @see setExceptionBreakpoints
* @see setDataBreakpoints
*/
supportsConditionalBreakpoints: true,
/**
* The debug adapter supports breakpoints that break execution after a specified number of hits.
* @see setBreakpoints
* @see setInstructionBreakpoints
* @see setFunctionBreakpoints
* @see setExceptionBreakpoints
* @see setDataBreakpoints
*/
supportsHitConditionalBreakpoints: true,
/**
* The debug adapter supports a (side effect free) `evaluate` request for data hovers.
* @see evaluate
*/
supportsEvaluateForHovers: true,
/**
* Available exception filter options for the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
exceptionBreakpointFilters: [
{
filter: "all",
label: "Caught Exceptions",
default: false,
supportsCondition: true,
description: "Breaks on all throw errors, even if they're caught later.",
conditionDescription: `error.name == "CustomError"`,
},
{
filter: "uncaught",
label: "Uncaught Exceptions",
default: false,
supportsCondition: true,
description: "Breaks only on errors or promise rejections that are not handled.",
conditionDescription: `error.name == "CustomError"`,
},
],
/**
* The debug adapter supports stepping back via the `stepBack` and `reverseContinue` requests.
* @see stepBack
* @see reverseContinue
*/
supportsStepBack: false,
/**
* The debug adapter supports setting a variable to a value.
* @see setVariable
*/
supportsSetVariable: false,
/**
* The debug adapter supports restarting a frame.
* @see restartFrame
*/
supportsRestartFrame: false,
/**
* The debug adapter supports the `gotoTargets` request.
* @see gotoTargets
*/
supportsGotoTargetsRequest: false,
/**
* The debug adapter supports the `stepInTargets` request.
* @see stepInTargets
*/
supportsStepInTargetsRequest: false,
/**
* The debug adapter supports the `completions` request.
* @see completions
*/
supportsCompletionsRequest: false,
/**
* The set of characters that should trigger completion in a REPL.
* If not specified, the UI should assume the `.` character.
* @see completions
*/
completionTriggerCharacters: [".", "[", '"', "'"],
/**
* The debug adapter supports the `modules` request.
* @see modules
*/
supportsModulesRequest: false,
/**
* The set of additional module information exposed by the debug adapter.
* @see modules
*/
additionalModuleColumns: [],
/**
* Checksum algorithms supported by the debug adapter.
*/
supportedChecksumAlgorithms: [],
/**
* The debug adapter supports the `restart` request.
* In this case a client should not implement `restart` by terminating
* and relaunching the adapter but by calling the `restart` request.
* @see restart
*/
supportsRestartRequest: false,
/**
* The debug adapter supports `exceptionOptions` on the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
supportsExceptionOptions: false,
/**
* The debug adapter supports a `format` attribute on the `stackTrace`, `variables`, and `evaluate` requests.
* @see stackTrace
* @see variables
* @see evaluate
*/
supportsValueFormattingOptions: false,
/**
* The debug adapter supports the `exceptionInfo` request.
* @see exceptionInfo
*/
supportsExceptionInfoRequest: true,
/**
* The debug adapter supports the `terminateDebuggee` attribute on the `disconnect` request.
* @see disconnect
*/
supportTerminateDebuggee: true,
/**
* The debug adapter supports the `suspendDebuggee` attribute on the `disconnect` request.
* @see disconnect
*/
supportSuspendDebuggee: false,
/**
* The debug adapter supports the delayed loading of parts of the stack,
* which requires that both the `startFrame` and `levels` arguments and
* the `totalFrames` result of the `stackTrace` request are supported.
* @see stackTrace
*/
supportsDelayedStackTraceLoading: true,
/**
* The debug adapter supports the `loadedSources` request.
* @see loadedSources
*/
supportsLoadedSourcesRequest: true,
/**
* The debug adapter supports log points by interpreting the `logMessage` attribute of the `SourceBreakpoint`.
* @see setBreakpoints
*/
supportsLogPoints: true,
/**
* The debug adapter supports the `terminateThreads` request.
* @see terminateThreads
*/
supportsTerminateThreadsRequest: false,
/**
* The debug adapter supports the `setExpression` request.
* @see setExpression
*/
supportsSetExpression: false,
/**
* The debug adapter supports the `terminate` request.
* @see terminate
*/
supportsTerminateRequest: true,
/**
* The debug adapter supports data breakpoints.
* @see setDataBreakpoints
*/
supportsDataBreakpoints: false,
/**
* The debug adapter supports the `readMemory` request.
* @see readMemory
*/
supportsReadMemoryRequest: false,
/**
* The debug adapter supports the `writeMemory` request.
* @see writeMemory
*/
supportsWriteMemoryRequest: false,
/**
* The debug adapter supports the `disassemble` request.
* @see disassemble
*/
supportsDisassembleRequest: false,
/**
* The debug adapter supports the `cancel` request.
* @see cancel
*/
supportsCancelRequest: false,
/**
* The debug adapter supports the `breakpointLocations` request.
* @see breakpointLocations
*/
supportsBreakpointLocationsRequest: true,
/**
* The debug adapter supports the `clipboard` context value in the `evaluate` request.
* @see evaluate
*/
supportsClipboardContext: false,
/**
* The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests.
* @see stepIn
*/
supportsSteppingGranularity: false,
/**
* The debug adapter supports adding breakpoints based on instruction references.
* @see setInstructionBreakpoints
*/
supportsInstructionBreakpoints: false,
/**
* The debug adapter supports `filterOptions` as an argument on the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
supportsExceptionFilterOptions: true,
/**
* The debug adapter supports the `singleThread` property on the execution requests
* (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`).
*/
supportsSingleThreadExecutionRequests: false,
};
export default capabilities;

View File

@@ -1,36 +0,0 @@
"use strict";
export default {
fetch(request) {
const animal = getAnimal(request.url);
const voice = animal.talk();
return new Response(voice);
},
};
function getAnimal(query) {
switch (query.split("/").pop()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
}
return new Bird();
}
class Dog {
name = "dog";
talk() {
return "woof";
}
}
class Cat {
name = "cat";
talk() {
return "meow";
}
}
class Bird {
name = "bird";
talk() {
return "chirp";
}
}
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicGFja2FnZXMvYnVuLWRlYnVnLWFkYXB0ZXItcHJvdG9jb2wvZGVidWdnZXIvZml4dHVyZXMvd2l0aC1zb3VyY2VtYXAudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImV4cG9ydCBkZWZhdWx0IHtcbiAgZmV0Y2gocmVxdWVzdDogUmVxdWVzdCk6IFJlc3BvbnNlIHtcbiAgICBjb25zdCBhbmltYWwgPSBnZXRBbmltYWwocmVxdWVzdC51cmwpO1xuICAgIGNvbnN0IHZvaWNlID0gYW5pbWFsLnRhbGsoKTtcbiAgICByZXR1cm4gbmV3IFJlc3BvbnNlKHZvaWNlKTtcbiAgfSxcbn07XG5cbmZ1bmN0aW9uIGdldEFuaW1hbChxdWVyeTogc3RyaW5nKTogQW5pbWFsIHtcbiAgc3dpdGNoIChxdWVyeS5zcGxpdChcIi9cIikucG9wKCkpIHtcbiAgICBjYXNlIFwiZG9nXCI6XG4gICAgICByZXR1cm4gbmV3IERvZygpO1xuICAgIGNhc2UgXCJjYXRcIjpcbiAgICAgIHJldHVybiBuZXcgQ2F0KCk7XG4gIH1cbiAgcmV0dXJuIG5ldyBCaXJkKCk7XG59XG5cbmludGVyZmFjZSBBbmltYWwge1xuICByZWFkb25seSBuYW1lOiBzdHJpbmc7XG4gIHRhbGsoKTogc3RyaW5nO1xufVxuXG5jbGFzcyBEb2cgaW1wbGVtZW50cyBBbmltYWwge1xuICBuYW1lID0gXCJkb2dcIjtcblxuICB0YWxrKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIFwid29vZlwiO1xuICB9XG59XG5cbmNsYXNzIENhdCBpbXBsZW1lbnRzIEFuaW1hbCB7XG4gIG5hbWUgPSBcImNhdFwiO1xuXG4gIHRhbGsoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gXCJtZW93XCI7XG4gIH1cbn1cblxuY2xhc3MgQmlyZCBpbXBsZW1lbnRzIEFuaW1hbCB7XG4gIG5hbWUgPSBcImJpcmRcIjtcblxuICB0YWxrKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIFwiY2hpcnBcIjtcbiAgfVxufVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFBLGVBQWU7QUFBQSxFQUNiLE1BQU0sU0FBNEI7QUFDaEMsVUFBTSxTQUFTLFVBQVUsUUFBUSxHQUFHO0FBQ3BDLFVBQU0sUUFBUSxPQUFPLEtBQUs7QUFDMUIsV0FBTyxJQUFJLFNBQVMsS0FBSztBQUFBLEVBQzNCO0FBQ0Y7QUFFQSxTQUFTLFVBQVUsT0FBdUI7QUFDeEMsVUFBUSxNQUFNLE1BQU0sR0FBRyxFQUFFLElBQUksR0FBRztBQUFBLElBQzlCLEtBQUs7QUFDSCxhQUFPLElBQUksSUFBSTtBQUFBLElBQ2pCLEtBQUs7QUFDSCxhQUFPLElBQUksSUFBSTtBQUFBLEVBQ25CO0FBQ0EsU0FBTyxJQUFJLEtBQUs7QUFDbEI7QUFPQSxNQUFNLElBQXNCO0FBQUEsRUFDMUIsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7QUFFQSxNQUFNLElBQXNCO0FBQUEsRUFDMUIsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7QUFFQSxNQUFNLEtBQXVCO0FBQUEsRUFDM0IsT0FBTztBQUFBLEVBRVAsT0FBZTtBQUNiLFdBQU87QUFBQSxFQUNUO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==

View File

@@ -1,46 +0,0 @@
export default {
fetch(request: Request): Response {
const animal = getAnimal(request.url);
const voice = animal.talk();
return new Response(voice);
},
};
function getAnimal(query: string): Animal {
switch (query.split("/").pop()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
}
return new Bird();
}
interface Animal {
readonly name: string;
talk(): string;
}
class Dog implements Animal {
name = "dog";
talk(): string {
return "woof";
}
}
class Cat implements Animal {
name = "cat";
talk(): string {
return "meow";
}
}
class Bird implements Animal {
name = "bird";
talk(): string {
return "chirp";
}
}

View File

@@ -1,20 +0,0 @@
export default {
fetch(request) {
return new Response(a());
},
};
function a() {
return b();
}
function b() {
return c();
}
function c() {
function d() {
return "hello";
}
return d();
}

View File

@@ -1,31 +0,0 @@
import { test, expect } from "bun:test";
import { readFileSync } from "node:fs";
import { SourceMap } from "./sourcemap";
test("works without source map", () => {
const sourceMap = getSourceMap("without-sourcemap.js");
expect(sourceMap.generatedLocation({ line: 7 })).toEqual({ line: 7, column: 0, verified: true });
expect(sourceMap.generatedLocation({ line: 7, column: 2 })).toEqual({ line: 7, column: 2, verified: true });
expect(sourceMap.originalLocation({ line: 11 })).toEqual({ line: 11, column: 0, verified: true });
expect(sourceMap.originalLocation({ line: 11, column: 2 })).toEqual({ line: 11, column: 2, verified: true });
});
test("works with source map", () => {
const sourceMap = getSourceMap("with-sourcemap.js");
// FIXME: Columns don't appear to be accurate for `generatedLocation`
expect(sourceMap.generatedLocation({ line: 3 })).toMatchObject({ line: 4, verified: true });
expect(sourceMap.generatedLocation({ line: 27 })).toMatchObject({ line: 20, verified: true });
expect(sourceMap.originalLocation({ line: 32 })).toEqual({ line: 43, column: 4, verified: true });
expect(sourceMap.originalLocation({ line: 13 })).toEqual({ line: 13, column: 6, verified: true });
});
function getSourceMap(filename: string): SourceMap {
const { pathname } = new URL(`./fixtures/${filename}`, import.meta.url);
const source = readFileSync(pathname, "utf-8");
const match = source.match(/\/\/# sourceMappingURL=(.*)$/m);
if (match) {
const [, url] = match;
return SourceMap(url);
}
return SourceMap();
}

View File

@@ -1,193 +0,0 @@
import type { LineRange, MappedPosition } from "source-map-js";
import { SourceMapConsumer } from "source-map-js";
export type LocationRequest = {
line?: number;
column?: number;
url?: string;
};
export type Location = {
line: number; // 0-based
column: number; // 0-based
} & (
| {
verified: true;
}
| {
verified?: false;
message?: string;
}
);
export interface SourceMap {
generatedLocation(request: LocationRequest): Location;
originalLocation(request: LocationRequest): Location;
}
class ActualSourceMap implements SourceMap {
#sourceMap: SourceMapConsumer;
#sources: string[];
constructor(sourceMap: SourceMapConsumer) {
this.#sourceMap = sourceMap;
this.#sources = (sourceMap as any)._absoluteSources;
}
#getSource(url?: string): string {
const sources = this.#sources;
if (!sources.length) {
return "";
}
if (sources.length === 1 || !url) {
return sources[0];
}
for (const source of sources) {
if (url.endsWith(source)) {
return source;
}
}
return "";
}
generatedLocation(request: LocationRequest): Location {
const { line, column, url } = request;
let lineRange: LineRange;
try {
const source = this.#getSource(url);
lineRange = this.#sourceMap.generatedPositionFor({
line: lineTo1BasedLine(line),
column: columnToColumn(column),
source,
});
} catch (error) {
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: false,
message: unknownToError(error),
};
}
if (!locationIsValid(lineRange)) {
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: false,
};
}
const { line: gline, column: gcolumn } = lineRange;
return {
line: lineToLine(gline),
column: columnToColumn(gcolumn),
verified: true,
};
}
originalLocation(request: LocationRequest): Location {
const { line, column } = request;
let mappedPosition: MappedPosition;
try {
mappedPosition = this.#sourceMap.originalPositionFor({
line: lineTo1BasedLine(line),
column: columnToColumn(column),
});
} catch (error) {
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: false,
message: unknownToError(error),
};
}
if (!locationIsValid(mappedPosition)) {
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: false,
};
}
const { line: oline, column: ocolumn } = mappedPosition;
return {
line: lineTo0BasedLine(oline),
column: columnToColumn(ocolumn),
verified: true,
};
}
}
class NoopSourceMap implements SourceMap {
generatedLocation(request: LocationRequest): Location {
const { line, column } = request;
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: true,
};
}
originalLocation(request: LocationRequest): Location {
const { line, column } = request;
return {
line: lineToLine(line),
column: columnToColumn(column),
verified: true,
};
}
}
const defaultSourceMap = new NoopSourceMap();
export function SourceMap(url?: string): SourceMap {
if (!url || !url.startsWith("data:")) {
return defaultSourceMap;
}
try {
const [_, base64] = url.split(",", 2);
const decoded = Buffer.from(base64, "base64url").toString("utf8");
const schema = JSON.parse(decoded);
const sourceMap = new SourceMapConsumer(schema);
return new ActualSourceMap(sourceMap);
} catch (error) {
console.warn("Failed to parse source map URL", url);
}
return defaultSourceMap;
}
function lineTo1BasedLine(line?: number): number {
return numberIsValid(line) ? line + 1 : 1;
}
function lineTo0BasedLine(line?: number): number {
return numberIsValid(line) ? line - 1 : 0;
}
function lineToLine(line?: number): number {
return numberIsValid(line) ? line : 0;
}
function columnToColumn(column?: number): number {
return numberIsValid(column) ? column : 0;
}
function locationIsValid(location: Location): location is Location {
const { line, column } = location;
return numberIsValid(line) && numberIsValid(column);
}
function numberIsValid(number?: number): number is number {
return typeof number === "number" && isFinite(number) && number >= 0;
}
function unknownToError(error: unknown): string {
if (error instanceof Error) {
const { message } = error;
return message;
}
return String(error);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +0,0 @@
export type Protocol = {
$schema: string;
title: string;
description: string;
type: "object";
definitions: Record<string, Type>;
};
export type Type = {
description?: string;
} & (
| {
type: "number" | "integer" | "boolean";
}
| {
type: "string";
enum?: string[];
enumDescriptions?: string[];
}
| {
type: "object";
properties?: Record<string, Type>;
required?: string[];
}
| {
type: "array";
items?: Type;
}
| {
type?: undefined;
$ref: string;
}
| {
type?: undefined;
allOf: Type[];
}
);

View File

@@ -1,5 +1,4 @@
{
"name": "bun-ecosystem-ci",
"private": true,
"dependencies": {
"globby": "^13.1.3"
@@ -12,4 +11,4 @@
"format": "prettier --write src",
"test": "bun run src/runner.ts"
}
}
}

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "nodenext",
"moduleDetection": "force",
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"inlineSourceMap": true,
"allowJs": true,
"noImplicitAny": false,
"outDir": "dist",
"types": ["node"]
},
"include": [".", "../bun-types/index.d.ts"]
}

View File

@@ -1,2 +0,0 @@
protocol/*/protocol.json linguist-generated=true
protocol/*/index.d.ts linguist-generated=true

View File

@@ -1,2 +0,0 @@
protocol/*.json
protocol/v8

View File

@@ -1 +0,0 @@
# bun-inspector-protocol

View File

@@ -1,4 +0,0 @@
export type * from "./src/protocol";
export type * from "./src/inspector";
export * from "./src/util/preview";
export * from "./src/inspector/websocket";

View File

@@ -1,7 +0,0 @@
{
"name": "bun-inspector-protocol",
"version": "0.0.1",
"dependencies": {
"ws": "^8.13.0"
}
}

View File

@@ -1,202 +0,0 @@
import type { Protocol, Domain, Property } from "../src/protocol/schema";
import { readFileSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
run().catch(console.error);
async function run() {
const cwd = new URL("../protocol/", import.meta.url);
const runner = "Bun" in globalThis ? "bunx" : "npx";
const write = (name: string, data: string) => {
const path = new URL(name, cwd);
writeFileSync(path, data);
spawnSync(runner, ["prettier", "--write", path.pathname], { cwd, stdio: "ignore" });
};
const base = readFileSync(new URL("protocol.d.ts", cwd), "utf-8");
const baseNoComments = base.replace(/\/\/.*/g, "");
const jsc = await downloadJsc();
write("jsc/protocol.json", JSON.stringify(jsc));
write("jsc/index.d.ts", "// GENERATED - DO NOT EDIT\n" + formatProtocol(jsc, baseNoComments));
const v8 = await downloadV8();
write("v8/protocol.json", JSON.stringify(v8));
write("v8/index.d.ts", "// GENERATED - DO NOT EDIT\n" + formatProtocol(v8, baseNoComments));
}
function formatProtocol(protocol: Protocol, extraTs?: string): string {
const { name, domains } = protocol;
const eventMap = new Map();
const commandMap = new Map();
let body = `export namespace ${name} {`;
for (const { domain, types = [], events = [], commands = [] } of domains) {
body += `export namespace ${domain} {`;
for (const type of types) {
body += formatProperty(type);
}
for (const { name, description, parameters = [] } of events) {
const symbol = `${domain}.${name}`;
const title = toTitle(name);
eventMap.set(symbol, `${domain}.${title}`);
body += formatProperty({
id: `${title}Event`,
type: "object",
description: `${description}\n@event \`${symbol}\``,
properties: parameters,
});
}
for (const { name, description, parameters = [], returns = [] } of commands) {
const symbol = `${domain}.${name}`;
const title = toTitle(name);
commandMap.set(symbol, `${domain}.${title}`);
body += formatProperty({
id: `${title}Request`,
type: "object",
description: `${description}\n@request \`${symbol}\``,
properties: parameters,
});
body += formatProperty({
id: `${title}Response`,
type: "object",
description: `${description}\n@response \`${symbol}\``,
properties: returns,
});
}
body += "};";
}
for (const type of ["Event", "Request", "Response"]) {
const sourceMap = type === "Event" ? eventMap : commandMap;
body += formatProperty({
id: `${type}Map`,
type: "object",
properties: [...sourceMap.entries()].map(([name, title]) => ({
name: `"${name}"`,
type: undefined,
$ref: `${title}${type}`,
})),
});
}
if (extraTs) {
body += extraTs;
}
return body + "};";
}
function formatProperty(property: Property): string {
const { id, description, type, optional } = property;
let body = "";
if (id) {
if (description) {
body += `\n${toComment(description)}\n`;
}
body += `export type ${id}=`;
}
if (type === "boolean") {
body += "boolean";
} else if (type === "number" || type === "integer") {
body += "number";
} else if (type === "string") {
const { enum: choices } = property;
if (choices) {
body += choices.map(value => `"${value}"`).join("|");
} else {
body += "string";
}
} else if (type === "array") {
const { items } = property;
const itemType = items ? formatProperty(items) : "unknown";
body += `${itemType}[]`;
} else if (type === "object") {
const { properties } = property;
if (!properties) {
body += "Record<string, unknown>";
} else if (properties.length === 0) {
body += "{}";
} else {
body += "{";
for (const { name, description, ...property } of properties) {
if (description) {
body += `\n${toComment(description)}`;
}
const delimit = property.optional ? "?:" : ":";
body += `\n${name}${delimit}${formatProperty({ ...property, id: undefined })};`;
}
body += "}";
}
} else if ("$ref" in property) {
body += property.$ref;
} else {
body += "unknown";
}
if (optional) {
body += "|undefined";
}
if (id) {
body += ";";
}
return body;
}
/**
* @link https://github.com/ChromeDevTools/devtools-protocol/tree/master/json
*/
async function downloadV8(): Promise<Protocol> {
const baseUrl = "https://raw.githubusercontent.com/ChromeDevTools/devtools-protocol/master/json";
const domains = ["Runtime", "Console", "Debugger", "Memory", "HeapProfiler", "Profiler", "Network", "Inspector"];
return Promise.all([
download<Protocol>(`${baseUrl}/js_protocol.json`),
download<Protocol>(`${baseUrl}/browser_protocol.json`),
]).then(([js, browser]) => ({
name: "V8",
version: js.version,
domains: [...js.domains, ...browser.domains]
.filter(domain => !domains.includes(domain.domain))
.sort((a, b) => a.domain.localeCompare(b.domain)),
}));
}
/**
* @link https://github.com/WebKit/WebKit/tree/main/Source/JavaScriptCore/inspector/protocol
*/
async function downloadJsc(): Promise<Protocol> {
const baseUrl = "https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/inspector/protocol";
const domains = [
"Runtime",
"Console",
"Debugger",
"Heap",
"ScriptProfiler",
"CPUProfiler",
"GenericTypes",
"Network",
"Inspector",
];
return {
name: "JSC",
version: {
major: 1,
minor: 3,
},
domains: await Promise.all(domains.map(domain => download<Domain>(`${baseUrl}/${domain}.json`))).then(domains =>
domains.sort((a, b) => a.domain.localeCompare(b.domain)),
),
};
}
async function download<V>(url: string): Promise<V> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status}: ${url}`);
}
return response.json();
}
function toTitle(name: string): string {
return name.charAt(0).toUpperCase() + name.slice(1);
}
function toComment(description?: string): string {
if (!description) {
return "";
}
const lines = ["/**", ...description.split("\n").map(line => ` * ${line.trim()}`), "*/"];
return lines.join("\n");
}

View File

@@ -1,49 +0,0 @@
import type { JSC } from "..";
/**
* A client that can send and receive messages to/from a debugger.
*/
export abstract class Inspector {
constructor(listener?: InspectorListener);
/**
* Starts the inspector.
*/
start(...args: unknown[]): void;
/**
* Sends a request to the debugger.
*/
send<M extends keyof JSC.RequestMap & keyof JSC.ResponseMap>(
method: M,
params?: JSC.RequestMap[M],
): Promise<JSC.ResponseMap[M]>;
/**
* Accepts a message from the debugger.
* @param message the unparsed message from the debugger
*/
accept(message: string): void;
/**
* If the inspector is closed.
*/
get closed(): boolean;
/**
* Closes the inspector.
*/
close(...args: unknown[]): void;
}
export type InspectorListener = {
/**
* Defines a handler when a debugger event is received.
*/
[M in keyof JSC.EventMap]?: (event: JSC.EventMap[M]) => void;
} & {
/**
* Defines a handler when the debugger is connected or reconnected.
*/
["Inspector.connected"]?: () => void;
/**
* Defines a handler when the debugger is disconnected.
* @param error the error that caused the disconnect, if any
*/
["Inspector.disconnected"]?: (error?: Error) => void;
};

View File

@@ -1,217 +0,0 @@
import type { Inspector, InspectorListener } from ".";
import type { JSC } from "../protocol";
import { WebSocket } from "ws";
export type WebSocketInspectorOptions = {
url?: string | URL;
listener?: InspectorListener;
logger?: (...messages: unknown[]) => void;
};
/**
* An inspector that communicates with a debugger over a WebSocket.
*/
export class WebSocketInspector implements Inspector {
#url?: URL;
#webSocket?: WebSocket;
#requestId: number;
#pendingRequests: Map<number, (result: unknown) => void>;
#pendingMessages: string[];
#listener: InspectorListener;
#log: (...messages: unknown[]) => void;
constructor({ url, listener, logger }: WebSocketInspectorOptions) {
this.#url = url ? new URL(url) : undefined;
this.#requestId = 1;
this.#pendingRequests = new Map();
this.#pendingMessages = [];
this.#listener = listener ?? {};
this.#log = logger ?? (() => {});
}
start(url?: string | URL): void {
if (url) {
this.#url = new URL(url);
}
if (this.#url) {
this.#connect();
}
}
#connect(): void {
if (!this.#url) {
return;
}
this.#webSocket?.close();
let webSocket: WebSocket;
try {
this.#log("connecting:", this.#url.href);
webSocket = new WebSocket(this.#url, {
headers: {
"Ref-Event-Loop": "0",
},
});
} catch (error) {
this.#close(unknownToError(error));
return;
}
webSocket.addEventListener("open", () => {
this.#log("connected");
for (const message of this.#pendingMessages) {
this.#send(message);
}
this.#pendingMessages.length = 0;
this.#listener["Inspector.connected"]?.();
});
webSocket.addEventListener("message", ({ data }) => {
if (typeof data === "string") {
this.accept(data);
}
});
webSocket.addEventListener("error", event => {
this.#log("error:", event);
this.#close(unknownToError(event));
});
webSocket.addEventListener("unexpected-response", () => {
this.#log("unexpected-response");
this.#close(new Error("WebSocket upgrade failed"));
});
webSocket.addEventListener("close", ({ code, reason }) => {
this.#log("closed:", code, reason);
if (code === 1001) {
this.#close();
} else {
this.#close(new Error(`WebSocket closed: ${code} ${reason}`.trimEnd()));
}
});
this.#webSocket = webSocket;
}
send<M extends keyof JSC.RequestMap & keyof JSC.ResponseMap>(
method: M,
params?: JSC.RequestMap[M] | undefined,
): Promise<JSC.ResponseMap[M]> {
const id = this.#requestId++;
const request = { id, method, params };
this.#log("-->", request);
return new Promise((resolve, reject) => {
const done = (result: any) => {
this.#pendingRequests.delete(id);
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
};
this.#pendingRequests.set(id, done);
this.#send(JSON.stringify(request));
});
}
#send(message: string): void {
if (this.#webSocket) {
const { readyState } = this.#webSocket!;
if (readyState === WebSocket.OPEN) {
this.#webSocket.send(message);
}
return;
}
if (!this.#pendingMessages.includes(message)) {
this.#pendingMessages.push(message);
}
}
accept(message: string): void {
let event: JSC.Event | JSC.Response;
try {
event = JSON.parse(message);
} catch (error) {
this.#log("Failed to parse message:", message);
return;
}
this.#log("<--", event);
if (!("id" in event)) {
const { method, params } = event;
try {
this.#listener[method]?.(params as any);
} catch (error) {
this.#log(`Failed to accept ${method} event:`, error);
}
return;
}
const { id } = event;
const resolve = this.#pendingRequests.get(id);
if (!resolve) {
this.#log("Failed to accept response with unknown ID:", id);
return;
}
this.#pendingRequests.delete(id);
if ("error" in event) {
const { error } = event;
const { message } = error;
resolve(new Error(message));
} else {
const { result } = event;
resolve(result);
}
}
get closed(): boolean {
if (!this.#webSocket) {
return true;
}
const { readyState } = this.#webSocket;
switch (readyState) {
case WebSocket.CLOSED:
case WebSocket.CLOSING:
return true;
}
return false;
}
close(code?: number, reason?: string): void {
this.#webSocket?.close(code ?? 1001, reason);
}
#close(error?: Error): void {
try {
this.#listener["Inspector.disconnected"]?.(error);
} finally {
for (const resolve of this.#pendingRequests.values()) {
resolve(error ?? new Error("WebSocket closed"));
}
this.#pendingRequests.clear();
}
}
}
function unknownToError(input: unknown): Error {
if (input instanceof Error) {
return input;
}
if (typeof input === "object" && input !== null && "message" in input) {
const { message } = input;
return new Error(`${message}`);
}
return new Error(`${input}`);
}

View File

@@ -1 +0,0 @@
export type { JSC } from "./jsc";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
// @ts-nocheck
// The content of this file is included in each generated protocol file.
export type Event<T extends keyof EventMap = keyof EventMap> = {
readonly method: T;
readonly params: EventMap[T];
};
export type Request<T extends keyof RequestMap = keyof RequestMap> = {
readonly id: number;
readonly method: T;
readonly params: RequestMap[T];
};
export type Response<T extends keyof ResponseMap = keyof ResponseMap> = {
readonly id: number;
} & (
| {
readonly method?: T;
readonly result: ResponseMap[T];
}
| {
readonly error: {
readonly code?: string;
readonly message: string;
};
}
);

View File

@@ -1,58 +0,0 @@
// Represents the schema of the protocol.json file.
export type Protocol = {
readonly name: string;
readonly version: {
readonly major: number;
readonly minor: number;
};
readonly domains: readonly Domain[];
};
export type Domain = {
readonly domain: string;
readonly dependencies?: readonly string[];
readonly types: readonly Property[];
readonly commands?: readonly Command[];
readonly events?: readonly Event[];
};
export type Command = {
readonly name: string;
readonly description?: string;
readonly parameters?: readonly Property[];
readonly returns?: readonly Property[];
};
export type Event = {
readonly name: string;
readonly description?: string;
readonly parameters: readonly Property[];
};
export type Property = {
readonly id?: string;
readonly name?: string;
readonly description?: string;
readonly optional?: boolean;
} & (
| {
readonly type: "array";
readonly items?: Property;
}
| {
readonly type: "object";
readonly properties?: readonly Property[];
}
| {
readonly type: "string";
readonly enum?: readonly string[];
}
| {
readonly type: "boolean" | "number" | "integer";
}
| {
readonly type: undefined;
readonly $ref: string;
}
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,113 +0,0 @@
import type { JSC } from "../protocol";
export function remoteObjectToString(remoteObject: JSC.Runtime.RemoteObject, topLevel?: boolean): 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":
if (topLevel) {
return String(value ?? description);
}
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";
}

View File

@@ -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: ƒ, …}"`;

View File

@@ -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",
},
}),
);

View File

@@ -1,61 +0,0 @@
import { beforeAll, afterAll, test, expect } from "bun:test";
import type { PipedSubprocess } from "bun";
import { spawn } from "bun";
import type { JSC } from "../..";
import { WebSocketInspector, remoteObjectToString } from "../..";
let subprocess: PipedSubprocess | undefined;
let objects: JSC.Runtime.RemoteObject[] = [];
beforeAll(async () => {
subprocess = spawn({
cwd: import.meta.dir,
cmd: [process.argv0, "--inspect-wait=0", "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();
}
});

View File

@@ -1,18 +0,0 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"inlineSourceMap": true,
"allowJs": true,
"outDir": "dist",
},
"include": [".", "../bun-types/index.d.ts"]
}

View File

@@ -1,6 +1,4 @@
{
"name": "bun-lambda",
"private": true,
"devDependencies": {
"bun-types": "^0.7.0",
"jszip": "^3.10.1",

View File

@@ -1,13 +1,13 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"lib": [
"ESNext"
],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
@@ -19,5 +19,8 @@
"bun-types" // add Bun global
]
},
"include": ["**/*.ts", "modules.d.ts"]
}
"include": [
"**/*.ts",
"modules.d.ts"
]
}

View File

@@ -1,13 +1,13 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"lib": [
"ESNext"
],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
@@ -19,5 +19,8 @@
"bun-types" // add Bun global
]
},
"include": ["**/*.ts", "modules.d.ts"]
}
"include": [
"**/*.ts",
"modules.d.ts"
]
}

Binary file not shown.

View File

@@ -15,13 +15,11 @@
"node": "node --enable-source-maps --import ./dist/src/repl.js",
"clean": "rm -rf dist",
"preprocess": "bun tools/updateversions.ts",
"build": "bun run clean && bun run preprocess && bunx tsc && bunx copyfiles \"./lib/**/*.wasm\" dist",
"build": "bun run clean && bun run preprocess && bunx tsc && bunx copyfiles \"./**/*.wasm\" dist",
"build/wasm": "bun run build/zighash",
"build/zighash": "cd lib/zighash && bun run build && cd ../.."
},
"dependencies": {
"bun-wasm": "link:bun-wasm",
"chalk": "^5.3.0",
"js-md4": "^0.3.2",
"open-editor": "^4.0.0",
"supports-color": "^9.4.0",

View File

@@ -16,7 +16,6 @@ import {
} from './bun/hashes.js';
import { ArrayBufferSink as ArrayBufferSinkPolyfill } from './bun/arraybuffersink.js';
import { FileBlob, NodeJSStreamFileBlob } from './bun/fileblob.js';
import TranspilerImpl from './bun/transpiler.js';
import fs from 'node:fs';
import v8 from 'node:v8';
import path from 'node:path';
@@ -33,7 +32,7 @@ export const main = path.resolve(process.cwd(), process.argv[1] ?? 'repl') satis
//? These are automatically updated on build by tools/updateversions.ts, do not edit manually.
export const version = '0.7.4' satisfies typeof Bun.version;
export const revision = '56816a3ec845a4b9fc40ade34dbe5c0033433d51' satisfies typeof Bun.revision;
export const revision = '7088d7e182635a58a50860302da0b1abc42c7ce7' satisfies typeof Bun.revision;
export const gc = (globalThis.gc ? (() => (globalThis.gc!(), process.memoryUsage().heapUsed)) : (() => {
const err = new Error('[bun-polyfills] Garbage collection polyfills are only available when Node.js is ran with the --expose-gc flag.');
@@ -72,8 +71,6 @@ export const unsafe = {
}
} satisfies typeof Bun['unsafe'];
export const Transpiler = TranspilerImpl satisfies typeof Bun.Transpiler;
export const SHA1 = SHA1Polyfill satisfies typeof Bun.SHA1;
export const MD5 = MD5Polyfill satisfies typeof Bun.MD5;
export const MD4 = MD4Polyfill satisfies typeof Bun.MD4;

View File

@@ -1,96 +1,103 @@
import type { JavaScriptLoader, TranspilerOptions, Transpiler as BunTranspiler, Import } from 'bun';
import { transformSync, scan, init } from 'bun-wasm';
import { Message } from 'bun-wasm/schema';
import $ from 'chalk';
import { NotImplementedError } from '../../utils/errors.js';
await init();
enum InternalImportKind {
'entry-point' = 1, // entry_point
'import-statement' = 2, // stmt
'require-call' = 3, // require
'dynamic-import' = 4, // dynamic
'require-resolve' = 5, // require_resolve
'import-rule' = 6, // at
'url-token' = 7, // url
'internal' = 8, // internal
}
export type ScanImportsEntry = {
kind: 'import-statement' | 'dynamic-import';
path: string;
};
// TODO: Possible implementation with WASM builds of bun with just the transpiler?
// NOTE: This is possible to implement with something like SWC, and was previously done,
// but it has lots of quirks due to the differences between SWC and Bun, so the plan is
// to not do that unless there is actual demand for using Bun.Transpiler in Node.js before
// the WASM build is worked on. The signatures are here for now as a placeholder.
export default class Transpiler implements BunTranspiler {
constructor(options?: TranspilerOptions) {
this.#options = options ?? {};
this.#rootFile = 'input.tsx'; // + (this.#options.loader ?? 'tsx');
//? ^ NOTE: with current bun-wasm builds, the loader option is ignored and hardcoded to tsx
}
#options: TranspilerOptions;
#rootFile: string;
#decoder?: TextDecoder;
#internallyCalled: boolean = false;
async transform(code: StringOrBuffer, loader: JavaScriptLoader): Promise<string> {
this.#internallyCalled = true;
return this.transformSync(code, loader);
if (typeof code !== 'string') code = new TextDecoder().decode(code);
throw new NotImplementedError('Bun.Transpiler', this.transform);
}
transformSync(code: StringOrBuffer, ctx: object): string;
transformSync(code: StringOrBuffer, loader: JavaScriptLoader, ctx: object): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | undefined): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | object, ctx: object = {}): string {
if (!code) return ''; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
if (typeof loader !== 'string') loader = this.#options.loader;
const result = transformSync(code, this.#rootFile, loader);
// status 1 = success, status 2 = error
if (result.status === 2) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.transform : this.transformSync);
this.#internallyCalled = false;
this.#decoder ??= new TextDecoder();
return this.#decoder.decode(result.files[0].data);
if (typeof code !== 'string') code = new TextDecoder().decode(code);
if (typeof loader !== 'string') loader = 'js';
throw new NotImplementedError('Bun.Transpiler', this.transformSync);
}
scan(code: StringOrBuffer): { exports: string[]; imports: Import[]; } {
if (!code) return { exports: [], imports: [] }; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
const result = scan(code, this.#rootFile, this.#options.loader);
if (result.errors.length) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.scanImports : this.scan);
this.#internallyCalled = false;
result.imports.forEach(imp => (imp.kind as unknown) = InternalImportKind[imp.kind]);
return {
exports: result.exports,
imports: result.imports as unknown as Import[],
};
if (typeof code !== 'string') code = new TextDecoder().decode(code);
throw new NotImplementedError('Bun.Transpiler', this.scan);
//return {
// imports: this.scanImports(code),
// exports: this.#scanExports(code)
//};
}
scanImports(code: StringOrBuffer): ScanImportsEntry[] {
this.#internallyCalled = true;
return this.scan(code).imports.filter(imp => imp.kind === 'import-statement' || imp.kind === 'dynamic-import') as ScanImportsEntry[];
scanImports(code: StringOrBuffer): {
kind: 'import-statement' | 'dynamic-import';
path: string;
}[] {
if (typeof code !== 'string') code = new TextDecoder().decode(code);
throw new NotImplementedError('Bun.Transpiler', this.scanImports);
//const imports: { kind: 'import-statement' | 'dynamic-import', path: string }[] = [];
//this.#scanTopLevelImports(code).forEach(x => imports.push({ kind: 'import-statement', path: x }));
//this.#scanDynamicImports(code).forEach(x => imports.push({ kind: 'dynamic-import', path: x }));
//return imports;
}
}
function formatBuildErrors(buildErrors: Message[], caller: Transpiler[keyof Transpiler]): AggregateError {
const formatted = buildErrors.map(err => {
const loc = err.data.location;
const str = `${$.redBright('error')}${$.gray(':')} ${$.bold(err.data.text)}\n` +
(loc
? `${highlightErrorChar(loc.line_text, loc.offset)}\n` +
$.redBright.bold('^'.padStart(loc.column)) + '\n' +
`${$.bold(loc.file)}${$.gray(':')}${$.yellowBright(loc.line)}${$.gray(':')}${$.yellowBright(loc.column)} ${$.gray(loc.offset)}`
: ''
/*#scanDynamicImports(code: string): string[] {
return this.parseSync(code, {
syntax: this.#syntax, target: 'es2022', tsx: this.#options.loader === 'tsx'
}).body.filter(x => x.type === 'ExpressionStatement' && x.expression.type === 'CallExpression' && x.expression.callee.type === 'Import')
.map(i => (((i as swc.ExpressionStatement).expression as swc.CallExpression).arguments[0].expression as swc.StringLiteral).value);
}*/
/*#scanTopLevelImports(code: string): string[] {
return this.parseSync(code, {
syntax: this.#syntax, target: 'es2022', tsx: this.#options.loader === 'tsx'
}).body.filter(x => x.type === 'ImportDeclaration' || x.type === 'ExportAllDeclaration' || x.type === 'ExportNamedDeclaration')
.filter(i => !(i as swc.ImportDeclaration).typeOnly)
.map(i => (i as swc.ImportDeclaration).source.value);
}*/
/*#scanExports(code: string, includeDefault: boolean = false): string[] {
const parsed = this.parseSync(code, {
syntax: this.#syntax, target: 'es2022', tsx: this.#options.loader === 'tsx'
}).body;
const exports = [];
exports.push(parsed.filter(x => x.type === 'ExportDeclaration' && !x.declaration.declare)
.flatMap(i => ((i as swc.ExportDeclaration).declaration as swc.ClassDeclaration).identifier?.value ??
((i as swc.ExportDeclaration).declaration as swc.VariableDeclaration).declarations.map(d => (d.id as swc.Identifier).value)
)
);
return { __proto__: Error.prototype, stack: str };
});
const aggregate = new AggregateError(formatted, `Input code has ${formatted.length} error${formatted.length === 1 ? '' : 's'}`);
Error.captureStackTrace(aggregate, caller);
aggregate.name = 'BuildError';
return aggregate;
}
exports.push(parsed.filter(x => x.type === 'ExportNamedDeclaration')
.flatMap(i => (i as swc.ExportNamedDeclaration).specifiers
.filter(s => s.type === 'ExportSpecifier' && !s.isTypeOnly)
.map(s => (s as swc.NamedExportSpecifier).exported?.value ?? (s as swc.NamedExportSpecifier).orig.value)
)
);
if (includeDefault) exports.push(this.#scanDefaultExport(code) ?? []);
return exports.flat();
}*/
function highlightErrorChar(str: string, at: number): string {
return str.slice(0, at) + $.red(str[at]) + str.slice(at + 1);
/*#scanDefaultExport(code: string): 'default' | undefined {
const parsed = this.parseSync(code, {
syntax: this.#syntax, target: 'es2022', tsx: this.#options.loader === 'tsx'
}).body;
const defaultExportDecl = parsed.find(x => x.type === 'ExportDefaultDeclaration') as swc.ExportDefaultDeclaration | undefined;
if (!defaultExportDecl) {
const defaultExportExpr = parsed.find(x => x.type === 'ExportDefaultExpression') as swc.ExportDefaultExpression | undefined;
if (!defaultExportExpr) return undefined;
if (!defaultExportExpr.expression.type.startsWith('Ts')) return 'default';
else return undefined;
}
if (!defaultExportDecl.decl.type.startsWith('Ts') && !Reflect.get(defaultExportDecl.decl, 'declare')) return 'default';
else return undefined;
}*/
#options: TranspilerOptions;
}

View File

@@ -15,6 +15,7 @@ globalThis.Bun = bun as typeof bun & {
mmap: typeof import('bun').mmap;
connect: typeof import('bun').connect;
listen: typeof import('bun').listen;
Transpiler: typeof import('bun').Transpiler;
password: typeof import('bun').password;
CryptoHashInterface: typeof import('bun').CryptoHashInterface;
CryptoHasher: typeof import('bun').CryptoHasher;

View File

@@ -15,5 +15,5 @@
"outDir": "dist",
"types": ["node"]
},
"include": ["src", "lib", "../bun-types/index.d.ts"],
"include": [".", "../bun-types/index.d.ts"],
}

View File

@@ -1,5 +1,4 @@
{
"name": "bun-release-action",
"private": true,
"dependencies": {
"aws4fetch": "^1.0.17",

View File

@@ -2308,7 +2308,7 @@ declare module "bun" {
*
* @param args
*/
export function inspect(arg: any, options?: BunInspectOptions): string;
export function inspect(arg: any, options: BunInspectOptions): string;
export namespace inspect {
/**
* That can be used to declare custom inspect functions.

View File

@@ -350,7 +350,7 @@ declare module "bun:ffi" {
type UNTYPED = never;
export type Pointer = number & {};
interface FFITypeToArgsType {
interface FFITypeToType {
[FFIType.char]: number;
[FFIType.int8_t]: number;
[FFIType.i8]: number;
@@ -365,54 +365,22 @@ declare module "bun:ffi" {
[FFIType.int]: number;
[FFIType.uint32_t]: number;
[FFIType.u32]: number;
[FFIType.int64_t]: number | bigint;
[FFIType.i64]: number | bigint;
[FFIType.uint64_t]: number | bigint;
[FFIType.u64]: number | bigint;
[FFIType.int64_t]: number;
[FFIType.i64]: number;
[FFIType.uint64_t]: number;
[FFIType.u64]: number;
[FFIType.double]: number;
[FFIType.f64]: number;
[FFIType.float]: number;
[FFIType.f32]: number;
[FFIType.bool]: boolean;
[FFIType.ptr]: TypedArray | Pointer | CString | null;
[FFIType.pointer]: TypedArray | Pointer | CString | null;
[FFIType.void]: void;
[FFIType.cstring]: TypedArray | Pointer | CString | null;
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | JSCallback; // cannot be null
}
interface FFITypeToReturnsType {
[FFIType.char]: number;
[FFIType.int8_t]: number;
[FFIType.i8]: number;
[FFIType.uint8_t]: number;
[FFIType.u8]: number;
[FFIType.int16_t]: number;
[FFIType.i16]: number;
[FFIType.uint16_t]: number;
[FFIType.u16]: number;
[FFIType.int32_t]: number;
[FFIType.i32]: number;
[FFIType.int]: number;
[FFIType.uint32_t]: number;
[FFIType.u32]: number;
[FFIType.int64_t]: bigint;
[FFIType.i64]: bigint;
[FFIType.uint64_t]: bigint;
[FFIType.u64]: bigint;
[FFIType.double]: number;
[FFIType.f64]: number;
[FFIType.float]: number;
[FFIType.f32]: number;
[FFIType.bool]: boolean;
[FFIType.ptr]: Pointer | null;
[FFIType.pointer]: Pointer | null;
[FFIType.ptr]: Pointer;
[FFIType.pointer]: Pointer;
[FFIType.void]: void;
[FFIType.cstring]: CString;
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | null;
[FFIType.function]: (...args: any[]) => any;
}
interface FFITypeStringToType {
["char"]: FFIType.char;
@@ -449,7 +417,36 @@ declare module "bun:ffi" {
export type FFITypeOrString =
| FFIType
| keyof FFITypeStringToType;
| "char"
| "int8_t"
| "i8"
| "uint8_t"
| "u8"
| "int16_t"
| "i16"
| "uint16_t"
| "u16"
| "int32_t"
| "i32"
| "int"
| "uint32_t"
| "u32"
| "int64_t"
| "i64"
| "uint64_t"
| "u64"
| "double"
| "f64"
| "float"
| "f32"
| "bool"
| "ptr"
| "pointer"
| "void"
| "cstring"
| "function"
| "usize"
| "callback";
interface FFIFunction {
/**
@@ -480,7 +477,7 @@ declare module "bun:ffi" {
* }
* ```
*/
readonly args?: readonly FFITypeOrString[];
args?: FFITypeOrString[];
/**
* Return type to a FFI function (C ABI)
*
@@ -508,7 +505,7 @@ declare module "bun:ffi" {
* }
* ```
*/
readonly returns?: FFITypeOrString;
returns?: FFITypeOrString;
/**
* Function pointer to the native function
@@ -519,7 +516,7 @@ declare module "bun:ffi" {
* This is useful if the library has already been loaded
* or if the module is also using Node-API.
*/
readonly ptr?: number | bigint;
ptr?: number | bigint;
/**
* Can C/FFI code call this function from a separate thread?
@@ -536,10 +533,10 @@ declare module "bun:ffi" {
*
* @default false
*/
readonly threadsafe?: boolean;
threadsafe?: boolean;
}
type Symbols = Readonly<Record<string, FFIFunction>>;
type Symbols = Record<string, FFIFunction>;
// /**
// * Compile a callback function
@@ -549,7 +546,7 @@ declare module "bun:ffi" {
// */
// export function callback(ffi: FFIFunction, cb: Function): number;
export interface Library<Fns extends Readonly<Record<string, Narrow<FFIFunction>>>> {
export interface Library<Fns extends Record<string, Narrow<FFIFunction>>> {
symbols: ConvertFns<Fns>;
/**
@@ -580,14 +577,12 @@ declare module "bun:ffi" {
| (T extends object ? { [K in keyof T]: Narrow<T[K]> } : never)
| Extract<{} | null | undefined, T>;
type ConvertFns<Fns extends Readonly<Record<string, FFIFunction>>> = {
type ConvertFns<Fns extends Record<string, FFIFunction>> = {
[K in keyof Fns]: (
...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[]
? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] }
: [unknown] extends [Fns[K]["args"]] ? [] : never
) => [unknown] extends [Fns[K]["returns"]]
? void
: FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
...args: Fns[K]["args"] extends infer A extends FFITypeOrString[]
? { [L in keyof A]: FFITypeToType[ToFFIType<A[L]>] }
: never
) => FFITypeToType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
};
/**
@@ -755,165 +750,6 @@ declare module "bun:ffi" {
byteLength?: number,
): ArrayBuffer;
export namespace read {
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function u8(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function i8(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function u16(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function i16(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function u32(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function i32(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function f32(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function u64(ptr: Pointer, byteOffset?: number): bigint;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function i64(ptr: Pointer, byteOffset?: number): bigint;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function f64(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function ptr(ptr: Pointer, byteOffset?: number): number;
/**
* The read function behaves similarly to DataView,
* but it's usually faster because it doesn't need to create a DataView or ArrayBuffer.
*
* @param ptr The memory address to read
* @param byteOffset bytes to skip before reading
*
* While there are some checks to catch invalid pointers, this is a difficult
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*/
export function intptr(ptr: Pointer, byteOffset?: number): number;
}
/**
* Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer}
*

View File

@@ -739,10 +739,11 @@ interface BlobInterface {
type BlobPart = string | Blob | BufferSource;
interface BlobPropertyBag {
/** Set a default "type". Not yet implemented. */
/** Set a default "type" */
type?: string;
/** Not implemented in Bun yet. */
// endings?: "transparent" | "native";
endings?: "transparent" | "native";
}
/**
@@ -3672,7 +3673,7 @@ declare module "*.txt" {
}
declare module "*.toml" {
var contents: any;
var contents: unknown;
export = contents;
}

View File

@@ -75,7 +75,7 @@ const tsConfig = {
skipLibCheck: true,
jsx: "react-jsx",
allowImportingTsExtensions: true,
noEmit: true,
emitDeclarationOnly: true,
composite: true,
allowSyntheticDefaultImports: true,
forceConsistentCasingInFileNames: true,

View File

@@ -4,8 +4,6 @@ import {
suffix,
CString,
Pointer,
JSCallback,
read,
// FFIFunction,
// ConvertFns,
// Narrow,
@@ -29,14 +27,6 @@ const lib = dlopen(
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
ptr_type: {
args: [FFIType.pointer],
returns: FFIType.pointer,
},
fn_type: {
args: [FFIType.function],
returns: FFIType.function,
},
allArgs: {
args: [
FFIType.char, // string
@@ -77,17 +67,6 @@ const lib = dlopen(
tsd.expectType<CString>(lib.symbols.sqlite3_libversion());
tsd.expectType<number>(lib.symbols.add(1, 2));
tsd.expectType<Pointer | null>(lib.symbols.ptr_type(0));
tc.assert<
tc.IsExact<
(typeof lib)["symbols"]["ptr_type"],
TypedArray | Pointer | CString
>
>;
tsd.expectType<Pointer | null>(lib.symbols.fn_type(0));
tc.assert<tc.IsExact<(typeof lib)["symbols"]["fn_type"], Pointer | JSCallback>>;
tc.assert<
tc.IsExact<
(typeof lib)["symbols"]["allArgs"],
@@ -124,41 +103,3 @@ tc.assert<
]
>
>;
const as_const_test = {
sqlite3_libversion: {
args: [],
returns: FFIType.cstring,
},
multi_args: {
args: [FFIType.i32, FFIType.f32],
returns: FFIType.void,
},
no_returns: {
args: [FFIType.i32],
},
no_args: {
returns: FFIType.i32,
},
} as const;
const lib2 = dlopen(path, as_const_test);
tsd.expectType<CString>(lib2.symbols.sqlite3_libversion());
tsd.expectType<void>(lib2.symbols.multi_args(1, 2));
tc.assert<tc.IsExact<ReturnType<(typeof lib2)["symbols"]["no_returns"]>, void>>;
tc.assert<tc.IsExact<Parameters<(typeof lib2)["symbols"]["no_args"]>, []>>;
tsd.expectType<number>(read.u8(0));
tsd.expectType<number>(read.u8(0, 0));
tsd.expectType<number>(read.i8(0, 0));
tsd.expectType<number>(read.u16(0, 0));
tsd.expectType<number>(read.i16(0, 0));
tsd.expectType<number>(read.u32(0, 0));
tsd.expectType<number>(read.i32(0, 0));
tsd.expectType<bigint>(read.u64(0, 0));
tsd.expectType<bigint>(read.i64(0, 0));
tsd.expectType<number>(read.f32(0, 0));
tsd.expectType<number>(read.f64(0, 0));
tsd.expectType<number>(read.ptr(0, 0));
tsd.expectType<number>(read.intptr(0, 0));

View File

@@ -1,4 +1,4 @@
import { expectType } from "tsd";
import data from "../../../bunfig.toml";
expectType<any>(data);
expectType<unknown>(data);

View File

@@ -1,8 +1,10 @@
{
"compilerOptions": {
"composite": true,
"noEmit": true,
"lib": ["ESNext"],
"emitDeclarationOnly": true,
"lib": [
"ESNext"
],
"baseUrl": ".",
"rootDir": ".",
"outFile": "./types.d.ts",
@@ -10,11 +12,14 @@
"target": "esnext",
"disableSolutionSearching": true
},
"include": ["./*.d.ts", "dist"],
"include": [
"./*.d.ts",
"dist"
],
"exclude": [
"node_modules",
"./node_modules",
"./node_modules/*",
"./node_modules/@types/node/index.d.ts"
]
}
}

View File

@@ -1,2 +0,0 @@
node_modules
extension

View File

@@ -5,7 +5,10 @@
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/example"],
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}/example"
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "Build (watch)"
},

View File

@@ -5,5 +5,5 @@
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsc.autoDetect": "off"
}
"typescript.tsc.autoDetect": "off",
}

View File

@@ -4,15 +4,13 @@
{
"label": "Build",
"type": "shell",
"command": "bun run build",
"problemMatcher": "$esbuild"
"command": "bun run build"
},
{
"label": "Build (watch)",
"type": "shell",
"command": "bun run build:watch",
"isBackground": true,
"problemMatcher": "$esbuild-watch"
"isBackground": true
}
]
}
}

View File

@@ -1,22 +1 @@
# Bun for Visual Studio Code
![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/oven.vscode-bun)
![Visual Studio Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/oven.vscode-bun)
![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/oven.vscode-bun)
<img align="right" src="https://user-images.githubusercontent.com/709451/182802334-d9c42afe-f35d-4a7b-86ea-9985f73f20c3.png" height="150px" style="float: right; padding: 30px;">
This extension adds support for using [Bun](https://bun.sh/) with Visual Studio Code. Bun is an all-in-one toolkit for JavaScript and TypeScript apps.
At its core is the _Bun runtime_, a fast JavaScript runtime designed as a drop-in replacement for Node.js. It's written in Zig and powered by JavaScriptCore under the hood, dramatically reducing startup times and memory usage.
<div align="center">
<a href="https://bun.sh/docs">Documentation</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://discord.com/invite/CXdq2DP29u">Discord</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://github.com/oven-sh/bun/issues/new">Issues</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://github.com/oven-sh/bun/issues/159">Roadmap</a>
<br/>
</div>
# Debug Adapter Protocol for Bun

View File

@@ -0,0 +1,5 @@
* Off-by-one for debug lines
* Formatting values in console (some code is wired up)
* Play button on debugger actually starting Bun
* bun debug or --inspect command added to Bun, not need Bun.serve
* Breakpoint actually setting

Binary file not shown.

View File

@@ -4,15 +4,16 @@
{
"type": "bun",
"request": "launch",
"name": "Debug Bun",
"program": "${file}",
"watch": true
"name": "Debug",
"program": "${workspaceFolder}/example.js",
"stopOnEntry": true
},
{
"type": "bun",
"request": "attach",
"name": "Attach to Bun",
"url": "ws://localhost:6499/",
"name": "Attach",
"program": "${workspaceFolder}/example.js",
"stopOnEntry": true
}
]
}

View File

@@ -1,30 +0,0 @@
// @bun
// example.ts
var a = function (request) {
b(request);
};
var b = function (request) {
c(request);
};
var c = function (request) {
console.log(request);
};
var example_default = {
async fetch(request, server) {
a(request);
const coolThing = new SuperCoolThing();
coolThing.doCoolThing();
debugger;
return new Response(request.url);
},
};
class SuperCoolThing {
doCoolThing() {
console.log("super cool thing!");
}
}
export { example_default as default };
//# debugId=9BB0B773A8E4771564756e2164756e21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZXhhbXBsZS50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJpbXBvcnQgdHlwZSB7IFNlcnZlciB9IGZyb20gXCJidW5cIjtcblxuZXhwb3J0IGRlZmF1bHQge1xuICBhc3luYyBmZXRjaChyZXF1ZXN0OiBSZXF1ZXN0LCBzZXJ2ZXI6IFNlcnZlcik6IFByb21pc2U8UmVzcG9uc2U+IHtcbiAgICBhKHJlcXVlc3QpO1xuICAgIGNvbnN0IGNvb2xUaGluZzogQ29vbFRoaW5nID0gbmV3IFN1cGVyQ29vbFRoaW5nKCk7XG4gICAgY29vbFRoaW5nLmRvQ29vbFRoaW5nKCk7XG4gICAgZGVidWdnZXI7XG4gICAgcmV0dXJuIG5ldyBSZXNwb25zZShyZXF1ZXN0LnVybCk7XG4gIH1cbn07XG5cbi8vIGFcbmZ1bmN0aW9uIGEocmVxdWVzdDogUmVxdWVzdCk6IHZvaWQge1xuICBiKHJlcXVlc3QpO1xufVxuXG4vLyBiXG5mdW5jdGlvbiBiKHJlcXVlc3Q6IFJlcXVlc3QpOiB2b2lkIHtcbiAgYyhyZXF1ZXN0KTtcbn1cblxuLy8gY1xuZnVuY3Rpb24gYyhyZXF1ZXN0OiBSZXF1ZXN0KSB7XG4gIGNvbnNvbGUubG9nKHJlcXVlc3QpO1xufVxuXG5pbnRlcmZhY2UgQ29vbFRoaW5nIHtcbiAgZG9Db29sVGhpbmcoKTogdm9pZDtcbn1cblxuY2xhc3MgU3VwZXJDb29sVGhpbmcgaW1wbGVtZW50cyBDb29sVGhpbmcge1xuICBkb0Nvb2xUaGluZygpOiB2b2lkIHtcbiAgICBjb25zb2xlLmxvZyhcInN1cGVyIGNvb2wgdGhpbmchXCIpO1xuICB9XG59XG4iCiAgXSwKICAibWFwcGluZ3MiOiAiOztBQS8vLy8vZkFhQSxJQUFTLFlBQUMsQ0FBQyxTQUF3QjtBQUNqQyxJQUFFLE9BQU87QUFBQTtBQUlYLElBQVMsWUFBQyxDQUFDLFNBQXdCO0FBQ2pDLElBQUUsT0FBTztBQUFBO0FBSVgsSUFBUyxZQUFDLENBQUMsU0FBa0I7QUFDM0IsVUFBUSxJQUFJLE9BQU87QUFBQTtBQXRCckIsSUFBZTtBQUFBLE9BQ1AsTUFBSyxDQUFDLFNBQWtCLFFBQW1DO0FBQy9ELE1BQUUsT0FBTztBQUNULFVBQU0sWUFBdUIsSUFBSTtBQUNqQyxjQUFVLFlBQVk7QUFDdEI7QUFDQSxXQUFPLElBQUksU0FBUyxRQUFRLEdBQUc7QUFBQTtBQUVuQztBQXFCQTtBQUFBLE1BQU0sZUFBb0M7QUFBQSxFQUN4QyxXQUFXLEdBQVM7QUFDbEIsWUFBUSxJQUFJLG1CQUFtQjtBQUFBO0FBRW5DOyIsCiAgImRlYnVnSWQiOiAiOUJCMEI3NzNBOEU0NzcxNTY0NzU2ZTIxNjQ3NTZlMjEiLAogICJuYW1lcyI6IFtdCn0=

View File

@@ -5,7 +5,7 @@ import { readFile } from "node:fs/promises";
app
.get("/", (req, res) => {
console.log("I am logging a request!??");
console.log("I am logging a request!");
readFile(import.meta.path, "utf-8").then(data => {
console.log(data.length);
debugger;
@@ -57,7 +57,7 @@ Bun.serve({
inspector: true,
development: true,
fetch(request, server) {
// console.log(request);
console.log(request);
return new Response(request.url);
},
});

View File

@@ -1,34 +0,0 @@
export default {
async fetch(request: Request): Promise<Response> {
a(request);
const coolThing: CoolThing = new SuperCoolThing();
coolThing.doCoolThing();
debugger;
return new Response("HELLO WHAT!");
},
};
// a
function a(request: Request): void {
b(request);
}
// b
function b(request: Request): void {
c(request);
}
// c
function c(request: Request) {
console.log(request);
}
interface CoolThing {
doCoolThing(): void;
}
class SuperCoolThing implements CoolThing {
doCoolThing(): void {
console.log("BLAH BLAH");
}
}

View File

@@ -1,101 +1,65 @@
{
"name": "bun-vscode",
"version": "0.0.1",
"author": "oven",
"repository": {
"type": "git",
"url": "https://github.com/oven-sh/bun"
},
"main": "dist/extension.js",
"dependencies": {
"semver": "^7.5.4",
"source-map-js": "^1.0.2",
"ws": "^8.13.0"
},
"main": "./dist/extension.js",
"devDependencies": {
"@types/vscode": "^1.81.0",
"@vscode/debugadapter": "^1.56.0",
"@vscode/debugadapter-testsupport": "^1.56.0",
"bun-types": "^0.7.3",
"typescript": "^5.0.0",
"esbuild": "^0.19.2"
"typescript": "^5.0.0"
},
"activationEvents": [
"onLanguage:javascript",
"onLanguage:javascriptreact",
"onLanguage:typescript",
"onLanguage:typescriptreact",
"workspaceContains:**/.lockb",
"onDebugResolve:bun",
"onDebugDynamicConfigurations:bun"
"onDebugDynamicConfigurations:bun",
"onCommand:extension.bun.getProgramName"
],
"browser": "dist/web-extension.js",
"bugs": {
"url": "https://github.com/oven-sh/bun/issues"
},
"capabilities": {
"untrustedWorkspaces": {
"supported": false,
"description": "This extension needs to be able to run your code using Bun."
}
},
"browser": "./dist/web-extension.js",
"categories": [
"Programming Languages",
"Debuggers",
"Testing"
"Debuggers"
],
"contributes": {
"configuration": {
"title": "Bun",
"properties": {
"bun.path": {
"type": "string",
"description": "A path to the `bun` executable. By default, the extension looks for `bun` in the `PATH`, but if set, it will use the specified path instead.",
"scope": "window",
"default": null
}
}
},
"commands": [
{
"command": "extension.bun.runFile",
"title": "Run File",
"category": "Bun",
"enablement": "!inDebugMode",
"icon": "$(play)"
},
{
"command": "extension.bun.debugFile",
"title": "Debug File",
"category": "Bun",
"enablement": "!inDebugMode",
"icon": "$(debug-alt)"
}
],
"menus": {
"editor/title/run": [
{
"command": "extension.bun.runFile",
"when": "resourceLangId == javascript || resourceLangId == javascriptreact || resourceLangId == typescript || resourceLangId == typescriptreact",
"command": "extension.bun.runEditorContents",
"when": "resourceLangId == javascript",
"group": "navigation@1"
},
{
"command": "extension.bun.debugFile",
"when": "resourceLangId == javascript || resourceLangId == javascriptreact || resourceLangId == typescript || resourceLangId == typescriptreact",
"command": "extension.bun.debugEditorContents",
"when": "resourceLangId == javascript",
"group": "navigation@2"
}
],
"commandPalette": [
{
"command": "extension.bun.runFile",
"when": "resourceLangId == javascript || resourceLangId == javascriptreact || resourceLangId == typescript || resourceLangId == typescriptreact"
"command": "extension.bun.debugEditorContents",
"when": "resourceLangId == javascript"
},
{
"command": "extension.bun.debugFile",
"when": "resourceLangId == javascript || resourceLangId == javascriptreact || resourceLangId == typescript || resourceLangId == typescriptreact"
"command": "extension.bun.runEditorContents",
"when": "resourceLangId == javascript"
}
]
},
"commands": [
{
"command": "extension.bun.debugEditorContents",
"title": "Debug File",
"category": "Bun Debug",
"enablement": "!inDebugMode",
"icon": "$(debug-alt)"
},
{
"command": "extension.bun.runEditorContents",
"title": "Run File",
"category": "Bun Debug",
"enablement": "!inDebugMode",
"icon": "$(play)"
}
],
"breakpoints": [
{
"language": "javascript"
@@ -121,64 +85,31 @@
"typescriptreact"
],
"runtime": "node",
"program": "dist/adapter.js",
"program": "./dist/adapter.js",
"configurationAttributes": {
"launch": {
"required": [
"program"
],
"properties": {
"runtime": {
"type": "string",
"description": "The path to Bun.",
"default": "bun"
},
"program": {
"type": "string",
"description": "The file to debug.",
"default": "${file}"
},
"cwd": {
"type": "string",
"description": "The working directory.",
"default": "${workspaceFolder}"
},
"args": {
"type": "array",
"description": "The arguments passed to Bun.",
"items": {
"type": "string"
},
"default": []
},
"env": {
"type": "object",
"description": "The environment variables passed to Bun.",
"default": {}
},
"inheritEnv": {
"type": "boolean",
"description": "If environment variables should be inherited from the parent process.",
"default": true
},
"watch": {
"type": ["boolean", "string"],
"description": "If the process should be restarted when files change.",
"enum": [
true,
false,
"hot"
],
"default": true
"description": "The file to run and debug.",
"default": "${workspaceFolder}/${command:AskForProgramName}"
}
}
},
"attach": {
"properties": {
"url": {
"port": {
"type": "integer",
"description": "The port to attach and debug.",
"default": 6499
},
"hostname": {
"type": "string",
"description": "The URL of the Bun process to attach to.",
"default": "ws://localhost:6499/"
"description": "The hostname to attach and debug.",
"default": "localhost"
}
}
}
@@ -188,13 +119,12 @@
"type": "bun",
"request": "launch",
"name": "Bun: Debug",
"program": "${file}"
"program": "${workspaceFolder}/${command:AskForProgramName}"
},
{
"type": "bun",
"request": "attach",
"name": "Bun: Attach",
"url": "ws://localhost:6499/"
"name": "Bun: Attach"
}
],
"configurationSnippets": [
@@ -205,7 +135,7 @@
"type": "bun",
"request": "launch",
"name": "Ask for file name",
"program": "^\"\\${file}\""
"program": "^\"\\${workspaceFolder}/\\${command:AskForProgramName}\""
}
},
{
@@ -215,10 +145,14 @@
"type": "bun",
"request": "attach",
"name": "Attach to Bun",
"url": "ws://localhost:6499/"
"port": 6499,
"hostname": "localhost"
}
}
]
],
"variables": {
"AskForProgramName": "extension.bun.getProgramName"
}
}
],
"languages": [
@@ -231,15 +165,15 @@
".lockb"
],
"icon": {
"dark": "src/assets/icon-small.png",
"light": "src/assets/icon-small.png"
"dark": "./src/assets/icon-small.png",
"light": "./src/assets/icon-small.png"
}
}
],
"jsonValidation": [
{
"fileMatch": "package.json",
"url": "src/resources/package.json"
"url": "./src/resources/package.json"
}
],
"customEditors": [
@@ -255,40 +189,26 @@
}
]
},
"description": "The Visual Studio Code extension for Bun.",
"displayName": "Bun",
"description": "Visual Studio Code extension for Bun.",
"engines": {
"vscode": "^1.81.0"
"vscode": "^1.66.0"
},
"extensionKind": [
"workspace"
],
"galleryBanner": {
"color": "#C80000",
"theme": "dark"
},
"homepage": "https://bun.sh/",
"icon": "src/assets/icon.png",
"keywords": [
"bun",
"node.js",
"javascript",
"typescript",
"vscode"
],
"icon": "./src/assets/icon.png",
"license": "MIT",
"private": true,
"publisher": "oven",
"scripts": {
"bundle": "./node_modules/.bin/esbuild src/extension.ts src/web-extension.ts --bundle --external:vscode --outdir=dist --platform=node --format=cjs",
"prebuild": "bun run bundle && rm -rf extension && mkdir -p extension/src && cp -r dist extension/dist && cp -r src/assets extension/src/assets && cp package.json extension && cp README.md extension",
"build": "cd extension && vsce package",
"build:watch": "./node_modules/.bin/esbuild --watch src/extension.ts src/web-extension.ts --bundle --external:vscode --outdir=dist --platform=node --format=cjs"
"build": "bunx esbuild src/extension.ts src/web-extension.ts --bundle --external:vscode --outdir=dist --platform=node --format=cjs",
"build:watch": "bunx esbuild --watch src/extension.ts src/web-extension.ts --bundle --external:vscode --outdir=dist --platform=node --format=cjs"
},
"workspaceTrust": {
"request": "never"
},
"workspaces": [
"../bun-debug-adapter-protocol",
"../bun-inspector-protocol"
]
"dependencies": {
"ws": "^8.13.0"
}
}

View File

@@ -0,0 +1,118 @@
import * as vscode from "vscode";
import { CancellationToken, DebugConfiguration, ProviderResult, WorkspaceFolder } from "vscode";
import { DAPAdapter } from "./dap";
import lockfile from "./lockfile";
export function activateBunDebug(context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) {
lockfile(context);
context.subscriptions.push(
vscode.commands.registerCommand("extension.bun.runEditorContents", (resource: vscode.Uri) => {
let targetResource = resource;
if (!targetResource && vscode.window.activeTextEditor) {
targetResource = vscode.window.activeTextEditor.document.uri;
}
if (targetResource) {
vscode.debug.startDebugging(
undefined,
{
type: "bun",
name: "Run File",
request: "launch",
program: targetResource.fsPath,
},
{ noDebug: true },
);
}
}),
vscode.commands.registerCommand("extension.bun.debugEditorContents", (resource: vscode.Uri) => {
let targetResource = resource;
if (!targetResource && vscode.window.activeTextEditor) {
targetResource = vscode.window.activeTextEditor.document.uri;
}
if (targetResource) {
vscode.debug.startDebugging(undefined, {
type: "bun",
name: "Debug File",
request: "launch",
program: targetResource.fsPath,
stopOnEntry: true,
});
}
}),
);
context.subscriptions.push(
vscode.commands.registerCommand("extension.bun.getProgramName", config => {
return vscode.window.showInputBox({
placeHolder: "Please enter the name of a file in the workspace folder",
value: "src/index.js",
});
}),
);
const provider = new BunConfigurationProvider();
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("bun", provider));
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
"bun",
{
provideDebugConfigurations(folder: WorkspaceFolder | undefined): ProviderResult<DebugConfiguration[]> {
return [
{
name: "Launch",
request: "launch",
type: "bun",
program: "${file}",
},
];
},
},
vscode.DebugConfigurationProviderTriggerKind.Dynamic,
),
);
if (!factory) {
factory = new InlineDebugAdapterFactory();
}
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("bun", factory));
if ("dispose" in factory) {
// @ts-expect-error ???
context.subscriptions.push(factory);
}
}
class BunConfigurationProvider implements vscode.DebugConfigurationProvider {
resolveDebugConfiguration(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken,
): ProviderResult<DebugConfiguration> {
// if launch.json is missing or empty
if (!config.type && !config.request && !config.name) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.languageId === "javascript") {
config.type = "bun";
config.name = "Launch";
config.request = "launch";
config.program = "${file}";
config.stopOnEntry = true;
}
}
if (!config.program) {
return vscode.window.showInformationMessage("Cannot find a program to debug").then(_ => {
return undefined; // abort launch
});
}
return config;
}
}
class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
return new vscode.DebugAdapterInlineImplementation(new DAPAdapter(_session));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,16 @@
import * as vscode from "vscode";
import activateLockfile from "./features/lockfile";
import activateDebug from "./features/debug";
import { activateBunDebug } from "./activate";
const runMode: "external" | "server" | "namedPipeServer" | "inline" = "inline";
export function activate(context: vscode.ExtensionContext) {
activateLockfile(context);
activateDebug(context);
if (runMode === "inline") {
activateBunDebug(context);
return;
}
throw new Error(`This extension does not support '${runMode}' mode.`);
}
export function deactivate() {}
export function deactivate() {
// No-op
}

View File

@@ -1,169 +0,0 @@
import * as vscode from "vscode";
import type { CancellationToken, DebugConfiguration, ProviderResult, WorkspaceFolder } from "vscode";
import type { DAP } from "../../../bun-debug-adapter-protocol";
import { DebugAdapter } from "../../../bun-debug-adapter-protocol";
import { DebugSession } from "@vscode/debugadapter";
const debugConfiguration: vscode.DebugConfiguration = {
type: "bun",
request: "launch",
name: "Debug Bun",
program: "${file}",
watch: true,
};
const runConfiguration: vscode.DebugConfiguration = {
type: "bun",
request: "launch",
name: "Run Bun",
program: "${file}",
watch: true,
};
const attachConfiguration: vscode.DebugConfiguration = {
type: "bun",
request: "attach",
name: "Attach to Bun",
url: "ws://localhost:6499/",
};
const debugConfigurations: vscode.DebugConfiguration[] = [debugConfiguration, attachConfiguration];
export default function (context: vscode.ExtensionContext, factory?: vscode.DebugAdapterDescriptorFactory) {
context.subscriptions.push(
vscode.commands.registerCommand("extension.bun.runFile", (resource: vscode.Uri) => {
let targetResource = resource;
if (!targetResource && vscode.window.activeTextEditor) {
targetResource = vscode.window.activeTextEditor.document.uri;
}
if (targetResource) {
vscode.debug.startDebugging(undefined, runConfiguration, {
noDebug: true,
});
}
}),
vscode.commands.registerCommand("extension.bun.debugFile", (resource: vscode.Uri) => {
let targetResource = resource;
if (!targetResource && vscode.window.activeTextEditor) {
targetResource = vscode.window.activeTextEditor.document.uri;
}
if (targetResource) {
vscode.debug.startDebugging(undefined, {
...debugConfiguration,
program: targetResource.fsPath,
});
}
}),
);
const provider = new BunConfigurationProvider();
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("bun", provider));
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider(
"bun",
{
provideDebugConfigurations(folder: WorkspaceFolder | undefined): ProviderResult<DebugConfiguration[]> {
return debugConfigurations;
},
},
vscode.DebugConfigurationProviderTriggerKind.Dynamic,
),
);
if (!factory) {
factory = new InlineDebugAdapterFactory();
}
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory("bun", factory));
if ("dispose" in factory && typeof factory.dispose === "function") {
// @ts-ignore
context.subscriptions.push(factory);
}
}
class BunConfigurationProvider implements vscode.DebugConfigurationProvider {
resolveDebugConfiguration(
folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
token?: CancellationToken,
): ProviderResult<DebugConfiguration> {
if (!config.type && !config.request && !config.name) {
const editor = vscode.window.activeTextEditor;
if (editor && isJavaScript(editor.document.languageId)) {
Object.assign(config, debugConfiguration);
}
}
return config;
}
}
class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(_session: vscode.DebugSession): ProviderResult<vscode.DebugAdapterDescriptor> {
const adapter = new VSCodeAdapter(_session);
return new vscode.DebugAdapterInlineImplementation(adapter);
}
}
function isJavaScript(languageId: string): boolean {
return (
languageId === "javascript" ||
languageId === "javascriptreact" ||
languageId === "typescript" ||
languageId === "typescriptreact"
);
}
export class VSCodeAdapter extends DebugSession {
#adapter: DebugAdapter;
#console: vscode.OutputChannel;
#dap: vscode.OutputChannel;
#jsc: vscode.OutputChannel;
constructor(session: vscode.DebugSession) {
super();
const output = (this.#console = vscode.window.createOutputChannel("Console (Bun)"));
this.#dap = vscode.window.createOutputChannel("Debug Adapter Protocol (Bun)");
const jsc = (this.#jsc = vscode.window.createOutputChannel("JavaScript Inspector (Bun)"));
this.#adapter = new DebugAdapter({
send: this.sendMessage.bind(this),
logger(...messages) {
console.log("[jsc]", ...messages);
jsc.appendLine(messages.map(v => (typeof v === "object" ? JSON.stringify(v) : v)).join(" "));
},
stdout(message) {
output.append(message);
},
stderr(message) {
output.append(message);
},
});
}
sendMessage(message: DAP.Request | DAP.Response | DAP.Event): void {
console.log("[dap] -->", message);
this.#dap.appendLine("--> " + JSON.stringify(message));
const { type } = message;
if (type === "response") {
this.sendResponse(message);
} else if (type === "event") {
this.sendEvent(message);
} else {
throw new Error(`Not supported: ${type}`);
}
}
handleMessage(message: DAP.Event | DAP.Request | DAP.Response): void {
console.log("[dap] <--", message);
this.#dap.appendLine("<-- " + JSON.stringify(message));
this.#adapter.accept(message);
}
dispose() {
this.#adapter.close();
this.#console.dispose();
this.#dap.dispose();
this.#jsc.dispose();
}
}

View File

@@ -0,0 +1,308 @@
import { Socket, createConnection } from "node:net";
import { inspect } from "node:util";
import type { JSC } from "../types/jsc";
export type { JSC };
export type JSCClientOptions = {
url: string | URL;
retry?: boolean;
onEvent?: (event: JSC.Event) => void;
onRequest?: (request: JSC.Request) => void;
onResponse?: (response: JSC.Response) => void;
onError?: (error: Error) => void;
onClose?: (code: number, reason: string) => void;
};
const headerInvalidNumber = 2147483646;
// We use non-printable characters to separate messages in the stream.
// These should never appear in textual messages.
// These are non-sequential so that code which just counts up from 0 doesn't accidentally parse them as messages.
// 0x12 0x11 0x13 0x14 as a little-endian 32-bit unsigned integer
const headerPrefix = "\x14\x13\x11\x12";
// 0x14 0x12 0x13 0x11 as a little-endian 32-bit unsigned integer
const headerSuffixString = "\x11\x13\x12\x14";
const headerSuffixInt = Buffer.from(headerSuffixString).readInt32LE(0);
const headerPrefixInt = Buffer.from(headerPrefix).readInt32LE(0);
const messageLengthBuffer = new ArrayBuffer(12);
const messageLengthDataView = new DataView(messageLengthBuffer);
messageLengthDataView.setInt32(0, headerPrefixInt, true);
messageLengthDataView.setInt32(8, headerSuffixInt, true);
function writeJSONMessageToBuffer(message: any) {
const asString = JSON.stringify(message);
const byteLength = Buffer.byteLength(asString, "utf8");
const buffer = Buffer.allocUnsafe(12 + byteLength);
buffer.writeInt32LE(headerPrefixInt, 0);
buffer.writeInt32LE(byteLength, 4);
buffer.writeInt32LE(headerSuffixInt, 8);
if (buffer.write(asString, 12, byteLength, "utf8") !== byteLength) {
throw new Error("Failed to write message to buffer");
}
return buffer;
}
let currentMessageLength = 0;
const DEBUGGING = true;
function extractMessageLengthAndOffsetFromBytes(buffer: Buffer, offset: number) {
const bufferLength = buffer.length;
while (offset < bufferLength) {
const headerStart = buffer.indexOf(headerPrefix, offset, "binary");
if (headerStart === -1) {
if (DEBUGGING) {
console.error("No header found in buffer of length " + bufferLength + " starting at offset " + offset);
}
return headerInvalidNumber;
}
// [headerPrefix (4), byteLength (4), headerSuffix (4)]
if (bufferLength <= headerStart + 12) {
if (DEBUGGING) {
console.error(
"Not enough bytes for header in buffer of length " + bufferLength + " starting at offset " + offset,
);
}
return headerInvalidNumber;
}
const prefix = buffer.readInt32LE(headerStart);
const byteLengthInt = buffer.readInt32LE(headerStart + 4);
const suffix = buffer.readInt32LE(headerStart + 8);
if (prefix !== headerPrefixInt || suffix !== headerSuffixInt) {
offset = headerStart + 1;
currentMessageLength = 0;
if (DEBUGGING) {
console.error(
"Invalid header in buffer of length " + bufferLength + " starting at offset " + offset + ": " + prefix,
byteLengthInt,
suffix,
);
}
continue;
}
if (byteLengthInt < 0) {
if (DEBUGGING) {
console.error(
"Invalid byteLength in buffer of length " + bufferLength + " starting at offset " + offset + ": " + prefix,
byteLengthInt,
suffix,
);
}
return headerInvalidNumber;
}
if (byteLengthInt === 0) {
// Ignore 0-length messages
// Shouldn't happen in practice
offset = headerStart + 12;
currentMessageLength = 0;
if (DEBUGGING) {
console.error(
"Ignoring 0-length message in buffer of length " + bufferLength + " starting at offset " + offset,
);
console.error({
buffer: buffer,
string: buffer.toString(),
});
}
continue;
}
currentMessageLength = byteLengthInt;
return headerStart + 12;
}
if (DEBUGGING) {
if (bufferLength > 0)
console.error("Header not found in buffer of length " + bufferLength + " starting at offset " + offset);
}
return headerInvalidNumber;
}
class StreamingReader {
pendingBuffer: Buffer;
constructor() {
this.pendingBuffer = Buffer.alloc(0);
}
*onMessage(chunk: Buffer) {
let buffer: Buffer;
if (this.pendingBuffer.length > 0) {
this.pendingBuffer = buffer = Buffer.concat([this.pendingBuffer, chunk]);
} else {
this.pendingBuffer = buffer = chunk;
}
currentMessageLength = 0;
for (
let offset = extractMessageLengthAndOffsetFromBytes(buffer, 0);
buffer.length > 0 && offset !== headerInvalidNumber;
currentMessageLength = 0, offset = extractMessageLengthAndOffsetFromBytes(buffer, 0)
) {
const messageLength = currentMessageLength;
const start = offset;
const end = start + messageLength;
offset = end;
const messageChunk = buffer.slice(start, end);
this.pendingBuffer = buffer = buffer.slice(offset);
yield messageChunk.toString();
}
}
}
export class JSCClient {
#options: JSCClientOptions;
#requestId: number;
#pendingMessages: Buffer[];
#pendingRequests: Map<number, (result: unknown) => void>;
#socket: Socket;
#ready?: Promise<void>;
#reader = new StreamingReader();
signal?: AbortSignal;
constructor(options: JSCClientOptions) {
this.#options = options;
this.#socket = undefined;
this.#requestId = 1;
this.#pendingMessages = [];
this.#pendingRequests = new Map();
}
get ready(): Promise<void> {
if (!this.#ready) {
this.#ready = this.#connect();
}
return this.#ready;
}
#connect(): Promise<void> {
const { url, retry, onError, onResponse, onEvent, onClose } = this.#options;
let [host, port] = typeof url === "string" ? url.split(":") : [url.hostname, url.port];
if (port == null) {
if (host == null) {
host = "localhost";
port = "9229";
} else {
port = "9229";
}
}
if (host == null) {
host = "localhost";
}
var resolve,
reject,
promise = new Promise<void>((r1, r2) => {
resolve = r1;
reject = r2;
}),
socket: Socket;
let didConnect = false;
this.#socket = socket = createConnection(
{
host,
port: Number(port),
},
() => {
for (const message of this.#pendingMessages) {
this.#send(message);
}
this.#pendingMessages.length = 0;
didConnect = true;
resolve();
},
)
.once("error", e => {
const error = new Error(`Socket error: ${e?.message || e}`);
reject(error);
})
.on("data", buffer => {
for (const message of this.#reader.onMessage(buffer)) {
let received: JSC.Event | JSC.Response;
try {
received = JSON.parse(message);
} catch {
const error = new Error(`Invalid WebSocket data: ${inspect(message)}`);
onError?.(error);
return;
}
console.log({ received });
if ("id" in received) {
onResponse?.(received);
if ("error" in received) {
const { message, code = "?" } = received.error;
const error = new Error(`${message} [code: ${code}]`);
error.code = code;
onError?.(error);
this.#pendingRequests.get(received.id)?.(error);
} else {
this.#pendingRequests.get(received.id)?.(received.result);
}
} else {
onEvent?.(received);
}
}
})
.on("close", hadError => {
if (didConnect) {
onClose?.(hadError ? 1 : 0, "Socket closed");
}
});
return promise;
}
#send(message: any): void {
const socket = this.#socket;
const framed = writeJSONMessageToBuffer(message);
if (socket && !socket.connecting) {
socket.write(framed);
} else {
this.#pendingMessages.push(framed);
}
}
async fetch<T extends keyof JSC.RequestMap>(
method: T,
params?: JSC.Request<T>["params"],
): Promise<JSC.ResponseMap[T]> {
const request: JSC.Request<T> = {
id: this.#requestId++,
method,
params,
};
this.#options.onRequest?.(request);
return new Promise((resolve, reject) => {
const done = (result: Error | JSC.ResponseMap[T]) => {
this.#pendingRequests.delete(request.id);
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
};
this.#pendingRequests.set(request.id, done);
this.#send(request);
});
}
close(): void {
if (this.#socket) this.#socket.end();
}
}

Some files were not shown because too many files have changed in this diff Show More