mirror of
https://github.com/oven-sh/bun
synced 2026-02-05 08:28:55 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeeef5aaf0 | ||
|
|
f9b966c13f | ||
|
|
d2ad4da1a0 | ||
|
|
eb4ef364f2 | ||
|
|
d0e2679fb5 | ||
|
|
764437eb6d | ||
|
|
824655e1cb | ||
|
|
21b2d5c3a5 | ||
|
|
10815a7d43 | ||
|
|
f839640c17 | ||
|
|
557e912d9a | ||
|
|
1e75a978e5 | ||
|
|
95e8c24db1 | ||
|
|
755f41fe2a | ||
|
|
9aabe4eea1 | ||
|
|
90f3bf2796 | ||
|
|
16b4bf341a | ||
|
|
1480889205 | ||
|
|
f269432d90 | ||
|
|
73b3fb7b0f | ||
|
|
2bcbafe7d3 | ||
|
|
f7f734788c | ||
|
|
2cd1d59387 | ||
|
|
b70210a005 | ||
|
|
b9c2309c8a | ||
|
|
43c4da8c9a | ||
|
|
8a48e8bb0b | ||
|
|
097ae4e982 | ||
|
|
213f5bef9d | ||
|
|
e115638cba | ||
|
|
6e57556fad | ||
|
|
339d2c7f19 | ||
|
|
d2bef4fbea | ||
|
|
19aa9d93de | ||
|
|
55eb4ffe8f | ||
|
|
a051a6f620 | ||
|
|
9c68abdb8d | ||
|
|
ad326b7734 | ||
|
|
aa08c35c06 | ||
|
|
20d42dfaa3 | ||
|
|
3556fa3b1e | ||
|
|
5e07fd4fbc | ||
|
|
c60385716b | ||
|
|
f3266ff436 | ||
|
|
b01764b31e | ||
|
|
851763174e | ||
|
|
8518fbb573 | ||
|
|
d86084dd8e | ||
|
|
52802a4c55 | ||
|
|
44e4d5852a | ||
|
|
3a45f2c71b | ||
|
|
9eeb7bdbff | ||
|
|
ed14b64e65 | ||
|
|
bca1bcf29c | ||
|
|
9027484ae1 | ||
|
|
91eacade97 | ||
|
|
6a02edef5d |
2
.github/ISSUE_TEMPLATE/2-bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/2-bug-report.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Bun is running?
|
||||
description: Copy the output of `bun -v`
|
||||
description: Copy the output of `bun --revision`
|
||||
- type: input
|
||||
attributes:
|
||||
label: What platform is your computer?
|
||||
|
||||
2
.github/workflows/bun-linux-aarch64.yml
vendored
2
.github/workflows/bun-linux-aarch64.yml
vendored
@@ -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-4/bun-webkit-linux-arm64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-linux-arm64-lto.tar.gz"
|
||||
webkit_basename: "bun-webkit-linux-arm64-lto"
|
||||
build_machine_arch: aarch64
|
||||
|
||||
|
||||
4
.github/workflows/bun-linux-build.yml
vendored
4
.github/workflows/bun-linux-build.yml
vendored
@@ -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-4/bun-webkit-linux-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-linux-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-linux-amd64-lto.tar.gz"
|
||||
webkit_basename: "bun-webkit-linux-amd64-lto"
|
||||
build_machine_arch: x86_64
|
||||
|
||||
|
||||
16
.github/workflows/bun-mac-aarch64.yml
vendored
16
.github/workflows/bun-mac-aarch64.yml
vendored
@@ -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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
runner: macos-arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
16
.github/workflows/bun-mac-x64-baseline.yml
vendored
16
.github/workflows/bun-mac-x64-baseline.yml
vendored
@@ -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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# runner: macos-arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
16
.github/workflows/bun-mac-x64.yml
vendored
16
.github/workflows/bun-mac-x64.yml
vendored
@@ -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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-amd64-lto.tar.gz"
|
||||
webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/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-4/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
# webkit_url: "https://github.com/oven-sh/WebKit/releases/download/2023-aug3-5/bun-webkit-macos-arm64-lto.tar.gz"
|
||||
# runner: macos-arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
2
.github/workflows/zig-fmt.yml
vendored
2
.github/workflows/zig-fmt.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: zig-fmt
|
||||
|
||||
env:
|
||||
ZIG_VERSION: 0.11.0-dev.4006+bf827d0b5
|
||||
ZIG_VERSION: 0.12.0-dev.163+6780a6bbf
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -130,3 +130,6 @@ src/js/out/tmp
|
||||
src/js/out/DebugPath.h
|
||||
|
||||
make-dev-stats.csv
|
||||
|
||||
.uuid
|
||||
tsconfig.tsbuildinfo
|
||||
@@ -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-4
|
||||
ARG WEBKIT_TAG=2023-aug3-5
|
||||
ARG ZIG_TAG=jul1
|
||||
ARG ZIG_VERSION="0.11.0-dev.4006+bf827d0b5"
|
||||
ARG ZIG_VERSION="0.12.0-dev.163+6780a6bbf"
|
||||
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.7
|
||||
ARG BUN_BASE_VERSION=0.8
|
||||
|
||||
FROM bitnami/minideb:bullseye as bun-base
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -38,7 +38,7 @@ NATIVE_OR_OLD_MARCH = -march=nehalem
|
||||
endif
|
||||
|
||||
MIN_MACOS_VERSION ?= $(DEFAULT_MIN_MACOS_VERSION)
|
||||
BUN_BASE_VERSION = 0.7
|
||||
BUN_BASE_VERSION = 0.8
|
||||
|
||||
CI ?= false
|
||||
|
||||
|
||||
@@ -123,7 +123,6 @@ 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
|
||||
|
||||
98
build.zig
98
build.zig
@@ -1,4 +1,5 @@
|
||||
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 {
|
||||
@@ -96,6 +97,7 @@ 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| {
|
||||
@@ -172,13 +174,12 @@ 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 = b.pathFromRoot(output_dir_);
|
||||
output_dir = try pathRel(b.allocator, b.install_prefix, output_dir_);
|
||||
} else {
|
||||
const output_dir_base = try std.fmt.bufPrint(&output_dir_buf, "{s}{s}", .{ bin_label, triplet });
|
||||
output_dir = b.pathFromRoot(output_dir_base);
|
||||
output_dir = try pathRel(b.allocator, b.install_prefix, 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)
|
||||
@@ -186,12 +187,12 @@ pub fn build(b: *Build) !void {
|
||||
else
|
||||
"root.zig";
|
||||
|
||||
const min_version: std.SemanticVersion = if (target.getOsTag() != .freestanding and !target.isWindows())
|
||||
const min_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
|
||||
target.getOsVersionMin().semver
|
||||
else
|
||||
.{ .major = 0, .minor = 0, .patch = 0 };
|
||||
|
||||
const max_version: std.SemanticVersion = if (target.getOsTag() != .freestanding and !target.isWindows())
|
||||
const max_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
|
||||
target.getOsVersionMax().semver
|
||||
else
|
||||
.{ .major = 0, .minor = 0, .patch = 0 };
|
||||
@@ -202,6 +203,7 @@ 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: {
|
||||
@@ -239,8 +241,6 @@ pub fn build(b: *Build) !void {
|
||||
};
|
||||
|
||||
{
|
||||
obj.setMainPkgPath(b.pathFromRoot("."));
|
||||
|
||||
try addInternalPackages(
|
||||
b,
|
||||
obj,
|
||||
@@ -271,9 +271,15 @@ 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);
|
||||
obj.emit_bin = .{
|
||||
.emit_to = b.fmt("{s}/{s}.o", .{ output_dir, bun_executable_name }),
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
var actual_build_options = default_build_options;
|
||||
if (b.option(bool, "generate-sizes", "Generate sizes of things") orelse false) {
|
||||
actual_build_options.sizegen = true;
|
||||
@@ -290,7 +296,8 @@ 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.emit_bin = .no_emit;
|
||||
obj.generated_bin = null;
|
||||
}
|
||||
|
||||
if (target.getOsTag() == .linux) {
|
||||
@@ -308,9 +315,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
var headers_build_options = default_build_options;
|
||||
headers_build_options.bindgen = true;
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
@@ -318,21 +326,22 @@ pub fn build(b: *Build) !void {
|
||||
}
|
||||
|
||||
{
|
||||
const wasm = b.step("bun-wasm", "Build WASM");
|
||||
var wasm_step = b.addStaticLibrary(.{
|
||||
const wasm_step = b.step("bun-wasm", "Build WASM");
|
||||
var wasm = 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.dependOn(&wasm_step.step);
|
||||
wasm_step.strip = false;
|
||||
defer wasm_step.dependOn(&wasm.step);
|
||||
wasm.strip = false;
|
||||
// wasm_step.link_function_sections = true;
|
||||
// wasm_step.link_emit_relocs = true;
|
||||
// wasm_step.single_threaded = true;
|
||||
try configureObjectStep(b, wasm_step, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, wasm, wasm_step, @TypeOf(target), target);
|
||||
var build_opts = default_build_options;
|
||||
wasm_step.addOptions("build_options", build_opts.step(b));
|
||||
wasm.addOptions("build_options", build_opts.step(b));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -342,9 +351,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -355,9 +365,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -368,9 +379,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -381,9 +393,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -394,9 +407,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -407,9 +421,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -420,9 +435,10 @@ 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, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
}
|
||||
|
||||
@@ -436,16 +452,23 @@ 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| headers_obj.emit_bin = .{
|
||||
.emit_to = b.fmt("{s}/{s}", .{ dir, headers_obj.name }),
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
try configureObjectStep(b, headers_obj, @TypeOf(target), target, obj.main_pkg_path.?);
|
||||
try configureObjectStep(b, headers_obj, headers_step, @TypeOf(target), target);
|
||||
|
||||
headers_step.dependOn(&headers_obj.step);
|
||||
headers_obj.addOptions("build_options", default_build_options.step(b));
|
||||
@@ -456,9 +479,7 @@ 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, comptime Target: type, target: Target, main_pkg_path: []const u8) !void {
|
||||
obj.setMainPkgPath(main_pkg_path);
|
||||
|
||||
pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, obj_step: *std.build.Step, comptime Target: type, target: Target) !void {
|
||||
// obj.setTarget(target);
|
||||
try addInternalPackages(b, obj, std.heap.page_allocator, b.zig_exe, target);
|
||||
|
||||
@@ -466,11 +487,16 @@ pub fn configureObjectStep(b: *std.build.Builder, obj: *CompileStep, comptime Ta
|
||||
|
||||
// obj.setBuildMode(optimize);
|
||||
obj.bundle_compiler_rt = false;
|
||||
if (obj.emit_bin == .default)
|
||||
obj.emit_bin = .{
|
||||
.emit_to = b.fmt("{s}/{s}.o", .{ output_dir, obj.name }),
|
||||
};
|
||||
if (obj.emit_directory == null) {
|
||||
var install = b.addInstallFileWithDir(
|
||||
obj.getEmittedBin(),
|
||||
.{ .custom = output_dir },
|
||||
b.fmt("{s}.o", .{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;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ To configure which port and hostname the server will listen on:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
port: 8080, // defaults to $PORT, then 3000
|
||||
port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
|
||||
hostname: "mydomain.com", // defaults to "0.0.0.0"
|
||||
fetch(req) {
|
||||
return new Response(`404!`);
|
||||
@@ -43,6 +43,17 @@ Bun.serve({
|
||||
});
|
||||
```
|
||||
|
||||
To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket):
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
unix: "/tmp/my-socket.sock", // path to socket
|
||||
fetch(req) {
|
||||
return new Response(`404!`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
To activate development mode, set `development: true`. By default, development mode is _enabled_ unless `NODE_ENV` is `production`.
|
||||
@@ -78,7 +89,7 @@ Bun.serve({
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Note** — Full debugger support is planned.
|
||||
[Learn more about debugging in Bun](https://bun.sh/docs/runtime/debugger)
|
||||
{% /callout %}
|
||||
|
||||
The call to `Bun.serve` returns a `Server` object. To stop the server, call the `.stop()` method.
|
||||
|
||||
@@ -428,6 +428,21 @@ 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.
|
||||
|
||||
@@ -31,3 +31,30 @@ All imported files and packages are bundled into the executable, along with a co
|
||||
- `--publicPath`
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Embedding files
|
||||
|
||||
Standalone executables support embedding files.
|
||||
|
||||
To embed files into an executable with `bun build --compile`, import the file in your code
|
||||
|
||||
```js
|
||||
// this becomes an internal file path
|
||||
import icon from "./icon.png";
|
||||
|
||||
import { file } from "bun";
|
||||
|
||||
export default {
|
||||
fetch(req) {
|
||||
return new Response(file(icon));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
You may need to specify a `--loader` for it to be treated as a `"file"` loader (so you get back a file path).
|
||||
|
||||
Embedded files can be read using `Bun.file`'s functions or the Node.js `fs.readFile` function (in `"node:fs"`).
|
||||
|
||||
## Minification
|
||||
|
||||
To trim down the size of the executable a little, pass `--minify` to `bun build --compile`. This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller.
|
||||
|
||||
@@ -30,14 +30,50 @@ 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 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.
|
||||
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.
|
||||
|
||||
```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.
|
||||
|
||||
65
docs/guides/ecosystem/nuxt.md
Normal file
65
docs/guides/ecosystem/nuxt.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
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.
|
||||
35
docs/guides/runtime/timezone.md
Normal file
35
docs/guides/runtime/timezone.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Set a time zone in Bun
|
||||
---
|
||||
|
||||
Bun supports programmatically setting a default time zone for the lifetime of the `bun` process. To do set, set the value of the `TZ` environment variable to a [valid timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
||||
|
||||
{% callout %}
|
||||
When running a file with `bun`, the timezone defaults to your system's configured local time zone.
|
||||
|
||||
When running tests with `bun test`, the timezone is set to `UTC` to make tests more deterministic.
|
||||
{% /callout %}
|
||||
|
||||
```ts
|
||||
process.env.TZ = "America/New_York";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Alternatively, this can be set from the command line when running a Bun command.
|
||||
|
||||
```sh
|
||||
$ TZ=America/New_York bun run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Once `TZ` is set, any `Date` instances will have that time zone. By default all dates use your system's configured time zone.
|
||||
|
||||
```ts
|
||||
new Date().getHours(); // => 18
|
||||
|
||||
process.env.TZ = "America/New_York";
|
||||
|
||||
new Date().getHours(); // => 21
|
||||
```
|
||||
82
docs/guides/runtime/web-debugger.md
Normal file
82
docs/guides/runtime/web-debugger.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
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" /%}
|
||||
23
docs/guides/test/bail.md
Normal file
23
docs/guides/test/bail.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bail early with the Bun test runner
|
||||
---
|
||||
|
||||
Use the `--bail` flag to bail on a test run after a single failure. This is useful for aborting as soon as possible in a continuous integration environment.
|
||||
|
||||
```sh
|
||||
# re-run each test 10 times
|
||||
$ bun test --bail
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To bail after a certain threshold of failures, optionally specify a number after the flag.
|
||||
|
||||
```sh
|
||||
# bail after 10 failures
|
||||
$ bun test --bail 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.
|
||||
58
docs/guides/test/coverage-threshold.md
Normal file
58
docs/guides/test/coverage-threshold.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: Set a code coverage threshold with the Bun test runner
|
||||
---
|
||||
|
||||
Bun's test runner supports built-in code coverage reporting via the `--coverage` flag.
|
||||
|
||||
```sh
|
||||
$ bun test --coverage
|
||||
|
||||
test.test.ts:
|
||||
✓ math > add [0.71ms]
|
||||
✓ math > multiply [0.03ms]
|
||||
✓ random [0.13ms]
|
||||
-------------|---------|---------|-------------------
|
||||
File | % Funcs | % Lines | Uncovered Line #s
|
||||
-------------|---------|---------|-------------------
|
||||
All files | 66.67 | 77.78 |
|
||||
math.ts | 50.00 | 66.67 |
|
||||
random.ts | 50.00 | 66.67 |
|
||||
-------------|---------|---------|-------------------
|
||||
|
||||
3 pass
|
||||
0 fail
|
||||
3 expect() calls
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To set a minimum coverage threshold, add the following line to your `bunfig.toml`. This requires that 90% of your codebase is covered by tests.
|
||||
|
||||
```toml
|
||||
[test]
|
||||
# to require 90% line-level and function-level coverage
|
||||
coverageThreshold = 0.9
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If your test suite does not meet this threshold, `bun test` will exit with a non-zero exit code to signal a failure.
|
||||
|
||||
```sh
|
||||
$ bun test --coverage
|
||||
<test output>
|
||||
$ echo $?
|
||||
1 # this is the exit code of the previous command
|
||||
```
|
||||
|
||||
Different thresholds can be set for line-level and function-level coverage.
|
||||
|
||||
```toml
|
||||
[test]
|
||||
# to set different thresholds for lines and functions
|
||||
coverageThreshold = { line = 0.5, function = 0.7 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test runner > Coverage](/docs/test/coverage) for complete documentation on code coverage reporting in Bun.
|
||||
44
docs/guides/test/coverage.md
Normal file
44
docs/guides/test/coverage.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Generate code coverage reports with the Bun test runner
|
||||
---
|
||||
|
||||
Bun's test runner 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.
|
||||
|
||||
---
|
||||
|
||||
Pass the `--coverage` flag to `bun test` to enable this feature. This will print a coverage report after the test run.
|
||||
|
||||
The coverage report lists the source files that were executed during the test run, the percentage of functions and lines that were executed, and the line ranges that were not executed during the run.
|
||||
|
||||
```sh
|
||||
$ bun test --coverage
|
||||
|
||||
test.test.ts:
|
||||
✓ math > add [0.71ms]
|
||||
✓ math > multiply [0.03ms]
|
||||
✓ random [0.13ms]
|
||||
-------------|---------|---------|-------------------
|
||||
File | % Funcs | % Lines | Uncovered Line #s
|
||||
-------------|---------|---------|-------------------
|
||||
All files | 66.67 | 77.78 |
|
||||
math.ts | 50.00 | 66.67 |
|
||||
random.ts | 50.00 | 66.67 |
|
||||
-------------|---------|---------|-------------------
|
||||
|
||||
3 pass
|
||||
0 fail
|
||||
3 expect() calls
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To always enable coverage reporting by default, add the following line to your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
coverage = true # always enable coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Refer to [Docs > Test runner > Coverage](/docs/test/coverage) for complete documentation on code coverage reporting in Bun.
|
||||
68
docs/guides/test/happy-dom.md
Normal file
68
docs/guides/test/happy-dom.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: Write browser DOM tests with Bun and happy-dom
|
||||
---
|
||||
|
||||
You can write and run browser tests with Bun's test runner in conjunction with [Happy DOM](https://github.com/capricorn86/happy-dom). Happy DOM implements mocked versions of browser APIs like `document` and `location`.
|
||||
|
||||
---
|
||||
|
||||
To get started, install `happy-dom`.
|
||||
|
||||
```sh
|
||||
$ bun add -d @happy-dom/global-registrator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This module exports a "registrator" that will adds the mocked browser APIs to the global scope.
|
||||
|
||||
```ts#happydom.ts
|
||||
import { GlobalRegistrator } from "@happy-dom/global-registrator";
|
||||
|
||||
GlobalRegistrator.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
We need to make sure this file is executed before any of our test files. That's a job for Bun's built-in _preload_ functionality. Create a `bunfig.toml` file in the root of your project (if it doesn't already exist) and add the following lines.
|
||||
|
||||
The `./happydom.ts` file should contain the registration code above.
|
||||
|
||||
```toml#bunfig.toml
|
||||
[test]
|
||||
preload = "./happydom.ts"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Now running `bun test` inside our project will automatically execute `happydom.ts` first. We can start writing tests that use browser APIs.
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("set button text", () => {
|
||||
document.body.innerHTML = `<button>My button</button>`;
|
||||
const button = document.querySelector("button");
|
||||
expect(button?.innerText).toEqual("My button");
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
With Happy DOM propertly configured, this test runs as expected.
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
|
||||
dom.test.ts:
|
||||
✓ set button text [0.82ms]
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
1 expect() calls
|
||||
Ran 1 tests across 1 files. 1 total [125.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Refer to the [Happy DOM repo](https://github.com/capricorn86/happy-dom) and [Docs > Test runner > DOM](/docs/test/dom) for complete documentation on writing browser tests with Bun.
|
||||
4
docs/guides/test/index.json
Normal file
4
docs/guides/test/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Test runner",
|
||||
"description": "A collection of guides for writing, running, and configuring tests in Bun"
|
||||
}
|
||||
48
docs/guides/test/mock-clock.md
Normal file
48
docs/guides/test/mock-clock.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Set the system time in Bun's test runner
|
||||
---
|
||||
|
||||
Bun's test runner supports setting the system time programmatically with the `setSystemTime` function.
|
||||
|
||||
```ts
|
||||
import { test, expect, beforeAll, setSystemTime } from "bun:test";
|
||||
|
||||
test("party like it's 1999", () => {
|
||||
const date = new Date("1999-01-01T00:00:00.000Z");
|
||||
setSystemTime(date); // it's now January 1, 1999
|
||||
|
||||
const now = new Date();
|
||||
expect(now.getFullYear()).toBe(1999);
|
||||
expect(now.getMonth()).toBe(0);
|
||||
expect(now.getDate()).toBe(1);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `setSystemTime` function is commonly used on conjunction with [Lifecycle Hooks](/docs/test/lifecycle) to configure a testing environment with a determinstic "fake clock".
|
||||
|
||||
```ts
|
||||
import { test, expect, beforeAll, setSystemTime } from "bun:test";
|
||||
|
||||
beforeAll(() => {
|
||||
const date = new Date("1999-01-01T00:00:00.000Z");
|
||||
setSystemTime(date); // it's now January 1, 1999
|
||||
});
|
||||
|
||||
// tests...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To reset the system clock to the actual time, call `setSystemTime` with no arguments.
|
||||
|
||||
```ts
|
||||
import { test, expect, beforeAll, setSystemTime } from "bun:test";
|
||||
|
||||
setSystemTime(); // reset to actual time
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner > Date and time](/docs/test/time) for complete documentation on mocking with the Bun test runner.
|
||||
68
docs/guides/test/mock-functions.md
Normal file
68
docs/guides/test/mock-functions.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: Mock functions in `bun test`
|
||||
---
|
||||
|
||||
Create mocks with the `mock` function from `bun:test`.
|
||||
|
||||
```ts
|
||||
import { test, expect, mock } from "bun:test";
|
||||
|
||||
const random = mock(() => Math.random());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The mock function can accept arguments.
|
||||
|
||||
```ts
|
||||
import { test, expect, mock } from "bun:test";
|
||||
|
||||
const random = mock((multiplier: number) => multiplier * Math.random());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The result of `mock()` is a new function that's been decorated with some additional properties.
|
||||
|
||||
```ts
|
||||
import { mock } from "bun:test";
|
||||
|
||||
const random = mock((multiplier: number) => multiplier * Math.random());
|
||||
|
||||
random(2);
|
||||
random(10);
|
||||
|
||||
random.mock.calls;
|
||||
// [[ 2 ], [ 10 ]]
|
||||
|
||||
random.mock.results;
|
||||
// [
|
||||
// { type: "return", value: 0.6533907460954099 },
|
||||
// { type: "return", value: 0.6452713933037312 }
|
||||
// ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
These extra properties make it possible to write `expect` assertions about usage of the mock function, including how many times it was called, the arguments, and the return values.
|
||||
|
||||
```ts
|
||||
import { test, mock } from "bun:test";
|
||||
|
||||
const random = mock((multiplier: number) => multiplier * Math.random());
|
||||
|
||||
test("random", async () => {
|
||||
const a = random(1);
|
||||
const b = random(2);
|
||||
const c = random(3);
|
||||
|
||||
expect(random).toHaveBeenCalled();
|
||||
expect(random).toHaveBeenCalledTimes(3);
|
||||
expect(random.mock.args).toEqual([[1], [2], [3]]);
|
||||
expect(random.mock.results[0]).toEqual({ type: "return", value: a });
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner > Mocks](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
|
||||
14
docs/guides/test/rerun-each.md
Normal file
14
docs/guides/test/rerun-each.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Re-run tests multiple times with the Bun test runner
|
||||
---
|
||||
|
||||
Use the `--rerun-each` flag to re-run every test multiple times with the Bun test runner. This is useful for finding flaky or non-deterministic tests.
|
||||
|
||||
```sh
|
||||
# re-run each test 10 times
|
||||
$ bun test --rerun-each 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.
|
||||
99
docs/guides/test/run-tests.md
Normal file
99
docs/guides/test/run-tests.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: Run your tests with the Bun test runner
|
||||
---
|
||||
|
||||
Bun has a built-in test runner with a Jest-like `expect` API. To use it, run `bun test` from your project directory. The test runner will search for all files in the directory that match the following patterns:
|
||||
|
||||
- `*.test.{js|jsx|ts|tsx}`
|
||||
- `*_test.{js|jsx|ts|tsx}`
|
||||
- `*.spec.{js|jsx|ts|tsx}`
|
||||
- `*_spec.{js|jsx|ts|tsx}`
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test.test.js:
|
||||
✓ add [0.87ms]
|
||||
✓ multiply [0.02ms]
|
||||
|
||||
test2.test.js:
|
||||
✓ add [0.72ms]
|
||||
✓ multiply [0.01ms]
|
||||
|
||||
test3.test.js:
|
||||
✓ add [0.54ms]
|
||||
✓ multiply [0.01ms]
|
||||
|
||||
6 pass
|
||||
0 fail
|
||||
6 expect() calls
|
||||
Ran 6 tests across 3 files. [9.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To only run certain test files, pass a positional argument to `bun test`. The runner will only execute files that contain that argument in their path.
|
||||
|
||||
```sh
|
||||
$ bun test test3
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test3.test.js:
|
||||
✓ add [1.40ms]
|
||||
✓ multiply [0.03ms]
|
||||
|
||||
2 pass
|
||||
0 fail
|
||||
2 expect() calls
|
||||
Ran 2 tests across 1 files. [15.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
All tests have a name, defined as the first parameter to the `test` function. Tests can also be inside a `describe` block.
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("add", () => {
|
||||
expect(2 + 2).toEqual(4);
|
||||
});
|
||||
|
||||
test("multiply", () => {
|
||||
expect(2 * 2).toEqual(4);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To filter which tests are executed by name, use the `-t`/`--test-name-pattern` flag.
|
||||
|
||||
Adding `-t add` will only run tests with "add" in the name. This flag also checks the name of the test suite (the first parameter to `describe`).
|
||||
|
||||
```sh
|
||||
$ bun test -t add
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test.test.js:
|
||||
✓ add [1.79ms]
|
||||
» multiply
|
||||
|
||||
test2.test.js:
|
||||
✓ add [2.30ms]
|
||||
» multiply
|
||||
|
||||
test3.test.js:
|
||||
✓ add [0.32ms]
|
||||
» multiply
|
||||
|
||||
3 pass
|
||||
3 skip
|
||||
0 fail
|
||||
3 expect() calls
|
||||
Ran 6 tests across 3 files. [59.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner](/docs/cli/test) for complete documentation on the test runner.
|
||||
37
docs/guides/test/skip-tests.md
Normal file
37
docs/guides/test/skip-tests.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: Skip tests with the Bun test runner
|
||||
---
|
||||
|
||||
To skip a test with the Bun test runner, use the `test.skip` function.
|
||||
|
||||
```ts-diff
|
||||
test.skip("unimplemented feature", ()=>{
|
||||
expect(Bun.isAwesome()).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Running `bun test` will not execute this test. It will be marked as skipped in the terminal output.
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
|
||||
test.test.ts:
|
||||
✓ add [0.03ms]
|
||||
✓ multiply [0.02ms]
|
||||
» unimplemented feature
|
||||
|
||||
2 pass
|
||||
1 skip
|
||||
0 fail
|
||||
2 expect() calls
|
||||
Ran 3 tests across 1 files. [74.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See also:
|
||||
|
||||
- [Mark a test as a todo](/guides/test/todo-tests)
|
||||
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)
|
||||
99
docs/guides/test/snapshot.md
Normal file
99
docs/guides/test/snapshot.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: Use snapshot testing in `bun test`
|
||||
---
|
||||
|
||||
Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.
|
||||
|
||||
{% callout %}
|
||||
The `.toMatchInlineSnapshot()` method is not yet supported.
|
||||
{% /callout %}
|
||||
|
||||
```ts#snap.test.ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("snapshot", () => {
|
||||
expect({ foo: "bar" }).toMatchSnapshot();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The first time this test is executed, Bun will evaluate the value passed into `expect()` (`{ foo: "bar" }`) and write it to disk in a directory called `__snapshots__` that lives alongside the test file.
|
||||
|
||||
```sh
|
||||
$ bun test test/snap
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test/snap.test.ts:
|
||||
✓ snapshot [1.48ms]
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
snapshots: +1 added # note: the snapshot is created automatically the first run
|
||||
1 expect() calls
|
||||
Ran 1 tests across 1 files. [82.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `__snapshots__` directory contains a `.snap` file for each test file in the directory.
|
||||
|
||||
```txt
|
||||
test
|
||||
├── __snapshots__
|
||||
│ └── snap.test.ts.snap
|
||||
└── snap.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `snap.test.ts.snap` file is a JavaScript file that exports a serialized version of the value passed into `expect()`. The `{foo: "bar"}` object has been serialized to JSON.
|
||||
|
||||
```js
|
||||
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`snapshot 1`] = `
|
||||
{
|
||||
"foo": "bar",
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Later, when this test file is executed again, Bun will read the snapshot file and compare it to the value passed into `expect()`. If the values are different, the test will fail.
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test/snap.test.ts:
|
||||
✓ snapshot [1.05ms]
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
1 snapshots, 1 expect() calls
|
||||
Ran 1 tests across 1 files. [101.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To update snapshots, use the `--update-snapshots` flag.
|
||||
|
||||
```sh
|
||||
$ bun test --update-snapshots
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test/snap.test.ts:
|
||||
✓ snapshot [0.86ms]
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
snapshots: +1 added # the snapshot was regenerated
|
||||
1 expect() calls
|
||||
Ran 1 tests across 1 files. [102.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner > Snapshots](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
|
||||
46
docs/guides/test/spy-on.md
Normal file
46
docs/guides/test/spy-on.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: Spy on methods in `bun test`
|
||||
---
|
||||
|
||||
Use the `spyOn` utility to track method calls with Bun's test runner.
|
||||
|
||||
```ts
|
||||
import { test, expect, spyOn } from "bun:test";
|
||||
|
||||
const leo = {
|
||||
name: "Leonard",
|
||||
sayHi(thing: string) {
|
||||
console.log(`Sup I'm ${this.name} and I like ${thing}`);
|
||||
},
|
||||
};
|
||||
|
||||
const spy = spyOn(leo, "sayHi");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Once the spy is created, it can be used to write `expect` assertions relating to method calls.
|
||||
|
||||
```ts-diff
|
||||
import { test, expect, spyOn } from "bun:test";
|
||||
|
||||
const leo = {
|
||||
name: "Leonardo",
|
||||
sayHi(thing: string) {
|
||||
console.log(`Sup, I'm ${this.name} and I like ${thing}`);
|
||||
},
|
||||
};
|
||||
|
||||
const spy = spyOn(leo, "sayHi");
|
||||
|
||||
+ test("turtles", ()=>{
|
||||
+ expect(spy).toHaveBeenCalledTimes(0);
|
||||
+ leo.sayHi("pizza");
|
||||
+ expect(spy).toHaveBeenCalledTimes(0);
|
||||
+ expect(spy.mock.calls).toEqual([[ "pizza" ]]);
|
||||
+ })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner > Mocks](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
|
||||
15
docs/guides/test/timeout.md
Normal file
15
docs/guides/test/timeout.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Set a per-test timeout with the Bun test runner
|
||||
---
|
||||
|
||||
Use the `--timeout` flag to set a timeout for each test in milliseconds. If any test exceeds this timeout, it will be marked as failed.
|
||||
|
||||
The default timeout is `5000` (5 seconds).
|
||||
|
||||
```sh
|
||||
$ bun test --timeout 3000 # 3 seconds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.
|
||||
60
docs/guides/test/todo-tests.md
Normal file
60
docs/guides/test/todo-tests.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: Mark a test as a "todo" with the Bun test runner
|
||||
---
|
||||
|
||||
To remind yourself to write a test later, use the `test.todo` function. There's no need to provide a test implementation.
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
// write this later
|
||||
test.todo("unimplemented feature");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Optionally, you can provide a test implementation.
|
||||
|
||||
```ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test.todo("unimplemented feature", () => {
|
||||
expect(Bun.isAwesome()).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The output of `bun test` indicates how many `todo` tests were encountered.
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
|
||||
test.test.ts:
|
||||
✓ add [0.03ms]
|
||||
✓ multiply [0.02ms]
|
||||
✎ unimplemented feature
|
||||
|
||||
2 pass
|
||||
1 todo
|
||||
0 fail
|
||||
2 expect() calls
|
||||
Ran 3 tests across 1 files. [74.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Note that `todo` tests _are executed_ by the test runner! They are _expected to fail_; if a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
|
||||
|
||||
```sh
|
||||
$ bun test
|
||||
$ echo $?
|
||||
1 # this is the exit code of the previous command
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See also:
|
||||
|
||||
- [Skip a test](/guides/test/skip-tests)
|
||||
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)
|
||||
50
docs/guides/test/update-snapshots.md
Normal file
50
docs/guides/test/update-snapshots.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: Update snapshots in `bun test`
|
||||
---
|
||||
|
||||
Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.
|
||||
|
||||
{% callout %}
|
||||
The `.toMatchInlineSnapshot()` method is not yet supported.
|
||||
{% /callout %}
|
||||
|
||||
```ts#snap.test.ts
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("snapshot", () => {
|
||||
expect({ foo: "bar" }).toMatchSnapshot();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The first time this test is executed, Bun will write a snapshot file to disk in a directory called `__snapshots__` that lives alongside the test file.
|
||||
|
||||
```txt
|
||||
test
|
||||
├── __snapshots__
|
||||
│ └── snap.test.ts.snap
|
||||
└── snap.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To regenerate snapshots, use the `--update-snapshots` flag.
|
||||
|
||||
```sh
|
||||
$ bun test --update-snapshots
|
||||
bun test v0.8.0 (9c68abdb)
|
||||
|
||||
test/snap.test.ts:
|
||||
✓ snapshot [0.86ms]
|
||||
|
||||
1 pass
|
||||
0 fail
|
||||
snapshots: +1 added # the snapshot was regenerated
|
||||
1 expect() calls
|
||||
Ran 1 tests across 1 files. [102.00ms]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner > Snapshots](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.
|
||||
19
docs/guides/test/watch-mode.md
Normal file
19
docs/guides/test/watch-mode.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Run tests in watch mode with Bun
|
||||
---
|
||||
|
||||
Use the `--watch` flag to run your tests in watch mode.
|
||||
|
||||
```sh
|
||||
$ bun test --watch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This will restart the running Bun process whenever a file change is detected. It's fast. In this example, the editor is configured to save the file on every keystroke.
|
||||
|
||||
{% image src="https://github.com/oven-sh/bun/assets/3084745/dc49a36e-ba82-416f-b960-1c883a924248" caption="Running tests in watch mode in Bun" /%}
|
||||
|
||||
---
|
||||
|
||||
See [Docs > Test Runner](/docs/cli/test) for complete documentation on the test runner.
|
||||
23
docs/guides/util/detect-bun.md
Normal file
23
docs/guides/util/detect-bun.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
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
|
||||
}
|
||||
```
|
||||
@@ -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/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4
|
||||
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -132,6 +132,9 @@ 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:
|
||||
@@ -188,13 +191,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",
|
||||
}),
|
||||
|
||||
@@ -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.11.0-dev.4006+bf827d0b5
|
||||
$ zigup 0.12.0-dev.163+6780a6bbf
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
90
docs/runtime/debugger.md
Normal file
90
docs/runtime/debugger.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
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" /%}
|
||||
@@ -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)
|
||||
|
||||
🟡 Missing `tty.ReadStream` and `tty.WriteStream`.
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`node:url`](https://nodejs.org/api/url.html)
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
`bun:test` supports seeing which lines of code are covered by tests. To use this feature, pass `--coverage` to the CLI:
|
||||
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.
|
||||
|
||||
```sh
|
||||
bun test --coverage
|
||||
```
|
||||
## Enabling coverage
|
||||
|
||||
It will print out a coverage report to the console:
|
||||
`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:
|
||||
|
||||
```js
|
||||
$ bun test --coverage
|
||||
-------------|---------|---------|-------------------
|
||||
File | % Funcs | % Lines | Uncovered Line #s
|
||||
-------------|---------|---------|-------------------
|
||||
@@ -26,32 +25,45 @@ All files | 38.89 | 42.11 |
|
||||
-------------|---------|---------|-------------------
|
||||
```
|
||||
|
||||
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:
|
||||
To always enable coverage reporting by default, add the following line to your `bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[test]
|
||||
|
||||
# Always enable coverage
|
||||
# always enable coverage
|
||||
coverage = true
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
`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.
|
||||
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`.
|
||||
|
||||
Coverage support was added in Bun v0.7.3.
|
||||
```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
|
||||
coverageThreshold = { line = 0.9, function = 0.9 }
|
||||
```
|
||||
|
||||
### Sourcemaps
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@@ -187,7 +187,17 @@ 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{};
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"mitata": "^0.1.3",
|
||||
"peechy": "latest",
|
||||
"peechy": "0.4.34",
|
||||
"prettier": "^2.4.1",
|
||||
"react": "next",
|
||||
"react-dom": "next",
|
||||
"source-map-js": "^1.0.2",
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"private": true,
|
||||
@@ -17,7 +18,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}/**/*.{mjs,ts,tsx,js,jsx}'",
|
||||
"fmt": "prettier --write --cache './{src,test,bench,packages/{bun-inspector-*,bun-vscode,bun-debug-adapter-protocol}}/**/*.{mjs,ts,tsx,js,jsx}'",
|
||||
"lint": "eslint './**/*.d.ts' --cache",
|
||||
"lint:fix": "eslint './**/*.d.ts' --cache --fix"
|
||||
},
|
||||
@@ -25,7 +26,7 @@
|
||||
"@types/react": "^18.0.25",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"bun-webkit": "0.0.1-fd79ce3120a692f4aed314c3da3dd452b4aa865f"
|
||||
"bun-webkit": "0.0.1-48c1316e907ca597e27e5a7624160dc18a4df8ec"
|
||||
},
|
||||
"version": "0.0.0",
|
||||
"prettier": "./.prettierrc.cjs"
|
||||
|
||||
2
packages/bun-debug-adapter-protocol/.gitattributes
vendored
Normal file
2
packages/bun-debug-adapter-protocol/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
protocol/*/protocol.json linguist-generated=true
|
||||
protocol/*/index.d.ts linguist-generated=true
|
||||
1
packages/bun-debug-adapter-protocol/.gitignore
vendored
Normal file
1
packages/bun-debug-adapter-protocol/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
protocol/*.json
|
||||
3
packages/bun-debug-adapter-protocol/README.md
Normal file
3
packages/bun-debug-adapter-protocol/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# bun-debug-adapter-protocol
|
||||
|
||||
https://microsoft.github.io/debug-adapter-protocol/overview
|
||||
BIN
packages/bun-debug-adapter-protocol/bun.lockb
Executable file
BIN
packages/bun-debug-adapter-protocol/bun.lockb
Executable file
Binary file not shown.
3
packages/bun-debug-adapter-protocol/index.ts
Normal file
3
packages/bun-debug-adapter-protocol/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type * from "./src/protocol";
|
||||
export * from "./src/debugger/adapter";
|
||||
export * from "./src/debugger/signal";
|
||||
8
packages/bun-debug-adapter-protocol/package.json
Normal file
8
packages/bun-debug-adapter-protocol/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "bun-debug-adapter-protocol",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.4",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
}
|
||||
176
packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts
Normal file
176
packages/bun-debug-adapter-protocol/scripts/generate-protocol.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
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();
|
||||
}
|
||||
1764
packages/bun-debug-adapter-protocol/src/debugger/adapter.ts
Normal file
1764
packages/bun-debug-adapter-protocol/src/debugger/adapter.ts
Normal file
File diff suppressed because it is too large
Load Diff
271
packages/bun-debug-adapter-protocol/src/debugger/capabilities.ts
Normal file
271
packages/bun-debug-adapter-protocol/src/debugger/capabilities.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
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;
|
||||
@@ -0,0 +1,36 @@
|
||||
"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==
|
||||
@@ -0,0 +1,46 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
fetch(request) {
|
||||
return new Response(a());
|
||||
},
|
||||
};
|
||||
|
||||
function a() {
|
||||
return b();
|
||||
}
|
||||
|
||||
function b() {
|
||||
return c();
|
||||
}
|
||||
|
||||
function c() {
|
||||
function d() {
|
||||
return "hello";
|
||||
}
|
||||
return d();
|
||||
}
|
||||
87
packages/bun-debug-adapter-protocol/src/debugger/signal.ts
Normal file
87
packages/bun-debug-adapter-protocol/src/debugger/signal.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Server } from "node:net";
|
||||
import { createServer } from "node:net";
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
const isDebug = process.env.NODE_ENV === "development";
|
||||
|
||||
export type UnixSignalEventMap = {
|
||||
"Signal.listening": [string];
|
||||
"Signal.error": [Error];
|
||||
"Signal.received": [string];
|
||||
"Signal.closed": [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a server that listens for signals on a UNIX domain socket.
|
||||
*/
|
||||
export class UnixSignal extends EventEmitter<UnixSignalEventMap> {
|
||||
#path: string;
|
||||
#server: Server;
|
||||
#ready: Promise<void>;
|
||||
|
||||
constructor(path?: string) {
|
||||
super();
|
||||
this.#path = path ? parseUnixPath(path) : randomUnixPath();
|
||||
this.#server = createServer();
|
||||
this.#server.on("listening", () => this.emit("Signal.listening", this.#path));
|
||||
this.#server.on("error", error => this.emit("Signal.error", error));
|
||||
this.#server.on("close", () => this.emit("Signal.closed"));
|
||||
this.#server.on("connection", socket => {
|
||||
socket.on("data", data => {
|
||||
this.emit("Signal.received", data.toString());
|
||||
});
|
||||
});
|
||||
this.#ready = new Promise((resolve, reject) => {
|
||||
this.#server.on("listening", resolve);
|
||||
this.#server.on("error", reject);
|
||||
});
|
||||
this.#server.listen(this.#path);
|
||||
}
|
||||
|
||||
emit<E extends keyof UnixSignalEventMap>(event: E, ...args: UnixSignalEventMap[E]): boolean {
|
||||
if (isDebug) {
|
||||
console.log(event, ...args);
|
||||
}
|
||||
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the UNIX domain socket.
|
||||
*/
|
||||
get url(): string {
|
||||
return `unix://${this.#path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves when the server is listening or rejects if an error occurs.
|
||||
*/
|
||||
get ready(): Promise<void> {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the server.
|
||||
*/
|
||||
close(): void {
|
||||
this.#server.close();
|
||||
}
|
||||
}
|
||||
|
||||
function randomUnixPath(): string {
|
||||
return join(tmpdir(), `${Math.random().toString(36).slice(2)}.sock`);
|
||||
}
|
||||
|
||||
function parseUnixPath(path: string): string {
|
||||
if (path.startsWith("/")) {
|
||||
return path;
|
||||
}
|
||||
try {
|
||||
const { pathname } = new URL(path);
|
||||
return pathname;
|
||||
} catch {
|
||||
throw new Error(`Invalid UNIX path: ${path}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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();
|
||||
}
|
||||
193
packages/bun-debug-adapter-protocol/src/debugger/sourcemap.ts
Normal file
193
packages/bun-debug-adapter-protocol/src/debugger/sourcemap.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
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);
|
||||
}
|
||||
2696
packages/bun-debug-adapter-protocol/src/protocol/index.d.ts
vendored
Normal file
2696
packages/bun-debug-adapter-protocol/src/protocol/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3761
packages/bun-debug-adapter-protocol/src/protocol/protocol.json
Normal file
3761
packages/bun-debug-adapter-protocol/src/protocol/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
37
packages/bun-debug-adapter-protocol/src/protocol/schema.d.ts
vendored
Normal file
37
packages/bun-debug-adapter-protocol/src/protocol/schema.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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[];
|
||||
}
|
||||
);
|
||||
21
packages/bun-debug-adapter-protocol/tsconfig.json
Normal file
21
packages/bun-debug-adapter-protocol/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"moduleDetection": "force",
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
"allowJs": true,
|
||||
"outDir": "dist",
|
||||
},
|
||||
"include": ["src", "scripts", "../bun-types/index.d.ts", "../bun-inspector-protocol/src"]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "bun-ecosystem-ci",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"globby": "^13.1.3"
|
||||
@@ -11,4 +12,4 @@
|
||||
"format": "prettier --write src",
|
||||
"test": "bun run src/runner.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# web-inspector-bun
|
||||
# bun-devtools-frontend
|
||||
|
||||
This is the WebKit Web Inspector bundled as standalone assets.
|
||||
|
||||
@@ -17,7 +17,12 @@ try {
|
||||
.on("script", {
|
||||
element(element) {
|
||||
const src = element.getAttribute("src");
|
||||
if (src && !src?.includes("External") && !src?.includes("WebKitAdditions")) {
|
||||
if (
|
||||
src &&
|
||||
!src?.includes("External") &&
|
||||
!src?.includes("WebKitAdditions") &&
|
||||
!src.includes("DOMUtilities.js")
|
||||
) {
|
||||
if (scriptsToBundle.length === 0) {
|
||||
element.replace("<script>var WI = {};\n</script>", { html: true });
|
||||
} else {
|
||||
@@ -35,7 +40,40 @@ try {
|
||||
})
|
||||
.on("head", {
|
||||
element(element) {
|
||||
element.prepend(` <base href="/" /> `, { html: true });
|
||||
element.prepend(
|
||||
`
|
||||
<script type="text/javascript">
|
||||
if (!Element.prototype.scrollIntoViewIfNeeded) {
|
||||
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
|
||||
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
|
||||
|
||||
var parent = this.parentNode,
|
||||
parentComputedStyle = window.getComputedStyle(parent, null),
|
||||
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
|
||||
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
|
||||
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
|
||||
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
|
||||
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
|
||||
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
|
||||
alignWithTop = overTop && !overBottom;
|
||||
|
||||
if ((overTop || overBottom) && centerIfNeeded) {
|
||||
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
|
||||
}
|
||||
|
||||
if ((overLeft || overRight) && centerIfNeeded) {
|
||||
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
|
||||
}
|
||||
|
||||
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||
this.scrollIntoView(alignWithTop);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<base href="/" /> `,
|
||||
{ html: true },
|
||||
);
|
||||
|
||||
element.append(
|
||||
`
|
||||
@@ -46,7 +84,7 @@ try {
|
||||
</style>
|
||||
<script src="${jsReplacementId}"></script>
|
||||
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
WI.sharedApp = new WI.AppController;
|
||||
WI.sharedApp.initialize();
|
||||
</script>`,
|
||||
@@ -71,6 +109,9 @@ try {
|
||||
const javascript = scriptsToBundle.map(a => `import '${join(basePath, a)}';`).join("\n") + "\n";
|
||||
// const css = stylesToBundle.map(a => `@import "${join(basePath, a)}";`).join("\n") + "\n";
|
||||
await Bun.write(join(import.meta.dir, "out/manifest.js"), javascript);
|
||||
mkdirSync("out/WebKitAdditions/WebInspectorUI/", { recursive: true });
|
||||
await Bun.write(join(import.meta.dir, "out/WebKitAdditions/WebInspectorUI/WebInspectorUIAdditions.js"), "");
|
||||
await Bun.write(join(import.meta.dir, "out/WebKitAdditions/WebInspectorUI/WebInspectorUIAdditions.css"), "");
|
||||
// await Bun.write(join(import.meta.dir, "manifest.css"), css);
|
||||
const jsBundle = await Bun.build({
|
||||
entrypoints: [join(import.meta.dir, "out/manifest.js")],
|
||||
20
packages/bun-inspector-frontend/tsconfig.json
Normal file
20
packages/bun-inspector-frontend/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
2
packages/bun-inspector-protocol/.gitattributes
vendored
Normal file
2
packages/bun-inspector-protocol/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
protocol/*/protocol.json linguist-generated=true
|
||||
protocol/*/index.d.ts linguist-generated=true
|
||||
2
packages/bun-inspector-protocol/.gitignore
vendored
Normal file
2
packages/bun-inspector-protocol/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
protocol/*.json
|
||||
protocol/v8
|
||||
1
packages/bun-inspector-protocol/README.md
Normal file
1
packages/bun-inspector-protocol/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# bun-inspector-protocol
|
||||
BIN
packages/bun-inspector-protocol/bun.lockb
Executable file
BIN
packages/bun-inspector-protocol/bun.lockb
Executable file
Binary file not shown.
4
packages/bun-inspector-protocol/index.ts
Normal file
4
packages/bun-inspector-protocol/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type * from "./src/protocol";
|
||||
export type * from "./src/inspector";
|
||||
export * from "./src/util/preview";
|
||||
export * from "./src/inspector/websocket";
|
||||
7
packages/bun-inspector-protocol/package.json
Normal file
7
packages/bun-inspector-protocol/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "bun-inspector-protocol",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"ws": "^8.13.0"
|
||||
}
|
||||
}
|
||||
202
packages/bun-inspector-protocol/scripts/generate-protocol.ts
Normal file
202
packages/bun-inspector-protocol/scripts/generate-protocol.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
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");
|
||||
}
|
||||
40
packages/bun-inspector-protocol/src/inspector/index.d.ts
vendored
Normal file
40
packages/bun-inspector-protocol/src/inspector/index.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { EventEmitter } from "node:events";
|
||||
import type { JSC } from "../protocol";
|
||||
|
||||
export type InspectorEventMap = {
|
||||
[E in keyof JSC.EventMap]: [JSC.EventMap[E]];
|
||||
} & {
|
||||
"Inspector.connecting": [string];
|
||||
"Inspector.connected": [];
|
||||
"Inspector.disconnected": [Error | undefined];
|
||||
"Inspector.error": [Error];
|
||||
"Inspector.pendingRequest": [JSC.Request];
|
||||
"Inspector.request": [JSC.Request];
|
||||
"Inspector.response": [JSC.Response];
|
||||
"Inspector.event": [JSC.Event];
|
||||
};
|
||||
|
||||
/**
|
||||
* A client that can send and receive messages to/from a debugger.
|
||||
*/
|
||||
export interface Inspector extends EventEmitter<InspectorEventMap> {
|
||||
/**
|
||||
* Starts the inspector.
|
||||
*/
|
||||
start(...args: unknown[]): Promise<boolean>;
|
||||
/**
|
||||
* 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]>;
|
||||
/**
|
||||
* If the inspector is closed.
|
||||
*/
|
||||
get closed(): boolean;
|
||||
/**
|
||||
* Closes the inspector.
|
||||
*/
|
||||
close(...args: unknown[]): void;
|
||||
}
|
||||
239
packages/bun-inspector-protocol/src/inspector/websocket.ts
Normal file
239
packages/bun-inspector-protocol/src/inspector/websocket.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import type { Inspector, InspectorEventMap } from ".";
|
||||
import type { JSC } from "../protocol";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { WebSocket } from "ws";
|
||||
|
||||
/**
|
||||
* An inspector that communicates with a debugger over a WebSocket.
|
||||
*/
|
||||
export class WebSocketInspector extends EventEmitter<InspectorEventMap> implements Inspector {
|
||||
#url?: string;
|
||||
#webSocket?: WebSocket;
|
||||
#ready: Promise<boolean> | undefined;
|
||||
#requestId: number;
|
||||
#pendingRequests: JSC.Request[];
|
||||
#pendingResponses: Map<number, (result: unknown) => void>;
|
||||
|
||||
constructor(url?: string | URL) {
|
||||
super();
|
||||
this.#url = url ? String(url) : undefined;
|
||||
this.#requestId = 1;
|
||||
this.#pendingRequests = [];
|
||||
this.#pendingResponses = new Map();
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return this.#url!;
|
||||
}
|
||||
|
||||
async start(url?: string | URL): Promise<boolean> {
|
||||
if (url) {
|
||||
this.#url = String(url);
|
||||
}
|
||||
|
||||
if (!this.#url) {
|
||||
this.emit("Inspector.error", new Error("Inspector needs a URL, but none was provided"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.#connect(this.#url);
|
||||
}
|
||||
|
||||
async #connect(url: string): Promise<boolean> {
|
||||
if (this.#ready) {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
this.close(1001, "Restarting...");
|
||||
this.emit("Inspector.connecting", url);
|
||||
|
||||
let webSocket: WebSocket;
|
||||
try {
|
||||
// @ts-expect-error: Support both Bun and Node.js version of `headers`.
|
||||
webSocket = new WebSocket(url, {
|
||||
headers: {
|
||||
"Ref-Event-Loop": "1",
|
||||
},
|
||||
finishRequest: (request: import("http").ClientRequest) => {
|
||||
request.setHeader("Ref-Event-Loop", "1");
|
||||
request.end();
|
||||
},
|
||||
});
|
||||
} catch (cause) {
|
||||
this.#close(unknownToError(cause));
|
||||
return false;
|
||||
}
|
||||
|
||||
webSocket.addEventListener("open", () => {
|
||||
this.emit("Inspector.connected");
|
||||
|
||||
for (const request of this.#pendingRequests) {
|
||||
if (this.#send(request)) {
|
||||
this.emit("Inspector.request", request);
|
||||
}
|
||||
}
|
||||
|
||||
this.#pendingRequests.length = 0;
|
||||
});
|
||||
|
||||
webSocket.addEventListener("message", ({ data }) => {
|
||||
if (typeof data === "string") {
|
||||
this.#accept(data);
|
||||
}
|
||||
});
|
||||
|
||||
webSocket.addEventListener("error", event => {
|
||||
this.#close(unknownToError(event));
|
||||
});
|
||||
|
||||
webSocket.addEventListener("unexpected-response", () => {
|
||||
this.#close(new Error("WebSocket upgrade failed"));
|
||||
});
|
||||
|
||||
webSocket.addEventListener("close", ({ code, reason }) => {
|
||||
if (code === 1001 || code === 1006) {
|
||||
this.#close();
|
||||
return;
|
||||
}
|
||||
this.#close(new Error(`WebSocket closed: ${code} ${reason}`.trimEnd()));
|
||||
});
|
||||
|
||||
this.#webSocket = webSocket;
|
||||
|
||||
const ready = new Promise<boolean>(resolve => {
|
||||
webSocket.addEventListener("open", () => resolve(true));
|
||||
webSocket.addEventListener("close", () => resolve(false));
|
||||
webSocket.addEventListener("error", () => resolve(false));
|
||||
}).finally(() => {
|
||||
this.#ready = undefined;
|
||||
});
|
||||
|
||||
this.#ready = ready;
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
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: params ?? {},
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const done = (result: any) => {
|
||||
this.#pendingResponses.delete(id);
|
||||
if (result instanceof Error) {
|
||||
reject(result);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
|
||||
this.#pendingResponses.set(id, done);
|
||||
if (this.#send(request)) {
|
||||
this.emit("Inspector.request", request);
|
||||
} else {
|
||||
this.emit("Inspector.pendingRequest", request);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#send(request: JSC.Request): boolean {
|
||||
if (this.#webSocket) {
|
||||
const { readyState } = this.#webSocket!;
|
||||
if (readyState === WebSocket.OPEN) {
|
||||
this.#webSocket.send(JSON.stringify(request));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.#pendingRequests.includes(request)) {
|
||||
this.#pendingRequests.push(request);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#accept(message: string): void {
|
||||
let data: JSC.Event | JSC.Response;
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
} catch (cause) {
|
||||
this.emit("Inspector.error", new Error(`Failed to parse message: ${message}`, { cause }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("id" in data)) {
|
||||
this.emit("Inspector.event", data);
|
||||
const { method, params } = data;
|
||||
this.emit(method, params);
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("Inspector.response", data);
|
||||
|
||||
const { id } = data;
|
||||
const resolve = this.#pendingResponses.get(id);
|
||||
if (!resolve) {
|
||||
this.emit("Inspector.error", new Error(`Failed to find matching request for ID: ${id}`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.#pendingResponses.delete(id);
|
||||
if ("error" in data) {
|
||||
const { error } = data;
|
||||
const { message } = error;
|
||||
resolve(new Error(message));
|
||||
} else {
|
||||
const { result } = data;
|
||||
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 {
|
||||
for (const resolve of this.#pendingResponses.values()) {
|
||||
resolve(error ?? new Error("WebSocket closed"));
|
||||
}
|
||||
this.#pendingResponses.clear();
|
||||
if (error) {
|
||||
this.emit("Inspector.error", error);
|
||||
}
|
||||
this.emit("Inspector.disconnected", error);
|
||||
}
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
1
packages/bun-inspector-protocol/src/protocol/index.d.ts
vendored
Normal file
1
packages/bun-inspector-protocol/src/protocol/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type { JSC } from "./jsc";
|
||||
3695
packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts
vendored
Normal file
3695
packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3114
packages/bun-inspector-protocol/src/protocol/jsc/protocol.json
Normal file
3114
packages/bun-inspector-protocol/src/protocol/jsc/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
28
packages/bun-inspector-protocol/src/protocol/protocol.d.ts
vendored
Normal file
28
packages/bun-inspector-protocol/src/protocol/protocol.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// @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;
|
||||
};
|
||||
}
|
||||
);
|
||||
58
packages/bun-inspector-protocol/src/protocol/schema.d.ts
vendored
Normal file
58
packages/bun-inspector-protocol/src/protocol/schema.d.ts
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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;
|
||||
}
|
||||
);
|
||||
17428
packages/bun-inspector-protocol/src/protocol/v8/index.d.ts
vendored
Normal file
17428
packages/bun-inspector-protocol/src/protocol/v8/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14136
packages/bun-inspector-protocol/src/protocol/v8/protocol.json
Normal file
14136
packages/bun-inspector-protocol/src/protocol/v8/protocol.json
Normal file
File diff suppressed because it is too large
Load Diff
113
packages/bun-inspector-protocol/src/util/preview.ts
Normal file
113
packages/bun-inspector-protocol/src/util/preview.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
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";
|
||||
}
|
||||
190
packages/bun-inspector-protocol/test/inspector/websocket.test.ts
Normal file
190
packages/bun-inspector-protocol/test/inspector/websocket.test.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { describe, test, expect, mock, beforeAll, afterAll } from "bun:test";
|
||||
import { WebSocketInspector } from "../../src/inspector/websocket";
|
||||
import type { Server } from "bun";
|
||||
import { serve } from "bun";
|
||||
|
||||
let server: Server;
|
||||
let url: URL;
|
||||
|
||||
describe("WebSocketInspector", () => {
|
||||
test("fails without a URL", () => {
|
||||
const ws = new WebSocketInspector();
|
||||
const fn = mock(error => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
ws.on("Inspector.error", fn);
|
||||
expect(ws.start()).resolves.toBeFalse();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("fails with invalid URL", () => {
|
||||
const ws = new WebSocketInspector("notaurl");
|
||||
const fn = mock(error => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
ws.on("Inspector.error", fn);
|
||||
expect(ws.start()).resolves.toBeFalse();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("fails with valid URL but no server", () => {
|
||||
const ws = new WebSocketInspector("ws://localhost:0/doesnotexist/");
|
||||
const fn = mock(error => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
ws.on("Inspector.error", fn);
|
||||
expect(ws.start()).resolves.toBeFalse();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("fails with invalid upgrade response", () => {
|
||||
const ws = new WebSocketInspector(new URL("/", url));
|
||||
const fn = mock(error => {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
});
|
||||
ws.on("Inspector.error", fn);
|
||||
expect(ws.start()).resolves.toBeFalse();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("can connect to a server", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn = mock(() => {
|
||||
expect(ws.closed).toBe(false);
|
||||
});
|
||||
ws.on("Inspector.connected", fn);
|
||||
expect(ws.start()).resolves.toBeTrue();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
ws.close();
|
||||
});
|
||||
|
||||
test("can disconnect from a server", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn = mock(() => {
|
||||
expect(ws.closed).toBeTrue();
|
||||
});
|
||||
ws.on("Inspector.disconnected", fn);
|
||||
expect(ws.start()).resolves.toBeTrue();
|
||||
ws.close();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("can connect to a server multiple times", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn0 = mock(() => {
|
||||
expect(ws.closed).toBeFalse();
|
||||
});
|
||||
ws.on("Inspector.connected", fn0);
|
||||
const fn1 = mock(() => {
|
||||
expect(ws.closed).toBeTrue();
|
||||
});
|
||||
ws.on("Inspector.disconnected", fn1);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(ws.start()).resolves.toBeTrue();
|
||||
ws.close();
|
||||
}
|
||||
expect(fn0).toHaveBeenCalledTimes(3);
|
||||
expect(fn1).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test("can send a request", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn0 = mock(request => {
|
||||
expect(request).toStrictEqual({
|
||||
id: 1,
|
||||
method: "Debugger.setPauseOnAssertions",
|
||||
params: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
ws.on("Inspector.request", fn0);
|
||||
const fn1 = mock(response => {
|
||||
expect(response).toStrictEqual({
|
||||
id: 1,
|
||||
result: {
|
||||
ok: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
ws.on("Inspector.response", fn1);
|
||||
expect(ws.start()).resolves.toBeTrue();
|
||||
expect(ws.send("Debugger.setPauseOnAssertions", { enabled: true })).resolves.toMatchObject({ ok: true });
|
||||
expect(fn0).toHaveBeenCalled();
|
||||
expect(fn1).toHaveBeenCalled();
|
||||
ws.close();
|
||||
});
|
||||
|
||||
test("can send a request before connecting", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn0 = mock(request => {
|
||||
expect(request).toStrictEqual({
|
||||
id: 1,
|
||||
method: "Runtime.enable",
|
||||
params: {},
|
||||
});
|
||||
});
|
||||
ws.on("Inspector.pendingRequest", fn0);
|
||||
ws.on("Inspector.request", fn0);
|
||||
const fn1 = mock(response => {
|
||||
expect(response).toStrictEqual({
|
||||
id: 1,
|
||||
result: {
|
||||
ok: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
ws.on("Inspector.response", fn1);
|
||||
const request = ws.send("Runtime.enable");
|
||||
expect(ws.start()).resolves.toBe(true);
|
||||
expect(request).resolves.toMatchObject({ ok: true });
|
||||
expect(fn0).toHaveBeenCalledTimes(2);
|
||||
expect(fn1).toHaveBeenCalled();
|
||||
ws.close();
|
||||
});
|
||||
|
||||
test("can receive an event", () => {
|
||||
const ws = new WebSocketInspector(url);
|
||||
const fn = mock(event => {
|
||||
expect(event).toStrictEqual({
|
||||
method: "Debugger.scriptParsed",
|
||||
params: {
|
||||
scriptId: "1",
|
||||
},
|
||||
});
|
||||
});
|
||||
ws.on("Inspector.event", fn);
|
||||
expect(ws.start()).resolves.toBeTrue();
|
||||
expect(ws.send("Debugger.enable")).resolves.toMatchObject({ ok: true });
|
||||
expect(fn).toHaveBeenCalled();
|
||||
ws.close();
|
||||
});
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
server = serve({
|
||||
port: 0,
|
||||
fetch(request, server) {
|
||||
if (request.url.endsWith("/ws") && server.upgrade(request)) {
|
||||
return;
|
||||
}
|
||||
return new Response();
|
||||
},
|
||||
websocket: {
|
||||
message(ws, message) {
|
||||
const { id, method } = JSON.parse(String(message));
|
||||
ws.send(JSON.stringify({ id, result: { ok: true } }));
|
||||
|
||||
if (method === "Debugger.enable") {
|
||||
ws.send(JSON.stringify({ method: "Debugger.scriptParsed", params: { scriptId: "1" } }));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
const { hostname, port } = server;
|
||||
url = new URL(`ws://${hostname}:${port}/ws`);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server?.stop(true);
|
||||
});
|
||||
18
packages/bun-inspector-protocol/tsconfig.json
Normal file
18
packages/bun-inspector-protocol/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"name": "bun-lambda",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"bun-types": "^0.7.0",
|
||||
"jszip": "^3.10.1",
|
||||
|
||||
@@ -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,8 +19,5 @@
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"modules.d.ts"
|
||||
]
|
||||
}
|
||||
"include": ["**/*.ts", "modules.d.ts"]
|
||||
}
|
||||
|
||||
@@ -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,8 +19,5 @@
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"modules.d.ts"
|
||||
]
|
||||
}
|
||||
"include": ["**/*.ts", "modules.d.ts"]
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -15,11 +15,13 @@
|
||||
"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 \"./**/*.wasm\" dist",
|
||||
"build": "bun run clean && bun run preprocess && bunx tsc && bunx copyfiles \"./lib/**/*.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",
|
||||
|
||||
@@ -16,6 +16,7 @@ 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';
|
||||
@@ -32,7 +33,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 = '7088d7e182635a58a50860302da0b1abc42c7ce7' satisfies typeof Bun.revision;
|
||||
export const revision = '56816a3ec845a4b9fc40ade34dbe5c0033433d51' 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.');
|
||||
@@ -71,6 +72,8 @@ 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;
|
||||
|
||||
@@ -1,103 +1,96 @@
|
||||
import type { JavaScriptLoader, TranspilerOptions, Transpiler as BunTranspiler, Import } from 'bun';
|
||||
import { NotImplementedError } from '../../utils/errors.js';
|
||||
import { transformSync, scan, init } from 'bun-wasm';
|
||||
import { Message } from 'bun-wasm/schema';
|
||||
import $ from 'chalk';
|
||||
|
||||
// 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.
|
||||
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;
|
||||
};
|
||||
|
||||
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> {
|
||||
if (typeof code !== 'string') code = new TextDecoder().decode(code);
|
||||
throw new NotImplementedError('Bun.Transpiler', this.transform);
|
||||
this.#internallyCalled = true;
|
||||
return this.transformSync(code, loader);
|
||||
}
|
||||
|
||||
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 (typeof code !== 'string') code = new TextDecoder().decode(code);
|
||||
if (typeof loader !== 'string') loader = 'js';
|
||||
throw new NotImplementedError('Bun.Transpiler', this.transformSync);
|
||||
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);
|
||||
}
|
||||
|
||||
scan(code: StringOrBuffer): { exports: string[]; imports: 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)
|
||||
//};
|
||||
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[],
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
scanImports(code: StringOrBuffer): ScanImportsEntry[] {
|
||||
this.#internallyCalled = true;
|
||||
return this.scan(code).imports.filter(imp => imp.kind === 'import-statement' || imp.kind === 'dynamic-import') as ScanImportsEntry[];
|
||||
}
|
||||
|
||||
/*#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)
|
||||
)
|
||||
);
|
||||
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();
|
||||
}*/
|
||||
|
||||
/*#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;
|
||||
}
|
||||
|
||||
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)}`
|
||||
: ''
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
function highlightErrorChar(str: string, at: number): string {
|
||||
return str.slice(0, at) + $.red(str[at]) + str.slice(at + 1);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ 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;
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"outDir": "dist",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": [".", "../bun-types/index.d.ts"],
|
||||
"include": ["src", "lib", "../bun-types/index.d.ts"],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "bun-release-action",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"aws4fetch": "^1.0.17",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user