Compare commits

...

142 Commits

Author SHA1 Message Date
snwy
db5ac3af8f 🫡 2024-11-22 17:01:15 -08:00
snwy
ba8cd9f015 phew 2024-11-22 16:15:38 -08:00
snwy
b8a1034113 the true fix 2024-11-22 14:43:02 -08:00
snwy
7b47174ea0 fix fix fix 2024-11-22 14:19:22 -08:00
snwy
e63cc09bc6 manually destroy response now 2024-11-21 19:23:51 -08:00
snwy
cbf4cd9a46 PLEASE PLEASE PLEASE 2024-11-21 18:55:49 -08:00
snwy
0ff41d37b6 it was actually the test 2024-11-21 14:54:28 -08:00
snwy
e252ad774f the corkening 2024-11-21 14:10:07 -08:00
snwy
b74a81c2a7 ok fixed 2024-11-21 12:57:31 -08:00
snwy
1f05160d95 quick fix 2024-11-21 12:37:17 -08:00
Jarred Sumner
f1919e7823 Remove Amazon Linux 2023 tests for now 2024-11-21 11:56:24 -08:00
Michael H
650ccbbd06 docs: --bail [n] -> --bail=[n] (#15301) 2024-11-21 11:56:24 -08:00
Ciro Spaciari
22a337e6b8 fix(Bun.file) throw OOM if read is too big (#15253) 2024-11-21 11:56:24 -08:00
Pham Minh Triet
2cc1ea4b1a Fix typo in 15276.test.ts (#15304) 2024-11-21 11:56:24 -08:00
Jarred Sumner
fa024fa350 fix(install): ensure aliases hash map is initialized (#15280)
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2024-11-21 11:56:24 -08:00
Meghan Denny
d789310d65 fix fuzzy-wuzzy test (#15297) 2024-11-21 11:56:24 -08:00
Meghan Denny
ed90889b19 zig: rename CallFrame.arguments to .arguments_old to free up decl name (#15296) 2024-11-21 11:56:24 -08:00
Ciro Spaciari
b690f98437 fix(root_cert) use a more reliable source for the latest cert (#15262) 2024-11-21 11:56:24 -08:00
Meghan Denny
999cce1305 zig: make throwTODO use JSError (#15264)
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2024-11-21 11:56:24 -08:00
Meghan Denny
e903c3b7d5 ci: bootstrap.sh: musl download of bun no longer has to be special-cased (#15265) 2024-11-21 11:56:24 -08:00
Meghan Denny
89a92bf66b zig: JSValue: make .get and .toSliceOrNull use JSError (#15270) 2024-11-21 11:56:24 -08:00
Meghan Denny
37b4dc9a58 bindings: make throwInvalidArgumentTypeValue print the value like the real ERR_INVALID_ARG_TYPE (#14804) 2024-11-21 11:56:24 -08:00
Meghan Denny
43bd25d2f4 bunjs: print received value when Bun.write is passed a bad argument (#14805) 2024-11-21 11:56:24 -08:00
Meghan Denny
6c46fbda0b zig: make throwError use JSError (#15267) 2024-11-21 11:56:24 -08:00
Meghan Denny
79c154a86b ci: disable testing on debian 10 2024-11-21 11:56:24 -08:00
Meghan Denny
815761beca zig: remove noop JSGlobalObject.ptr() (#15268) 2024-11-21 11:56:23 -08:00
Ashcon Partovi
c6c5fac1d8 ci: Disable changed files detection until bugs are fixed 2024-11-21 11:56:23 -08:00
Meghan Denny
f82dd21816 zig: make throwNotEnoughArguments use JSError (#15266) 2024-11-21 11:56:23 -08:00
Meghan Denny
4feb300b3b zig: align getTruthy to use JSError (#15199) 2024-11-21 11:56:23 -08:00
Ashcon Partovi
1212cb3efa ci: Expand automated build images to Debian, Ubuntu, and Amazon Linux (#15250) 2024-11-21 11:56:23 -08:00
Jarred Sumner
70acad11c0 bump 2024-11-21 11:56:23 -08:00
Ashcon Partovi
a76079fa6f Bun v1.1.36 [release] 2024-11-21 11:56:23 -08:00
Ashcon Partovi
9ffd2f3eab Bun v1.1.36 [release] 2024-11-21 11:56:23 -08:00
Meghan Denny
ada495f26e ci: changedFiles can be undefined 2024-11-21 11:56:23 -08:00
Meghan Denny
7ad883733f ci: always build images when core ci files change (#15229) 2024-11-21 11:56:23 -08:00
Meghan Denny
879319b569 docker:alpine: update to 3.20 and use bun musl build (#15241) 2024-11-21 11:56:23 -08:00
Meghan Denny
edb1992a85 us_bun_verify_error_t: ensure c struct matches zig extern (#15244) 2024-11-21 11:56:23 -08:00
Meghan Denny
8489f3d3ac Revert "fix(tls) fix type matching" (#15243) 2024-11-21 11:56:23 -08:00
Kai Tamkun
c562d6f627 Call Bun__onExit + std.os.windows.kernel32.ExitProcess to exit on Windows (#15237)
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2024-11-21 11:56:23 -08:00
Jarred Sumner
87f044c014 Implement junit test reporter (#15205)
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2024-11-21 11:56:23 -08:00
Ashcon Partovi
a2fb3cde8d Revert "cmake: Set explicit rustc target"
This reverts commit cba3bda8ec.
2024-11-21 11:56:23 -08:00
Jarred Sumner
0bc7cf3649 [Bun.sql] Support TLS (#15217)
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2024-11-21 11:56:23 -08:00
pfg
3fcf232ae5 Fix setTimeout with node:util.promisify (#15230) 2024-11-21 11:56:23 -08:00
pfg
2a09f2ac6e Fix docs on todo tests (#15233) 2024-11-21 11:56:23 -08:00
Ciro Spaciari
bc648ab555 fix(tls) fix type matching (#15224) 2024-11-21 11:56:23 -08:00
Ashcon Partovi
2a9508de9d ci: Do not check changed files on main 2024-11-21 11:56:23 -08:00
Zack Radisic
e94fdbdc10 Fix bundler crash with onLoad plugins on copy-file loaders used on entrypoints (#15231) 2024-11-21 11:56:23 -08:00
Jarred Sumner
2def4ab3a1 Support Headers & URLSearchParams in expect().toEqual() (#15195)
Co-authored-by: Meghan Denny <meghan@bun.sh>
2024-11-21 11:56:23 -08:00
Ashcon Partovi
8b0a84a0ad cmake: Set explicit rustc target 2024-11-21 11:56:23 -08:00
Ashcon Partovi
67d44cabdb Revert "Ensure that lolhtml builds the target platform"
This reverts commit b023bb805b.
2024-11-21 11:56:23 -08:00
Ashcon Partovi
89b909ce2f Ensure that lolhtml builds the target platform 2024-11-21 11:56:22 -08:00
Dennis Dudek
32ee2130a9 Fixed Responses to OPTIONS Requests ignore Body (#15108) 2024-11-21 11:56:22 -08:00
Pham Minh Triet
73cc3bc6f4 Fix(doc): update cluster.md (#15214) 2024-11-21 11:56:22 -08:00
Ciro Spaciari
3b60023e59 fix(HttpParser) always check if content length is valid before calling requestHandler (#15179) 2024-11-21 11:56:22 -08:00
Meghan Denny
b2018c725b musl: fix test/js/node/process/process.test.js (#15185) 2024-11-21 11:56:22 -08:00
Jarred Sumner
f141ecc06c Fixes #15177 (#15180) 2024-11-21 11:56:22 -08:00
Meghan Denny
7eb625e702 musl: fix third_party/prisma.test.ts (#15186) 2024-11-21 11:56:22 -08:00
Dylan Conway
106fc30c62 fix auto-install on windows when symlinks aren't available (#15182) 2024-11-21 11:56:22 -08:00
Meghan Denny
69c634598c ci: skip running tests on main branch 2024-11-21 11:56:22 -08:00
Meghan Denny
6ee27ab8cb zig: align fromJS methods to using JSError (#15165) 2024-11-21 11:56:22 -08:00
Ashcon Partovi
e2eb791c30 ci: musl builds (#15154)
Co-authored-by: Electroid <Electroid@users.noreply.github.com>
Co-authored-by: Meghan Denny <meghan@bun.sh>
2024-11-21 11:56:22 -08:00
Meghan Denny
c2330b3826 musl: fix 'bun upgrade' (#15178) 2024-11-21 11:56:22 -08:00
dave caruso
5bb846de7e docs: remove contributing instructions involving winget (#15176) 2024-11-21 11:56:22 -08:00
Jarred Sumner
8df0f460f4 Try linker script (#15158) 2024-11-21 11:56:22 -08:00
Grigory
fdaa632d9a docs(contributing): group os-specific code tabs (#15173) 2024-11-21 11:56:22 -08:00
ippsav
8ad91e8b10 Fix node:net not handling path in listen (#15162)
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2024-11-21 11:56:22 -08:00
Meghan Denny
1bc2d45950 node:https: fix prototype chain of Agent (#15160)
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2024-11-21 11:56:22 -08:00
Ashcon Partovi
5459e9c171 ci: Fix changed files detection on forks 2024-11-21 11:56:09 -08:00
Meghan Denny
7da115f073 zig: make all JS constructors use JSError (#15146)
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
2024-11-21 11:56:09 -08:00
Michael H
11da00dccc fix vscode debugger (#14995) 2024-11-21 11:56:09 -08:00
Jarred Sumner
e4d0b133ee Shrink Bun's binary by 3.5 MB (#15121) 2024-11-21 11:56:09 -08:00
pfg
a734bf31de Remove assertion in js printer triggering for unicode comments (#15143) 2024-11-21 11:56:09 -08:00
Meghan Denny
140131ebb8 allow zig js host functions to return JSError (#15120) 2024-11-21 11:56:09 -08:00
dave caruso
9ba4b0f349 bake: csr, streaming ssr, serve integration, safer jsvalue functions, &more (#14900)
Co-authored-by: paperdave <paperdave@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2024-11-21 11:56:09 -08:00
Meghan Denny
f056fd23b3 ci: fix release script (#15129) 2024-11-21 11:56:09 -08:00
Meghan Denny
250e5cc2c6 cpp: Bun::toStringRef: return dead when exception has been thrown (#15127)
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2024-11-21 11:56:09 -08:00
Meghan Denny
5550a006e5 zig: remove JSValue.isEmpty (#15128) 2024-11-21 11:56:09 -08:00
Meghan Denny
85da3d48cb rid nearly all use of ExceptionRef in zig (#15100)
Co-authored-by: nektro <nektro@users.noreply.github.com>
2024-11-21 11:56:09 -08:00
ippsav
161bb764f0 Pass missing signal code for child_process.spawnSync (#15137) 2024-11-21 11:56:09 -08:00
Meghan Denny
25d113ec23 test: dont overwrite root package.json when running bun-ipc-inherit.test.ts (#15126) 2024-11-21 11:56:09 -08:00
Dylan Conway
61ae03662c #15059 follow-up (#15118) 2024-11-21 11:56:09 -08:00
Ciro Spaciari
cc708ae7d5 fix(bundler) fix pretty path resolution (#15119) 2024-11-21 11:56:09 -08:00
Ciro Spaciari
c503caa619 fix(socket) Support named pipes on Windows using forward slashes (#15112) 2024-11-21 11:56:09 -08:00
Dennis Dudek
1be985d2e6 ci: Fix detection of changed files (#15114)
Co-authored-by: Ashcon Partovi <ashcon@partovi.net>
2024-11-21 11:56:09 -08:00
Meghan Denny
405456f2a1 musl patches [v4] (#15066) 2024-11-21 11:56:09 -08:00
Ciro Spaciari
b629430aaa fix behavior when destroyed or pending and cork/uncork and ready event so we can match node.js (#15096) 2024-11-12 09:19:35 -08:00
snwy
027fbfa350 Merge branch 'main' into snoglobe/streams_compat 2024-11-11 15:52:28 -08:00
snwy
95c191b28e revert revert revert 2024-11-11 15:26:17 -08:00
snwy
172f8563cd apply fix everywhere 2024-11-11 14:39:46 -08:00
snwy
e08063eddb ... oops again 2024-11-11 14:16:17 -08:00
snwy
f858021be2 actual. fix 2024-11-08 21:24:12 -08:00
snwy
c28905371a oops 2024-11-07 20:53:00 -08:00
snwy
d7e8e35133 fix 2024-11-07 19:42:52 -08:00
snwy
85c3f93e6c actual fix 2024-11-07 19:14:48 -08:00
snwy
cb49bd6711 test fix (? might be wrong) 2024-11-07 18:12:12 -08:00
snwy
0ca1ee5c2a fixes 2024-11-07 17:23:26 -08:00
snwy
bb07e4a18c 3 more passing tests. yaayy 2024-11-07 16:08:04 -08:00
snwy
5bcd6bb18a test fix and now it works 2024-11-07 15:22:36 -08:00
snwy
1c5c453fa0 err fixes 2024-11-07 14:37:26 -08:00
snwy
a6e038303a fix test to reflect new writable semantics 2024-11-07 14:00:13 -08:00
snwy
8601e5b257 forgot one 2024-11-07 13:28:49 -08:00
snwy
8d7f1fa7d8 fixes 2024-11-07 13:07:17 -08:00
snwy
8db904f3ef snapshot 2024-11-07 12:04:29 -08:00
snwy
7ade017a42 Merge remote-tracking branch 'origin/main' into snoglobe/streams_compat 2024-11-06 15:29:02 -08:00
snwy
edbdea9af7 oops 2024-11-05 20:21:48 -08:00
snwy
ecfe30c3b1 who knows ........ 2024-11-05 20:11:13 -08:00
snwy
d0ae9abfd7 whyyy 2024-11-05 18:27:51 -08:00
snwy
0e741f1a50 constructor part 3 aaah 2024-11-05 16:55:39 -08:00
snwy
3e4524656d constructors part 2 electric boogaloo 2024-11-05 16:21:33 -08:00
snwy
848c1a0547 constructors 2024-11-05 14:04:18 -08:00
snwy
fb1d674354 stupid bug 2024-11-05 13:52:17 -08:00
snwy
7dcb92b325 errors might have been messed up 2024-11-05 13:17:34 -08:00
snwy
6db159aa3e fixes 2024-11-04 21:27:34 -08:00
snwy
5a209d93ff Update package.json
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2024-11-04 21:12:54 -08:00
snwy
cbe1c9e5f7 Update src/js/internal/webstreams/adapters.js
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2024-11-04 21:08:29 -08:00
snwy
03eaf077aa Update src/js/internal/webstreams/util.js
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2024-11-04 21:08:24 -08:00
snwy
50915067ff Merge branch 'main' into snoglobe/streams_compat 2024-11-04 15:58:31 -08:00
snwy
7a8b30b883 ,,, 2024-11-04 15:55:26 -08:00
snwy
afba1deb6e so cooked it's unreal 2024-11-04 15:54:51 -08:00
snwy
b592d8a3d2 let it roll, let it crash down low 2024-11-01 22:00:08 -07:00
snwy
8e294b2636 Merge branch 'main' into snoglobe/streams_compat 2024-11-01 21:47:42 -07:00
snwy
98633f1eaa there's a house down there but i lost it long ago 2024-11-01 21:46:06 -07:00
snwy
e8fd3a37ad let it roll, let it crash down low 2024-11-01 21:15:51 -07:00
snwy
a35c5d0b89 did he raise both fists and say, "to hell with this," and just let the rock roll? 2024-10-31 22:11:49 -07:00
snwy
b61bcd08ed did he jump or did he fall as he gazed into the maw of the morning mist? 2024-10-31 21:33:30 -07:00
snwy
40eb80725c a stone's throw from the precipice, paused 2024-10-31 21:33:01 -07:00
snwy
25930dda09 sisyphus peered into the mist 2024-10-31 21:31:21 -07:00
snwy
7798af8524 one more passing test ... 2024-10-31 17:57:01 -07:00
snwy
c168e26987 so close agghghghh 2024-10-31 15:18:46 -07:00
snwy
b186f0ba71 Merge branch 'main' into snoglobe/streams_compat 2024-10-31 12:59:39 -07:00
snwy
b7864fd4a8 readable stuff 2024-10-29 16:45:46 -07:00
snwy
120bfbfafa more passing 2024-10-29 15:51:31 -07:00
snwy
188778412d no logs 2024-10-28 17:07:02 -07:00
snwy
5adc16b7a8 acgh 2024-10-28 17:02:08 -07:00
snwy
c9e51c9e69 small mistake. might have broken some tests 2024-10-28 16:10:17 -07:00
snwy
35c2456a60 more error fixes 2024-10-28 16:04:08 -07:00
snwy
8a6395f496 minor fixes. big error fixes 2024-10-28 16:04:00 -07:00
snwy
9046b3feaa more tests more changes 2024-10-28 15:32:43 -07:00
snwy
6f3426360d Merge branch 'main' into snoglobe/streams_compat 2024-10-28 14:29:26 -07:00
snwy
cb0b182cb1 fix some more 2024-10-28 14:28:38 -07:00
snwy
b9dd00894b the motherlode 2024-10-25 18:25:33 -07:00
snoglobe
4e58381927 a 2024-10-18 17:55:50 -07:00
533 changed files with 38065 additions and 16997 deletions

824
.buildkite/ci.mjs Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -164,7 +164,9 @@ function upload_s3_file() {
function send_bench_webhook() {
if [ -z "$BENCHMARK_URL" ]; then
return 1
echo "error: \$BENCHMARK_URL is not set"
# exit 1 # TODO: this isn't live yet
return
fi
local tag="$1"
@@ -200,6 +202,12 @@ function create_release() {
bun-linux-x64-profile.zip
bun-linux-x64-baseline.zip
bun-linux-x64-baseline-profile.zip
bun-linux-aarch64-musl.zip
bun-linux-aarch64-musl-profile.zip
bun-linux-x64-musl.zip
bun-linux-x64-musl-profile.zip
bun-linux-x64-musl-baseline.zip
bun-linux-x64-musl-baseline-profile.zip
bun-windows-x64.zip
bun-windows-x64-profile.zip
bun-windows-x64-baseline.zip

30
.vscode/launch.json generated vendored
View File

@@ -13,7 +13,7 @@
"request": "launch",
"name": "bun test [file]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["test", "${file}"],
"args": ["test", "${file}", "--timeout", "0"],
"cwd": "${workspaceFolder}",
"env": {
"FORCE_COLOR": "1",
@@ -25,6 +25,20 @@
// Don't pause when the GC runs while the debugger is open.
"postRunCommands": ["process handle -p true -s false -n false SIGUSR1"],
},
{
"type": "bun",
"request": "launch",
"name": "bun test [file] JS debugger",
"runtimeArgs": ["test"],
"program": "${file}",
"cwd": "${workspaceFolder}",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"env": {
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "1",
},
},
{
"type": "lldb",
"request": "launch",
@@ -179,6 +193,20 @@
// Don't pause when the GC runs while the debugger is open.
"postRunCommands": ["process handle -p true -s false -n false SIGUSR1"],
},
{
"type": "bun",
"request": "launch",
"name": "bun run [file] JS debugger",
"runtimeArgs": ["run"],
"program": "${file}",
"cwd": "${fileDirname}",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"env": {
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "1",
},
},
{
"type": "lldb",
"request": "launch",

View File

@@ -11,7 +11,7 @@ Bun currently requires `glibc >=2.32` in development which means if you're on Ub
Using your system's package manager, install Bun's dependencies:
{% codetabs %}
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rust ruby
@@ -60,7 +60,7 @@ $ brew install bun
Bun requires LLVM 16 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
{% codetabs %}
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install llvm@18
@@ -97,7 +97,7 @@ $ which clang-16
If not, run this to manually add it:
{% codetabs %}
{% codetabs group="os" %}
```bash#macOS (Homebrew)
# use fish_add_path if you're using fish

2
LATEST
View File

@@ -1 +1 @@
1.1.34
1.1.36

18
ci/linux/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
ARG IMAGE=debian:11
FROM $IMAGE
COPY ./scripts/bootstrap.sh /tmp/bootstrap.sh
ENV CI=true
RUN sh /tmp/bootstrap.sh && rm -rf /tmp/*
WORKDIR /workspace/bun
COPY bunfig.toml bunfig.toml
COPY package.json package.json
COPY CMakeLists.txt CMakeLists.txt
COPY cmake/ cmake/
COPY scripts/ scripts/
COPY patches/ patches/
COPY *.zig ./
COPY src/ src/
COPY packages/ packages/
COPY test/ test/
RUN bun i
RUN bun run build:ci

View File

@@ -0,0 +1,27 @@
#!/bin/sh
# This script sets the hostname of the current machine.
execute() {
echo "$ $@" >&2
if ! "$@"; then
echo "Command failed: $@" >&2
exit 1
fi
}
main() {
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <hostname>" >&2
exit 1
fi
if [ -f "$(which hostnamectl)" ]; then
execute hostnamectl set-hostname "$1"
else
echo "Error: hostnamectl is not installed." >&2
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,22 @@
#!/bin/sh
# This script starts tailscale on the current machine.
execute() {
echo "$ $@" >&2
if ! "$@"; then
echo "Command failed: $@" >&2
exit 1
fi
}
main() {
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <auth-key>" >&2
exit 1
fi
execute tailscale up --reset --ssh --accept-risk=lose-ssh --auth-key="$1"
}
main "$@"

View File

@@ -2,7 +2,7 @@
"private": true,
"scripts": {
"bootstrap": "brew install gh jq cirruslabs/cli/tart cirruslabs/cli/sshpass hashicorp/tap/packer && packer init darwin",
"login": "gh auth token | tart login ghcr.io --username $(gh api user --jq .login) --password-stdin",
"login": "token=$(gh auth token); username=$(gh api user --jq .login); echo \"Login as $username...\"; echo \"$token\" | tart login ghcr.io --username \"$username\" --password-stdin; echo \"$token\" | docker login ghcr.io --username \"$username\" --password-stdin",
"fetch:image-name": "echo ghcr.io/oven-sh/bun-vm",
"fetch:darwin-version": "echo 1",
"fetch:macos-version": "sw_vers -productVersion | cut -d. -f1",

View File

@@ -265,7 +265,7 @@ if(ENABLE_LTO)
endif()
# --- Remapping ---
if(UNIX)
if(UNIX AND CI)
register_compiler_flags(
DESCRIPTION "Remap source files"
-ffile-prefix-map=${CWD}=.

View File

@@ -105,14 +105,6 @@ else()
unsupported(CMAKE_HOST_SYSTEM_NAME)
endif()
if(EXISTS "/lib/ld-musl-aarch64.so.1")
set(IS_MUSL ON)
elseif(EXISTS "/lib/ld-musl-x86_64.so.1")
set(IS_MUSL ON)
else()
set(IS_MUSL OFF)
endif()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64")
set(HOST_OS "aarch64")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64")
@@ -144,6 +136,16 @@ else()
set(WARNING WARNING)
endif()
if(LINUX)
if(EXISTS "/etc/alpine-release")
set(DEFAULT_ABI "musl")
else()
set(DEFAULT_ABI "gnu")
endif()
optionx(ABI "musl|gnu" "The ABI to use (e.g. musl, gnu)" DEFAULT ${DEFAULT_ABI})
endif()
# TODO: This causes flaky zig builds in CI, so temporarily disable it.
# if(CI)
# set(DEFAULT_VENDOR_PATH ${CACHE_PATH}/vendor)

View File

@@ -484,14 +484,12 @@ set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
set(IS_ARM64 ON)
if(APPLE)
set(ZIG_CPU "apple_m1")
else()
set(ZIG_CPU "native")
endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64")
set(IS_X86_64 ON)
if(ENABLE_BASELINE)
set(ZIG_CPU "nehalem")
else()
@@ -528,6 +526,7 @@ register_command(
-Dcanary=${CANARY_REVISION}
-Dcodegen_path=${CODEGEN_PATH}
-Dcodegen_embed=$<IF:$<BOOL:${CODEGEN_EMBED}>,true,false>
--prominent-compile-errors
${ZIG_FLAGS_BUN}
ARTIFACTS
${BUN_ZIG_OUTPUT}
@@ -760,8 +759,8 @@ if(NOT WIN32)
)
if(DEBUG)
# TODO: this shouldn't be necessary long term
if (NOT IS_MUSL)
set(ABI_PUBLIC_FLAGS
if (NOT ABI STREQUAL "musl")
target_compile_options(${bun} PUBLIC
-fsanitize=null
-fsanitize-recover=all
-fsanitize=bounds
@@ -772,14 +771,9 @@ if(NOT WIN32)
-fsanitize=returns-nonnull-attribute
-fsanitize=unreachable
)
set(ABI_PRIVATE_FLAGS
target_link_libraries(${bun} PRIVATE
-fsanitize=null
)
else()
set(ABI_PUBLIC_FLAGS
)
set(ABI_PRIVATE_FLAGS
)
endif()
target_compile_options(${bun} PUBLIC
@@ -797,10 +791,6 @@ if(NOT WIN32)
-Wno-unused-function
-Wno-nullability-completeness
-Werror
${ABI_PUBLIC_FLAGS}
)
target_link_libraries(${bun} PRIVATE
${ABI_PRIVATE_FLAGS}
)
else()
# Leave -Werror=unused off in release builds so we avoid errors from being used in ASSERT
@@ -845,65 +835,48 @@ if(WIN32)
/delayload:IPHLPAPI.dll
)
endif()
elseif(APPLE)
endif()
if(APPLE)
target_link_options(${bun} PUBLIC
-dead_strip
-dead_strip_dylibs
-Wl,-ld_new
-Wl,-no_compact_unwind
-Wl,-stack_size,0x1200000
-fno-keep-static-consts
-Wl,-map,${bun}.linker-map
)
else()
# Try to use lld-16 if available, otherwise fallback to lld
# Cache it so we don't have to re-run CMake to pick it up
if((NOT DEFINED LLD_NAME) AND (NOT CI OR BUN_LINK_ONLY))
find_program(LLD_EXECUTABLE_NAME lld-${LLVM_VERSION_MAJOR})
endif()
if(NOT LLD_EXECUTABLE_NAME)
if(CI)
# Ensure we don't use a differing version of lld in CI vs clang
message(FATAL_ERROR "lld-${LLVM_VERSION_MAJOR} not found. Please make sure you have LLVM ${LLVM_VERSION_MAJOR}.x installed and set to lld-${LLVM_VERSION_MAJOR}")
endif()
# To make it easier for contributors, allow differing versions of lld vs clang/cmake
find_program(LLD_EXECUTABLE_NAME lld)
if(LINUX)
if(NOT ABI STREQUAL "musl")
if(ARCH STREQUAL "aarch64")
target_link_options(${bun} PUBLIC
-Wl,--wrap=fcntl64
-Wl,--wrap=statx
)
endif()
if(ARCH STREQUAL "x64")
target_link_options(${bun} PUBLIC
-Wl,--wrap=fcntl
-Wl,--wrap=fcntl64
-Wl,--wrap=fstat
-Wl,--wrap=fstat64
-Wl,--wrap=fstatat
-Wl,--wrap=fstatat64
-Wl,--wrap=lstat
-Wl,--wrap=lstat64
-Wl,--wrap=mknod
-Wl,--wrap=mknodat
-Wl,--wrap=stat
-Wl,--wrap=stat64
-Wl,--wrap=statx
)
endif()
if(NOT LLD_EXECUTABLE_NAME)
message(FATAL_ERROR "LLD not found. Please make sure you have LLVM ${LLVM_VERSION_MAJOR}.x installed and lld is available in your PATH as lld-${LLVM_VERSION_MAJOR}")
endif()
# normalize to basename so it can be used with -fuse-ld
get_filename_component(LLD_NAME ${LLD_EXECUTABLE_NAME} NAME CACHE)
message(STATUS "Using linker: ${LLD_NAME} (${LLD_EXECUTABLE_NAME})")
elseif(NOT DEFINED LLD_NAME)
set(LLD_NAME lld-${LLVM_VERSION_MAJOR})
endif()
if (IS_ARM64)
set(ARCH_WRAP_FLAGS
-Wl,--wrap=fcntl64
-Wl,--wrap=statx
)
elseif(IS_X86_64)
set(ARCH_WRAP_FLAGS
-Wl,--wrap=fcntl
-Wl,--wrap=fcntl64
-Wl,--wrap=fstat
-Wl,--wrap=fstat64
-Wl,--wrap=fstatat
-Wl,--wrap=fstatat64
-Wl,--wrap=lstat
-Wl,--wrap=lstat64
-Wl,--wrap=mknod
-Wl,--wrap=mknodat
-Wl,--wrap=stat
-Wl,--wrap=stat64
-Wl,--wrap=statx
)
endif()
if (NOT IS_MUSL)
set(ABI_WRAP_FLAGS
target_link_options(${bun} PUBLIC
-Wl,--wrap=cosf
-Wl,--wrap=exp
-Wl,--wrap=expf
@@ -920,26 +893,37 @@ else()
-Wl,--wrap=sinf
-Wl,--wrap=tanf
)
endif()
if(NOT ABI STREQUAL "musl")
target_link_options(${bun} PUBLIC
-static-libstdc++
-static-libgcc
)
else()
set(ABI_WRAP_FLAGS
target_link_options(${bun} PUBLIC
-lstdc++
-lgcc
)
endif()
target_link_options(${bun} PUBLIC
-fuse-ld=${LLD_NAME}
--ld-path=${LLD_PROGRAM}
-fno-pic
-static-libstdc++
-static-libgcc
-Wl,-no-pie
-Wl,-icf=safe
-Wl,--as-needed
-Wl,--gc-sections
-Wl,-z,stack-size=12800000
${ARCH_WRAP_FLAGS}
${ABI_WRAP_FLAGS}
-Wl,--compress-debug-sections=zlib
-Wl,-z,lazy
-Wl,-z,norelro
-Wl,-z,combreloc
-Wl,--no-eh-frame-hdr
-Wl,--sort-section=name
-Wl,--hash-style=gnu
-Wl,--build-id=sha1 # Better for debugging than default
-Wl,-Map=${bun}.linker-map
)
endif()
@@ -1079,6 +1063,18 @@ endif()
# --- Packaging ---
if(NOT BUN_CPP_ONLY)
set(CMAKE_STRIP_FLAGS "")
if(APPLE)
# We do not build with exceptions enabled. These are generated by lolhtml
# and other dependencies. We build lolhtml with abort on panic, so it
# shouldn't be including these in the first place.
set(CMAKE_STRIP_FLAGS --remove-section=__TEXT,__eh_frame --remove-section=__TEXT,__unwind_info --remove-section=__TEXT,__gcc_except_tab)
elseif(LINUX AND NOT ABI STREQUAL "musl")
# When you use llvm-strip to do this, it doesn't delete it from the binary and instead keeps it as [LOAD #2 [R]]
# So, we must use GNU strip to do this.
set(CMAKE_STRIP_FLAGS -R .eh_frame -R .gcc_except_table)
endif()
if(bunStrip)
register_command(
TARGET
@@ -1090,6 +1086,7 @@ if(NOT BUN_CPP_ONLY)
COMMAND
${CMAKE_STRIP}
${bunExe}
${CMAKE_STRIP_FLAGS}
--strip-all
--strip-debug
--discard-all
@@ -1165,10 +1162,12 @@ if(NOT BUN_CPP_ONLY)
endif()
if(CI)
set(bunTriplet bun-${OS}-${ARCH})
if(ABI STREQUAL "musl")
set(bunTriplet ${bunTriplet}-musl)
endif()
if(ENABLE_BASELINE)
set(bunTriplet bun-${OS}-${ARCH}-baseline)
else()
set(bunTriplet bun-${OS}-${ARCH})
set(bunTriplet ${bunTriplet}-baseline)
endif()
string(REPLACE bun ${bunTriplet} bunPath ${bun})
set(bunFiles ${bunExe} features.json)
@@ -1177,6 +1176,12 @@ if(NOT BUN_CPP_ONLY)
elseif(APPLE)
list(APPEND bunFiles ${bun}.dSYM)
endif()
if(APPLE OR LINUX)
list(APPEND bunFiles ${bun}.linker-map)
endif()
register_command(
TARGET
${bun}

View File

@@ -26,6 +26,13 @@ if(RELEASE)
list(APPEND LOLHTML_BUILD_ARGS --release)
endif()
# Windows requires unwind tables, apparently.
if (NOT WIN32)
# The encoded escape sequences are intentional. They're how you delimit multiple arguments in a single environment variable.
# Also add rust optimization flag for smaller binary size, but not huge speed penalty.
set(RUSTFLAGS "-Cpanic=abort-Cdebuginfo=0-Cforce-unwind-tables=no-Copt-level=s")
endif()
register_command(
TARGET
lolhtml
@@ -37,6 +44,11 @@ register_command(
${LOLHTML_BUILD_ARGS}
ARTIFACTS
${LOLHTML_LIBRARY}
ENVIRONMENT
CARGO_TERM_COLOR=always
CARGO_TERM_VERBOSE=true
CARGO_TERM_DIAGNOSTIC=true
CARGO_ENCODED_RUSTFLAGS=${RUSTFLAGS}
)
target_link_libraries(${bun} PRIVATE ${LOLHTML_LIBRARY})

View File

@@ -0,0 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,5 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,6 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ENABLE_BASELINE ON)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -0,0 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ENABLE_BASELINE ON)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -0,0 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,5 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -29,7 +29,7 @@ execute_process(
)
if(NOT GIT_DIFF_RESULT EQUAL 0)
message(${WARNING} "Command failed: ${GIT_DIFF_COMMAND} ${GIT_DIFF_ERROR}")
message(WARNING "Command failed: ${GIT_DIFF_COMMAND} ${GIT_DIFF_ERROR}")
return()
endif()

View File

@@ -4,7 +4,7 @@ if(NOT ENABLE_LLVM)
return()
endif()
if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR IS_MUSL)
if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR ABI STREQUAL "musl")
set(DEFAULT_LLVM_VERSION "18.1.8")
else()
set(DEFAULT_LLVM_VERSION "16.0.6")
@@ -52,6 +52,7 @@ if(UNIX)
/usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}/bin
/usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}/bin
/usr/lib/llvm-${LLVM_VERSION_MAJOR}/bin
/usr/lib/llvm${LLVM_VERSION_MAJOR}/bin
)
endif()
endif()
@@ -108,8 +109,23 @@ else()
find_llvm_command(CMAKE_CXX_COMPILER clang++)
find_llvm_command(CMAKE_LINKER llvm-link)
find_llvm_command(CMAKE_AR llvm-ar)
find_llvm_command(CMAKE_STRIP llvm-strip)
if (LINUX)
# On Linux, strip ends up being more useful for us.
find_command(
VARIABLE
CMAKE_STRIP
COMMAND
strip
REQUIRED
ON
)
else()
find_llvm_command(CMAKE_STRIP llvm-strip)
endif()
find_llvm_command(CMAKE_RANLIB llvm-ranlib)
if(LINUX)
find_llvm_command(LLD_PROGRAM ld.lld)
endif()
if(APPLE)
find_llvm_command(CMAKE_DSYMUTIL dsymutil)
endif()

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 73b551e25d97e463e8e2c86cb819b8639fcbda06)
set(WEBKIT_VERSION 3bc4abf2d5875baf500b4687ef869987f6d19e00)
endif()
if(WEBKIT_LOCAL)
@@ -63,7 +63,7 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(IS_MUSL)
if(ABI STREQUAL "musl")
set(WEBKIT_SUFFIX "-musl")
endif()

View File

@@ -11,7 +11,7 @@ if(APPLE)
elseif(WIN32)
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-windows-msvc)
elseif(LINUX)
if(IS_MUSL)
if(ABI STREQUAL "musl")
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-musl)
else()
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-gnu)

View File

@@ -1,30 +1,13 @@
FROM alpine:3.18 AS build
FROM alpine:3.20 AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
# TODO: Instead of downloading glibc from a third-party source, we should
# build it from source. This is a temporary solution.
# See: https://github.com/sgerrand/alpine-pkg-glibc
# https://github.com/sgerrand/alpine-pkg-glibc/releases
# https://github.com/sgerrand/alpine-pkg-glibc/issues/176
ARG GLIBC_VERSION=2.34-r0
# https://github.com/oven-sh/bun/issues/5545#issuecomment-1722461083
ARG GLIBC_VERSION_AARCH64=2.26-r1
RUN apk --no-cache add \
ca-certificates \
curl \
dirmngr \
gpg \
gpg-agent \
unzip \
RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \
&& arch="$(apk --print-arch)" \
&& case "${arch##*-}" in \
x86_64) build="x64-baseline";; \
aarch64) build="aarch64";; \
x86_64) build="x64-musl-baseline";; \
aarch64) build="aarch64-musl";; \
*) echo "error: unsupported architecture: $arch"; exit 1 ;; \
esac \
&& version="$BUN_VERSION" \
@@ -59,37 +42,9 @@ RUN apk --no-cache add \
&& unzip "bun-linux-$build.zip" \
&& mv "bun-linux-$build/bun" /usr/local/bin/bun \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& chmod +x /usr/local/bin/bun \
&& cd /tmp \
&& case "${arch##*-}" in \
x86_64) curl "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk" \
-fsSLO \
--compressed \
--retry 5 \
|| (echo "error: failed to download: glibc v${GLIBC_VERSION}" && exit 1) \
&& mv "glibc-${GLIBC_VERSION}.apk" glibc.apk \
&& curl "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-bin-${GLIBC_VERSION}.apk" \
-fsSLO \
--compressed \
--retry 5 \
|| (echo "error: failed to download: glibc-bin v${GLIBC_VERSION}" && exit 1) \
&& mv "glibc-bin-${GLIBC_VERSION}.apk" glibc-bin.apk ;; \
aarch64) curl "https://raw.githubusercontent.com/squishyu/alpine-pkg-glibc-aarch64-bin/master/glibc-${GLIBC_VERSION_AARCH64}.apk" \
-fsSLO \
--compressed \
--retry 5 \
|| (echo "error: failed to download: glibc v${GLIBC_VERSION_AARCH64}" && exit 1) \
&& mv "glibc-${GLIBC_VERSION_AARCH64}.apk" glibc.apk \
&& curl "https://raw.githubusercontent.com/squishyu/alpine-pkg-glibc-aarch64-bin/master/glibc-bin-${GLIBC_VERSION_AARCH64}.apk" \
-fsSLO \
--compressed \
--retry 5 \
|| (echo "error: failed to download: glibc-bin v${GLIBC_VERSION_AARCH64}" && exit 1) \
&& mv "glibc-bin-${GLIBC_VERSION_AARCH64}.apk" glibc-bin.apk ;; \
*) echo "error: unsupported architecture '$arch'"; exit 1 ;; \
esac
&& chmod +x /usr/local/bin/bun
FROM alpine:3.18
FROM alpine:3.20
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful
@@ -107,10 +62,8 @@ COPY docker-entrypoint.sh /usr/local/bin/
RUN --mount=type=bind,from=build,source=/tmp,target=/tmp \
addgroup -g 1000 bun \
&& adduser -u 1000 -G bun -s /bin/sh -D bun \
&& apk --no-cache --force-overwrite --allow-untrusted add \
/tmp/glibc.apk \
/tmp/glibc-bin.apk \
&& ln -s /usr/local/bin/bun /usr/local/bin/bunx \
&& apk add libgcc libstdc++ \
&& which bun \
&& which bunx \
&& bun --version

View File

@@ -55,6 +55,49 @@ $ bun test ./test/specific-file.test.ts
The test runner runs all tests in a single process. It loads all `--preload` scripts (see [Lifecycle](https://bun.sh/docs/test/lifecycle) for details), then runs all tests. If a test fails, the test runner will exit with a non-zero exit code.
## CI/CD integration
`bun test` supports a variety of CI/CD integrations.
### GitHub Actions
`bun test` automatically detects if it's running inside GitHub Actions and will emit GitHub Actions annotations to the console directly.
No configuration is needed, other than installing `bun` in the workflow and running `bun test`.
#### How to install `bun` in a GitHub Actions workflow
To use `bun test` in a GitHub Actions workflow, add the following step:
```yaml
jobs:
build:
name: build-app
runs-on: ubuntu-latest
steps:
- name: Install bun
uses: oven-sh/setup-bun
- name: Install dependencies # (assuming your project has dependencies)
run: bun install # You can use npm/yarn/pnpm instead if you prefer
- name: Run tests
run: bun test
```
From there, you'll get GitHub Actions annotations.
### JUnit XML reports (GitLab, etc.)
To use `bun test` with a JUnit XML reporter, you can use the `--reporter=junit` in combination with `--reporter-outfile`.
```sh
$ bun test --reporter=junit --reporter-outfile=./bun.xml
```
This will continue to output to stdout/stderr as usual, and also write a JUnit
XML report to the given path at the very end of the test run.
JUnit XML is a popular format for reporting test results in CI/CD pipelines.
## 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`.
@@ -81,7 +124,7 @@ Use the `--bail` flag to abort the test run early after a pre-determined number
$ bun test --bail
# bail after 10 failure
$ bun test --bail 10
$ bun test --bail=10
```
## Watch mode

View File

@@ -1,11 +0,0 @@
- pages
- auto-bundle dependencies
- pages is function that returns a list of pages?
- plugins for svelte and vue
- custom loaders
- HMR
- server endpoints
```ts
Bun.serve({});
```

View File

@@ -1,31 +0,0 @@
To create a new React app:
```bash
$ bun create react ./app
$ cd app
$ bun dev # start dev server
```
To use an existing React app:
```bash
$ bun add -d react-refresh # install React Fast Refresh
$ bun bun ./src/index.js # generate a bundle for your entry point(s)
$ bun dev # start the dev server
```
From there, Bun relies on the filesystem for mapping dev server paths to source files. All URL paths are relative to the project root (where `package.json` is located).
Here are examples of routing source code file paths:
| Dev Server URL | File Path (relative to cwd) |
| -------------------------- | --------------------------- |
| /src/components/Button.tsx | src/components/Button.tsx |
| /src/index.tsx | src/index.tsx |
| /pages/index.js | pages/index.js |
You do not need to include file extensions in `import` paths. CommonJS-style import paths without the file extension work.
You can override the public directory by passing `--public-dir="path-to-folder"`.
If no directory is specified and `./public/` doesnt exist, Bun will try `./static/`. If `./static/` does not exist, but wont serve from a public directory. If you pass `--public-dir=./` Bun will serve from the current directory, but it will check the current directory last instead of first.

View File

@@ -1,77 +0,0 @@
## With `bun dev`
When importing CSS in JavaScript-like loaders, CSS is treated special.
By default, Bun will transform a statement like this:
```js
import "../styles/global.css";
```
### When `platform` is `browser`
```js
globalThis.document?.dispatchEvent(
new CustomEvent("onimportcss", {
detail: "http://localhost:3000/styles/globals.css",
}),
);
```
An event handler for turning that into a `<link>` is automatically registered when HMR is enabled. That event handler can be turned off either in a frameworks `package.json` or by setting `globalThis["Bun_disableCSSImports"] = true;` in client-side code. Additionally, you can get a list of every .css file imported this way via `globalThis["__BUN"].allImportedStyles`.
### When `platform` is `bun`
```js
//@import url("http://localhost:3000/styles/globals.css");
```
Additionally, Bun exposes an API for SSR/SSG that returns a flat list of URLs to css files imported. That function is `Bun.getImportedStyles()`.
```ts
// This specifically is for "framework" in package.json when loaded via `bun dev`
// This API needs to be changed somewhat to work more generally with Bun.js
// Initially, you could only use Bun.js through `bun dev`
// and this API was created at that time
addEventListener("fetch", async (event: FetchEvent) => {
let route = Bun.match(event);
const App = await import("pages/_app");
// This returns all .css files that were imported in the line above.
// Its recursive, so any file that imports a CSS file will be included.
const appStylesheets = bun.getImportedStyles();
// ...rest of code
});
```
This is useful for preventing flash of unstyled content.
## With `bun bun`
Bun bundles `.css` files imported via `@import` into a single file. It doesnt auto-prefix or minify CSS today. Multiple `.css` files imported in one JavaScript file will _not_ be bundled into one file. Youll have to import those from a `.css` file.
This input:
```css
@import url("./hi.css");
@import url("./hello.css");
@import url("./yo.css");
```
Becomes:
```css
/* hi.css */
/* ...contents of hi.css */
/* hello.css */
/* ...contents of hello.css */
/* yo.css */
/* ...contents of yo.css */
```
## CSS runtime
To support hot CSS reloading, Bun inserts `@supports` annotations into CSS that tag which files a stylesheet is composed of. Browsers ignore this, so it doesnt impact styles.
By default, Buns runtime code automatically listens to `onimportcss` and will insert the `event.detail` into a `<link rel="stylesheet" href={${event.detail}}>` if there is no existing `link` tag with that stylesheet. Thats how Buns equivalent of `style-loader` works.

View File

@@ -1,26 +0,0 @@
## Creating a Discord bot with Bun
Discord bots perform actions in response to _application commands_. There are 3 types of commands accessible in different interfaces: the chat input, a message's context menu (top-right menu or right-clicking in a message), and a user's context menu (right-clicking on a user).
To get started you can use the interactions template:
```bash
bun create discord-interactions my-interactions-bot
cd my-interactions-bot
```
If you don't have a Discord bot/application yet, you can create one [here (https://discord.com/developers/applications/me)](https://discord.com/developers/applications/me).
Invite bot to your server by visiting `https://discord.com/api/oauth2/authorize?client_id=<your_application_id>&scope=bot%20applications.commands`
Afterwards you will need to get your bot's token, public key, and application id from the application page and put them into `.env.example` file
Then you can run the http server that will handle your interactions:
```bash
$ bun install
$ mv .env.example .env
$ bun run.js # listening on port 1337
```
Discord does not accept an insecure HTTP server, so you will need to provide an SSL certificate or put the interactions server behind a secure reverse proxy. For development, you can use ngrok/cloudflare tunnel to expose local ports as secure URL.

View File

@@ -63,4 +63,4 @@ process.on("exit", kill);
---
At the time of writing, Bun hasn't implemented the `node:cluster` module yet, but this is a faster, simple, and limited alternative. We will also implement `node:cluster` in the future.
Bun has also implemented the `node:cluster` module, but this is a faster, simple, and limited alternative.

View File

@@ -14,7 +14,7 @@ To bail after a certain threshold of failures, optionally specify a number after
```sh
# bail after 10 failures
$ bun test --bail 10
$ bun test --bail=10
```
---

View File

@@ -57,7 +57,7 @@ Replace `bail` in your Jest config with the `--bail` CLI flag.
``` -->
```sh
$ bun test --bail 3
$ bun test --bail=3
```
---

View File

@@ -44,10 +44,17 @@ test.todo("unimplemented feature", () => {
---
If an implementation is provided, it will be executed and _expected to fail_ by test runner! If a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
If an implementation is provided, it will not be run unless the `--todo` flag is passed. If the `--todo` flag is passed, the test will be executed and _expected to fail_ by test runner! If a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
```sh
$ bun test
$ bun test --todo
my.test.ts:
✗ unimplemented feature
^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.
0 pass
1 fail
1 expect() calls
$ echo $?
1 # this is the exit code of the previous command
```

View File

@@ -73,15 +73,10 @@ After Visual Studio, you need the following:
**Note** The Zig compiler is automatically downloaded, installed, and updated by the building process.
{% /callout %}
[WinGet](https://learn.microsoft.com/windows/package-manager/winget) or [Scoop](https://scoop.sh) can be used to install these remaining tools easily:
[Scoop](https://scoop.sh) can be used to install these remaining tools easily.
{% codetabs group="a" %}
```ps1#WinGet
## Select "Add LLVM to the system PATH for all users" in the LLVM installer
> winget install -i LLVM.LLVM -v 18.1.8 && winget install GoLang.Go Rustlang.Rustup NASM.NASM StrawberryPerl.StrawberryPerl RubyInstallerTeam.Ruby.3.2 OpenJS.NodeJS.LTS Ccache.Ccache
```
```ps1#Scoop
> irm https://get.scoop.sh | iex
> scoop install nodejs-lts go rust nasm ruby perl ccache
@@ -91,20 +86,16 @@ After Visual Studio, you need the following:
{% /codetabs %}
{% callout %}
Please do not use WinGet/other package manager for these, as you will likely install Strawberry Perl instead of a more minimal installation of Perl. Strawberry Perl includes many other utilities that get installed into `$Env:PATH` that will conflict with MSVC and break the build.
{% /callout %}
If you intend on building WebKit locally (optional), you should install these packages:
{% codetabs group="a" %}
```ps1#WinGet
> winget install ezwinports.make Cygwin.Cygwin Python.Python.3.12
```
```ps1#Scoop
> scoop install make cygwin python
```
{% /codetabs %}
From here on out, it is **expected you use a PowerShell Terminal with `.\scripts\vs-shell.ps1` sourced**. This script is available in the Bun repository and can be loaded by executing it:
```ps1

View File

@@ -1,4 +0,0 @@
# RFCs
| Number | Name | Issue |
| ------ | ---- | ----- |

View File

@@ -97,7 +97,7 @@ test.skip("wat", () => {
## `test.todo`
Mark a test as a todo with `test.todo`. These tests _will_ be run, and the test runner will expect them to fail. If they pass, you will be prompted to mark it as a regular test.
Mark a test as a todo with `test.todo`. These tests will not be run.
```ts
import { expect, test } from "bun:test";
@@ -107,12 +107,22 @@ test.todo("fix this", () => {
});
```
To exclusively run tests marked as _todo_, use `bun test --todo`.
To run todo tests and find any which are passing, use `bun test --todo`.
```sh
$ bun test --todo
my.test.ts:
✗ unimplemented feature
^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.
0 pass
1 fail
1 expect() calls
```
With this flag, failing todo tests will not cause an error, but todo tests which pass will be marked as failing so you can remove the todo mark or
fix the test.
## `test.only`
To run a particular test or suite of tests use `test.only()` or `describe.only()`. Once declared, running `bun test --only` will only execute tests/suites that have been marked with `.only()`. Running `bun test` without the `--only` option with `test.only()` declared will result in all tests in the given suite being executed _up to_ the test with `.only()`. `describe.only()` functions the same in both execution scenarios.

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.1.35",
"version": "1.1.37",
"workspaces": [
"./packages/bun-types"
],

View File

@@ -387,9 +387,9 @@ declare module "bun:test" {
/**
* Marks this test as to be written or to be fixed.
*
* When a test function is passed, it will be marked as `todo` in the test results
* as long the test does not pass. When the test passes, the test will be marked as
* `fail` in the results; you will have to remove the `.todo` or check that your test
* These tests will not be executed unless the `--todo` flag is passed. With the flag,
* if the test passes, the test will be marked as `fail` in the results; you will have to
* remove the `.todo` or check that your test
* is implemented correctly.
*
* @param label the label for the test

View File

@@ -18,8 +18,7 @@ const __filename = fileURLToPath(import.meta.url);
const now = new Date();
const formatDate = d => {
const iso = d.toISOString();
return iso.substring(0, iso.indexOf("T"));
return d;
};
const getCertdataURL = version => {
@@ -146,26 +145,35 @@ if (values.help) {
process.exit(0);
}
const scheduleURL = "https://wiki.mozilla.org/NSS:Release_Versions";
if (values.verbose) {
console.log(`Fetching NSS release schedule from ${scheduleURL}`);
}
const schedule = await fetch(scheduleURL);
if (!schedule.ok) {
console.error(`Failed to fetch ${scheduleURL}: ${schedule.status}: ${schedule.statusText}`);
process.exit(-1);
}
const scheduleText = await schedule.text();
const nssReleases = getReleases(scheduleText);
const versions = await fetch("https://nucleus.mozilla.org/rna/all-releases.json").then(res => res.json());
// Retrieve metadata for the NSS release being updated to.
const version = positionals[0] ?? (await getLatestVersion(nssReleases));
const release = nssReleases.find(r => {
return new RegExp(`^${version.replace(".", "\\.")}\\b`).test(r[kNSSVersion]);
});
if (!pastRelease(release)) {
console.warn(`Warning: NSS ${version} is not due to be released until ${formatDate(release[kNSSDate])}`);
const today = new Date().toISOString().split("T")[0].trim();
const releases = versions
.filter(
version =>
version.channel == "Release" &&
version.product === "Firefox" &&
version.is_public &&
version.release_date <= today,
)
.sort((a, b) => (a > b ? (a == b ? 0 : -1) : 1));
const latest = releases[0];
const release_tag = `FIREFOX_${latest.version.replaceAll(".", "_")}_RELEASE`;
if (values.verbose) {
console.log(`Fetching NSS release from ${release_tag}`);
}
const version = await fetch(
`https://hg.mozilla.org/releases/mozilla-release/raw-file/${release_tag}/security/nss/TAG-INFO`,
)
.then(res => res.text())
.then(txt => txt.trim().split("NSS_")[1].split("_RTM").join("").split("_").join(".").trim());
const release = {
version: version,
firefoxVersion: latest.version,
firefoxDate: latest.release_date,
date: latest.release_date,
};
if (values.verbose) {
console.log("Found NSS version:");
console.log(release);

View File

@@ -201,7 +201,7 @@ struct loop_ssl_data * us_internal_set_loop_ssl_data(struct us_internal_ssl_sock
struct us_internal_ssl_socket_t *ssl_on_open(struct us_internal_ssl_socket_t *s,
int is_client, char *ip,
int ip_length) {
int ip_length, const char* sni) {
struct us_internal_ssl_socket_context_t *context =
(struct us_internal_ssl_socket_context_t *)us_socket_context(0, &s->s);
@@ -231,6 +231,10 @@ struct us_internal_ssl_socket_t *ssl_on_open(struct us_internal_ssl_socket_t *s,
if (is_client) {
SSL_set_renegotiate_mode(s->ssl, ssl_renegotiate_explicit);
SSL_set_connect_state(s->ssl);
if (sni) {
SSL_set_tlsext_host_name(s->ssl, sni);
}
} else {
SSL_set_accept_state(s->ssl);
// we do not allow renegotiation on the server side (should be the default for BoringSSL, but we set to make openssl compatible)
@@ -1603,6 +1607,10 @@ struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_unix(
socket_ext_size);
}
static void ssl_on_open_without_sni(struct us_internal_ssl_socket_t *s, int is_client, char *ip, int ip_length) {
ssl_on_open(s, is_client, ip, ip_length, NULL);
}
void us_internal_ssl_socket_context_on_open(
struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_open)(
@@ -1611,7 +1619,7 @@ void us_internal_ssl_socket_context_on_open(
us_socket_context_on_open(
0, &context->sc,
(struct us_socket_t * (*)(struct us_socket_t *, int, char *, int))
ssl_on_open);
ssl_on_open_without_sni);
context->on_open = on_open;
}
@@ -2005,7 +2013,30 @@ us_internal_ssl_socket_open(struct us_internal_ssl_socket_t *s, int is_client,
return s;
// start SSL open
return ssl_on_open(s, is_client, ip, ip_length);
return ssl_on_open(s, is_client, ip, ip_length, NULL);
}
struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r new_context, const char *sni) {
// Resize to tls + ext size
void** prev_ext_ptr = (void**)us_socket_ext(0, s);
void* prev_ext = *prev_ext_ptr;
struct us_internal_ssl_socket_t *socket =
(struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
0, new_context, s,
(sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t)) + sizeof(void*));
socket->ssl = NULL;
socket->ssl_write_wants_read = 0;
socket->ssl_read_wants_write = 0;
socket->fatal_error = 0;
socket->handshake_state = HANDSHAKE_PENDING;
void** new_ext_ptr = (void**)us_socket_ext(1, (struct us_socket_t *)socket);
*new_ext_ptr = prev_ext;
ssl_on_open(socket, 1, NULL, 0, sni);
return (struct us_socket_t *)socket;
}
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(

View File

@@ -7,12 +7,6 @@
#include <openssl/x509.h>
#include <string.h>
static const int root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]);
static X509 *root_cert_instances[sizeof(root_certs) / sizeof(root_certs[0])] = {
NULL};
static X509 *root_extra_cert_instances = {NULL};
static std::atomic_flag root_cert_instances_lock = ATOMIC_FLAG_INIT;
static std::atomic_bool root_cert_instances_initialized = 0;
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
@@ -78,7 +72,9 @@ end:
return NULL;
}
static void us_internal_init_root_certs() {
static void us_internal_init_root_certs(X509 *root_cert_instances[sizeof(root_certs) / sizeof(root_certs[0])], X509 *&root_extra_cert_instances) {
static std::atomic_flag root_cert_instances_lock = ATOMIC_FLAG_INIT;
static std::atomic_bool root_cert_instances_initialized = 0;
if (std::atomic_load(&root_cert_instances_initialized) == 1)
return;
@@ -123,7 +119,11 @@ extern "C" X509_STORE *us_get_default_ca_store() {
return NULL;
}
us_internal_init_root_certs();
static X509 *root_cert_instances[sizeof(root_certs) / sizeof(root_certs[0])] = {
NULL};
static X509 *root_extra_cert_instances = NULL;
us_internal_init_root_certs(root_cert_instances, root_extra_cert_instances);
// load all root_cert_instances on the default ca store
for (size_t i = 0; i < root_certs_size; i++) {

View File

@@ -190,7 +190,7 @@ struct us_socket_context_options_t {
};
struct us_bun_verify_error_t {
long error;
int error;
const char* code;
const char* reason;
};
@@ -338,6 +338,8 @@ struct us_loop_t *us_socket_context_loop(int ssl, us_socket_context_r context) n
* Used mainly for "socket upgrades" such as when transitioning from HTTP to WebSocket. */
struct us_socket_t *us_socket_context_adopt_socket(int ssl, us_socket_context_r context, us_socket_r s, int ext_size);
struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r new_context, const char *sni);
/* Create a child socket context which acts much like its own socket context with its own callbacks yet still relies on the
* parent socket context for some shared resources. Child socket contexts should be used together with socket adoptions and nothing else. */
struct us_socket_context_t *us_create_child_socket_context(int ssl, us_socket_context_r context, int context_ext_size);

View File

@@ -613,7 +613,9 @@ namespace uWS
* ought to be handled as an error. */
std::string_view transferEncodingString = req->getHeader("transfer-encoding");
std::string_view contentLengthString = req->getHeader("content-length");
if (transferEncodingString.length() && contentLengthString.length()) {
auto transferEncodingStringLen = transferEncodingString.length();
auto contentLengthStringLen = contentLengthString.length();
if (transferEncodingStringLen && contentLengthStringLen) {
/* Returning fullptr is the same as calling the errorHandler */
/* We could be smart and set an error in the context along with this, to indicate what
* http error response we might want to return */
@@ -623,6 +625,15 @@ namespace uWS
/* Parse query */
const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length());
req->querySeparator = (unsigned int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data());
// lets check if content len is valid before calling requestHandler
if(contentLengthStringLen) {
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
if (remainingStreamingBytes == UINT64_MAX) {
/* Parser error */
return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
}
}
/* If returned socket is not what we put in we need
* to break here as we either have upgraded to
@@ -642,7 +653,7 @@ namespace uWS
/* RFC 9112 6.3
* If a message is received with both a Transfer-Encoding and a Content-Length header field,
* the Transfer-Encoding overrides the Content-Length. */
if (transferEncodingString.length()) {
if (transferEncodingStringLen) {
/* If a proxy sent us the transfer-encoding header that 100% means it must be chunked or else the proxy is
* not RFC 9112 compliant. Therefore it is always better to assume this is the case, since that entirely eliminates
@@ -665,6 +676,7 @@ namespace uWS
dataHandler(user, chunk, chunk.length() == 0);
}
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
// TODO: what happen if we already responded?
return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
}
unsigned int consumed = (length - (unsigned int) dataToConsume.length());
@@ -672,13 +684,8 @@ namespace uWS
length = (unsigned int) dataToConsume.length();
consumedTotal += consumed;
}
} else if (contentLengthString.length()) {
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
if (remainingStreamingBytes == UINT64_MAX) {
/* Parser error */
return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
}
} else if (contentLengthStringLen) {
if (!CONSUME_MINIMALLY) {
unsigned int emittable = (unsigned int) std::min<uint64_t>(remainingStreamingBytes, length);
dataHandler(user, std::string_view(data, emittable), emittable == remainingStreamingBytes);

246
scripts/agent.mjs Executable file
View File

@@ -0,0 +1,246 @@
#!/usr/bin/env node
// An agent that starts buildkite-agent and runs others services.
import { join } from "node:path";
import { realpathSync } from "node:fs";
import {
isWindows,
getOs,
getArch,
getKernel,
getAbi,
getAbiVersion,
getDistro,
getDistroVersion,
getHostname,
getCloud,
getCloudMetadataTag,
which,
getEnv,
writeFile,
spawnSafe,
} from "./utils.mjs";
import { parseArgs } from "node:util";
/**
* @param {"install" | "start"} action
*/
async function doBuildkiteAgent(action) {
const username = "buildkite-agent";
const command = which("buildkite-agent", { required: true });
let homePath, cachePath, logsPath, agentLogPath, pidPath;
if (isWindows) {
homePath = "C:\\buildkite-agent";
cachePath = join(homePath, "cache");
logsPath = join(homePath, "logs");
agentLogPath = join(logsPath, "buildkite-agent.log");
} else {
homePath = "/var/lib/buildkite-agent";
cachePath = "/var/cache/buildkite-agent";
logsPath = "/var/log/buildkite-agent";
agentLogPath = join(logsPath, "buildkite-agent.log");
pidPath = join(logsPath, "buildkite-agent.pid");
}
async function install() {
const command = process.execPath;
const args = [realpathSync(process.argv[1]), "start"];
if (isWindows) {
const serviceCommand = [
"New-Service",
"-Name",
"buildkite-agent",
"-StartupType",
"Automatic",
"-BinaryPathName",
`${escape(command)} ${escape(args.map(escape).join(" "))}`,
];
await spawnSafe(["powershell", "-Command", serviceCommand.join(" ")], { stdio: "inherit" });
}
if (isOpenRc()) {
const servicePath = "/etc/init.d/buildkite-agent";
const service = `#!/sbin/openrc-run
name="buildkite-agent"
description="Buildkite Agent"
command=${escape(command)}
command_args=${escape(args.map(escape).join(" "))}
command_user=${escape(username)}
pidfile=${escape(pidPath)}
start_stop_daemon_args=" \
--background \
--make-pidfile \
--stdout ${escape(agentLogPath)} \
--stderr ${escape(agentLogPath)}"
depend() {
need net
use dns logger
}
`;
writeFile(servicePath, service, { mode: 0o755 });
await spawnSafe(["rc-update", "add", "buildkite-agent", "default"], { stdio: "inherit", privileged: true });
}
if (isSystemd()) {
const servicePath = "/etc/systemd/system/buildkite-agent.service";
const service = `
[Unit]
Description=Buildkite Agent
After=syslog.target
After=network-online.target
[Service]
Type=simple
User=${username}
ExecStart=${escape(command)} ${args.map(escape).join(" ")}
RestartSec=5
Restart=on-failure
KillMode=process
[Journal]
Storage=persistent
StateDirectory=${escape(agentLogPath)}
[Install]
WantedBy=multi-user.target
`;
writeFile(servicePath, service);
await spawnSafe(["systemctl", "daemon-reload"], { stdio: "inherit", privileged: true });
await spawnSafe(["systemctl", "enable", "buildkite-agent"], { stdio: "inherit", privileged: true });
}
}
async function start() {
const cloud = await getCloud();
let token = getEnv("BUILDKITE_AGENT_TOKEN", false);
if (!token && cloud) {
token = await getCloudMetadataTag("buildkite:token");
}
let shell;
if (isWindows) {
const pwsh = which(["pwsh", "powershell"], { required: true });
shell = `${pwsh} -Command`;
} else {
const sh = which(["bash", "sh"], { required: true });
shell = `${sh} -c`;
}
const flags = ["enable-job-log-tmpfile", "no-feature-reporting"];
const options = {
"name": getHostname(),
"token": token || "xxx",
"shell": shell,
"job-log-path": logsPath,
"build-path": join(homePath, "builds"),
"hooks-path": join(homePath, "hooks"),
"plugins-path": join(homePath, "plugins"),
"experiment": "normalised-upload-paths,resolve-commit-after-checkout,agent-api",
};
let ephemeral;
if (cloud) {
const jobId = await getCloudMetadataTag("buildkite:job-uuid");
if (jobId) {
options["acquire-job"] = jobId;
flags.push("disconnect-after-job");
ephemeral = true;
}
}
if (ephemeral) {
options["git-clone-flags"] = "-v --depth=1";
options["git-fetch-flags"] = "-v --prune --depth=1";
} else {
options["git-mirrors-path"] = join(cachePath, "git");
}
const tags = {
"os": getOs(),
"arch": getArch(),
"kernel": getKernel(),
"abi": getAbi(),
"abi-version": getAbiVersion(),
"distro": getDistro(),
"distro-version": getDistroVersion(),
"cloud": cloud,
};
if (cloud) {
const requiredTags = ["robobun", "robobun2"];
for (const tag of requiredTags) {
const value = await getCloudMetadataTag(tag);
if (typeof value === "string") {
tags[tag] = value;
}
}
}
options["tags"] = Object.entries(tags)
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join(",");
await spawnSafe(
[
command,
"start",
...flags.map(flag => `--${flag}`),
...Object.entries(options).map(([key, value]) => `--${key}=${value}`),
],
{
stdio: "inherit",
},
);
}
if (action === "install") {
await install();
} else if (action === "start") {
await start();
}
}
/**
* @returns {boolean}
*/
function isSystemd() {
return !!which("systemctl");
}
/**
* @returns {boolean}
*/
function isOpenRc() {
return !!which("rc-service");
}
function escape(string) {
return JSON.stringify(string);
}
async function main() {
const { positionals: args } = parseArgs({
allowPositionals: true,
});
if (!args.length || args.includes("install")) {
console.log("Installing agent...");
await doBuildkiteAgent("install");
console.log("Agent installed.");
}
if (args.includes("start")) {
console.log("Starting agent...");
await doBuildkiteAgent("start");
console.log("Agent started.");
}
}
await main();

339
scripts/bootstrap.ps1 Executable file
View File

@@ -0,0 +1,339 @@
# Version: 4
# A powershell script that installs the dependencies needed to build and test Bun.
# This should work on Windows 10 or newer.
# If this script does not work on your machine, please open an issue:
# https://github.com/oven-sh/bun/issues
# If you need to make a change to this script, such as upgrading a dependency,
# increment the version comment to indicate that a new image should be built.
# Otherwise, the existing image will be retroactively updated.
param (
[Parameter(Mandatory = $false)]
[switch]$CI = $false,
[Parameter(Mandatory = $false)]
[switch]$Optimize = $CI
)
function Execute-Command {
$command = $args -join ' '
Write-Output "$ $command"
& $args[0] $args[1..$args.Length]
if ((-not $?) -or ($LASTEXITCODE -ne 0 -and $null -ne $LASTEXITCODE)) {
throw "Command failed: $command"
}
}
function Which {
param ([switch]$Required = $false)
foreach ($command in $args) {
$result = Get-Command $command -ErrorAction SilentlyContinue
if ($result -and $result.Path) {
return $result.Path
}
}
if ($Required) {
$commands = $args -join ', '
throw "Command not found: $commands"
}
}
function Install-Chocolatey {
if (Which choco) {
return
}
Write-Output "Installing Chocolatey..."
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex -Command ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
Refresh-Path
}
function Refresh-Path {
$paths = @(
[System.Environment]::GetEnvironmentVariable("Path", "Machine"),
[System.Environment]::GetEnvironmentVariable("Path", "User"),
[System.Environment]::GetEnvironmentVariable("Path", "Process")
)
$uniquePaths = $paths |
Where-Object { $_ } |
ForEach-Object { $_.Split(';', [StringSplitOptions]::RemoveEmptyEntries) } |
Where-Object { $_ -and (Test-Path $_) } |
Select-Object -Unique
$env:Path = ($uniquePaths -join ';').TrimEnd(';')
if ($env:ChocolateyInstall) {
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 -ErrorAction SilentlyContinue
}
}
function Add-To-Path {
$absolutePath = Resolve-Path $args[0]
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($currentPath -like "*$absolutePath*") {
return
}
$newPath = $currentPath.TrimEnd(";") + ";" + $absolutePath
if ($newPath.Length -ge 2048) {
Write-Warning "PATH is too long, removing duplicate and old entries..."
$paths = $currentPath.Split(';', [StringSplitOptions]::RemoveEmptyEntries) |
Where-Object { $_ -and (Test-Path $_) } |
Select-Object -Unique
$paths += $absolutePath
$newPath = $paths -join ';'
while ($newPath.Length -ge 2048 -and $paths.Count -gt 1) {
$paths = $paths[1..$paths.Count]
$newPath = $paths -join ';'
}
}
Write-Output "Adding $absolutePath to PATH..."
[Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
Refresh-Path
}
function Install-Package {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Name,
[Parameter(Mandatory = $false)]
[string]$Command = $Name,
[Parameter(Mandatory = $false)]
[string]$Version,
[Parameter(Mandatory = $false)]
[switch]$Force = $false,
[Parameter(Mandatory = $false)]
[string[]]$ExtraArgs = @()
)
if (-not $Force `
-and (Which $Command) `
-and (-not $Version -or (& $Command --version) -like "*$Version*")) {
return
}
Write-Output "Installing $Name..."
$flags = @(
"--yes",
"--accept-license",
"--no-progress",
"--force"
)
if ($Version) {
$flags += "--version=$Version"
}
Execute-Command choco install $Name @flags @ExtraArgs
Refresh-Path
}
function Install-Packages {
foreach ($package in $args) {
Install-Package -Name $package
}
}
function Install-Common-Software {
Install-Chocolatey
Install-Pwsh
Install-Git
Install-Packages curl 7zip
Install-NodeJs
Install-Bun
Install-Cygwin
if ($CI) {
Install-Tailscale
Install-Buildkite
}
}
function Install-Pwsh {
Install-Package powershell-core -Command pwsh
if ($CI) {
$shellPath = (Which pwsh -Required)
New-ItemProperty `
-Path "HKLM:\\SOFTWARE\\OpenSSH" `
-Name DefaultShell `
-Value $shellPath `
-PropertyType String `
-Force
}
}
function Install-Git {
Install-Packages git
if ($CI) {
Execute-Command git config --system --add safe.directory "*"
Execute-Command git config --system core.autocrlf false
Execute-Command git config --system core.eol lf
Execute-Command git config --system core.longpaths true
}
}
function Install-NodeJs {
Install-Package nodejs -Command node -Version "22.9.0"
}
function Install-Bun {
Install-Package bun -Version "1.1.30"
}
function Install-Cygwin {
Install-Package cygwin
Add-To-Path "C:\tools\cygwin\bin"
}
function Install-Tailscale {
Install-Package tailscale
}
function Install-Buildkite {
if (Which buildkite-agent) {
return
}
Write-Output "Installing Buildkite agent..."
$env:buildkiteAgentToken = "xxx"
iex ((New-Object System.Net.WebClient).DownloadString("https://raw.githubusercontent.com/buildkite/agent/main/install.ps1"))
Refresh-Path
}
function Install-Build-Essentials {
# Install-Visual-Studio
Install-Packages `
cmake `
make `
ninja `
ccache `
python `
golang `
nasm `
ruby `
mingw
Install-Rust
Install-Llvm
}
function Install-Visual-Studio {
$components = @(
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Component.Windows10SDK.18362",
"Microsoft.VisualStudio.Component.Windows11SDK.22000",
"Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit",
"Microsoft.VisualStudio.Component.VC.ASAN", # C++ AddressSanitizer
"Microsoft.VisualStudio.Component.VC.ATL", # C++ ATL for latest v143 build tools (x86 & x64)
"Microsoft.VisualStudio.Component.VC.DiagnosticTools", # C++ Diagnostic Tools
"Microsoft.VisualStudio.Component.VC.CLI.Support", # C++/CLI support for v143 build tools (Latest)
"Microsoft.VisualStudio.Component.VC.CoreIde", # C++ core features
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest" # C++ 2022 Redistributable Update
)
$arch = (Get-WmiObject Win32_Processor).Architecture
if ($arch -eq 9) {
$components += @(
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64", # MSVC v143 build tools (x86 & x64)
"Microsoft.VisualStudio.Component.VC.Modules.x86.x64" # MSVC v143 C++ Modules for latest v143 build tools (x86 & x64)
)
} elseif ($arch -eq 5) {
$components += @(
"Microsoft.VisualStudio.Component.VC.Tools.ARM64", # MSVC v143 build tools (ARM64)
"Microsoft.VisualStudio.Component.UWP.VC.ARM64" # C++ Universal Windows Platform support for v143 build tools (ARM64/ARM64EC)
)
}
$packageParameters = $components | ForEach-Object { "--add $_" }
Install-Package visualstudio2022community `
-ExtraArgs "--package-parameters '--add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --includeOptional'"
}
function Install-Rust {
if (Which rustc) {
return
}
Write-Output "Installing Rust..."
$rustupInit = "$env:TEMP\rustup-init.exe"
(New-Object System.Net.WebClient).DownloadFile("https://win.rustup.rs/", $rustupInit)
Execute-Command $rustupInit -y
Add-To-Path "$env:USERPROFILE\.cargo\bin"
}
function Install-Llvm {
Install-Package llvm `
-Command clang-cl `
-Version "18.1.8"
Add-To-Path "C:\Program Files\LLVM\bin"
}
function Optimize-System {
Disable-Windows-Defender
Disable-Windows-Threat-Protection
Disable-Windows-Services
Disable-Power-Management
Uninstall-Windows-Defender
}
function Disable-Windows-Defender {
Write-Output "Disabling Windows Defender..."
Set-MpPreference -DisableRealtimeMonitoring $true
Add-MpPreference -ExclusionPath "C:\", "D:\"
}
function Disable-Windows-Threat-Protection {
$itemPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection"
if (Test-Path $itemPath) {
Write-Output "Disabling Windows Threat Protection..."
Set-ItemProperty -Path $itemPath -Name "ForceDefenderPassiveMode" -Value 1 -Type DWORD
}
}
function Uninstall-Windows-Defender {
Write-Output "Uninstalling Windows Defender..."
Uninstall-WindowsFeature -Name Windows-Defender
}
function Disable-Windows-Services {
$services = @(
"WSearch", # Windows Search
"wuauserv", # Windows Update
"DiagTrack", # Connected User Experiences and Telemetry
"dmwappushservice", # WAP Push Message Routing Service
"PcaSvc", # Program Compatibility Assistant
"SysMain" # Superfetch
)
foreach ($service in $services) {
Stop-Service $service -Force
Set-Service $service -StartupType Disabled
}
}
function Disable-Power-Management {
Write-Output "Disabling power management features..."
powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c # High performance
powercfg /change monitor-timeout-ac 0
powercfg /change monitor-timeout-dc 0
powercfg /change standby-timeout-ac 0
powercfg /change standby-timeout-dc 0
powercfg /change hibernate-timeout-ac 0
powercfg /change hibernate-timeout-dc 0
}
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
if ($Optimize) {
Optimize-System
}
Install-Common-Software
Install-Build-Essentials

File diff suppressed because it is too large Load Diff

0
scripts/build.mjs Normal file → Executable file
View File

0
scripts/features.mjs Normal file → Executable file
View File

1305
scripts/machine.mjs Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ import {
} from "./utils.mjs";
import { userInfo } from "node:os";
const cwd = dirname(import.meta.dirname);
const cwd = import.meta.dirname ? dirname(import.meta.dirname) : process.cwd();
const testsPath = join(cwd, "test");
const spawnTimeout = 5_000;
@@ -232,7 +232,7 @@ async function runTests() {
if (testRunner === "bun") {
await runTest(title, () => spawnBunTest(execPath, testPath, { cwd: vendorPath }));
} else {
const testRunnerPath = join(import.meta.dirname, "..", "test", "runners", `${testRunner}.ts`);
const testRunnerPath = join(cwd, "test", "runners", `${testRunner}.ts`);
if (!existsSync(testRunnerPath)) {
throw new Error(`Unsupported test runner: ${testRunner}`);
}
@@ -632,7 +632,7 @@ function parseTestStdout(stdout, testPath) {
const removeStart = lines.length - skipCount;
const removeCount = skipCount - 2;
const omitLine = `${getAnsi("gray")}... omitted ${removeCount} tests ...${getAnsi("reset")}`;
lines = lines.toSpliced(removeStart, removeCount, omitLine);
lines.splice(removeStart, removeCount, omitLine);
}
skipCount = 0;
}
@@ -1133,6 +1133,13 @@ function addPath(...paths) {
return paths.join(":");
}
/**
* @returns {string | undefined}
*/
function getTestLabel() {
return getBuildLabel()?.replace(" - test-bun", "");
}
/**
* @param {TestResult | TestResult[]} result
* @param {boolean} concise
@@ -1140,7 +1147,7 @@ function addPath(...paths) {
*/
function formatTestToMarkdown(result, concise) {
const results = Array.isArray(result) ? result : [result];
const buildLabel = getBuildLabel();
const buildLabel = getTestLabel();
const buildUrl = getBuildUrl();
const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel;
@@ -1273,7 +1280,7 @@ function reportAnnotationToBuildKite({ label, content, style = "error", priority
const cause = error ?? signal ?? `code ${status}`;
throw new Error(`Failed to create annotation: ${label}`, { cause });
}
const buildLabel = getBuildLabel();
const buildLabel = getTestLabel();
const buildUrl = getBuildUrl();
const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel;
let errorMessage = `<details><summary><a><code>${label}</code></a> - annotation error on ${platform}</summary>`;

666
scripts/utils.mjs Normal file → Executable file
View File

@@ -3,9 +3,18 @@
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { appendFileSync, existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { writeFile } from "node:fs/promises";
import { hostname, tmpdir as nodeTmpdir, userInfo } from "node:os";
import {
appendFileSync,
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
writeFileSync,
} from "node:fs";
import { connect } from "node:net";
import { hostname, tmpdir as nodeTmpdir, userInfo, release } from "node:os";
import { dirname, join, relative, resolve } from "node:path";
import { normalize as normalizeWindows } from "node:path/win32";
@@ -53,8 +62,9 @@ export function getSecret(name, options = { required: true, redact: true }) {
command.push("--skip-redaction");
}
const { error, stdout: secret } = spawnSync(command);
if (error || !secret.trim()) {
const { error, stdout } = spawnSync(command);
const secret = stdout.trim();
if (error || !secret) {
const orgId = getEnv("BUILDKITE_ORGANIZATION_SLUG", false);
const clusterId = getEnv("BUILDKITE_CLUSTER_ID", false);
@@ -106,8 +116,8 @@ export function setEnv(name, value) {
* @property {string} [cwd]
* @property {number} [timeout]
* @property {Record<string, string | undefined>} [env]
* @property {string} [stdout]
* @property {string} [stderr]
* @property {string} [stdin]
* @property {boolean} [privileged]
*/
/**
@@ -119,20 +129,93 @@ export function setEnv(name, value) {
* @property {Error} [error]
*/
/**
* @param {TemplateStringsArray} strings
* @param {...any} values
* @returns {string[]}
*/
export function $(strings, ...values) {
const result = [];
for (let i = 0; i < strings.length; i++) {
result.push(...strings[i].trim().split(/\s+/).filter(Boolean));
if (i < values.length) {
const value = values[i];
if (Array.isArray(value)) {
result.push(...value);
} else if (typeof value === "string") {
if (result.at(-1)?.endsWith("=")) {
result[result.length - 1] += value;
} else {
result.push(value);
}
}
}
}
return result;
}
/** @type {string[] | undefined} */
let priviledgedCommand;
/**
* @param {string[]} command
* @param {SpawnOptions} options
*/
function parseCommand(command, options) {
if (options?.privileged) {
return [...getPrivilegedCommand(), ...command];
}
return command;
}
/**
* @returns {string[]}
*/
function getPrivilegedCommand() {
if (typeof priviledgedCommand !== "undefined") {
return priviledgedCommand;
}
if (isWindows) {
return (priviledgedCommand = []);
}
const sudo = ["sudo", "-n"];
const { error: sudoError } = spawnSync([...sudo, "true"]);
if (!sudoError) {
return (priviledgedCommand = sudo);
}
const su = ["su", "-s", "sh", "root", "-c"];
const { error: suError } = spawnSync([...su, "true"]);
if (!suError) {
return (priviledgedCommand = su);
}
const doas = ["doas", "-u", "root"];
const { error: doasError } = spawnSync([...doas, "true"]);
if (!doasError) {
return (priviledgedCommand = doas);
}
return (priviledgedCommand = []);
}
/**
* @param {string[]} command
* @param {SpawnOptions} options
* @returns {Promise<SpawnResult>}
*/
export async function spawn(command, options = {}) {
debugLog("$", ...command);
const [cmd, ...args] = parseCommand(command, options);
debugLog("$", cmd, ...args);
const [cmd, ...args] = command;
const stdin = options["stdin"];
const spawnOptions = {
cwd: options["cwd"] ?? process.cwd(),
timeout: options["timeout"] ?? undefined,
env: options["env"] ?? undefined,
stdio: ["ignore", "pipe", "pipe"],
stdio: [stdin ? "pipe" : "ignore", "pipe", "pipe"],
...options,
};
@@ -145,6 +228,16 @@ export async function spawn(command, options = {}) {
const result = new Promise((resolve, reject) => {
const subprocess = nodeSpawn(cmd, args, spawnOptions);
if (typeof stdin !== "undefined") {
subprocess.stdin?.on("error", error => {
if (error.code !== "EPIPE") {
reject(error);
}
});
subprocess.stdin?.write(stdin);
subprocess.stdin?.end();
}
subprocess.stdout?.on("data", chunk => {
stdout += chunk;
});
@@ -215,9 +308,9 @@ export async function spawnSafe(command, options) {
* @returns {SpawnResult}
*/
export function spawnSync(command, options = {}) {
debugLog("$", ...command);
const [cmd, ...args] = parseCommand(command, options);
debugLog("$", cmd, ...args);
const [cmd, ...args] = command;
const spawnOptions = {
cwd: options["cwd"] ?? process.cwd(),
timeout: options["timeout"] ?? undefined,
@@ -245,8 +338,8 @@ export function spawnSync(command, options = {}) {
} else {
exitCode = status ?? 1;
signalCode = signal || undefined;
stdout = stdoutBuffer.toString();
stderr = stderrBuffer.toString();
stdout = stdoutBuffer?.toString();
stderr = stderrBuffer?.toString();
}
if (exitCode !== 0 && isWindows) {
@@ -258,7 +351,7 @@ export function spawnSync(command, options = {}) {
if (error || signalCode || exitCode !== 0) {
const description = command.map(arg => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg)).join(" ");
const cause = error || stderr.trim() || stdout.trim() || undefined;
const cause = error || stderr?.trim() || stdout?.trim() || undefined;
if (signalCode) {
error = new Error(`Command killed with ${signalCode}: ${description}`, { cause });
@@ -376,6 +469,20 @@ export function getRepository(cwd) {
}
}
/**
* @param {string} [cwd]
* @returns {string | undefined}
*/
export function getRepositoryOwner(cwd) {
const repository = getRepository(cwd);
if (repository) {
const [owner] = repository.split("/");
if (owner) {
return owner;
}
}
}
/**
* @param {string} [cwd]
* @returns {string | undefined}
@@ -490,7 +597,7 @@ export function isMainBranch(cwd) {
*/
export function isPullRequest() {
if (isBuildkite) {
return getEnv("BUILDKITE_PULL_REQUEST", false) === "true";
return !isNaN(parseInt(getEnv("BUILDKITE_PULL_REQUEST", false)));
}
if (isGithubAction) {
@@ -656,7 +763,7 @@ export async function curl(url, options = {}) {
try {
if (filename && ok) {
const buffer = await response.arrayBuffer();
await writeFile(filename, new Uint8Array(buffer));
writeFile(filename, new Uint8Array(buffer));
} else if (arrayBuffer && ok) {
body = await response.arrayBuffer();
} else if (json && ok) {
@@ -721,7 +828,7 @@ export function readFile(filename, options = {}) {
}
const relativePath = relative(process.cwd(), absolutePath);
debugLog("cat", relativePath);
debugLog("$", "cat", relativePath);
let content;
try {
@@ -738,6 +845,53 @@ export function readFile(filename, options = {}) {
return content;
}
/**
* @param {string} filename
* @param {string | Buffer} content
* @param {object} [options]
* @param {number} [options.mode]
*/
export function writeFile(filename, content, options = {}) {
const parent = dirname(filename);
if (!existsSync(parent)) {
mkdirSync(parent, { recursive: true });
}
writeFileSync(filename, content);
if (options["mode"]) {
chmodSync(filename, options["mode"]);
}
}
/**
* @param {string | string[]} command
* @param {object} [options]
* @param {boolean} [options.required]
* @returns {string | undefined}
*/
export function which(command, options = {}) {
const commands = Array.isArray(command) ? command : [command];
const executables = isWindows ? commands.flatMap(name => [name, `${name}.exe`, `${name}.cmd`]) : commands;
const path = getEnv("PATH", false) || "";
const binPaths = path.split(isWindows ? ";" : ":");
for (const binPath of binPaths) {
for (const executable of executables) {
const executablePath = join(binPath, executable);
if (existsSync(executablePath)) {
return executablePath;
}
}
}
if (options["required"]) {
const description = commands.join(" or ");
throw new Error(`Command not found: ${description}`);
}
}
/**
* @param {string} [cwd]
* @param {string} [base]
@@ -746,10 +900,10 @@ export function readFile(filename, options = {}) {
*/
export async function getChangedFiles(cwd, base, head) {
const repository = getRepository(cwd);
base ||= getCommit(cwd);
head ||= `${base}^1`;
head ||= getCommit(cwd);
base ||= `${head}^1`;
const url = `https://api.github.com/repos/${repository}/compare/${head}...${base}`;
const url = `https://api.github.com/repos/${repository}/compare/${base}...${head}`;
const { error, body } = await curl(url, { json: true });
if (error) {
@@ -826,7 +980,7 @@ export function getBuildUrl() {
*/
export function getBuildLabel() {
if (isBuildkite) {
const label = getEnv("BUILDKITE_GROUP_LABEL", false) || getEnv("BUILDKITE_LABEL", false);
const label = getEnv("BUILDKITE_LABEL", false) || getEnv("BUILDKITE_GROUP_LABEL", false);
if (label) {
return label;
}
@@ -840,6 +994,22 @@ export function getBuildLabel() {
}
}
/**
* @returns {number}
*/
export function getBootstrapVersion() {
if (isWindows) {
return 0; // TODO
}
const scriptPath = join(import.meta.dirname, "bootstrap.sh");
const scriptContent = readFile(scriptPath, { cache: true });
const match = /# Version: (\d+)/.exec(scriptContent);
if (match) {
return parseInt(match[1]);
}
return 0;
}
/**
* @typedef {object} BuildArtifact
* @property {string} [job]
@@ -1013,6 +1183,17 @@ export async function getLastSuccessfulBuild() {
}
}
/**
* @param {string} filename
* @param {string} [cwd]
*/
export async function uploadArtifact(filename, cwd) {
if (isBuildkite) {
const relativePath = relative(cwd ?? process.cwd(), filename);
await spawnSafe(["buildkite-agent", "artifact", "upload", relativePath], { cwd, stdio: "inherit" });
}
}
/**
* @param {string} string
* @returns {string}
@@ -1021,6 +1202,17 @@ export function stripAnsi(string) {
return string.replace(/\u001b\[\d+m/g, "");
}
/**
* @param {string} string
* @returns {string}
*/
export function escapeYaml(string) {
if (/[:"{}[\],&*#?|\-<>=!%@`]/.test(string)) {
return `"${string.replace(/"/g, '\\"')}"`;
}
return string;
}
/**
* @param {string} string
* @returns {string}
@@ -1059,6 +1251,14 @@ export function escapeCodeBlock(string) {
return string.replace(/`/g, "\\`");
}
/**
* @param {string} string
* @returns {string}
*/
export function escapePowershell(string) {
return string.replace(/'/g, "''").replace(/`/g, "``");
}
/**
* @returns {string}
*/
@@ -1090,14 +1290,6 @@ export function tmpdir() {
return nodeTmpdir();
}
/**
* @param {string} string
* @returns {string}
*/
function escapePowershell(string) {
return string.replace(/'/g, "''").replace(/`/g, "``");
}
/**
* @param {string} filename
* @param {string} [output]
@@ -1159,24 +1351,79 @@ export function getArch() {
return parseArch(process.arch);
}
/**
* @returns {string}
*/
export function getKernel() {
const kernel = release();
const match = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(kernel);
if (match) {
const [, major, minor, patch] = match;
if (patch) {
return `${major}.${minor}.${patch}`;
}
return `${major}.${minor}`;
}
return kernel;
}
/**
* @returns {"musl" | "gnu" | undefined}
*/
export function getAbi() {
if (isLinux) {
const arch = getArch() === "x64" ? "x86_64" : "aarch64";
const muslLibPath = `/lib/ld-musl-${arch}.so.1`;
if (existsSync(muslLibPath)) {
if (!isLinux) {
return;
}
if (existsSync("/etc/alpine-release")) {
return "musl";
}
const arch = getArch() === "x64" ? "x86_64" : "aarch64";
const muslLibPath = `/lib/ld-musl-${arch}.so.1`;
if (existsSync(muslLibPath)) {
return "musl";
}
const gnuLibPath = `/lib/ld-linux-${arch}.so.2`;
if (existsSync(gnuLibPath)) {
return "gnu";
}
const { error, stdout } = spawnSync(["ldd", "--version"]);
if (!error) {
if (/musl/i.test(stdout)) {
return "musl";
}
const gnuLibPath = `/lib/ld-linux-${arch}.so.2`;
if (existsSync(gnuLibPath)) {
if (/gnu|glibc/i.test(stdout)) {
return "gnu";
}
}
}
/**
* @returns {string | undefined}
*/
export function getAbiVersion() {
if (!isLinux) {
return;
}
const { error, stdout } = spawnSync(["ldd", "--version"]);
if (!error) {
const match = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(stdout);
if (match) {
const [, major, minor, patch] = match;
if (patch) {
return `${major}.${minor}.${patch}`;
}
return `${major}.${minor}`;
}
}
}
/**
* @typedef {object} Target
* @property {"darwin" | "linux" | "windows"} os
@@ -1346,17 +1593,24 @@ export async function downloadTarget(target, release) {
}
/**
* @returns {string | undefined}
* @returns {string}
*/
export function getTailscaleIp() {
let tailscale = "tailscale";
export function getTailscale() {
if (isMacOS) {
const tailscaleApp = "/Applications/Tailscale.app/Contents/MacOS/tailscale";
if (existsSync(tailscaleApp)) {
tailscale = tailscaleApp;
return tailscaleApp;
}
}
return "tailscale";
}
/**
* @returns {string | undefined}
*/
export function getTailscaleIp() {
const tailscale = getTailscale();
const { error, stdout } = spawnSync([tailscale, "ip", "--1"]);
if (!error) {
return stdout.trim();
@@ -1405,7 +1659,31 @@ export function getUsername() {
}
/**
* @returns {string}
* @typedef {object} User
* @property {string} username
* @property {number} uid
* @property {number} gid
*/
/**
* @param {string} username
* @returns {Promise<User>}
*/
export async function getUser(username) {
if (isWindows) {
throw new Error("TODO: Windows");
}
const [uid, gid] = await Promise.all([
spawnSafe(["id", "-u", username]).then(({ stdout }) => parseInt(stdout.trim())),
spawnSafe(["id", "-g", username]).then(({ stdout }) => parseInt(stdout.trim())),
]);
return { username, uid, gid };
}
/**
* @returns {string | undefined}
*/
export function getDistro() {
if (isMacOS) {
@@ -1413,10 +1691,15 @@ export function getDistro() {
}
if (isLinux) {
const alpinePath = "/etc/alpine-release";
if (existsSync(alpinePath)) {
return "alpine";
}
const releasePath = "/etc/os-release";
if (existsSync(releasePath)) {
const releaseFile = readFile(releasePath, { cache: true });
const match = releaseFile.match(/ID=\"(.*)\"/);
const match = releaseFile.match(/^ID=\"?(.*)\"?/m);
if (match) {
return match[1];
}
@@ -1424,10 +1707,8 @@ export function getDistro() {
const { error, stdout } = spawnSync(["lsb_release", "-is"]);
if (!error) {
return stdout.trim();
return stdout.trim().toLowerCase();
}
return "Linux";
}
if (isWindows) {
@@ -1435,17 +1716,13 @@ export function getDistro() {
if (!error) {
return stdout.trim();
}
return "Windows";
}
return `${process.platform} ${process.arch}`;
}
/**
* @returns {string | undefined}
*/
export function getDistroRelease() {
export function getDistroVersion() {
if (isMacOS) {
const { error, stdout } = spawnSync(["sw_vers", "-productVersion"]);
if (!error) {
@@ -1454,10 +1731,20 @@ export function getDistroRelease() {
}
if (isLinux) {
const alpinePath = "/etc/alpine-release";
if (existsSync(alpinePath)) {
const release = readFile(alpinePath, { cache: true }).trim();
if (release.includes("_")) {
const [version] = release.split("_");
return `${version}-edge`;
}
return release;
}
const releasePath = "/etc/os-release";
if (existsSync(releasePath)) {
const releaseFile = readFile(releasePath, { cache: true });
const match = releaseFile.match(/VERSION_ID=\"(.*)\"/);
const match = releaseFile.match(/^VERSION_ID=\"?(.*)\"?/m);
if (match) {
return match[1];
}
@@ -1477,6 +1764,231 @@ export function getDistroRelease() {
}
}
/**
* @typedef {"aws" | "google"} Cloud
*/
/** @type {Cloud | undefined} */
let detectedCloud;
/**
* @returns {Promise<boolean | undefined>}
*/
export async function isAws() {
if (typeof detectedCloud === "string") {
return detectedCloud === "aws";
}
async function checkAws() {
if (isLinux) {
const kernel = release();
if (kernel.endsWith("-aws")) {
return true;
}
const { error: systemdError, stdout } = await spawn(["systemd-detect-virt"]);
if (!systemdError) {
if (stdout.includes("amazon")) {
return true;
}
}
const dmiPath = "/sys/devices/virtual/dmi/id/board_asset_tag";
if (existsSync(dmiPath)) {
const dmiFile = readFileSync(dmiPath, { encoding: "utf-8" });
if (dmiFile.startsWith("i-")) {
return true;
}
}
}
if (isWindows) {
const executionEnv = getEnv("AWS_EXECUTION_ENV", false);
if (executionEnv === "EC2") {
return true;
}
const { error: powershellError, stdout } = await spawn([
"powershell",
"-Command",
"Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object Manufacturer",
]);
if (!powershellError) {
return stdout.includes("Amazon");
}
}
const instanceId = await getCloudMetadata("instance-id", "google");
if (instanceId) {
return true;
}
}
if (await checkAws()) {
detectedCloud = "aws";
return true;
}
}
/**
* @returns {Promise<boolean | undefined>}
*/
export async function isGoogleCloud() {
if (typeof detectedCloud === "string") {
return detectedCloud === "google";
}
async function detectGoogleCloud() {
if (isLinux) {
const vendorPaths = [
"/sys/class/dmi/id/sys_vendor",
"/sys/class/dmi/id/bios_vendor",
"/sys/class/dmi/id/product_name",
];
for (const vendorPath of vendorPaths) {
if (existsSync(vendorPath)) {
const vendorFile = readFileSync(vendorPath, { encoding: "utf-8" });
if (vendorFile.includes("Google")) {
return true;
}
}
}
}
const instanceId = await getCloudMetadata("id", "google");
if (instanceId) {
return true;
}
}
if (await detectGoogleCloud()) {
detectedCloud = "google";
return true;
}
}
/**
* @returns {Promise<Cloud | undefined>}
*/
export async function getCloud() {
if (typeof detectedCloud === "string") {
return detectedCloud;
}
if (await isAws()) {
return "aws";
}
if (await isGoogleCloud()) {
return "google";
}
}
/**
* @param {string | Record<Cloud, string>} name
* @param {Cloud} [cloud]
* @returns {Promise<string | undefined>}
*/
export async function getCloudMetadata(name, cloud) {
cloud ??= await getCloud();
if (!cloud) {
return;
}
if (typeof name === "object") {
name = name[cloud];
}
let url;
let headers;
if (cloud === "aws") {
url = new URL(name, "http://169.254.169.254/latest/meta-data/");
} else if (cloud === "google") {
url = new URL(name, "http://metadata.google.internal/computeMetadata/v1/instance/");
headers = { "Metadata-Flavor": "Google" };
} else {
throw new Error(`Unsupported cloud: ${inspect(cloud)}`);
}
const { error, body } = await curl(url, { headers, retries: 0 });
if (error) {
return;
}
return body.trim();
}
/**
* @param {string} tag
* @param {Cloud} [cloud]
* @returns {Promise<string | undefined>}
*/
export function getCloudMetadataTag(tag, cloud) {
const metadata = {
"aws": `tags/instance/${tag}`,
};
return getCloudMetadata(metadata, cloud);
}
/**
* @param {string} name
* @returns {Promise<string | undefined>}
*/
export async function getBuildMetadata(name) {
if (isBuildkite) {
const { error, stdout } = await spawn(["buildkite-agent", "meta-data", "get", name]);
if (!error) {
const value = stdout.trim();
if (value) {
return value;
}
}
}
}
/**
* @typedef ConnectOptions
* @property {string} hostname
* @property {number} port
* @property {number} [retries]
*/
/**
* @param {ConnectOptions} options
* @returns {Promise<Error | undefined>}
*/
export async function waitForPort(options) {
const { hostname, port, retries = 10 } = options;
let cause;
for (let i = 0; i < retries; i++) {
if (cause) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
const connected = new Promise((resolve, reject) => {
const socket = connect({ host: hostname, port });
socket.on("connect", () => {
socket.destroy();
resolve();
});
socket.on("error", error => {
socket.destroy();
reject(error);
});
});
try {
return await connected;
} catch (error) {
cause = error;
}
}
return cause;
}
/**
* @returns {Promise<number | undefined>}
*/
@@ -1522,6 +2034,52 @@ export function getGithubUrl() {
return new URL(getEnv("GITHUB_SERVER_URL", false) || "https://github.com");
}
/**
* @param {object} obj
* @param {number} indent
* @returns {string}
*/
export function toYaml(obj, indent = 0) {
const spaces = " ".repeat(indent);
let result = "";
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
continue;
}
if (value === null) {
result += `${spaces}${key}: null\n`;
continue;
}
if (Array.isArray(value)) {
result += `${spaces}${key}:\n`;
value.forEach(item => {
if (typeof item === "object" && item !== null) {
result += `${spaces}- \n${toYaml(item, indent + 2)
.split("\n")
.map(line => `${spaces} ${line}`)
.join("\n")}\n`;
} else {
result += `${spaces}- ${item}\n`;
}
});
continue;
}
if (typeof value === "object") {
result += `${spaces}${key}:\n${toYaml(value, indent + 2)}`;
continue;
}
if (
typeof value === "string" &&
(value.includes(":") || value.includes("#") || value.includes("'") || value.includes('"') || value.includes("\n"))
) {
result += `${spaces}${key}: "${value.replace(/"/g, '\\"')}"\n`;
continue;
}
result += `${spaces}${key}: ${value}\n`;
}
return result;
}
/**
* @param {string} title
* @param {function} [fn]
@@ -1561,11 +2119,13 @@ export function printEnvironment() {
startGroup("Machine", () => {
console.log("Operating System:", getOs());
console.log("Architecture:", getArch());
console.log("Kernel:", getKernel());
if (isLinux) {
console.log("ABI:", getAbi());
console.log("ABI Version:", getAbiVersion());
}
console.log("Distro:", getDistro());
console.log("Release:", getDistroRelease());
console.log("Distro Version:", getDistroVersion());
console.log("Hostname:", getHostname());
if (isCI) {
console.log("Tailscale IP:", getTailscaleIp());

View File

@@ -1,248 +0,0 @@
const std = @import("std");
const bun = @import("root").bun;
const assert = bun.assert;
const mem = std.mem;
const Allocator = std.mem.Allocator;
/// This allocator takes an existing allocator, wraps it, and provides an interface
/// where you can allocate without freeing, and then free it all together.
pub const ArenaAllocator = struct {
child_allocator: Allocator,
state: State,
/// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator
/// as a memory-saving optimization.
pub const State = struct {
buffer_list: std.SinglyLinkedList(usize) = .{},
end_index: usize = 0,
pub fn promote(self: State, child_allocator: Allocator) ArenaAllocator {
return .{
.child_allocator = child_allocator,
.state = self,
};
}
};
pub fn allocator(self: *ArenaAllocator) Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
const BufNode = std.SinglyLinkedList(usize).Node;
pub fn init(child_allocator: Allocator) ArenaAllocator {
return (State{}).promote(child_allocator);
}
pub fn deinit(self: ArenaAllocator) void {
// NOTE: When changing this, make sure `reset()` is adjusted accordingly!
var it = self.state.buffer_list.first;
while (it) |node| {
// this has to occur before the free because the free frees node
const next_it = node.next;
const align_bits = std.math.log2_int(usize, @alignOf(BufNode));
const alloc_buf = @as([*]u8, @ptrCast(node))[0..node.data];
self.child_allocator.rawFree(alloc_buf, align_bits, @returnAddress());
it = next_it;
}
}
pub const ResetMode = union(enum) {
/// Releases all allocated memory in the arena.
free_all,
/// This will pre-heat the arena for future allocations by allocating a
/// large enough buffer for all previously done allocations.
/// Preheating will speed up the allocation process by invoking the backing allocator
/// less often than before. If `reset()` is used in a loop, this means that after the
/// biggest operation, no memory allocations are performed anymore.
retain_capacity,
/// This is the same as `retain_capacity`, but the memory will be shrunk to
/// this value if it exceeds the limit.
retain_with_limit: usize,
};
/// Queries the current memory use of this arena.
/// This will **not** include the storage required for internal keeping.
pub fn queryCapacity(self: ArenaAllocator) usize {
var size: usize = 0;
var it = self.state.buffer_list.first;
while (it) |node| : (it = node.next) {
// Compute the actually allocated size excluding the
// linked list node.
size += node.data - @sizeOf(BufNode);
}
return size;
}
/// Resets the arena allocator and frees all allocated memory.
///
/// `mode` defines how the currently allocated memory is handled.
/// See the variant documentation for `ResetMode` for the effects of each mode.
///
/// The function will return whether the reset operation was successful or not.
/// If the reallocation failed `false` is returned. The arena will still be fully
/// functional in that case, all memory is released. Future allocations just might
/// be slower.
///
/// NOTE: If `mode` is `free_mode`, the function will always return `true`.
pub fn reset(self: *ArenaAllocator, mode: ResetMode) bool {
// Some words on the implementation:
// The reset function can be implemented with two basic approaches:
// - Counting how much bytes were allocated since the last reset, and storing that
// information in State. This will make reset fast and alloc only a teeny tiny bit
// slower.
// - Counting how much bytes were allocated by iterating the chunk linked list. This
// will make reset slower, but alloc() keeps the same speed when reset() as if reset()
// would not exist.
//
// The second variant was chosen for implementation, as with more and more calls to reset(),
// the function will get faster and faster. At one point, the complexity of the function
// will drop to amortized O(1), as we're only ever having a single chunk that will not be
// reallocated, and we're not even touching the backing allocator anymore.
//
// Thus, only the first hand full of calls to reset() will actually need to iterate the linked
// list, all future calls are just taking the first node, and only resetting the `end_index`
// value.
const requested_capacity = switch (mode) {
.retain_capacity => self.queryCapacity(),
.retain_with_limit => |limit| @min(limit, self.queryCapacity()),
.free_all => 0,
};
if (requested_capacity == 0) {
// just reset when we don't have anything to reallocate
self.deinit();
self.state = State{};
return true;
}
const total_size = requested_capacity + @sizeOf(BufNode);
const align_bits = std.math.log2_int(usize, @alignOf(BufNode));
// Free all nodes except for the last one
var it = self.state.buffer_list.first;
const maybe_first_node = while (it) |node| {
// this has to occur before the free because the free frees node
const next_it = node.next;
if (next_it == null)
break node;
const alloc_buf = @as([*]u8, @ptrCast(node))[0..node.data];
self.child_allocator.rawFree(alloc_buf, align_bits, @returnAddress());
it = next_it;
} else null;
assert(maybe_first_node == null or maybe_first_node.?.next == null);
// reset the state before we try resizing the buffers, so we definitely have reset the arena to 0.
self.state.end_index = 0;
if (maybe_first_node) |first_node| {
self.state.buffer_list.first = first_node;
// perfect, no need to invoke the child_allocator
if (first_node.data == total_size)
return true;
const first_alloc_buf = @as([*]u8, @ptrCast(first_node))[0..first_node.data];
if (self.child_allocator.rawResize(first_alloc_buf, align_bits, total_size, @returnAddress())) {
// successful resize
first_node.data = total_size;
} else {
// manual realloc
const new_ptr = self.child_allocator.rawAlloc(total_size, align_bits, @returnAddress()) orelse {
// we failed to preheat the arena properly, signal this to the user.
return false;
};
self.child_allocator.rawFree(first_alloc_buf, align_bits, @returnAddress());
const node: *BufNode = @ptrCast(@alignCast(new_ptr));
node.* = .{ .data = total_size };
self.state.buffer_list.first = node;
}
}
return true;
}
fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) ?*BufNode {
const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16);
const big_enough_len = prev_len + actual_min_size;
const len = big_enough_len + big_enough_len / 2;
const log2_align = comptime std.math.log2_int(usize, @alignOf(BufNode));
const ptr = self.child_allocator.rawAlloc(len, log2_align, @returnAddress()) orelse
return null;
const buf_node: *BufNode = @ptrCast(@alignCast(ptr));
buf_node.* = .{ .data = len };
self.state.buffer_list.prepend(buf_node);
self.state.end_index = 0;
return buf_node;
}
fn alloc(ctx: *anyopaque, n: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 {
const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));
_ = ra;
const ptr_align = @as(usize, 1) << @as(Allocator.Log2Align, @intCast(log2_ptr_align));
var cur_node = if (self.state.buffer_list.first) |first_node|
first_node
else
(self.createNode(0, n + ptr_align) orelse return null);
while (true) {
const cur_alloc_buf = @as([*]u8, @ptrCast(cur_node))[0..cur_node.data];
const cur_buf = cur_alloc_buf[@sizeOf(BufNode)..];
const addr = @intFromPtr(cur_buf.ptr) + self.state.end_index;
const adjusted_addr = mem.alignForward(usize, addr, ptr_align);
const adjusted_index = self.state.end_index + (adjusted_addr - addr);
const new_end_index = adjusted_index + n;
if (new_end_index <= cur_buf.len) {
const result = cur_buf[adjusted_index..new_end_index];
self.state.end_index = new_end_index;
return result.ptr;
}
const bigger_buf_size = @sizeOf(BufNode) + new_end_index;
const log2_align = comptime std.math.log2_int(usize, @alignOf(BufNode));
if (self.child_allocator.rawResize(cur_alloc_buf, log2_align, bigger_buf_size, @returnAddress())) {
cur_node.data = bigger_buf_size;
} else {
// Allocate a new node if that's not possible
cur_node = self.createNode(cur_buf.len, n + ptr_align) orelse return null;
}
}
}
fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));
_ = log2_buf_align;
_ = ret_addr;
const cur_node = self.state.buffer_list.first orelse return false;
const cur_buf = @as([*]u8, @ptrCast(cur_node))[@sizeOf(BufNode)..cur_node.data];
if (@intFromPtr(cur_buf.ptr) + self.state.end_index != @intFromPtr(buf.ptr) + buf.len) {
// It's not the most recent allocation, so it cannot be expanded,
// but it's fine if they want to make it smaller.
return new_len <= buf.len;
}
if (buf.len >= new_len) {
self.state.end_index -= buf.len - new_len;
return true;
} else if (cur_buf.len - self.state.end_index >= new_len - buf.len) {
self.state.end_index += new_len - buf.len;
return true;
} else {
return false;
}
}
fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
_ = log2_buf_align;
_ = ret_addr;
const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));
const cur_node = self.state.buffer_list.first orelse return;
const cur_buf = @as([*]u8, @ptrCast(cur_node))[@sizeOf(BufNode)..cur_node.data];
if (@intFromPtr(cur_buf.ptr) + self.state.end_index == @intFromPtr(buf.ptr) + buf.len) {
self.state.end_index -= buf.len;
}
}
};

View File

@@ -118,6 +118,10 @@ pub fn exit(code: u32) noreturn {
switch (Environment.os) {
.mac => std.c.exit(@bitCast(code)),
.windows => {
Bun__onExit();
std.os.windows.kernel32.ExitProcess(code);
},
else => bun.C.quick_exit(@bitCast(code)),
}
}

View File

@@ -177,6 +177,11 @@ pub fn OverflowList(comptime ValueType: type, comptime count: comptime_int) type
};
}
/// "Formerly-BSSList"
/// It's not actually BSS anymore.
///
/// We do keep a pointer to it globally, but because the data is not zero-initialized, it ends up taking space in the object file.
/// We don't want to spend 1-2 MB on these structs.
pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
const count = _count * 2;
const max_index = count - 1;
@@ -205,7 +210,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
backing_buf: [count]ValueType = undefined,
used: u32 = 0,
pub var instance: Self = undefined;
pub var instance: *Self = undefined;
pub var loaded = false;
pub inline fn blockIndex(index: u31) usize {
@@ -214,7 +219,8 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
instance = Self{
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.* = Self{
.allocator = allocator,
.tail = OverflowBlock{},
};
@@ -222,7 +228,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
loaded = true;
}
return &instance;
return instance;
}
pub fn isOverflowing() bool {
@@ -289,7 +295,7 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
slice_buf: [count][]const u8 = undefined,
slice_buf_used: u16 = 0,
mutex: Mutex = .{},
pub var instance: Self = undefined;
pub var instance: *Self = undefined;
var loaded: bool = false;
// only need the mutex on append
@@ -299,14 +305,15 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
instance = Self{
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.* = Self{
.allocator = allocator,
.backing_buf_used = 0,
};
loaded = true;
}
return &instance;
return instance;
}
pub inline fn isOverflowing() bool {
@@ -469,20 +476,21 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
backing_buf: [count]ValueType = undefined,
backing_buf_used: u16 = 0,
pub var instance: Self = undefined;
pub var instance: *Self = undefined;
var loaded: bool = false;
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
instance = Self{
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.* = Self{
.index = IndexMap{},
.allocator = allocator,
};
loaded = true;
}
return &instance;
return instance;
}
pub fn isOverflowing() bool {
@@ -621,18 +629,19 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
key_list_overflow: OverflowList([]u8, count / 4) = OverflowList([]u8, count / 4){},
const Self = @This();
pub var instance: Self = undefined;
pub var instance: *Self = undefined;
pub var instance_loaded = false;
pub fn init(allocator: std.mem.Allocator) *Self {
if (!instance_loaded) {
instance = Self{
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.* = Self{
.map = BSSMapType.init(allocator),
};
instance_loaded = true;
}
return &instance;
return instance;
}
pub fn isOverflowing() bool {

View File

@@ -2,38 +2,50 @@
#include "JSNextTickQueue.h"
#include "JavaScriptCore/GlobalObjectMethodTable.h"
#include "JavaScriptCore/JSInternalPromise.h"
#include "ProcessIdentifier.h"
#include "headers-handwritten.h"
#include "JavaScriptCore/JSModuleLoader.h"
#include "JavaScriptCore/Completion.h"
extern "C" BunString BakeProdResolve(JSC::JSGlobalObject*, BunString a, BunString b);
namespace Bake {
extern "C" void BakeInitProcessIdentifier()
{
// assert is on main thread
WebCore::Process::identifier();
}
JSC::JSInternalPromise*
bakeModuleLoaderImportModule(JSC::JSGlobalObject* jsGlobalObject,
JSC::JSModuleLoader*, JSC::JSString* moduleNameValue,
bakeModuleLoaderImportModule(JSC::JSGlobalObject* global,
JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleNameValue,
JSC::JSValue parameters,
const JSC::SourceOrigin& sourceOrigin)
{
// TODO: forward this to the runtime?
JSC::VM& vm = jsGlobalObject->vm();
WTF::String keyString = moduleNameValue->getString(jsGlobalObject);
auto err = JSC::createTypeError(
jsGlobalObject,
WTF::makeString(
"Dynamic import to '"_s, keyString,
"' should have been replaced with a hook into the module runtime"_s));
auto* promise = JSC::JSInternalPromise::create(
vm, jsGlobalObject->internalPromiseStructure());
promise->reject(jsGlobalObject, err);
return promise;
}
WTF::String keyString = moduleNameValue->getString(global);
if (keyString.startsWith("bake:/"_s)) {
JSC::VM& vm = global->vm();
return JSC::importModule(global, JSC::Identifier::fromString(vm, keyString),
JSC::jsUndefined(), parameters, JSC::jsUndefined());
}
extern "C" BunString BakeProdResolve(JSC::JSGlobalObject*, BunString a, BunString b);
if (!sourceOrigin.isNull() && sourceOrigin.string().startsWith("bake:/"_s)) {
JSC::VM& vm = global->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
WTF::String refererString = sourceOrigin.string();
WTF::String keyString = moduleNameValue->getString(global);
if (!keyString) {
auto promise = JSC::JSInternalPromise::create(vm, global->internalPromiseStructure());
promise->reject(global, JSC::createError(global, "import() requires a string"_s));
return promise;
}
BunString result = BakeProdResolve(global, Bun::toString(refererString), Bun::toString(keyString));
RETURN_IF_EXCEPTION(scope, nullptr);
return JSC::importModule(global, JSC::Identifier::fromString(vm, result.toWTFString()),
JSC::jsUndefined(), parameters, JSC::jsUndefined());
}
// Use Zig::GlobalObject's function
return jsCast<Zig::GlobalObject*>(global)->moduleLoaderImportModule(global, moduleLoader, moduleNameValue, parameters, sourceOrigin);
}
JSC::Identifier bakeModuleLoaderResolve(JSC::JSGlobalObject* jsGlobal,
JSC::JSModuleLoader* loader, JSC::JSValue key,
@@ -43,19 +55,21 @@ JSC::Identifier bakeModuleLoaderResolve(JSC::JSGlobalObject* jsGlobal,
JSC::VM& vm = global->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (global->isProduction()) {
WTF::String keyString = key.toWTFString(global);
ASSERT(referrer.isString());
WTF::String refererString = jsCast<JSC::JSString*>(referrer)->getString(global);
WTF::String keyString = key.toWTFString(global);
RETURN_IF_EXCEPTION(scope, vm.propertyNames->emptyIdentifier);
if (refererString.startsWith("bake:/"_s) || (refererString == "."_s && keyString.startsWith("bake:/"_s))) {
BunString result = BakeProdResolve(global, Bun::toString(referrer.getString(global)), Bun::toString(keyString));
RETURN_IF_EXCEPTION(scope, vm.propertyNames->emptyIdentifier);
ASSERT(referrer.isString());
auto refererString = jsCast<JSC::JSString*>(referrer)->value(global);
BunString result = BakeProdResolve(global, Bun::toString(referrer.getString(global)), Bun::toString(keyString));
return JSC::Identifier::fromString(vm, result.toWTFString(BunString::ZeroCopy));
} else {
JSC::throwTypeError(global, scope, "External imports are not allowed in Bun Bake's dev server. This is a bug in Bun's bundler."_s);
return vm.propertyNames->emptyIdentifier;
}
// Use Zig::GlobalObject's function
return Zig::GlobalObject::moduleLoaderResolve(jsGlobal, loader, key, referrer, origin);
}
#define INHERIT_HOOK_METHOD(name) \
@@ -100,12 +114,12 @@ void GlobalObject::finishCreation(JSC::VM& vm)
ASSERT(inherits(info()));
}
struct BunVirtualMachine;
extern "C" BunVirtualMachine* Bun__getVM();
// A lot of this function is taken from 'Zig__GlobalObject__create'
// TODO: remove this entire method
extern "C" GlobalObject* BakeCreateDevGlobal(DevServer* owner,
void* console)
extern "C" GlobalObject* BakeCreateProdGlobal(void* console)
{
JSC::VM& vm = JSC::VM::create(JSC::HeapType::Large).leakRef();
vm.heap.acquireAccess();
@@ -119,7 +133,6 @@ extern "C" GlobalObject* BakeCreateDevGlobal(DevServer* owner,
if (!global)
BUN_PANIC("Failed to create BakeGlobalObject");
global->m_devServer = owner;
global->m_bunVM = bunVM;
JSC::gcProtect(global);
@@ -142,25 +155,4 @@ extern "C" GlobalObject* BakeCreateDevGlobal(DevServer* owner,
return global;
}
extern "C" GlobalObject* BakeCreateProdGlobal(JSC::VM* vm, void* console)
{
JSC::JSLockHolder locker(vm);
BunVirtualMachine* bunVM = Bun__getVM();
JSC::Structure* structure = GlobalObject::createStructure(*vm);
GlobalObject* global = GlobalObject::create(*vm, structure, &GlobalObject::s_globalObjectMethodTable);
if (!global)
BUN_PANIC("Failed to create BakeGlobalObject");
global->m_devServer = nullptr;
global->m_bunVM = bunVM;
JSC::gcProtect(global);
global->setConsole(console);
global->setStackTraceLimit(10); // Node.js defaults to 10
return global;
}
}; // namespace Bake

View File

@@ -4,17 +4,10 @@
namespace Bake {
struct DevServer; // DevServer.zig
struct Route; // DevServer.zig
struct BunVirtualMachine;
class GlobalObject : public Zig::GlobalObject {
public:
using Base = Zig::GlobalObject;
/// Null if in production
DevServer* m_devServer;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
@@ -31,16 +24,10 @@ public:
static const JSC::GlobalObjectMethodTable s_globalObjectMethodTable;
static GlobalObject* create(JSC::VM& vm, JSC::Structure* structure, const JSC::GlobalObjectMethodTable* methodTable);
ALWAYS_INLINE bool isProduction() const { return !m_devServer; }
void finishCreation(JSC::VM& vm);
GlobalObject(JSC::VM& vm, JSC::Structure* structure, const JSC::GlobalObjectMethodTable* methodTable)
: Zig::GlobalObject(vm, structure, methodTable) { }
};
// Zig API
extern "C" void KitInitProcessIdentifier();
extern "C" GlobalObject* KitCreateDevGlobal(DevServer* owner, void* console);
}; // namespace Kit

View File

@@ -6,25 +6,33 @@
namespace Bake {
extern "C" JSC::JSPromise* BakeRenderRoutesForProd(
extern "C" JSC::JSPromise* BakeRenderRoutesForProdStatic(
JSC::JSGlobalObject* global,
BunString outbase,
JSC::JSValue renderStaticCallback,
BunString outBase,
JSC::JSValue allServerFiles,
JSC::JSValue renderStatic,
JSC::JSValue clientEntryUrl,
JSC::JSValue pattern,
JSC::JSValue files,
JSC::JSValue patterns,
JSC::JSValue typeAndFlags,
JSC::JSValue sourceRouteFiles,
JSC::JSValue paramInformation,
JSC::JSValue styles)
{
JSC::VM& vm = global->vm();
JSC::JSFunction* cb = JSC::JSFunction::create(vm, global, WebCore::bakeRenderRoutesForProdCodeGenerator(vm), global);
JSC::JSFunction* cb = JSC::JSFunction::create(vm, global, WebCore::bakeRenderRoutesForProdStaticCodeGenerator(vm), global);
JSC::CallData callData = JSC::getCallData(cb);
JSC::MarkedArgumentBuffer args;
args.append(JSC::jsString(vm, outbase.toWTFString()));
args.append(renderStaticCallback);
args.append(JSC::jsString(vm, outBase.toWTFString()));
args.append(allServerFiles);
args.append(renderStatic);
args.append(clientEntryUrl);
args.append(pattern);
args.append(files);
args.append(patterns);
args.append(typeAndFlags);
args.append(sourceRouteFiles);
args.append(paramInformation);
args.append(styles);
NakedPtr<JSC::Exception> returnedException = nullptr;

View File

@@ -8,33 +8,40 @@
#include "JavaScriptCore/JSLock.h"
#include "JavaScriptCore/JSMap.h"
#include "JavaScriptCore/JSModuleLoader.h"
#include "JavaScriptCore/JSModuleRecord.h"
#include "JavaScriptCore/JSString.h"
#include "JavaScriptCore/JSModuleNamespaceObject.h"
#include "ImportMetaObject.h"
namespace Bake {
extern "C" LoadServerCodeResult BakeLoadInitialServerCode(GlobalObject* global, BunString source) {
extern "C" JSC::EncodedJSValue BakeLoadInitialServerCode(GlobalObject* global, BunString source, bool separateSSRGraph) {
JSC::VM& vm = global->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
String string = "bake://server.js"_s;
JSC::JSString* key = JSC::jsString(vm, string);
String string = "bake://server-runtime.js"_s;
JSC::SourceOrigin origin = JSC::SourceOrigin(WTF::URL(string));
JSC::SourceCode sourceCode = JSC::SourceCode(DevSourceProvider::create(
source.toWTFString(),
origin,
WTFMove(string),
WTF::TextPosition(),
JSC::SourceProviderSourceType::Module
JSC::SourceProviderSourceType::Program
));
global->moduleLoader()->provideFetch(global, key, sourceCode);
RETURN_IF_EXCEPTION(scope, {});
JSC::JSInternalPromise* internalPromise = global->moduleLoader()->loadAndEvaluateModule(global, key, JSC::jsUndefined(), JSC::jsUndefined());
RETURN_IF_EXCEPTION(scope, {});
JSC::JSValue fnValue = vm.interpreter.executeProgram(sourceCode, global, global);
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode({}));
return { internalPromise, key };
RELEASE_ASSERT(fnValue);
JSC::JSFunction* fn = jsCast<JSC::JSFunction*>(fnValue);
JSC::CallData callData = JSC::getCallData(fn);
JSC::MarkedArgumentBuffer args;
args.append(JSC::jsBoolean(separateSSRGraph)); // separateSSRGraph
args.append(Zig::ImportMetaObject::create(global, "bake://server-runtime.js"_s)); // importMeta
return JSC::JSValue::encode(JSC::call(global, fn, callData, JSC::jsUndefined(), args));
}
extern "C" JSC::JSInternalPromise* BakeLoadModuleByKey(GlobalObject* global, JSC::JSString* key) {

View File

@@ -6,11 +6,6 @@
namespace Bake {
struct LoadServerCodeResult {
JSC::JSInternalPromise* promise;
JSC::JSString* key;
};
class DevSourceProvider final : public JSC::StringSourceProvider {
public:
static Ref<DevSourceProvider> create(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

487
src/bake/bake.d.ts vendored
View File

@@ -1,75 +1,129 @@
declare module "bun" {
declare function wipDevServerExpectHugeBreakingChanges(options: Bake.Options): never;
// This API is under heavy development. See #bake in the Bun Discord for more info.
// Definitions that are commented out are planned but not implemented.
//
// To use, add a TypeScript reference comment mentioning this file:
// /// <reference path="/path/to/bun/src/bake/bake.d.ts" />
declare module "bun" {
type Awaitable<T> = T | Promise<T>;
declare namespace Bake {
interface Options {
/** Will be replaced by fileSystemRouters */
routes: {}[];
/**
* Bun provides built-in support for using React as a framework by
* passing 'react-server-components' as the framework name.
* Bun provides built-in support for using React as a framework by passing
* 'react' as the framework name. Otherwise, frameworks are config objects.
*
* Has external dependencies:
* External dependencies:
* ```
* bun i react@experimental react-dom@experimental react-server-dom-webpack@experimental react-refresh@experimental
* ```
*/
framework: Framework | "react-server-components";
framework: Framework | "react";
// Note: To contribute to 'bun-framework-react', it can be run from this file:
// https://github.com/oven-sh/bun/blob/main/src/bake/bun-framework-react/index.ts
/**
* Route patterns must be statically known.
* TODO: Static at dev-server start is bad and this API must be revisited
* A subset of the options from Bun.build can be configured. Keep in mind,
* your framework may set different defaults.
*
* @default {}
*/
routes: Record<RoutePattern, RouteOptions>;
// TODO: many other options
bundlerOptions?: BundlerOptions | undefined;
}
/** Bake only allows a subset of options from `Bun.build` */
type BuildConfigSubset = Pick<
BuildConfig,
"conditions" | "plugins" | "define" | "loader" | "ignoreDCEAnnotations" | "drop"
// - format is not allowed because it is set to an internal "hmr" format
// - entrypoints/outfile/outdir doesnt make sense to set
// - disabling sourcemap is not allowed because it makes code impossible to debug
// - enabling minifyIdentifiers in dev is not allowed because some generated code does not support it
// - publicPath is set elsewhere (TODO:)
// - emitDCEAnnotations is not useful
// - banner and footer do not make sense in these multi-file builds
// - experimentalCss cannot be disabled
// - disabling external would make it exclude imported files.
// TODO: jsx customization
// TODO: chunk naming
>;
type BundlerOptions = BuildConfigSubset & {
/** Customize the build options of the client-side build */
client?: BuildConfigSubset;
/** Customize the build options of the server build */
server?: BuildConfigSubset;
/** Customize the build options of the separated SSR graph */
ssr?: BuildConfigSubset;
};
/**
* A "Framework" in our eyes is simply a set of bundler options that a
* framework author would set in order to integrate framework code with the
* application. Many of the configuration options are paths, which are
* resolved as import specifiers. The first thing the bundler does is
* ensure that all import specifiers are fully resolved.
* resolved as import specifiers.
*/
interface Framework {
/**
* This file is the true entrypoint of the server application. This module
* must `export default` a fetch function, which takes a request and the
* bundled route module, and returns a response. See `ServerEntryPoint`
*
* When `serverComponents` is configured, this can access the component
* manifest using the special 'bun:bake/server' import:
*
* import { serverManifest } from 'bun:bake/server'
* Customize the bundler options. Plugins in this array are merged
* with any plugins the user has.
* @default {}
*/
serverEntryPoint: ImportSource;
bundlerOptions?: BundlerOptions | undefined;
/**
* This file is the true entrypoint of the client application.
*
* When `serverComponents` is configured, this can access the component
* manifest using the special 'bun:bake/client' import:
*
* import { clientManifest } from 'bun:bake/client'
* The translation of files to routes is unopinionated and left
* to framework authors. This interface allows most flexibility
* between the already established conventions while allowing
* new ideas to be explored too.
* @default []
*/
clientEntryPoint: ImportSource;
fileSystemRouterTypes?: FrameworkFileSystemRouterType[];
/**
* Add extra modules
* A list of directories that should be served statically. If the directory
* does not exist in the user's project, it is ignored.
*
* Example: 'public' or 'static'
*
* Different frameworks have different opinions, some use 'static', some
* use 'public'.
* @default []
*/
builtInModules?: Record<string, BuiltInModule>;
staticRouters?: Array<StaticRouter> | undefined;
/**
* Add extra modules. This can be used to, for example, replace `react`
* with a different resolution.
*
* Internally, Bun's `react-server-components` framework uses this to
* embed its files in the `bun` binary.
* @default {}
*/
builtInModules?: BuiltInModule[] | undefined;
/**
* Bun offers integration for React's Server Components with an
* interface that is generic enough to adapt to any framework.
* @default undefined
*/
serverComponents?: ServerComponentsOptions | undefined;
/**
* While it is unlikely that Fast Refresh is useful outside of
* React, it can be enabled regardless.
* @default false
*/
reactFastRefresh?: ReactFastRefreshOptions | true | undefined;
reactFastRefresh?: boolean | ReactFastRefreshOptions | undefined;
// /**
// * Called after the list of routes is updated. This can be used to
// * implement framework-specific features like `.d.ts` generation:
// * https://nextjs.org/docs/app/building-your-application/configuring/typescript#statically-typed-links
// */
// onRouteListUpdate?: (routes: OnRouteListUpdateItem) => void;
}
type BuiltInModule = { code: string } | { path: string };
type BuiltInModule = { import: string; code: string } | { import: string; path: string };
/**
* A high-level overview of what server components means exists
@@ -100,7 +154,7 @@ declare module "bun" {
* To cross from the server graph to the SSR graph, use the bun_bake_graph
* import attribute:
*
* import * as ReactDOM from 'react-dom/server' with { bun_bake_graph: 'ssr' };
* import * as ReactDOM from 'react-dom/server' with { bunBakeGraph: 'ssr' };
*
* Since these models are so subtley different, there is no default value
* provided for this.
@@ -130,14 +184,15 @@ declare module "bun" {
*
* Additionally, the bundler will assemble a component manifest to be used
* during rendering.
* @default "registerClientReference"
*/
serverRegisterClientReferenceExport: string | undefined;
serverRegisterClientReferenceExport?: string | undefined;
// /**
// * Allow creating client components inside of server-side files by using "use client"
// * as the first line of a function declaration. This is useful for small one-off
// * interactive components. This is behind a flag because it is not a feature of
// * React or Next.js, but rather is implemented because it is possible to.
// *
// *
// * The client versions of these are tree-shaked extremely aggressively: anything
// * not referenced by the function body will be removed entirely.
// */
@@ -146,52 +201,235 @@ declare module "bun" {
/** Customize the React Fast Refresh transform. */
interface ReactFastRefreshOptions {
/**
/**
* This import has four exports, mirroring "react-refresh/runtime":
*
*
* `injectIntoGlobalHook(window): void`
* Called on first startup, before the user entrypoint.
*
*
* `register(component, uniqueId: string): void`
* Called on every function that starts with an uppercase letter. These
* may or may not be components, but they are always functions.
*
*
* `createSignatureFunctionForTransform(): ReactRefreshSignatureFunction`
* TODO: document. A passing no-op for this api is `return () => {}`
*
*
* @default "react-refresh/runtime"
*/
importSource: ImportSource | undefined;
}
type ReactRefreshSignatureFunction = () => void | ((func: Function, hash: string, force?: bool, customHooks?: () => Function[]) => void);
type ReactRefreshSignatureFunction = () =>
| void
| ((func: Function, hash: string, force?: bool, customHooks?: () => Function[]) => void);
/// Will be resolved from the point of view of the framework user's project root
/// Examples: `react-dom`, `./entry_point.tsx`, `/absolute/path.js`
type ImportSource = string;
/** This API is similar, but unrelated to `Bun.FileSystemRouter` */
interface FrameworkFileSystemRouterType {
/**
* Relative to project root. For example: `src/pages`.
*/
root: string;
/**
* The prefix to serve this directory on.
* @default "/"
*/
prefix?: string | undefined;
/**
* This file is the entrypoint of the server application. This module
* must `export default` a fetch function, which takes a request and the
* bundled route module, and returns a response. See `ServerEntryPoint`
*
* When `serverComponents` is configured, this can access the component
* manifest using the special 'bun:bake/server' import:
*
* import { serverManifest } from 'bun:bake/server'
*/
serverEntryPoint: ImportSource<ServerEntryPoint>;
/**
* This file is the true entrypoint of the client application. If null,
* a client will not be bundled, and the route will not receive bundling
* for client-side interactivity.
*/
clientEntryPoint?: ImportSource<ClientEntryPoint> | undefined;
/**
* Do not traverse into directories and files that start with an `_`. Do
* not index pages that start with an `_`. Does not prevent stuff like
* `_layout.tsx` from being recognized.
*/
ignoreUnderscores?: boolean;
/**
* @default ["node_modules", ".git"]
*/
ignoreDirs?: string[];
/**
* Extensions to match on.
* '*' - any extension
*/
extensions: string[] | "*";
/**
* 'nextjs-app' builds routes out of directories with `page.tsx` and `layout.tsx`
* 'nextjs-pages' builds routes out of any `.tsx` file and layouts with `_layout.tsx`.
*
* Eventually, an API will be added to add custom styles.
*/
style: "nextjs-pages" | "nextjs-app-ui" | "nextjs-app-routes" | CustomFileSystemRouterFunction;
/**
* If true, this will track route layouts and provide them as an array during SSR.
* @default false
*/
layouts?: boolean | undefined;
// /**
// * If true, layouts act as navigation endpoints. This can be used to
// * implement Remix.run's router design, where `hello._index` and `hello`
// * are the same URL, but an allowed collision.
// *
// * @default false
// */
// navigatableLayouts?: boolean | undefined;
// /**
// * Controls how the route entry point is bundled with regards to server components:
// * - server-component: Default server components.
// * - client-boundary: As if "use client" was used on every route.
// * - disabled: As if server components was completely disabled.
// *
// * @default "server-component" if serverComponents is enabled, "disabled" otherwise
// */
// serverComponentsMode?: "server-component" | "client-boundary" | "disabled";
}
type StaticRouter =
/** Alias for { source: ..., prefix: "/" } */
| string
| {
/** The source directory to observe. */
source: string;
/** The prefix to serve this directory on. */
prefix: string;
};
/**
* Bun will call this function for every found file. This
* function classifies each file's role in the file system routing.
*/
type CustomFileSystemRouterFunction = (candidatePath: string) => CustomFileSystemRouterResult;
type CustomFileSystemRouterResult =
/** Skip this file */
| undefined
| null
/**
* Use this file as a route. Routes may nest, where a framework
* can use parent routes to implement layouts.
*/
| {
/**
* Route pattern can include `:param` for parameters, '*' for
* catch-all, and '*?' for optional catch-all. Parameters must take
* the full component of a path segment. Parameters cannot have
* constraints at this moment.
*/
pattern: string;
type: "route" | "layout" | "extra";
};
/**
* Will be resolved from the point of view of the framework user's project root
* Examples: `react-dom`, `./entry_point.tsx`, `/absolute/path.js`
*/
type ImportSource<T = unknown> = string;
interface ServerEntryPoint {
/**
* The framework implementation decides and enforces the shape
* of the route module. Bun passes it as an opaque value.
* Bun passes the route's module as an opaque argument `routeModule`. The
* framework implementation decides and enforces the shape of the module.
*
* A common pattern would be to enforce the object is
* `{ default: ReactComponent }`
*/
default: (request: Request, routeModule: unknown, routeMetadata: RouteMetadata) => Awaitable<Response>;
render: (request: Request, routeMetadata: RouteMetadata) => Awaitable<Response>;
/**
* Static rendering does not take a response in, and can generate
* multiple output files. Note that `import.meta.env.STATIC` will
* be inlined to true during a static build.
* Prerendering does not use a request, and is allowed to generate
* multiple responses. This is used for static site generation, but not
* not named `staticRender` as it is invoked during a dynamic build to
* allow deterministic routes to be prerendered.
*
* Note that `import.meta.env.STATIC` will be inlined to true during
* a static build.
*/
staticRender: (routeModule: unknown, routeMetadata: RouteMetadata) => Awaitable<Record<string, Blob | ArrayBuffer>>;
prerender?: (routeMetadata: RouteMetadata) => Awaitable<PrerenderResult | null>;
// TODO: prerenderWithoutProps (for partial prerendering)
/**
* For prerendering routes with dynamic parameters, such as `/blog/:slug`,
* this will be called to get the list of parameters to prerender. This
* allows static builds to render every page at build time.
*
* `getParams` may return an object with an array of pages. For example,
* to generate two pages, `/blog/hello` and `/blog/world`:
*
* return {
* pages: [{ slug: 'hello' }, { slug: 'world' }],
* exhaustive: true,
* }
*
* "exhaustive" tells Bun that the list is complete. If it is not, a
* static site cannot be generated as it would otherwise be missing
* routes. A non-exhaustive list can speed up build times by only
* specifying a few important pages (such as 10 most recent), leaving
* the rest to be generated on-demand at runtime.
*
* To stream results, `getParams` may return an async iterator, which
* Bun will start rendering as more parameters are provided:
*
* export async function* getParams(meta: Bake.ParamsMetadata) {
* yield { slug: await fetchSlug() };
* yield { slug: await fetchSlug() };
* return { exhaustive: false };
* }
*/
getParams?: (paramsMetadata: ParamsMetadata) => Awaitable<ParamsResult>;
/**
* When a dynamic build uses static assets, Bun can map content types in the
* user's `Accept` header to the different static files.
*/
contentTypeToStaticFile?: Record<string, string>;
}
type GetParamIterator =
| AsyncIterable<Record<string, string>, GetParamsFinalOpts>
| Iterable<Record<string, string>, GetParamsFinalOpts>
| ({ pages: Array<Record<String, String>> } & GetParamsFinalOpts);
type GetParamsFinalOpts = void | null | {
/**
* @default true
*/
exhaustive?: boolean | undefined;
};
interface PrerenderResult {
files?: Record<string, Blob | NodeJS.TypedArray | ArrayBufferLike | string | Bun.BlobPart[]>;
// /**
// * For dynamic builds, `partialData` will be provided to `render` to allow
// * to implement Partial Pre-rendering, a technique where the a page shell
// * is rendered first, and the rendering is resumed. The bytes passed
// * here will be passed to the `render` function as `partialData`.
// */
// partialData?: Uint8Array;
// TODO: support incremental static regeneration + stale while revalidate here
// cache: unknown;
}
interface ClientEntryPoint {
/**
* Called when server-side code is changed. This can be used to fetch a
* non-html version of the updated page to perform a faster reload.
* non-html version of the updated page to perform a faster reload. If
* this function does not exist or throws, the client will perform a
* hard reload.
*
* Tree-shaken away in production builds.
*/
onServerSideReload?: () => void;
onServerSideReload?: () => Promise<void> | void;
}
/**
@@ -199,86 +437,119 @@ declare module "bun" {
* is not safe to mutate it at all.
*/
interface RouteMetadata {
/**
* The loaded module of the page itself.
*/
readonly pageModule: any;
/**
* The loaded module of all of the route layouts. The first one is the
* inner-most, the last is the root layout.
*
* An example of converting the layout list into a nested JSX structure:
* const Page = meta.pageModule.default;
* let route = <Page />
* for (const layout of meta.layouts) {
* const Layout = layout.default;
* route = <Layout>{route}</Layout>;
* }
*/
readonly layouts: ReadonlyArray<any>;
/** Received route params. `null` if the route does not take params */
readonly params: null | Record<string, string>;
/**
* A list of js files that the route will need to be interactive.
*/
readonly scripts: ReadonlyArray<string>;
/**
* A list of js files that should be preloaded.
*
* <link rel="modulepreload" href="..." />
*/
readonly modulepreload: ReadonlyArray<string>;
/**
* A list of css files that the route will need to be styled.
*/
readonly styles: ReadonlyArray<string>;
/**
* Can be used by the framework to mention the route file. Only provided in
* development mode to prevent leaking these details into production
* builds.
*/
devRoutePath?: string;
}
/**
* This object and it's children may be re-used between invocations, so it
* is not safe to mutate it at all.
*/
interface ParamsMetadata {
readonly pageModule: any;
readonly layouts: ReadonlyArray<any>;
}
}
// declare class Bake {
// constructor(options: Bake.Options);
// }
interface GenericServeOptions {
/** Add a fullstack web app to this server using Bun Bake */
app?: Bake.Options | undefined;
}
}
declare module "bun:bake/server" {
// NOTE: The format of these manifests will likely be customizable in the future.
/**
* Entries in this manifest can be loaded by using dynamic `await import()` or
* `require`. The bundler always ensures that all modules are ready on the server.
*/
declare const clientManifest: ReactClientManifest;
/**
* This follows the requirements for React's Server Components manifest, which
* does not actually include usable module specifiers. Calling `import()` on
* these specifiers wont work, but they will work client-side. Use
* `clientManifest` on the server for SSR.
* is a mapping of component IDs to the client-side file it is exported in.
* The specifiers from here are to be imported in the client.
*
* To perform SSR with client components, see `clientManifest`
*/
declare const serverManifest: ReactServerManifest;
/**
* Entries in this manifest map from client-side files to their respective SSR
* bundles. They can be loaded by `await import()` or `require()`.
*/
declare const clientManifest: ReactClientManifest;
/** (insert teaser trailer) */
declare const actionManifest: never;
declare interface ReactServerManifest {
/**
* Concatenation of the component file ID and the instance id with '#'
* Example: 'components/Navbar.tsx#default' (dev) or 'l2#a' (prod/minified)
*
* The component file ID and the instance id are both passed to `registerClientReference`
*/
[combinedComponentId: string]: {
/**
* The `id` in ReactClientManifest.
* Correlates but is not required to be the filename
*/
id: string;
/**
* The `name` in ReactServerManifest
* Correlates but is not required to be the export name
*/
name: string;
/** Currently not implemented; always an empty array */
chunks: [];
};
}
declare interface ReactClientManifest {
/** ReactServerManifest[...].id */
[id: string]: {
/** ReactServerManifest[...].name */
[name: string]: {
/** Valid specifier to import */
specifier: string;
/** Export name */
name: string;
};
};
}
}
declare module "bun:bake/client" {
/**
* Entries in this manifest can be loaded by using dynamic `await import()` or
* `require`. The bundler currently ensures that all modules are ready.
* Due to the current implementation of the Dev Server, it must be informed of
* client-side routing so it can load client components. This is not necessary
* in production, and calling this in that situation will fail to compile.
*/
declare const clientManifest: ReactClientManifest;
}
declare interface ReactClientManifest {
[id: string]: {
[name: string]: {
/** Valid specifier to import */
specifier: string;
/** Export name */
name: string;
};
};
}
declare interface ReactServerManifest {
/**
* Concatenation of the component file ID and the instance id with '#'
* Example: 'components/Navbar.tsx#default' (dev) or 'l2#a' (prod/minified)
*
* The component file ID and the instance id are both passed to `registerClientReference`
*/
[combinedComponentId: string]: {
/**
* The `id` in ReactClientManifest.
* Correlates but is not required to be the filename
*/
id: string;
/**
* The `name` in ReactServerManifest
* Correlates but is not required to be the export name
*/
name: string;
/** Currently not implemented; always an empty array */
chunks: [];
};
declare function bundleRouteForDevelopment(href: string, options?: { signal?: AbortSignal }): Promise<void>;
}

View File

@@ -31,37 +31,79 @@ declare const config: Config;
/**
* The runtime is bundled for server and client, which influences
* how hmr connection should be established, as well if there is
* how HMR connection should be established, as well if there is
* a window to visually display errors with.
*/
declare const side: "client" | "server";
/*
* This variable becomes the default export. Kit uses this
* interface as opposed to a WebSocket connection.
*/
declare var server_exports: {
handleRequest: (req: Request, routeModuleId: Id, clientEntryUrl: string, styles: string[]) => any;
registerUpdate: (
modules: any,
componentManifestAdd: null | string[],
componentManifestDelete: null | string[],
) => void;
};
/*
* If you are running a debug build of Bun. These debug builds should provide
* helpful information to someone working on the bundler itself.
* helpful information to someone working on the bundler itself. Assertions
* aimed for the end user should always be enabled.
*/
declare const IS_BUN_DEVELOPMENT: any;
// shims for experimental react types
declare module "react" {
export function use<T>(promise: Promise<T>): T;
}
declare var __bun_f: any;
// The following interfaces have been transcribed manually.
declare module "react-server-dom-webpack/client.browser" {
export function createFromReadableStream<T = any>(readable: ReadableStream, manifest?: any): Promise<T>;
export function createFromReadableStream<T = any>(readable: ReadableStream<Uint8Array>): Promise<T>;
}
declare module "react-server-dom-webpack/server.browser" {
export function renderToReadableStream<T = any>(element: JSX.Element, manifest: any): ReadableStream;
declare module "react-server-dom-webpack/client.node.unbundled.js" {
import type { ReactClientManifest } from "bun:bake/server";
import type { Readable } from "node:stream";
export interface Manifest {
moduleMap: ReactClientManifest;
moduleLoading?: ModuleLoading;
}
export interface ModuleLoading {
prefix: string;
crossOrigin?: string;
}
export interface Options {
encodeFormAction?: any;
findSourceMapURL?: any;
environmentName?: string;
}
export function createFromNodeStream<T = any>(readable: Readable, manifest?: Manifest): Promise<T>;
}
declare module "react-server-dom-webpack/server.node.unbundled.js" {
import type { ReactServerManifest } from "bun:bake/server";
import type { ReactElement, ReactElement } from "react";
import type { Writable } from "node:stream";
export interface PipeableStream<T> {
/** Returns the input, which should match the Node.js writable interface */
pipe: <T>(destination: T) => T;
abort: () => void;
}
export function renderToPipeableStream<T = any>(
model: ReactElement,
webpackMap: ReactServerManifest,
options?: RenderToPipeableStreamOptions,
): PipeableStream<T>;
export interface RenderToPipeableStreamOptions {
onError?: Function;
identifierPrefix?: string;
onPostpone?: Function;
temporaryReferences?: any;
environmentName?: string;
filterStackFrame?: Function;
}
}
declare module "react-dom/server.node" {
import type { PipeableStream } from "react-server-dom-webpack/server.node.unbundled.js";
import type { ReactElement } from "react";
export type RenderToPipeableStreamOptions = any;
export function renderToPipeableStream(
model: ReactElement,
options: RenderToPipeableStreamOptions,
): PipeableStream<Uint8Array>;
}

View File

@@ -3,88 +3,193 @@
//! server, server components, and other integrations. Instead of taking the
//! role as a framework, Bake is tool for frameworks to build on top of.
/// Zig version of TS definition 'Bake.Options' in 'bake.d.ts'
pub const production = @import("./production.zig");
pub const DevServer = @import("./DevServer.zig");
pub const FrameworkRouter = @import("./FrameworkRouter.zig");
/// export default { app: ... };
pub const api_name = "app";
/// Zig version of the TS definition 'Bake.Options' in 'bake.d.ts'
pub const UserOptions = struct {
arena: std.heap.ArenaAllocator.State,
allocations: StringRefList,
root: []const u8,
framework: Framework,
bundler_options: SplitBundlerOptions,
pub fn deinit(options: *UserOptions) void {
options.arena.promote(bun.default_allocator).deinit();
options.allocations.free();
}
pub fn fromJS(config: JSValue, global: *JSC.JSGlobalObject) !UserOptions {
if (!config.isObject()) {
return global.throwInvalidArguments2("'" ++ api_name ++ "' is not an object", .{});
}
var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
errdefer arena.deinit();
const alloc = arena.allocator();
var allocations = StringRefList.empty;
errdefer allocations.free();
var bundler_options: SplitBundlerOptions = .{};
const framework = try Framework.fromJS(
try config.get(global, "framework") orelse {
return global.throwInvalidArguments2("'" ++ api_name ++ "' is missing 'framework'", .{});
},
global,
&allocations,
&bundler_options,
alloc,
);
const root = if (try config.getOptional(global, "root", ZigString.Slice)) |slice|
allocations.track(slice)
else
bun.getcwdAlloc(alloc) catch |err| switch (err) {
error.OutOfMemory => {
global.throwOutOfMemory();
return error.JSError;
},
else => {
return global.throwError(err, "while querying current working directory");
},
};
return .{
.arena = arena.state,
.allocations = allocations,
.root = root,
.framework = framework,
.bundler_options = bundler_options,
};
}
};
/// Each string stores its allocator since some may hold reference counts to JSC
const StringRefList = struct {
strings: std.ArrayListUnmanaged(ZigString.Slice),
pub fn track(al: *StringRefList, str: ZigString.Slice) []const u8 {
al.strings.append(bun.default_allocator, str) catch bun.outOfMemory();
return str.slice();
}
pub fn free(al: *StringRefList) void {
for (al.strings.items) |item| item.deinit();
al.strings.clearAndFree(bun.default_allocator);
}
pub const empty: StringRefList = .{ .strings = .{} };
};
const SplitBundlerOptions = struct {
all: BuildConfigSubset = .{},
client: BuildConfigSubset = .{},
server: BuildConfigSubset = .{},
ssr: BuildConfigSubset = .{},
};
const BuildConfigSubset = struct {
loader: ?bun.Schema.Api.LoaderMap = null,
ignoreDCEAnnotations: ?bool = null,
conditions: bun.StringArrayHashMapUnmanaged(void) = .{},
drop: bun.StringArrayHashMapUnmanaged(void) = .{},
// TODO: plugins
};
/// Temporary function to invoke dev server via JavaScript. Will be
/// replaced with a user-facing API. Refs the event loop forever.
pub fn jsWipDevServer(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn jsWipDevServer(global: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
_ = global;
_ = callframe;
if (!bun.FeatureFlags.bake) return .undefined;
BakeInitProcessIdentifier();
bun.Output.warn(
\\Be advised that Bun Bake is highly experimental, and its API
\\will have breaking changes. Join the <magenta>#bake<r> Discord
\\channel to help us find bugs: <blue>https://bun.sh/discord<r>
bun.Output.errGeneric(
\\This api has moved to the `app` property of the default export.
\\
\\ export default {{
\\ port: 3000,
\\ app: {{
\\ framework: 'react'
\\ }},
\\ }};
\\
, .{});
bun.Output.flush();
const options = bakeOptionsFromJs(global, callframe.argument(0)) catch {
if (!global.hasException())
global.throwInvalidArguments("invalid arguments", .{});
return .zero;
};
// TODO: this should inherit the current VM, running on the main thread.
const t = std.Thread.spawn(.{}, wipDevServer, .{options}) catch @panic("Failed to start");
t.detach();
{
var futex = std.atomic.Value(u32).init(0);
while (true) std.Thread.Futex.wait(&futex, 0);
}
,
.{},
);
return .undefined;
}
extern fn BakeInitProcessIdentifier() void;
/// A "Framework" in our eyes is simply set of bundler options that a framework
/// author would set in order to integrate the framework with the application.
/// Since many fields have default values which may point to static memory, this
/// structure is always arena-allocated, usually owned by the arena in `UserOptions`
///
/// Full documentation on these fields is located in the TypeScript definitions.
pub const Framework = struct {
entry_client: []const u8,
entry_server: []const u8,
file_system_router_types: []FileSystemRouterType,
// static_routers: [][]const u8,
server_components: ?ServerComponents = null,
react_fast_refresh: ?ReactFastRefresh = null,
built_in_modules: bun.StringArrayHashMapUnmanaged(BuiltInModule) = .{},
/// Bun provides built-in support for using React as a framework.
/// Depends on externally provided React
///
/// $ bun i react@experimental react-dom@experimental react-server-dom-webpack@experimental react-refresh@experimental
pub fn react() Framework {
pub fn react(arena: std.mem.Allocator) !Framework {
return .{
.server_components = .{
.separate_ssr_graph = true,
.server_runtime_import = "react-server-dom-webpack/server",
// .client_runtime_import = "react-server-dom-webpack/client",
.server_runtime_import = "react-server-dom-bun/server",
},
.react_fast_refresh = .{},
.entry_client = "bun-framework-rsc/client.tsx",
.entry_server = "bun-framework-rsc/server.tsx",
.built_in_modules = bun.StringArrayHashMapUnmanaged(BuiltInModule).init(bun.default_allocator, &.{
"bun-framework-rsc/client.tsx",
"bun-framework-rsc/server.tsx",
"bun-framework-rsc/ssr.tsx",
.file_system_router_types = try arena.dupe(FileSystemRouterType, &.{
.{
.root = "pages",
.prefix = "/",
.entry_client = "bun-framework-react/client.tsx",
.entry_server = "bun-framework-react/server.tsx",
.ignore_underscores = true,
.ignore_dirs = &.{ "node_modules", ".git" },
.extensions = &.{ ".tsx", ".jsx" },
.style = .@"nextjs-pages-ui",
},
}),
// .static_routers = try arena.dupe([]const u8, &.{"public"}),
.built_in_modules = bun.StringArrayHashMapUnmanaged(BuiltInModule).init(arena, &.{
"bun-framework-react/client.tsx",
"bun-framework-react/server.tsx",
"bun-framework-react/ssr.tsx",
}, if (Environment.codegen_embed) &.{
.{ .code = @embedFile("./bun-framework-rsc/client.tsx") },
.{ .code = @embedFile("./bun-framework-rsc/server.tsx") },
.{ .code = @embedFile("./bun-framework-rsc/ssr.tsx") },
.{ .code = @embedFile("./bun-framework-react/client.tsx") },
.{ .code = @embedFile("./bun-framework-react/server.tsx") },
.{ .code = @embedFile("./bun-framework-react/ssr.tsx") },
} else &.{
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-rsc/client.tsx") },
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-rsc/server.tsx") },
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-rsc/ssr.tsx") },
// Cannot use .import because resolution must happen from the user's POV
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-react/client.tsx") },
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-react/server.tsx") },
.{ .code = bun.runtimeEmbedFile(.src, "bake/bun-framework-react/ssr.tsx") },
}) catch bun.outOfMemory(),
};
}
const FileSystemRouterType = struct {
root: []const u8,
prefix: []const u8,
entry_server: []const u8,
entry_client: ?[]const u8,
ignore_underscores: bool,
ignore_dirs: []const []const u8,
extensions: []const []const u8,
style: FrameworkRouter.Style,
};
const BuiltInModule = union(enum) {
import: []const u8,
code: []const u8,
@@ -111,9 +216,6 @@ pub const Framework = struct {
var clone = f;
var had_errors: bool = false;
f.resolveHelper(client, &clone.entry_client, &had_errors, "client entrypoint");
f.resolveHelper(server, &clone.entry_server, &had_errors, "server entrypoint");
if (clone.react_fast_refresh) |*react_fast_refresh| {
f.resolveHelper(client, &react_fast_refresh.import_source, &had_errors, "react refresh runtime");
}
@@ -123,6 +225,13 @@ pub const Framework = struct {
// f.resolveHelper(client, &sc.client_runtime_import, &had_errors);
}
for (clone.file_system_router_types) |*fsr| {
// TODO: unonwned memory
fsr.root = bun.path.joinAbs(server.fs.top_level_dir, .auto, fsr.root);
if (fsr.entry_client) |*entry_client| f.resolveHelper(client, entry_client, &had_errors, "client side entrypoint");
f.resolveHelper(client, &fsr.entry_server, &had_errors, "server side entrypoint");
}
if (had_errors) return error.ModuleNotFound;
return clone;
@@ -142,141 +251,221 @@ pub const Framework = struct {
had_errors.* = true;
return;
};
path.* = result.path().?.text; // TODO: what is the lifetime of this string
path.* = result.path().?.text;
}
// TODO: This function always leaks memory.
// `Framework` has no way to specify what is allocated, nor should it.
fn fromJS(opts: JSValue, global: *JSC.JSGlobalObject) !Framework {
fn fromJS(
opts: JSValue,
global: *JSC.JSGlobalObject,
refs: *StringRefList,
bundler_options: *SplitBundlerOptions,
arena: Allocator,
) !Framework {
_ = bundler_options; // autofix
if (opts.isString()) {
const str = opts.toBunString(global);
const str = try opts.toBunString2(global);
defer str.deref();
// Deprecated
if (str.eqlComptime("react-server-components")) {
return Framework.react();
bun.Output.warn("deprecation notice: 'react-server-components' will be renamed to 'react'", .{});
return Framework.react(arena);
}
if (str.eqlComptime("react")) {
return Framework.react();
return Framework.react(arena);
}
}
if (!opts.isObject()) {
global.throwInvalidArguments("Framework must be an object", .{});
return error.JSError;
return global.throwInvalidArguments2("Framework must be an object", .{});
}
return .{
.entry_server = brk: {
const prop: JSValue = opts.get(global, "serverEntryPoint") orelse {
if (!global.hasException())
global.throwInvalidArguments("Missing 'framework.serverEntryPoint'", .{});
return error.JSError;
};
const str = prop.toBunString(global);
defer str.deref();
if (global.hasException())
return error.JSError;
if (try opts.get(global, "serverEntryPoint") != null) {
bun.Output.warn("deprecation notice: 'framework.serverEntryPoint' has been replaced with 'fileSystemRouterTypes[n].serverEntryPoint'", .{});
}
if (try opts.get(global, "clientEntryPoint") != null) {
bun.Output.warn("deprecation notice: 'framework.clientEntryPoint' has been replaced with 'fileSystemRouterTypes[n].clientEntryPoint'", .{});
}
// Leak
break :brk str.toUTF8(bun.default_allocator).slice();
},
.entry_client = brk: {
const prop: JSValue = opts.get(global, "clientEntryPoint") orelse {
if (!global.hasException())
global.throwInvalidArguments("Missing 'framework.clientEntryPoint'", .{});
return error.JSError;
};
const str = prop.toBunString(global);
defer str.deref();
const react_fast_refresh: ?ReactFastRefresh = brk: {
const rfr: JSValue = try opts.get(global, "reactFastRefresh") orelse
break :brk null;
if (global.hasException())
return error.JSError;
if (rfr == .true) break :brk .{};
if (rfr == .false or rfr == .null or rfr == .undefined) break :brk null;
// Leak
break :brk str.toUTF8(bun.default_allocator).slice();
},
.react_fast_refresh = brk: {
const rfr: JSValue = opts.get(global, "reactFastRefresh") orelse {
if (global.hasException())
return error.JSError;
break :brk null;
};
if (rfr == .true) break :brk .{};
if (rfr == .false or rfr == .null or rfr == .undefined) break :brk null;
if (!rfr.isObject()) {
global.throwInvalidArguments("'framework.reactFastRefresh' must be an object or 'true'", .{});
return error.JSError;
if (!rfr.isObject()) {
return global.throwInvalidArguments2("'framework.reactFastRefresh' must be an object or 'true'", .{});
}
const prop = try rfr.get(global, "importSource") orelse {
return global.throwInvalidArguments2("'framework.reactFastRefresh' is missing 'importSource'", .{});
};
const str = try prop.toBunString2(global);
defer str.deref();
break :brk .{
.import_source = refs.track(str.toUTF8(arena)),
};
};
const server_components: ?ServerComponents = sc: {
const sc: JSValue = try opts.get(global, "serverComponents") orelse
break :sc null;
if (sc == .false or sc == .null or sc == .undefined) break :sc null;
if (!sc.isObject()) {
return global.throwInvalidArguments2("'framework.serverComponents' must be an object or 'undefined'", .{});
}
break :sc .{
.separate_ssr_graph = brk: {
// Intentionally not using a truthiness check
const prop = try sc.getOptional(global, "separateSSRGraph", JSValue) orelse {
return global.throwInvalidArguments2("Missing 'framework.serverComponents.separateSSRGraph'", .{});
};
if (prop == .true) break :brk true;
if (prop == .false) break :brk false;
return global.throwInvalidArguments2("'framework.serverComponents.separateSSRGraph' must be a boolean", .{});
},
.server_runtime_import = refs.track(
try sc.getOptional(global, "serverRuntimeImportSource", ZigString.Slice) orelse {
return global.throwInvalidArguments2("Missing 'framework.serverComponents.serverRuntimeImportSource'", .{});
},
),
.server_register_client_reference = refs.track(
try sc.getOptional(global, "serverRegisterClientReferenceExport", ZigString.Slice) orelse {
return global.throwInvalidArguments2("Missing 'framework.serverComponents.serverRegisterClientReferenceExport'", .{});
},
),
};
};
const built_in_modules: bun.StringArrayHashMapUnmanaged(BuiltInModule) = built_in_modules: {
const array = try opts.getArray(global, "builtInModules") orelse
break :built_in_modules .{};
const len = array.getLength(global);
var files: bun.StringArrayHashMapUnmanaged(BuiltInModule) = .{};
try files.ensureTotalCapacity(arena, len);
var it = array.arrayIterator(global);
var i: usize = 0;
while (it.next()) |file| : (i += 1) {
if (!file.isObject()) {
return global.throwInvalidArguments2("'builtInModules[{d}]' is not an object", .{i});
}
const prop = rfr.get(global, "importSource") orelse {
global.throwInvalidArguments("'framework.reactFastRefresh' is missing 'importSource'", .{});
return error.JSError;
const path = try getOptionalString(file, global, "import", refs, arena) orelse {
return global.throwInvalidArguments2("'builtInModules[{d}]' is missing 'import'", .{i});
};
const str = prop.toBunString(global);
defer str.deref();
const value: BuiltInModule = if (try getOptionalString(file, global, "path", refs, arena)) |str|
.{ .import = str }
else if (try getOptionalString(file, global, "code", refs, arena)) |str|
.{ .code = str }
else
return global.throwInvalidArguments2("'builtInModules[{d}]' needs either 'path' or 'code'", .{i});
if (global.hasException())
return error.JSError;
files.putAssumeCapacity(path, value);
}
// Leak
break :brk .{
.import_source = str.toUTF8(bun.default_allocator).slice(),
};
},
.server_components = sc: {
const sc: JSValue = opts.get(global, "serverComponents") orelse {
if (global.hasException())
return error.JSError;
break :sc null;
};
if (sc == .null or sc == .undefined) break :sc null;
break :sc .{
// .client_runtime_import = "",
.separate_ssr_graph = brk: {
const prop: JSValue = sc.get(global, "separateSSRGraph") orelse {
if (!global.hasException())
global.throwInvalidArguments("Missing 'framework.serverComponents.separateSSRGraph'", .{});
return error.JSError;
};
if (prop == .true) break :brk true;
if (prop == .false) break :brk false;
global.throwInvalidArguments("'framework.serverComponents.separateSSRGraph' must be a boolean", .{});
return error.JSError;
},
.server_runtime_import = brk: {
const prop: JSValue = sc.get(global, "serverRuntimeImportSource") orelse {
if (!global.hasException())
global.throwInvalidArguments("Missing 'framework.serverComponents.serverRuntimeImportSource'", .{});
return error.JSError;
};
const str = prop.toBunString(global);
defer str.deref();
if (global.hasException())
return error.JSError;
// Leak
break :brk str.toUTF8(bun.default_allocator).slice();
},
.server_register_client_reference = brk: {
const prop: JSValue = sc.get(global, "serverRegisterClientReferenceExport") orelse {
if (!global.hasException())
global.throwInvalidArguments("Missing 'framework.serverComponents.serverRegisterClientReferenceExport'", .{});
return error.JSError;
};
const str = prop.toBunString(global);
defer str.deref();
if (global.hasException())
return error.JSError;
// Leak
break :brk str.toUTF8(bun.default_allocator).slice();
},
};
},
break :built_in_modules files;
};
const file_system_router_types: []FileSystemRouterType = brk: {
const array: JSValue = try opts.getArray(global, "fileSystemRouterTypes") orelse {
return global.throwInvalidArguments2("Missing 'framework.fileSystemRouterTypes'", .{});
};
const len = array.getLength(global);
if (len > 256) {
return global.throwInvalidArguments2("Framework can only define up to 256 file-system router types", .{});
}
const file_system_router_types = try arena.alloc(FileSystemRouterType, len);
var it = array.arrayIterator(global);
var i: usize = 0;
while (it.next()) |fsr_opts| : (i += 1) {
const root = try getOptionalString(fsr_opts, global, "root", refs, arena) orelse {
return global.throwInvalidArguments2("'fileSystemRouterTypes[{d}]' is missing 'root'", .{i});
};
const server_entry_point = try getOptionalString(fsr_opts, global, "serverEntryPoint", refs, arena) orelse {
return global.throwInvalidArguments2("'fileSystemRouterTypes[{d}]' is missing 'serverEntryPoint'", .{i});
};
const client_entry_point = try getOptionalString(fsr_opts, global, "clientEntryPoint", refs, arena);
const prefix = try getOptionalString(fsr_opts, global, "prefix", refs, arena) orelse "/";
const ignore_underscores = try fsr_opts.getBooleanStrict(global, "ignoreUnderscores") orelse false;
const style = try validators.validateStringEnum(
FrameworkRouter.Style,
global,
try opts.getOptional(global, "style", JSValue) orelse .undefined,
"style",
.{},
);
const extensions: []const []const u8 = if (try fsr_opts.get(global, "extensions")) |exts_js| exts: {
if (exts_js.isString()) {
const str = try exts_js.toSlice2(global, arena);
defer str.deinit();
if (bun.strings.eqlComptime(str.slice(), "*")) {
break :exts &.{};
}
} else if (exts_js.isArray()) {
var it_2 = array.arrayIterator(global);
var i_2: usize = 0;
const extensions = try arena.alloc([]const u8, len);
while (it_2.next()) |array_item| : (i_2 += 1) {
// TODO: remove/add the prefix `.`, throw error if specifying '*' as an array item instead of as root
extensions[i_2] = refs.track(try array_item.toSlice2(global, arena));
}
break :exts extensions;
}
return global.throwInvalidArguments2("'extensions' must be an array of strings or \"*\" for all extensions", .{});
} else &.{ ".jsx", ".tsx", ".js", ".ts", ".cjs", ".cts", ".mjs", ".mts" };
const ignore_dirs: []const []const u8 = if (try fsr_opts.get(global, "ignoreDirs")) |exts_js| exts: {
if (exts_js.isArray()) {
var it_2 = array.arrayIterator(global);
var i_2: usize = 0;
const dirs = try arena.alloc([]const u8, len);
while (it_2.next()) |array_item| : (i_2 += 1) {
dirs[i_2] = refs.track(try array_item.toSlice2(global, arena));
}
break :exts dirs;
}
return global.throwInvalidArguments2("'ignoreDirs' must be an array of strings or \"*\" for all extensions", .{});
} else &.{ ".git", "node_modules" };
file_system_router_types[i] = .{
.root = root,
.prefix = prefix,
.style = style,
.entry_server = server_entry_point,
.entry_client = client_entry_point,
.ignore_underscores = ignore_underscores,
.extensions = extensions,
.ignore_dirs = ignore_dirs,
};
}
break :brk file_system_router_types;
};
const framework: Framework = .{
.file_system_router_types = file_system_router_types,
.react_fast_refresh = react_fast_refresh,
.server_components = server_components,
.built_in_modules = built_in_modules,
};
if (try opts.getOptional(global, "bundlerOptions", JSValue)) |js_options| {
_ = js_options; // autofix
// try SplitBundlerOptions.parseInto(global, js_options, bundler_options, .root);
}
return framework;
}
pub fn initBundler(
@@ -306,11 +495,11 @@ pub const Framework = struct {
out.options.log = log;
out.options.output_format = switch (mode) {
.development => .internal_bake_dev,
.production => .esm,
.production_dynamic, .production_static => .esm,
};
out.options.out_extensions = bun.StringHashMap([]const u8).init(out.allocator);
out.options.hot_module_reloading = mode == .development;
out.options.code_splitting = mode == .production;
out.options.code_splitting = mode != .development;
// force disable filesystem output, even though bundle_v2
// is special cased to return before that code is reached.
@@ -325,12 +514,12 @@ pub const Framework = struct {
try out.options.conditions.appendSlice(&.{"react-server"});
}
out.options.production = mode == .production;
out.options.production = mode != .development;
out.options.tree_shaking = mode == .production;
out.options.tree_shaking = mode != .development;
out.options.minify_syntax = true; // required for DCE
// out.options.minify_identifiers = mode == .production;
// out.options.minify_whitespace = mode == .production;
// out.options.minify_identifiers = mode != .development;
// out.options.minify_whitespace = mode != .development;
out.options.experimental_css = true;
out.options.css_chunking = true;
@@ -347,7 +536,7 @@ pub const Framework = struct {
.server, .ssr => .server,
});
if (mode == .production) {
if (mode != .development) {
out.options.entry_naming = "[name]-[hash].[ext]";
out.options.chunk_naming = "chunk-[name]-[hash].[ext]";
}
@@ -356,78 +545,48 @@ pub const Framework = struct {
}
};
// TODO: this function leaks memory and bad error handling, but that is OK since
pub fn bakeOptionsFromJs(global: *JSC.JSGlobalObject, options: JSValue) !DevServer.Options {
if (!options.isObject()) return error.Invalid;
const routes_js = try options.getArray(global, "routes") orelse return error.Invalid;
const len = routes_js.getLength(global);
const routes = try bun.default_allocator.alloc(DevServer.Route, len);
var it = routes_js.arrayIterator(global);
var i: usize = 0;
while (it.next()) |route| : (i += 1) {
if (!route.isObject()) return error.Invalid;
const pattern_js = route.get(global, "pattern") orelse return error.Invalid;
if (!pattern_js.isString()) return error.Invalid;
const entry_point_js = route.get(global, "entrypoint") orelse return error.Invalid;
if (!entry_point_js.isString()) return error.Invalid;
const pattern = pattern_js.toBunString(global).toUTF8(bun.default_allocator);
defer pattern.deinit();
// TODO: this dupe is stupid
const pattern_z = try bun.default_allocator.dupeZ(u8, pattern.slice());
const entry_point = entry_point_js.toBunString(global).toUTF8(bun.default_allocator).slice(); // leak
routes[i] = .{
.pattern = pattern_z,
.entry_point = entry_point,
};
}
const framework_js = options.get(global, "framework") orelse {
return error.Invalid;
};
const framework = try Framework.fromJS(framework_js, global);
return .{
.cwd = bun.getcwdAlloc(bun.default_allocator) catch bun.outOfMemory(),
.routes = routes,
.framework = framework,
};
fn getOptionalString(
target: JSValue,
global: *JSC.JSGlobalObject,
property: []const u8,
allocations: *StringRefList,
arena: Allocator,
) !?[]const u8 {
const value = try target.get(global, property) orelse
return null;
if (value == .undefined or value == .null)
return null;
const str = try value.toBunString2(global);
return allocations.track(str.toUTF8(arena));
}
export fn Bun__getTemporaryDevServer(global: *JSC.JSGlobalObject) JSValue {
if (!bun.FeatureFlags.bake) return .undefined;
return JSC.JSFunction.create(global, "wipDevServer", bun.JSC.toJSHostFunction(jsWipDevServer), 0, .{});
return JSC.JSFunction.create(global, "wipDevServer", jsWipDevServer, 0, .{});
}
pub fn wipDevServer(options: DevServer.Options) noreturn {
bun.Output.Source.configureNamedThread("Dev Server");
const dev = DevServer.init(options) catch |err| switch (err) {
error.FrameworkInitialization => bun.Global.exit(1),
else => {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
bun.Output.panic("Failed to init DevServer: {}", .{err});
},
};
dev.runLoopForever();
}
pub fn getHmrRuntime(mode: Side) []const u8 {
pub inline fn getHmrRuntime(side: Side) [:0]const u8 {
return if (Environment.codegen_embed)
switch (mode) {
switch (side) {
.client => @embedFile("bake-codegen/bake.client.js"),
.server => @embedFile("bake-codegen/bake.server.js"),
}
else switch (mode) {
inline else => |m| bun.runtimeEmbedFile(.codegen_eager, "bake." ++ @tagName(m) ++ ".js"),
else switch (side) {
.client => bun.runtimeEmbedFile(.codegen_eager, "bake.client.js"),
// may not be live-reloaded
.server => bun.runtimeEmbedFile(.codegen, "bake.server.js"),
};
}
pub const Mode = enum { production, development };
pub const Side = enum { client, server };
pub const Mode = enum {
development,
production_dynamic,
production_static,
};
pub const Side = enum(u1) {
client,
server,
};
pub const Graph = enum(u2) {
client,
server,
@@ -444,7 +603,9 @@ pub fn addImportMetaDefines(
const Define = bun.options.Define;
// The following are from Vite: https://vitejs.dev/guide/env-and-mode
// TODO: MODE, BASE_URL
// Note that it is not currently possible to have mixed
// modes (production + hmr dev server)
// TODO: BASE_URL
try define.insert(
allocator,
"import.meta.env.DEV",
@@ -453,13 +614,28 @@ pub fn addImportMetaDefines(
try define.insert(
allocator,
"import.meta.env.PROD",
Define.Data.initBoolean(mode == .production),
Define.Data.initBoolean(mode != .development),
);
try define.insert(
allocator,
"import.meta.env.MODE",
Define.Data.initStaticString(switch (mode) {
.development => &.{ .data = "development" },
.production_dynamic, .production_static => &.{ .data = "production" },
}),
);
try define.insert(
allocator,
"import.meta.env.SSR",
Define.Data.initBoolean(side == .server),
);
// To indicate a static build, `STATIC` is set to true then.
try define.insert(
allocator,
"import.meta.env.STATIC",
Define.Data.initBoolean(mode == .production_static),
);
}
pub const server_virtual_source: bun.logger.Source = .{
@@ -474,13 +650,49 @@ pub const client_virtual_source: bun.logger.Source = .{
.index = bun.JSAst.Index.bake_client_data,
};
pub const production = @import("./production.zig");
pub const DevServer = @import("./DevServer.zig");
/// Stack-allocated structure that is written to from end to start.
/// Used as a staging area for building pattern strings.
pub const PatternBuffer = struct {
bytes: bun.PathBuffer,
i: std.math.IntFittingRange(0, @sizeOf(bun.PathBuffer)),
pub const empty: PatternBuffer = .{
.bytes = undefined,
.i = @sizeOf(bun.PathBuffer),
};
pub fn prepend(pb: *PatternBuffer, chunk: []const u8) void {
bun.assert(pb.i >= chunk.len);
pb.i -= @intCast(chunk.len);
@memcpy(pb.slice()[0..chunk.len], chunk);
}
pub fn prependPart(pb: *PatternBuffer, part: FrameworkRouter.Part) void {
switch (part) {
.text => |text| {
pb.prepend(text);
pb.prepend("/");
},
.param, .catch_all, .catch_all_optional => |name| {
pb.prepend(name);
pb.prepend("/:");
},
.group => {},
}
}
pub fn slice(pb: *PatternBuffer) []u8 {
return pb.bytes[pb.i..];
}
};
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("root").bun;
const Environment = bun.Environment;
const ZigString = bun.JSC.ZigString;
const JSC = bun.JSC;
const JSValue = JSC.JSValue;
const validators = bun.JSC.Node.validators;

View File

@@ -0,0 +1,177 @@
// This file contains the client-side logic for the built in React Server
// Components integration. It is designed as a minimal base to build RSC
// applications on, and to showcase what features that Bake offers.
/// <reference lib="dom" />
import * as React from "react";
import { hydrateRoot } from "react-dom/client";
import { createFromReadableStream } from "react-server-dom-bun/client.browser";
import { bundleRouteForDevelopment } from "bun:bake/client";
let encoder = new TextEncoder();
let promise = createFromReadableStream(
new ReadableStream({
start(controller) {
let handleChunk = chunk =>
typeof chunk === "string" //
? controller.enqueue(encoder.encode(chunk))
: controller.enqueue(chunk);
(self.__bun_f ||= []).forEach((__bun_f.push = handleChunk));
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
controller.close();
});
} else {
controller.close();
}
},
}),
);
// This is a function component that uses the `use` hook, which unwraps a
// promise. The promise results in a component containing suspense boundaries.
// This is the same logic that happens on the server, except there is also a
// hook to update the promise when the client navigates.
let setPage;
const Root = () => {
setPage = React.useState(promise)[1];
return React.use(promise);
};
const root = hydrateRoot(document, <Root />, {
// handle `onUncaughtError` here
});
// Client side navigation is implemented by updating the app's `useState` with a
// new RSC payload promise. An abort controller is used to cancel a previous
// navigation. Callers of `goto` are expected to manage history state.
let currentReloadCtrl: AbortController | null = null;
async function goto(href: string) {
// TODO: this abort signal stuff doesnt work
// if (currentReloadCtrl) {
// currentReloadCtrl.abort();
// }
// const signal = (currentReloadCtrl = new AbortController()).signal;
// Due to the current implementation of the Dev Server, it must be informed of
// client-side routing so it can load client components. This is not necessary
// in production, and calling this in that situation will fail to compile.
if (import.meta.env.DEV) {
await bundleRouteForDevelopment(href, {
// signal
});
}
let response;
try {
// When using static builds, it isn't possible for the server to reliably
// branch on the `Accept` header. Instead, a static build creates a `.rsc`
// file that can be fetched. `import.meta.env.STATIC` is inlined by Bake.
response = await fetch(
import.meta.env.STATIC //
? `${href.replace(/\/(?:index)?$/, "")}/index.rsc`
: href,
{
headers: {
Accept: "text/x-component",
},
// signal: signal,
},
);
if (!response.ok) {
throw new Error(`Failed to fetch ${href}: ${response.status} ${response.statusText}`);
}
} catch (err) {
// Bail out to browser navigation if this fetch fails.
console.error(err);
location.href = href;
return;
}
// if (signal.aborted) return;
// TODO: error handling? abort handling?
const p = createFromReadableStream(response.body!);
// TODO: ensure CSS is ready
// Right now you can see a flash of unstyled content, since react does not
// wait for new link tags to load before they are injected.
// Use a react transition to update the page promise.
// TODO: How to get this show after 100ms, it hangs until all suspenses resolve
// React.startTransition(() => {
// if (signal.aborted) return;
// setPage((promise = p));
// });
setPage?.((promise = p));
}
// Instead of relying on a "<Link />" component, a global event listener on all
// clicks can be used. Care must be taken to intercept only anchor elements that
// did not have their default behavior prevented, non-left clicks, and more.
//
// This technique was inspired by SvelteKit which was inspired by https://github.com/visionmedia/page.js
document.addEventListener("click", async (event, element = event.target as HTMLAnchorElement) => {
if (
event.button ||
event.which != 1 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey ||
event.defaultPrevented
)
return;
while (element && element !== document.body) {
// This handles shadow roots
if (element.nodeType === 11) element = (element as any).host;
// If the current tag is an anchor.
if (element.nodeName.toUpperCase() === "A" && element.hasAttribute("href")) {
let url;
try {
url = new URL(element instanceof SVGAElement ? element.href.baseVal : element.href, document.baseURI);
} catch {
// Bail out to browser logic
return;
}
let pathname = url.pathname;
if (pathname.endsWith("/")) {
pathname = pathname.slice(0, -1);
}
// Ignore if the link is external
if (url.origin !== origin || (element.getAttribute("rel") || "").split(/\s+/).includes("external")) {
return;
}
// TODO: consider `target` attribute
// Take no action at all if the url is the same page.
// However if there is a hash, don't call preventDefault()
if (pathname === location.pathname && url.search === location.search) {
return url.hash || event.preventDefault();
}
const href = url.href;
history.pushState({}, "", href);
goto(href);
return event.preventDefault();
}
// Walk up the tree until an anchor or the body is found.
element = (element.assignedSlot ?? element.parentNode) as HTMLAnchorElement;
}
});
// Handle browser navigation events
window.addEventListener("popstate", () => goto(location.href));
// Frameworks can export a `onServerSideReload` function to hook into server-side
// hot module reloading. This export is not used in production and tree-shaken.
export async function onServerSideReload() {
await goto(location.href);
}

View File

@@ -0,0 +1,40 @@
// This file is unused by Bun itself, but rather is a tool for
// contributors to hack on `bun-framework-react` without needing
// to compile bun itself. If changes to this are made, please
// update 'pub fn react' in 'bake.zig'
import type { Bake } from "bun";
export function react(): Bake.Framework {
return {
// When the files are embedded in the Bun binary,
// relative path resolution does not work.
builtInModules: [
{ import: "bun-framework-react/client.tsx", path: require.resolve("./client.tsx") },
{ import: "bun-framework-react/server.tsx", path: require.resolve("./server.tsx") },
{ import: "bun-framework-react/ssr.tsx", path: require.resolve("./ssr.tsx") },
],
fileSystemRouterTypes: [
{
root: "pages",
clientEntryPoint: "bun-framework-react/client.tsx",
serverEntryPoint: "bun-framework-react/server.tsx",
extensions: ["jsx", "tsx"],
style: "nextjs-pages-ui",
},
],
staticRouters: ["public"],
reactFastRefresh: {
importSource: "react-refresh/runtime",
},
serverComponents: {
separateSSRGraph: true,
serverRegisterClientReferenceExport: "registerClientReference",
serverRuntimeImportSource: "react-server-dom-webpack/server",
},
bundlerOptions: {
ssr: {
conditions: ["react-server"],
},
},
};
}

View File

@@ -0,0 +1,117 @@
import type { Bake } from "bun";
import { renderToPipeableStream } from "react-server-dom-bun/server.node.unbundled.js";
import { renderToHtml, renderToStaticHtml } from "bun-framework-react/ssr.tsx" with { bunBakeGraph: "ssr" };
import { serverManifest } from "bun:bake/server";
import { PassThrough } from "node:stream";
function assertReactComponent(Component: any) {
if (typeof Component !== "function") {
console.log("Expected a React component", Component, typeof Component);
throw new Error("Expected a React component");
}
}
// This function converts the route information into a React component tree.
function getPage(meta: Bake.RouteMetadata) {
const { styles } = meta;
const Page = meta.pageModule.default;
if (import.meta.env.DEV) assertReactComponent(Page);
let route = <Page />;
for (const layout of meta.layouts) {
const Layout = layout.default;
if (import.meta.env.DEV) assertReactComponent(Layout);
route = <Layout>{route}</Layout>;
}
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bun + React Server Components</title>
{styles.map(url => (
<link key={url} rel="stylesheet" href={url} />
))}
</head>
<body>{route}</body>
</html>
);
}
// `server.tsx` exports a function to be used for handling user routes. It takes
// in the Request object, the route's module, and extra route metadata.
export async function render(request: Request, meta: Bake.RouteMetadata): Promise<Response> {
// The framework generally has two rendering modes.
// - Standard browser navigation
// - Client-side navigation
//
// For React, this means we will always perform `renderToReadableStream` to
// generate the RSC payload, but only generate HTML for the former of these
// rendering modes. This is signaled by `client.tsx` via the `Accept` header.
const skipSSR = request.headers.get("Accept")?.includes("text/x-component");
const page = getPage(meta);
// This renders Server Components to a ReadableStream "RSC Payload"
const rscPayload = renderToPipeableStream(page, serverManifest)
// TODO: write a lightweight version of PassThrough
.pipe(new PassThrough());
if (skipSSR) {
return new Response(rscPayload as any, {
status: 200,
headers: { "Content-Type": "text/x-component" },
});
}
// Then the RSC payload is rendered into HTML
return new Response(await renderToHtml(rscPayload, meta.scripts), {
headers: {
"Content-Type": "text/html; charset=utf8",
},
});
}
// When a production build is performed, pre-rendering is invoked here. If this
// function returns no files, the route is always dynamic. When building an app
// to static files, all routes get pre-rendered (build failure if not possible).
export async function prerender(meta: Bake.RouteMetadata) {
const page = getPage(meta);
const rscPayload = renderToPipeableStream(page, serverManifest)
// TODO: write a lightweight version of PassThrough
.pipe(new PassThrough());
let rscChunks: Uint8Array[] = [];
rscPayload.on("data", chunk => rscChunks.push(chunk));
const html = await renderToStaticHtml(rscPayload, meta.scripts);
const rsc = new Blob(rscChunks, { type: "text/x-component" });
return {
// Each route generates a directory with framework-provided files. Keys are
// files relative to the route path, and values are anything `Bun.write`
// supports. Streams may result in lower memory usage.
files: {
// Directories like `blog/index.html` are preferred over `blog.html` because
// certain static hosts do not support this conversion. By using `index.html`,
// the static build is more portable.
"/index.html": html,
// The RSC payload is provided so client-side can use this file for seamless
// client-side navigation. This is equivalent to 'Accept: text/x-component'
// for the non-static build.s
"/index.rsc": rsc,
},
// In the future, it will be possible to return data for a partially
// pre-rendered page instead of a fully rendered route. Bun might also
// expose caching options here.
};
}
// When a dynamic build uses static assets, Bun can map content types in the
// user's `Accept` header to the different static files.
export const contentTypeToStaticFile = {
"text/html": "index.html",
"text/x-component": "index.rsc",
};

View File

@@ -0,0 +1,366 @@
// This file is loaded in the SSR graph, meaning the `react-server` condition is
// no longer set. This means we can import client components, using `react-dom`
// to perform Server-side rendering (creating HTML) out of the RSC payload.
import * as React from "react";
import { clientManifest } from "bun:bake/server";
import type { Readable } from "node:stream";
import { EventEmitter } from "node:events";
import { createFromNodeStream, type Manifest } from "react-server-dom-bun/client.node.unbundled.js";
import { renderToPipeableStream } from "react-dom/server.node";
// Verify that React 19 is being used.
if (!React.use) {
throw new Error("Bun's React integration requires React 19");
}
const createFromNodeStreamOptions: Manifest = {
moduleMap: clientManifest,
moduleLoading: { prefix: "/" },
};
// The `renderToHtml` function not only implements converting the RSC payload
// into HTML via react-dom, but also streaming the RSC payload via injected
// script tags. While the page is streaming, the client is loading the RSC
// payload in the `__bun_f` ('f' meaning flight) global. `client.tsx` can
// convert that array into a `ReadableStream` to incrementally hydrate the page.
//
// Some techniques have been taken from what Next.js and `rsc-html-stream` do,
// but this version is [1] uses more efficient streaming APIs and [2] streams
// the RSC data alongside the HTML, rather than injecting it at the very end.
//
// References:
// - https://github.com/vercel/next.js/blob/15.0.2/packages/next/src/server/app-render/use-flight-response.tsx
// - https://github.com/devongovett/rsc-html-stream
export function renderToHtml(rscPayload: Readable, bootstrapModules: readonly string[]): ReadableStream {
// Bun supports a special type of readable stream type called "direct",
// which provides a raw handle to the controller. We can bypass all of
// the Web Streams API (slow) and use the controller directly.
let stream: RscInjectionStream | null = null;
let abort: () => void;
return new ReadableStream({
type: "direct",
pull(controller) {
// Initialize the injection stream so it gets the first "data" listener.
stream = new RscInjectionStream(rscPayload, controller);
// `createFromNodeStream` turns the RSC payload into a React component.
const promise = createFromNodeStream(rscPayload, {
// React takes in a manifest mapping client-side assets
// to the imports needed for server-side rendering.
moduleMap: clientManifest,
moduleLoading: { prefix: "/" },
});
// The root is this "Root" component that unwraps the streamed promise
// with `use`, and then returning the parsed React component for the UI.
const Root = () => React.use(promise);
// `renderToPipeableStream` is what actually generates HTML.
// Here is where React is told what script tags to inject.
let pipe: (stream: any) => void;
({ pipe, abort } = renderToPipeableStream(<Root />, {
bootstrapModules,
}));
pipe(stream);
// Promise resolved after all data is combined.
return stream.finished;
},
cancel() {
stream?.destroy();
abort?.();
},
} as Bun.DirectUnderlyingSource as any);
}
// Static builds can not stream suspense boundaries as they finish, but instead
// produce a single HTML blob. The approach is otherwise similar to `renderToHtml`.
export function renderToStaticHtml(rscPayload: Readable, bootstrapModules: readonly string[]): Promise<Blob> {
const stream = new StaticRscInjectionStream(rscPayload);
const promise = createFromNodeStream(rscPayload, createFromNodeStreamOptions);
const Root = () => React.use(promise);
const { pipe } = renderToPipeableStream(<Root />, {
bootstrapModules,
// Only begin flowing HTML once all of it is ready. This tells React
// to not emit the flight chunks, just the entire HTML.
onAllReady: () => pipe(stream),
});
return stream.result;
}
const closingBodyTag = "</body></html>";
const startScriptTag = "<script>(self.__bun_f||=[]).push(";
const continueScriptTag = "<script>__bun_f.push(";
const enum HtmlState {
/** HTML is flowing, it is not an okay time to inject RSC data. */
Flowing,
/** It is safe to inject RSC data. */
Boundary,
}
const enum RscState {
/** No RSC data has been written yet */
Waiting,
/** Some but not all RSC data has been written */
Paused,
/** All RSC data has been written */
Done,
}
class RscInjectionStream extends EventEmitter {
controller: ReadableStreamDirectController;
html: HtmlState = HtmlState.Flowing;
rsc: RscState = RscState.Waiting;
/** Chunks of RSC that will be injected at the next available point. */
rscChunks: Uint8Array[] = [];
/** If all RSC chunks have been processed */
rscHasEnded = false;
/** Shared state for decoding RSC data into UTF-8 strings */
decoder = new TextDecoder("utf-8", { fatal: true });
/** Resolved when all data is written */
finished: Promise<void>;
finalize: () => void;
constructor(rscPayload: Readable, controller: ReadableStreamDirectController) {
super();
this.controller = controller;
const { resolve, promise } = Promise.withResolvers<void>();
this.finished = promise;
this.finalize = resolve;
rscPayload.on("data", this.writeRscData.bind(this));
rscPayload.on("end", () => {
this.rscHasEnded = true;
});
}
write(data: Uint8Array) {
if (endsWithClosingScript(data)) {
// The HTML is not done yet, but it's a suitible time to inject RSC data.
const { controller } = this;
controller.write(data);
this.html = HtmlState.Boundary;
this.drainRscChunks();
} else if (endsWithClosingBody(data)) {
// The HTML is about to finish. When this happens there cannot be more RSC
// chunks, since if that was truly the case, the HTML wouldn't be done.
const { controller } = this;
controller.write(data.subarray(0, data.length - closingBodyTag.length));
this.drainRscChunks();
controller.write(closingBodyTag);
controller.end();
this.finalize();
} else {
this.controller.write(data);
this.html = HtmlState.Flowing;
}
}
drainRscChunks() {
const { rsc } = this;
if (rsc === RscState.Done) return;
const { controller, decoder, rscChunks } = this;
if (rscChunks.length === 0) return;
if (rsc === RscState.Waiting) {
controller.write(startScriptTag);
} else {
controller.write(continueScriptTag);
this.rsc = RscState.Paused;
}
writeManyFlightScriptData(rscChunks, decoder, controller);
if (this.rscHasEnded) {
this.rsc = RscState.Done;
}
this.rscChunks = [];
}
writeRscData(chunk: Uint8Array) {
if (this.html === HtmlState.Boundary) {
const { controller, decoder } = this;
if (this.rsc === RscState.Waiting) {
controller.write(startScriptTag);
} else {
controller.write(continueScriptTag);
this.rsc = RscState.Paused;
}
writeSingleFlightScriptData(chunk, decoder, controller);
} else {
this.rscChunks.push(chunk);
}
}
flush() {
// Ignore flush requests from React. Bun will automatically flush when reasonable.
}
destroy() {
// TODO:
}
end() {
this.finalize();
}
}
class StaticRscInjectionStream extends EventEmitter {
rscPayloadChunks: Uint8Array[] = [];
chunks: (Uint8Array | string)[] = [];
result: Promise<Blob>;
finalize: (blob: Blob) => void;
reject: (error: Error) => void;
constructor(rscPayload: Readable) {
super();
const { resolve, promise, reject } = Promise.withResolvers<Blob>();
this.result = promise;
this.finalize = resolve;
this.reject = reject;
rscPayload.on("data", chunk => this.rscPayloadChunks.push(chunk));
}
write(chunk) {
this.chunks.push(chunk);
}
end() {
// Inject the finalized RSC payload into the last chunk
const lastChunk = this.chunks[this.chunks.length - 1];
// Release assertions for React's behavior. If these break there will be malformed HTML.
if (typeof lastChunk === "string") {
this.destroy(new Error("The last chunk was expected to be a Uint8Array"));
return;
}
if (!endsWithClosingBody(lastChunk)) {
this.destroy(new Error("The last chunk did not end with a closing </body></html> tag"));
return;
}
this.chunks[this.chunks.length - 1] = lastChunk.slice(0, lastChunk.length - closingBodyTag.length);
let string = startScriptTag;
writeManyFlightScriptData(this.rscPayloadChunks, new TextDecoder("utf-8"), { write: str => (string += str) });
this.chunks.push(string + closingBodyTag);
this.finalize(new Blob(this.chunks, { type: "text/html" }));
}
flush() {
// Ignore flush requests from React.
}
destroy(error) {
console.error(error);
this.reject(error);
}
}
/** Assumes the opening script tag and function call have been written */
function writeSingleFlightScriptData(
chunk: Uint8Array,
decoder: TextDecoder,
controller: { write: (str: string) => void },
) {
try {
// `decode()` will throw on invalid UTF-8 sequences.
controller.write("'" + toSingleQuote(decoder.decode(chunk, { stream: true })) + "')</script>");
} catch {
// The chunk cannot be embedded as a UTF-8 string in the script tag.
// No data should have been written yet, so a base64 fallback can be used.
const base64 = btoa(String.fromCodePoint(...chunk));
controller.write(`Uint8Array.from(atob(\"${base64}\"),m=>m.codePointAt(0))</script>`);
}
}
/**
* Attempts to combine RSC chunks together to minimize the number of chunks the
* client processes.
*/
function writeManyFlightScriptData(
chunks: Uint8Array[],
decoder: TextDecoder,
controller: { write: (str: string) => void },
) {
if (chunks.length === 1) return writeSingleFlightScriptData(chunks[0], decoder, controller);
let i = 0;
try {
// Combine all chunks into a single string if possible.
for (; i < chunks.length; i++) {
// `decode()` will throw on invalid UTF-8 sequences.
const str = toSingleQuote(decoder.decode(chunks[i], { stream: true }));
if (i === 0) controller.write("'");
controller.write(str);
}
controller.write("')</script>");
} catch {
// The chunk cannot be embedded as a UTF-8 string in the script tag.
// Since this is rare, just make the rest of the chunks base64.
if (i > 0) controller.write("');__bun_f.push(");
controller.write('Uint8Array.from(atob("');
for (; i < chunks.length; i++) {
const chunk = chunks[i];
const base64 = btoa(String.fromCodePoint(...chunk));
controller.write(base64.slice(1, -1));
}
controller.write('"),m=>m.codePointAt(0))</script>');
}
}
// Instead of using `JSON.stringify`, this uses a single quote variant of it, since
// the RSC payload includes a ton of " characters. This is slower, but an easy
// component to move into native code.
function toSingleQuote(str: string): string {
return (
str // Escape single quotes, backslashes, and newlines
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/\n/g, "\\n")
// Escape closing script tags and HTML comments in JS content.
.replace(/<!--/g, "<\\!--")
.replace(/<\/(script)/gi, "</\\$1")
);
}
// Note that the bundler special cases constant folding for `charCodeAt`.
function endsWithClosingScript(view: Uint8Array): boolean {
const length = view.length;
return (
length >= 9 &&
view[length - 9] === "<".charCodeAt(0) &&
view[length - (9 - 1)] === "/".charCodeAt(0) &&
view[length - (9 - 2)] === "s".charCodeAt(0) &&
view[length - (9 - 3)] === "c".charCodeAt(0) &&
view[length - (9 - 4)] === "r".charCodeAt(0) &&
view[length - (9 - 5)] === "i".charCodeAt(0) &&
view[length - (9 - 6)] === "p".charCodeAt(0) &&
view[length - (9 - 7)] === "t".charCodeAt(0) &&
view[length - (9 - 8)] === ">".charCodeAt(0)
);
}
function endsWithClosingBody(view: Uint8Array): boolean {
const length = view.length;
return (
length >= 14 &&
view[length - 14] === "<".charCodeAt(0) &&
view[length - (14 - 1)] === "/".charCodeAt(0) &&
view[length - (14 - 2)] === "b".charCodeAt(0) &&
view[length - (14 - 3)] === "o".charCodeAt(0) &&
view[length - (14 - 4)] === "d".charCodeAt(0) &&
view[length - (14 - 5)] === "y".charCodeAt(0) &&
view[length - (14 - 6)] === ">".charCodeAt(0) &&
view[length - (14 - 7)] === "<".charCodeAt(0) &&
view[length - (14 - 8)] === "/".charCodeAt(0) &&
view[length - (14 - 9)] === "h".charCodeAt(0) &&
view[length - (14 - 10)] === "t".charCodeAt(0) &&
view[length - (14 - 11)] === "m".charCodeAt(0) &&
view[length - (14 - 12)] === "l".charCodeAt(0) &&
view[length - (14 - 13)] === ">".charCodeAt(0)
);
}

View File

@@ -1,41 +0,0 @@
/// <reference lib="dom" />
import { use } from "react";
import { hydrateRoot } from "react-dom/client";
import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
function assertionFailed(msg: string) {
throw new Error(`Assertion Failure: ${msg}. This is a bug in Bun's React integration`);
}
// Client-side entry point expects an RSC payload. In development, let's fail
// loudly if this is somehow missing.
const initialPayload = document.getElementById("rsc_payload");
if (import.meta.env.DEV) {
if (!initialPayload) assertionFailed("Missing #rsc_payload in HTML response");
}
// React takes in a ReadableStream with the payload.
let promise = createFromReadableStream(new Response(initialPayload!.innerText).body!);
initialPayload!.remove();
const Async = () => use(promise);
const root = hydrateRoot(document, <Async />, {
// handle `onUncaughtError` here
});
export async function onServerSideReload() {
const response = await fetch(location.href + '/index.rsc', {
headers: {
Accept: "text/x-component",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
promise = createFromReadableStream(response.body!);
root.render(<Async />);
}
globalThis.onServerSideReload = onServerSideReload;

View File

@@ -1,27 +0,0 @@
// This file is unused by Bun itself, but rather is a tool for
// contributors to hack on `bun-framework-react` without needing
// to compile bun itself. If changes to this are made, please
// update 'pub fn react' in 'bake.zig'
import type { Bake } from "bun";
export function react(): Bake.Framework {
return {
// When the files are embedded in the Bun binary, relative
// path resolution does not work.
builtInModules: {
'bun-framework-react/client.tsx': { path: require.resolve('./client.tsx') },
'bun-framework-react/server.tsx': { path: require.resolve('./server.tsx') },
'bun-framework-react/ssr.tsx': { path: require.resolve('./ssr.tsx') },
},
clientEntryPoint: "bun-framework-react/client.tsx",
serverEntryPoint: "bun-framework-react/server.tsx",
reactFastRefresh: {
importSource: "react-refresh/runtime",
},
serverComponents: {
separateSSRGraph: true,
serverRegisterClientReferenceExport: 'registerClientReference',
serverRuntimeImportSource: 'react-server-dom-webpack/server'
}
};
}

View File

@@ -1,127 +0,0 @@
import type { Bake } from "bun";
import { renderToReadableStream } from "react-server-dom-webpack/server.browser";
import { renderToHtml } from "bun-framework-rsc/ssr.tsx" with { bunBakeGraph: "ssr" };
import { clientManifest, serverManifest } from "bun:bake/server";
import { join } from 'node:path';
function getPage(route, meta: Bake.RouteMetadata) {
const Route = route.default;
const { styles } = meta;
if (import.meta.env.DEV) {
if (typeof Route !== "function") {
throw new Error(
"Expected the default export of " +
JSON.stringify(meta.devRoutePath) +
" to be a React component, got " +
JSON.stringify(Route),
);
}
}
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bun + React Server Components</title>
{styles.map(url => (
<link key={url} rel="stylesheet" href={url} />
))}
</head>
<body>
<Route />
</body>
</html>
);
}
// `server.tsx` exports a function to be used for handling user routes. It takes
// in the Request object, the route's module, and extra route metadata.
export default async function render(request: Request, route: any, meta: Bake.RouteMetadata): Promise<Response> {
// The framework generally has two rendering modes.
// - Standard browser navigation
// - Client-side navigation
//
// For React, this means we will always perform `renderToReadableStream` to
// generate the RSC payload, but only generate HTML for the former of these
// rendering modes. This is signaled by `client.tsx` via the `Accept` header.
const skipSSR = request.headers.get("Accept")?.includes("text/x-component");
const page = getPage(route, meta);
// This renders Server Components to a ReadableStream "RSC Payload"
const rscPayload = renderToReadableStream(page, serverManifest);
if (skipSSR) {
return new Response(rscPayload, {
status: 200,
headers: { "Content-Type": "text/x-component" },
});
}
// One straem is used to render SSR. The second is embedded into the html for browser hydration.
// Note: This approach does not stream the response. That practice is called "react flight" and should be added
const [rscPayload1, rscPayload2] = rscPayload.tee();
const rscPayloadBuffer = Bun.readableStreamToText(rscPayload1);
const rw = new HTMLRewriter();
rw.on("body", {
element(element) {
element.onEndTag(async end => {
end.before(
`<script id="rsc_payload" type="json">${await rscPayloadBuffer}</script>` +
meta.scripts.map(url => `<script src=${JSON.stringify(url)}></script>`).join(""),
{ html: true },
);
});
},
});
// TODO: readableStreamToText is needed due to https://github.com/oven-sh/bun/issues/14216
const output = await Bun.readableStreamToText(await renderToHtml(rscPayload2));
return rw.transform(
new Response(output, {
headers: {
"Content-Type": "text/html; charset=utf8",
},
}),
);
}
// For static site generation, a different function is given, one without a request object.
export async function renderStatic(route: any, meta: Bake.RouteMetadata) {
const page = getPage(route, meta);
const rscPayload = renderToReadableStream(page, serverManifest);
const [rscPayload1, rscPayload2] = rscPayload.tee();
// Prepare both files in parallel
let [html, rscPayloadBuffer] = await Promise.all([
Bun.readableStreamToText(await renderToHtml(rscPayload2)),
Bun.readableStreamToText(rscPayload1),
]);
const scripts = meta.scripts.map(url => `<script src=${JSON.stringify(url)}></script>`);
html = html.replace('</body>', `<script id="rsc_payload" type="json">${rscPayloadBuffer}</script>${scripts.join('\n')}</body>`);
// Each route generates a directory with framework-provided files. Keys are
// files relative to the route path, and values are anything `Bun.write`
// supports. Streams may result in lower memory usage.
return {
// Directories like `blog/index.html` are preferred over `blog.html` because
// certain static hosts do not support this conversion. By using `index.html`,
// the static build is more portable.
'/index.html': html,
// The RSC payload is provided so client-side can use this file for seamless
// client-side navigation. This is equivalent to 'Accept: text/x-component'
// for the non-static build.s
'/index.rsc': rscPayloadBuffer,
}
}
// This is a hack to make react-server-dom-webpack work with Bun's bundler.
// It will be removed once Bun acquires react-server-dom-bun.
if (!import.meta.env.DEV) {
globalThis.__webpack_require__ = (id: string) => {
console.log("Bun: __webpack_require__", id);
const y = import.meta.require(join(import.meta.dir, id));
console.log({y});
return y;
};
}

View File

@@ -1,16 +0,0 @@
// This file is loaded in the SSR graph, meaning the `react-server` condition is
// no longer set. This means we can import client components, using `react-dom`
// to perform SSR from the RSC payload.
import { use } from "react";
import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
import { renderToReadableStream } from "react-dom/server";
import { clientManifest } from "bun:bake/server";
export function renderToHtml(rscPayload: ReadableStream): Promise<ReadableStream> {
const promise = createFromReadableStream(rscPayload, {
moduleMap: clientManifest,
moduleLoading: { prefix: "" },
});
const Async = () => use(promise);
return renderToReadableStream(<Async />);
}

View File

@@ -84,35 +84,36 @@ function initImportMeta(m: HotModule): ImportMeta {
* present, or is something a user is able to dynamically specify.
*/
export function loadModule<T = any>(key: Id, type: LoadModuleType): HotModule<T> {
let module = registry.get(key);
if (module) {
let mod = registry.get(key);
if (mod) {
// Preserve failures until they are re-saved.
if (module._state == State.Error) throw module._cached_failure;
if (mod._state == State.Error) throw mod._cached_failure;
return module;
return mod;
}
module = new HotModule(key);
mod = new HotModule(key);
const load = input_graph[key];
if (!load) {
if (type == LoadModuleType.AssertPresent) {
throw new Error(
`Failed to load bundled module '${key}'. This is not a dynamic import, and therefore is a bug in Bun Kit's bundler.`,
`Failed to load bundled module '${key}'. This is not a dynamic import, and therefore is a bug in Bun's bundler.`,
);
} else {
throw new Error(
`Failed to resolve dynamic import '${key}'. In Bun Kit, all imports must be statically known at compile time so that the bundler can trace everything.`,
`Failed to resolve dynamic import '${key}'. In Bun Bake, all imports must be statically known at compile time so that the bundler can trace everything.`,
);
}
}
try {
registry.set(key, module);
load(module);
registry.set(key, mod);
load(mod);
} catch (err) {
module._cached_failure = err;
module._state = State.Error;
console.error(err);
mod._cached_failure = err;
mod._state = State.Error;
throw err;
}
return module;
return mod;
}
export const getModule = registry.get.bind(registry);
@@ -169,16 +170,14 @@ if (side === "client") {
refreshRuntime = loadModule(refresh, LoadModuleType.AssertPresent).exports;
refreshRuntime.injectIntoGlobalHook(window);
}
}
// TODO: Remove this after `react-server-dom-bun` is uploaded
globalThis.__webpack_require__ = (id: string) => {
if (side == "server" && config.separateSSRGraph && !id.startsWith("ssr:")) {
return loadModule("ssr:" + id, LoadModuleType.UserDynamic).exports;
} else {
return loadModule(id, LoadModuleType.UserDynamic).exports;
}
};
const server_module = new HotModule("bun:bake/client");
server_module.__esModule = true;
server_module.exports = {
bundleRouteForDevelopment: async () => {},
};
registry.set(server_module.id, server_module);
}
runtimeHelpers.__name(HotModule.prototype.importSync, "<HMR runtime> importSync");
runtimeHelpers.__name(HotModule.prototype.require, "<HMR runtime> require");

View File

@@ -1,36 +1,66 @@
// This file is the entrypoint to the hot-module-reloading runtime.
// On the server, communication is facilitated using the default
// export, which is assigned via `server_exports`.
// On the server, communication is established with `server_exports`.
import type { Bake } from "bun";
import { loadModule, LoadModuleType, replaceModules, clientManifest, serverManifest, getModule } from "./hmr-module";
import { loadModule, LoadModuleType, replaceModules, clientManifest, serverManifest } from "./hmr-module";
if (typeof IS_BUN_DEVELOPMENT !== "boolean") {
throw new Error("DCE is configured incorrectly");
}
interface Exports {
handleRequest: (
req: Request,
routerTypeMain: Id,
routeModules: Id[],
clientEntryUrl: string,
styles: string[],
params: Record<string, string> | null,
) => any;
registerUpdate: (
modules: any,
componentManifestAdd: null | string[],
componentManifestDelete: null | string[],
) => void;
}
declare let server_exports: Exports;
server_exports = {
async handleRequest(req, routeModuleId, clientEntryUrl, styles) {
const serverRenderer = loadModule<Bake.ServerEntryPoint>(config.main, LoadModuleType.AssertPresent).exports.default;
async handleRequest(req, routerTypeMain, routeModules, clientEntryUrl, styles, params) {
if (IS_BUN_DEVELOPMENT) {
console.log("handleRequest", {
routeModules,
clientEntryUrl,
styles,
params,
});
}
const serverRenderer = loadModule<Bake.ServerEntryPoint>(routerTypeMain, LoadModuleType.AssertPresent).exports
.render;
if (!serverRenderer) {
throw new Error('Framework server entrypoint is missing a "default" export.');
throw new Error('Framework server entrypoint is missing a "render" export.');
}
if (typeof serverRenderer !== "function") {
throw new Error('Framework server entrypoint\'s "default" export is not a function.');
throw new Error('Framework server entrypoint\'s "render" export is not a function.');
}
const response = await serverRenderer(req, loadModule(routeModuleId, LoadModuleType.AssertPresent).exports, {
const [pageModule, ...layouts] = routeModules.map(id => loadModule(id, LoadModuleType.AssertPresent).exports);
const response = await serverRenderer(req, {
styles: styles,
scripts: [clientEntryUrl],
devRoutePath: routeModuleId,
layouts,
pageModule,
modulepreload: [],
params,
});
if (!(response instanceof Response)) {
throw new Error(`Server-side request handler was expected to return a Response object.`);
}
// TODO: support streaming
return await response.text();
return response;
},
registerUpdate(modules, componentManifestAdd, componentManifestDelete) {
replaceModules(modules);
@@ -42,14 +72,21 @@ server_exports = {
const { exports, __esModule } = mod;
const exp = __esModule ? exports : (mod._ext_exports ??= { ...exports, default: exports });
const client = {};
for (const exportName of Object.keys(exp)) {
serverManifest[uid] = {
id: uid,
name: exportName,
chunks: [],
};
client[exportName] = {
specifier: "ssr:" + uid,
name: exportName,
};
}
clientManifest[uid] = client;
} catch (err) {
console.log("caught error");
console.log(err);
}
}
@@ -65,4 +102,4 @@ server_exports = {
}
}
},
};
} satisfies Exports;

View File

@@ -59,13 +59,6 @@
blue: "#89b4fa",
yellow: "#f9e2af",
};
const ws = new WebSocket(location.origin + "/_bun/hmr");
ws.binaryType = "arraybuffer"; // We are expecting binary data
ws.onopen = function () {
ws.send(new Uint8Array([118])); // Send 'v' byte to initiate connection
};
// Store nodes and edges
let clientFiles = [];
@@ -74,15 +67,34 @@
let serverEdges = [];
let currentEdgeIds = new Set();
let currentNodeIds = new Set();
let isFirst = true;
let = true;
// When a message is received
ws.onmessage = function (event) {
const buffer = new Uint8Array(event.data);
// Initialize Vis.js network
const nodes = new vis.DataSet();
const edges = new vis.DataSet();
const container = document.getElementById("network");
const data = { nodes, edges };
const options = {};
const network = new vis.Network(container, data, options);
// Crash handler injects `inlinedData` to produce an offline-compatible version of IncrementalVisualizer
if (typeof inlinedData !== "undefined") {
decodeAndUpdate(inlinedData);
} else {
const ws = new WebSocket(location.origin + "/_bun/hmr");
ws.binaryType = "arraybuffer"; // We are expecting binary data
ws.onopen = function () {
ws.send(new Uint8Array([118])); // Send 'v' byte to initiate connection
};
ws.onmessage = function (event) {
decodeAndUpdate(new Uint8Array(event.data));
};
}
function decodeAndUpdate(buffer) {
// Only process messages starting with 'v' (ASCII code 118)
if (buffer[0] !== 118) return;
console.log("Got Event");
let offset = 1; // Skip the 'v' byte
@@ -118,7 +130,7 @@
// Update the graph visualization
updateGraph();
};
}
// Helper to read 4-byte unsigned int
function readUint32(buffer, offset) {
@@ -126,7 +138,8 @@
}
function basename(path) {
return path.slice(path.lastIndexOf("/") + 1);
const match = /node_modules\/(.*?)\//.exec(path);
return match ? match[1] : path;
}
// Parse the files from the buffer
@@ -182,22 +195,13 @@
return { edges, offset };
}
// Initialize Vis.js network
const nodes = new vis.DataSet();
const edges = new vis.DataSet();
const container = document.getElementById("network");
const data = { nodes, edges };
const options = {};
const network = new vis.Network(container, data, options);
// Helper function to add or update nodes in the graph
function updateNode(id, file, group) {
const label = basename(file.name);
const color = file.name.includes("/node_modules/")
? c.gray
: file.isStale
? c.red
const color = file.isStale
? c.red
: file.name.startsWith("node_modules/")
? c.gray
: file.isBoundary
? c.orange
: group === "client"
@@ -216,7 +220,7 @@
color: color,
borderWidth: file.isRoute ? 3 : 1,
group,
font: file.name.includes("/node_modules/") ? "10px sans-serif #cdd6f488" : "20px sans-serif #cdd6f4",
font: file.name.startsWith("node_modules/") ? "10px sans-serif #cdd6f488" : "20px sans-serif #cdd6f4",
};
if (nodes.get(id)) {
nodes.update(props);

View File

@@ -1,3 +1,4 @@
{
"sideEffects": false
"sideEffects": false,
"type": "module"
}

View File

@@ -1,11 +1,7 @@
//! Implements building a Bake application to production
const log = bun.Output.scoped(.production, false);
pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
if (!bun.Environment.isDebug) {
Output.errGeneric("Not yet stable. Sorry!", .{});
bun.Global.crash();
}
Output.warn(
\\Be advised that Bun Bake is highly experimental, and its API
\\will have breaking changes. Join the <magenta>#bake<r> Discord
@@ -26,24 +22,32 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
}
var cwd_buf: bun.PathBuffer = undefined;
const cwd = try bun.getcwd(&cwd_buf);
const cwd = bun.getcwd(&cwd_buf) catch |err| {
Output.err(err, "Could not query current working directory", .{});
bun.Global.crash();
};
// Create a VM + global for loading the config file, plugins, and
// performing build time prerendering.
bun.JSC.initialize(false);
bun.JSAst.Expr.Data.Store.create();
bun.JSAst.Stmt.Data.Store.create();
var arena = try bun.MimallocArena.init();
defer arena.deinit();
const allocator = bun.default_allocator;
const vm = try VirtualMachine.init(.{
const vm = try VirtualMachine.initBake(.{
.allocator = arena.allocator(),
.log = ctx.log,
.args = ctx.args,
.smol = ctx.runtime_options.smol,
});
defer vm.deinit();
var b = &vm.bundler;
vm.global = BakeCreateProdGlobal(vm.console);
vm.regular_event_loop.global = vm.global;
vm.jsc = vm.global.vm();
vm.event_loop.ensureWaker();
const b = &vm.bundler;
vm.preload = ctx.preloads;
vm.argv = ctx.passthrough;
vm.arena = &arena;
@@ -83,8 +87,25 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
const api_lock = vm.jsc.getAPILock();
defer api_lock.release();
buildWithVm(ctx, cwd, vm) catch |err| switch (err) {
error.JSError => |e| {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
vm.printErrorLikeObjectToConsole(vm.global.takeException(e));
if (vm.exit_handler.exit_code == 0) {
vm.exit_handler.exit_code = 1;
}
vm.globalExit();
},
else => |e| return e,
};
}
pub fn buildWithVm(ctx: bun.CLI.Command.Context, cwd: []const u8, vm: *VirtualMachine) !void {
// Load and evaluate the configuration module
const global = vm.global;
const b = &vm.bundler;
const allocator = bun.default_allocator;
Output.prettyErrorln("Loading configuration", .{});
Output.flush();
const unresolved_config_entry_point = if (ctx.args.entry_points.len > 0) ctx.args.entry_points[0] else "./bun.app";
@@ -110,9 +131,12 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
const config_entry_point_string = bun.String.createUTF8(config_entry_point.pathConst().?.text);
defer config_entry_point_string.deref();
const config_promise = bun.JSC.JSModuleLoader.loadAndEvaluateModule(vm.global, &config_entry_point_string) orelse {
@panic("TODO");
const config_promise = bun.JSC.JSModuleLoader.loadAndEvaluateModule(global, &config_entry_point_string) orelse {
bun.assert(global.hasException());
return error.JSError;
};
vm.waitForPromise(.{ .internal = config_promise });
var options = switch (config_promise.unwrap(vm.jsc, .mark_handled)) {
.pending => unreachable,
@@ -124,17 +148,11 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
Output.panic("TODO: print this error better, default export is not an object", .{});
}
const app = default.get(vm.global, "app") orelse {
const app = try default.get(vm.global, "app") orelse {
Output.panic("TODO: print this error better, default export needs an 'app' object", .{});
};
if (vm.global.hasException()) {
@panic("pending exception");
}
break :config bake.bakeOptionsFromJs(vm.global, app) catch |err| {
Output.panic("TODO, print this error better: {}", .{err});
};
break :config try bake.UserOptions.fromJS(app, vm.global);
},
.rejected => |err| {
// dont run on rejected since we fail the build here
@@ -161,13 +179,13 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
var client_bundler: bun.bundler.Bundler = undefined;
var server_bundler: bun.bundler.Bundler = undefined;
var ssr_bundler: bun.bundler.Bundler = undefined;
try framework.initBundler(allocator, vm.log, .production, .server, &server_bundler);
try framework.initBundler(allocator, vm.log, .production, .client, &client_bundler);
try framework.initBundler(allocator, vm.log, .production_static, .server, &server_bundler);
try framework.initBundler(allocator, vm.log, .production_static, .client, &client_bundler);
if (separate_ssr_graph) {
try framework.initBundler(allocator, vm.log, .production, .ssr, &ssr_bundler);
try framework.initBundler(allocator, vm.log, .production_static, .ssr, &ssr_bundler);
}
// these share pointers right now, so setting NODE_ENV == production
// these share pointers right now, so setting NODE_ENV == production on one should affect all
bun.assert(server_bundler.env == client_bundler.env);
framework.* = framework.resolve(&server_bundler.resolver, &client_bundler.resolver) catch {
@@ -183,23 +201,49 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
var root_dir_buf: bun.PathBuffer = undefined;
const root_dir_path = bun.path.joinAbsStringBuf(cwd, &root_dir_buf, &.{"dist"}, .auto);
const root_path_trailing = root_dir_path.ptr[0 .. root_dir_path.len + 1];
_ = root_path_trailing; // autofix
root_dir_buf[root_dir_path.len] = std.fs.path.sep;
// server_bundler.options.public_path = root_path_trailing;
// server_bundler.resolver.opts.public_path = root_path_trailing;
// const root_path_trailing = root_dir_path.ptr[0 .. root_dir_path.len + 1];
// root_dir_buf[root_dir_path.len] = std.fs.path.sep;
// _ = root_path_trailing;
var entry_points = std.ArrayList(BakeEntryPoint).init(allocator);
// the ordering of these entrypoints is relied on when inspecting the output chunks.
try entry_points.append(BakeEntryPoint.init(framework.entry_server, .server));
try entry_points.append(BakeEntryPoint.initClientWrapped(framework.entry_client, .client));
var router_types = try std.ArrayListUnmanaged(FrameworkRouter.Type).initCapacity(allocator, options.framework.file_system_router_types.len);
for (options.routes) |route| {
try entry_points.append(BakeEntryPoint.init(route.entry_point, .server));
var path_map: ProductionPathMap = .{
.allocator = allocator,
.entry_points = .{},
.route_files = .{},
.out_index_map = &.{},
.out_module_keys = &.{},
};
for (options.framework.file_system_router_types, 0..) |fsr, i| {
const joined_root = bun.path.joinAbs(cwd, .auto, fsr.root);
const entry = server_bundler.resolver.readDirInfoIgnoreError(joined_root) orelse
continue;
try router_types.append(allocator, .{
.abs_root = bun.strings.withoutTrailingSlash(entry.abs_path),
.prefix = fsr.prefix,
.ignore_underscores = fsr.ignore_underscores,
.ignore_dirs = fsr.ignore_dirs,
.extensions = fsr.extensions,
.style = fsr.style,
.server_file = try path_map.getFileIdForRouter(fsr.entry_server, FrameworkRouter.Route.Index.init(@intCast(i)), .page),
.client_file = if (fsr.entry_client) |client|
(try path_map.getClientFile(client)).toOptional()
else
.none,
.server_file_string = .{},
});
}
const bundled_outputs = try bun.BundleV2.generateFromBakeProductionCLI(
entry_points.items,
var router = try FrameworkRouter.initEmpty(router_types.items, allocator);
try router.scanAll(
allocator,
&server_bundler.resolver,
FrameworkRouter.InsertionContext.wrap(ProductionPathMap, &path_map),
);
const bundled_outputs_list = try bun.BundleV2.generateFromBakeProductionCLI(
path_map.entry_points.items,
&server_bundler,
.{
.framework = framework.*,
@@ -209,47 +253,44 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
allocator,
.{ .js = vm.event_loop },
);
const bundled_outputs = bundled_outputs_list.items;
Output.prettyErrorln("Rendering routes", .{});
Output.flush();
// A separate global object is used for isolation + controlling the available modules
const render_global = BakeCreateProdGlobal(vm.jsc, vm.console);
var root_dir = try std.fs.cwd().makeOpenPath("dist", .{});
defer root_dir.close();
var client_entry_id: u32 = std.math.maxInt(u32);
var server_entry_module_key: JSValue = .undefined;
const route_module_keys = JSValue.createEmptyArray(render_global, options.routes.len);
const route_output_indices = try allocator.alloc(OutputFile.Index, options.routes.len);
var css_chunks_count: usize = 0;
var css_chunks_first: usize = 0;
for (bundled_outputs.items, 0..) |file, i| {
// std.debug.print("{s} - {s} : {s} - {?d}\n", .{
// if (file.side) |s| @tagName(s) else "null",
// file.src_path.text,
// file.dest_path,
// file.entry_point_index,
// });
// std.debug.print("css: {d}\n", .{bun.fmt.fmtSlice(file.referenced_css_files, ", ")});
path_map.out_index_map = try allocator.alloc(u32, path_map.entry_points.items.len);
path_map.out_module_keys = try allocator.alloc(JSValue, bundled_outputs.len);
@memset(path_map.out_module_keys, .undefined);
const all_server_files = JSValue.createEmptyArray(global, bundled_outputs.len);
for (bundled_outputs, 0..) |file, i| {
log("{s} - {s} : {s} - {?d}\n", .{
if (file.side) |s| @tagName(s) else "null",
file.src_path.text,
file.dest_path,
file.entry_point_index,
});
if (file.loader == .css) {
if (css_chunks_count == 0) css_chunks_first = i;
css_chunks_count += 1;
}
if (file.entry_point_index) |entry_point| {
if (entry_point < path_map.out_index_map.len) {
path_map.out_index_map[entry_point] = @intCast(i);
}
}
switch (file.side orelse .client) {
.client => {
// client-side resources will be written to disk for usage in on the client side
// Client-side resources will be written to disk for usage in on the client side
_ = try file.writeToDisk(root_dir, root_dir_path);
if (file.entry_point_index) |entry_point| {
switch (entry_point) {
1 => client_entry_id = @intCast(i),
else => {},
}
}
},
.server => {
// For Debugging
@@ -258,42 +299,27 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
switch (file.output_kind) {
.@"entry-point", .chunk => {
var buf: bun.PathBuffer = undefined;
const without_prefix = if (bun.strings.hasPrefixComptime(file.dest_path, "./") or
(Environment.isWindows and bun.strings.hasPrefixComptime(file.dest_path, ".\\")))
file.dest_path[2..]
else
file.dest_path;
// TODO: later we can lazily register modules
const module_key = BakeRegisterProductionChunk(
render_global,
bun.String.createUTF8(bun.path.joinAbsStringBuf(cwd, &buf, &.{
root_dir_path,
file.dest_path,
}, .auto)),
global,
try bun.String.createFormat("bake:/{s}", .{without_prefix}),
file.value.toBunString(),
) catch |err| {
vm.printErrorLikeObjectToConsole(render_global.takeException(err));
if (vm.exit_handler.exit_code == 0) {
vm.exit_handler.exit_code = 1;
}
Output.errGeneric("could not load bundled chunk {} for server-side rendering", .{
bun.fmt.quote(file.dest_path),
bun.fmt.quote(without_prefix),
});
vm.globalExit();
return err;
};
if (file.entry_point_index) |entry_point| {
// classify the entry point. since entry point source indices are
// deterministic, we can map every single one back to the route or
// framework file.
switch (entry_point) {
0 => server_entry_module_key = module_key,
1 => {}, // client entry
else => |j| {
// SCBs are entry points past the two framework entry points
const route_index = j - 2;
if (route_index < options.routes.len) {
route_module_keys.putIndex(vm.global, route_index, module_key);
route_output_indices[route_index] = OutputFile.Index.init(@intCast(i));
}
},
}
bun.assert(module_key.isString());
if (file.entry_point_index != null) {
path_map.out_module_keys[i] = module_key;
all_server_files.putIndex(global, @intCast(i), module_key);
}
},
.asset => {},
@@ -304,63 +330,147 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
}
}
// TODO: umm...
// const primary_global = vm.global;
// vm.global = render_global;
// _ = primary_global;
bun.assert(client_entry_id != std.math.maxInt(u32));
bun.assert(server_entry_module_key != .undefined);
// HACK: react-server-dom-webpack assigns to `__webpack_require__.u`
// We never call this in this context, so we will just make '__webpack_require__' an empty object.
// Right now server.tsx is what controls the value, but imports happen first.
render_global.toJSValue().put(render_global, "__webpack_require__", JSValue.createEmptyObject(render_global, 0));
// Static site generator
const server_entry_point = loadModule(vm, render_global, server_entry_module_key);
const server_render_func: JSValue = BakeGetOnModuleNamespace(render_global, server_entry_point, "renderStatic") orelse {
Output.errGeneric("Framework does not support static site generation", .{});
Output.note("The file {s} is missing the \"renderStatic\" export", .{bun.fmt.quote(framework.entry_server)});
bun.Global.crash();
};
const server_render_funcs = JSValue.createEmptyArray(global, router.types.len);
const client_entry_urls = JSValue.createEmptyArray(global, router.types.len);
for (router.types, 0..) |router_type, i| {
if (router_type.client_file.unwrap()) |client_file| {
const str = (try bun.String.createFormat("{s}{s}", .{
public_path,
bundled_outputs[path_map.getOutIndex(client_file)].dest_path,
})).toJS(global);
client_entry_urls.putIndex(global, @intCast(i), str);
} else {
client_entry_urls.putIndex(global, @intCast(i), .null);
}
const server_entry_module_key = path_map.out_module_keys[path_map.getOutIndex(router_type.server_file)];
bun.assert(server_entry_module_key != .undefined);
const server_entry_point = try loadModule(vm, global, server_entry_module_key);
const server_render_func = brk: {
const raw = BakeGetOnModuleNamespace(global, server_entry_point, "prerender") orelse
break :brk null;
if (!raw.isCallable(vm.jsc)) {
break :brk null;
}
break :brk raw;
} orelse {
Output.errGeneric("Framework does not support static site generation", .{});
Output.note("The file {s} is missing the \"prerender\" export, which defines how to generate static files.", .{
bun.fmt.quote(path_map.route_files.keys()[router_type.server_file.get()]),
});
bun.Global.crash();
};
server_render_funcs.putIndex(global, @intCast(i), server_render_func);
}
var navigatable_routes = std.ArrayList(FrameworkRouter.Route.Index).init(allocator);
for (router.routes.items, 0..) |route, i| {
_ = route.file_page.unwrap() orelse continue;
try navigatable_routes.append(FrameworkRouter.Route.Index.init(@intCast(i)));
}
const route_patterns = JSValue.createEmptyArray(render_global, options.routes.len);
const route_style_references = JSValue.createEmptyArray(render_global, options.routes.len);
const css_chunk_js_strings = try allocator.alloc(JSValue, css_chunks_count);
for (bundled_outputs.items[css_chunks_first..][0..css_chunks_count], css_chunk_js_strings) |output_file, *str| {
for (bundled_outputs[css_chunks_first..][0..css_chunks_count], css_chunk_js_strings) |output_file, *str| {
bun.assert(output_file.dest_path[0] != '.');
bun.assert(output_file.loader == .css);
str.* = (try bun.String.createFormat("{s}{s}", .{ public_path, output_file.dest_path })).toJS(render_global);
str.* = (try bun.String.createFormat("{s}{s}", .{ public_path, output_file.dest_path })).toJS(global);
}
for (
options.routes,
route_output_indices,
0..,
) |route, output_file_i, i| {
route_patterns.putIndex(render_global, @intCast(i), bun.String.createUTF8(route.pattern).toJS(render_global));
const output_file = &bundled_outputs.items[output_file_i.get()];
const route_patterns = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const route_nested_files = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const route_type_and_flags = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const route_source_files = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const route_param_info = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const route_style_references = JSValue.createEmptyArray(global, navigatable_routes.items.len);
const styles = JSValue.createEmptyArray(render_global, output_file.referenced_css_files.len);
for (output_file.referenced_css_files, 0..) |ref, j| {
styles.putIndex(render_global, @intCast(j), css_chunk_js_strings[ref.get() - css_chunks_first]);
var params_buf: std.ArrayListUnmanaged([]const u8) = .{};
for (navigatable_routes.items, 0..) |route_index, nav_index| {
defer params_buf.clearRetainingCapacity();
var pattern = bake.PatternBuffer.empty;
const route = router.routePtr(route_index);
const main_file_route_index = route.file_page.unwrap().?;
const main_file_index = path_map.getOutIndex(main_file_route_index);
pattern.prependPart(route.part);
var file_count: u32 = 1;
var css_file_count: u32 = @intCast(bundled_outputs[main_file_index].referenced_css_files.len);
if (route.file_layout.unwrap()) |file| {
css_file_count += @intCast(bundled_outputs[path_map.getOutIndex(file)].referenced_css_files.len);
file_count += 1;
}
route_style_references.putIndex(render_global, @intCast(i), styles);
var next: ?FrameworkRouter.Route.Index = route.parent.unwrap();
while (next) |parent_index| {
const parent = router.routePtr(parent_index);
pattern.prependPart(parent.part);
if (parent.file_layout.unwrap()) |file| {
css_file_count += @intCast(bundled_outputs[path_map.getOutIndex(file)].referenced_css_files.len);
file_count += 1;
}
next = parent.parent.unwrap();
}
const styles = JSValue.createEmptyArray(global, css_chunks_count);
const file_list = JSValue.createEmptyArray(global, file_count);
next = route.parent.unwrap();
file_count = 1;
css_file_count = 0;
file_list.putIndex(global, 0, JSValue.jsNumberFromInt32(@intCast(main_file_route_index.get())));
for (bundled_outputs[main_file_index].referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
if (route.file_layout.unwrap()) |file| {
file_list.putIndex(global, file_count, JSValue.jsNumberFromInt32(@intCast(file.get())));
for (bundled_outputs[path_map.getOutIndex(file)].referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
file_count += 1;
}
while (next) |parent_index| {
const parent = router.routePtr(parent_index);
if (parent.file_layout.unwrap()) |file| {
file_list.putIndex(global, file_count, JSValue.jsNumberFromInt32(@intCast(file.get())));
for (bundled_outputs[path_map.getOutIndex(file)].referenced_css_files) |ref| {
styles.putIndex(global, css_file_count, css_chunk_js_strings[ref.get() - css_chunks_first]);
css_file_count += 1;
}
file_count += 1;
}
next = parent.parent.unwrap();
}
var pattern_string = bun.String.createUTF8(pattern.slice());
defer pattern_string.deref();
route_patterns.putIndex(global, @intCast(nav_index), pattern_string.toJS(global));
var src_path = bun.String.createUTF8(bun.path.relative(cwd, path_map.route_files.keys()[main_file_route_index.get()]));
route_source_files.putIndex(global, @intCast(nav_index), src_path.transferToJS(global));
route_nested_files.putIndex(global, @intCast(nav_index), file_list);
route_type_and_flags.putIndex(global, @intCast(nav_index), JSValue.jsNumberFromInt32(@bitCast(TypeAndFlags{
.type = route.type.get(),
})));
route_param_info.putIndex(global, @intCast(nav_index), .null);
route_style_references.putIndex(global, @intCast(nav_index), styles);
}
const client_entry_url = (try bun.String.createFormat("{s}{s}", .{
public_path,
bundled_outputs.items[client_entry_id].dest_path,
})).toJS(render_global);
const render_promise = BakeRenderRoutesForProd(
render_global,
const render_promise = BakeRenderRoutesForProdStatic(
global,
bun.String.init(root_dir_path),
server_render_func,
client_entry_url,
route_module_keys,
all_server_files,
server_render_funcs,
client_entry_urls,
route_patterns,
route_nested_files,
route_type_and_flags,
route_source_files,
route_param_info,
route_style_references,
);
vm.waitForPromise(.{ .normal = render_promise });
@@ -371,18 +481,15 @@ pub fn buildCommand(ctx: bun.CLI.Command.Context) !void {
Output.flush();
},
.rejected => |err| {
vm.printErrorLikeObjectToConsole(err);
if (vm.exit_handler.exit_code == 0) {
vm.exit_handler.exit_code = 1;
}
vm.globalExit();
vm.global.throwValue(err);
return error.JSError;
},
}
}
/// unsafe function, must be run outside of the event loop
/// quits the process on exception
fn loadModule(vm: *VirtualMachine, global: *JSC.JSGlobalObject, key: JSValue) JSValue {
fn loadModule(vm: *VirtualMachine, global: *JSC.JSGlobalObject, key: JSValue) !JSValue {
const promise = BakeLoadModuleByKey(global, key).asAnyPromise().?.internal;
vm.waitForPromise(.{ .internal = promise });
switch (promise.unwrap(vm.jsc, .mark_handled)) {
@@ -392,11 +499,8 @@ fn loadModule(vm: *VirtualMachine, global: *JSC.JSGlobalObject, key: JSValue) JS
return BakeGetModuleNamespace(global, key);
},
.rejected => |err| {
vm.printErrorLikeObjectToConsole(err);
if (vm.exit_handler.exit_code == 0) {
vm.exit_handler.exit_code = 1;
}
vm.globalExit();
vm.global.throwValue(err);
return error.JSError;
},
}
}
@@ -417,17 +521,21 @@ fn BakeGetOnModuleNamespace(global: *JSC.JSGlobalObject, module: JSValue, proper
return result;
}
extern fn BakeRenderRoutesForProd(
extern fn BakeRenderRoutesForProdStatic(
*JSC.JSGlobalObject,
out_base: bun.String,
render_static_cb: JSValue,
client_entry_url: JSValue,
arr: JSValue,
all_server_files: JSValue,
render_static: JSValue,
client_entry_urls: JSValue,
patterns: JSValue,
files: JSValue,
type_and_flags: JSValue,
src_route_files: JSValue,
param_information: JSValue,
styles: JSValue,
) *JSC.JSPromise;
extern fn BakeCreateProdGlobal(vm: *JSC.VM, console_ptr: *anyopaque) *JSC.JSGlobalObject;
extern fn BakeCreateProdGlobal(console_ptr: *anyopaque) *JSC.JSGlobalObject;
/// The result of this function is a JSValue that wont be garbage collected, as
/// it will always have at least one reference by the module loader.
@@ -441,7 +549,7 @@ fn BakeRegisterProductionChunk(global: *JSC.JSGlobalObject, key: bun.String, sou
return result;
}
fn BakeProdResolve(global: *JSC.JSGlobalObject, a_str: bun.String, specifier_str: bun.String) callconv(.C) bun.String {
export fn BakeProdResolve(global: *JSC.JSGlobalObject, a_str: bun.String, specifier_str: bun.String) callconv(.C) bun.String {
var sfa = std.heap.stackFallback(@sizeOf(bun.PathBuffer) * 2, bun.default_allocator);
const alloc = sfa.get();
@@ -463,27 +571,76 @@ fn BakeProdResolve(global: *JSC.JSGlobalObject, a_str: bun.String, specifier_str
return bun.String.dead;
}
return bun.String.createUTF8(bun.path.joinAbs(
bun.Dirname.dirname(u8, referrer.slice()) orelse referrer.slice(),
if (Environment.allow_assert)
bun.assert(bun.strings.hasPrefix(referrer.slice(), "bake:"));
return bun.String.createFormat("bake:{s}", .{bun.path.joinAbs(
bun.Dirname.dirname(u8, referrer.slice()[5..]) orelse referrer.slice()[5..],
.auto,
specifier.slice(),
));
)}) catch return bun.String.dead;
}
comptime {
if (bun.FeatureFlags.bake)
@export(BakeProdResolve, .{ .name = "BakeProdResolve" });
}
/// Tracks all entry points for a production bundle
const ProductionPathMap = struct {
allocator: std.mem.Allocator,
entry_points: std.ArrayListUnmanaged(BakeEntryPoint),
route_files: bun.StringArrayHashMapUnmanaged(void),
/// OpaqueFileId -> index into output_files
out_index_map: []u32,
/// index into output_files -> key to load them in JavaScript
out_module_keys: []JSValue,
const RouteType = struct {
client_file: FrameworkRouter.OpaqueFileId.Optional,
server_file: FrameworkRouter.OpaqueFileId,
};
fn getOutIndex(ppm: *ProductionPathMap, index: FrameworkRouter.OpaqueFileId) u32 {
return ppm.out_index_map[index.get()];
}
pub fn getFileIdForRouter(ctx: *ProductionPathMap, abs_path: []const u8, _: FrameworkRouter.Route.Index, _: FrameworkRouter.Route.FileKind) !FrameworkRouter.OpaqueFileId {
const gop = try ctx.route_files.getOrPut(ctx.allocator, abs_path);
if (!gop.found_existing) {
const dupe = try ctx.allocator.dupe(u8, abs_path);
try ctx.entry_points.append(ctx.allocator, BakeEntryPoint.init(dupe, .server));
gop.key_ptr.* = dupe;
gop.value_ptr.* = {};
}
return FrameworkRouter.OpaqueFileId.init(@intCast(gop.index));
}
pub fn getClientFile(ctx: *ProductionPathMap, abs_path: []const u8) !FrameworkRouter.OpaqueFileId {
// Bug: if server and client reference the same file, it is impossible to know which side it was already queued for, leading to a missing output file.
const gop = try ctx.route_files.getOrPut(ctx.allocator, abs_path);
if (!gop.found_existing) {
const dupe = try ctx.allocator.dupe(u8, abs_path);
try ctx.entry_points.append(ctx.allocator, BakeEntryPoint.initClientWrapped(dupe, .client));
gop.key_ptr.* = dupe;
gop.value_ptr.* = {};
}
return FrameworkRouter.OpaqueFileId.init(@intCast(gop.index));
}
};
const TypeAndFlags = packed struct(i32) {
type: u8,
unused: u24 = 0,
};
const std = @import("std");
const bun = @import("root").bun;
const bake = bun.bake;
const Environment = bun.Environment;
const Output = bun.Output;
const BakeEntryPoint = bun.bundle_v2.BakeEntryPoint;
const OutputFile = bun.options.OutputFile;
const bake = bun.bake;
const FrameworkRouter = bake.FrameworkRouter;
const JSC = bun.JSC;
const JSValue = JSC.JSValue;
const VirtualMachine = JSC.VirtualMachine;

View File

@@ -12,10 +12,11 @@
"downlevelIteration": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react",
"paths": {
"bun-framework-rsc/*": ["./bun-framework-rsc/*"]
}
"bun-framework-react/*": ["./bun-framework-react/*"]
},
"jsx": "react-jsx",
"types": ["react/experimental"]
},
"include": ["**/*.ts", "**/*.tsx"]
}

View File

@@ -19,12 +19,8 @@ pub const BuildMessage = struct {
pub usingnamespace JSC.Codegen.JSBuildMessage;
pub fn constructor(
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) ?*BuildMessage {
globalThis.throw("BuildMessage is not constructable", .{});
return null;
pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*BuildMessage {
return globalThis.throw2("BuildMessage is not constructable", .{});
}
pub fn getNotes(this: *BuildMessage, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
@@ -81,7 +77,7 @@ pub const BuildMessage = struct {
this: *BuildMessage,
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
return this.toStringFn(globalThis);
}
@@ -89,8 +85,8 @@ pub const BuildMessage = struct {
this: *BuildMessage,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
const args_ = callframe.arguments(1);
) bun.JSError!JSC.JSValue {
const args_ = callframe.arguments_old(1);
const args = args_.ptr[0..args_.len];
if (args.len > 0) {
if (!args[0].isString()) {
@@ -110,7 +106,7 @@ pub const BuildMessage = struct {
this: *BuildMessage,
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
var object = JSC.JSValue.createEmptyObject(globalThis, 4);
object.put(globalThis, ZigString.static("name"), bun.String.static("BuildMessage").toJS(globalThis));
object.put(globalThis, ZigString.static("position"), this.getPosition(globalThis));

View File

@@ -81,7 +81,7 @@ threadlocal var stdout_lock_count: u16 = 0;
/// https://console.spec.whatwg.org/#formatter
pub fn messageWithTypeAndLevel(
//console_: ConsoleObject.Type,
_: ConsoleObject.Type,
ctype: ConsoleObject.Type,
message_type: MessageType,
//message_level: u32,
level: MessageLevel,
@@ -89,6 +89,21 @@ pub fn messageWithTypeAndLevel(
vals: [*]const JSValue,
len: usize,
) callconv(JSC.conv) void {
messageWithTypeAndLevel_(ctype, message_type, level, global, vals, len) catch |err| switch (err) {
error.JSError => {},
error.OutOfMemory => global.throwOutOfMemory(),
};
}
fn messageWithTypeAndLevel_(
//console_: ConsoleObject.Type,
_: ConsoleObject.Type,
message_type: MessageType,
//message_level: u32,
level: MessageLevel,
global: *JSGlobalObject,
vals: [*]const JSValue,
len: usize,
) bun.JSError!void {
if (comptime is_bindgen) {
return;
}
@@ -197,13 +212,13 @@ pub fn messageWithTypeAndLevel(
print_length = 1;
var opts = vals[1];
if (opts.isObject()) {
if (opts.get(global, "depth")) |depth_prop| {
if (try opts.get(global, "depth")) |depth_prop| {
if (depth_prop.isInt32() or depth_prop.isNumber() or depth_prop.isBigInt())
print_options.max_depth = depth_prop.toU16()
else if (depth_prop.isNull())
print_options.max_depth = std.math.maxInt(u16);
}
if (opts.get(global, "colors")) |colors_prop| {
if (try opts.get(global, "colors")) |colors_prop| {
if (colors_prop.isBoolean())
print_options.enable_colors = colors_prop.toBoolean();
}
@@ -453,7 +468,7 @@ pub const TablePrinter = struct {
value = row_value.getOwn(this.globalObject, col.name) orelse JSValue.zero;
}
if (value.isEmpty()) {
if (value == .zero) {
try writer.writeByteNTimes(' ', col.width + (PADDING * 2));
} else {
const len: u32 = this.getWidthForValue(value);
@@ -683,11 +698,11 @@ pub const FormatOptions = struct {
single_line: bool = false,
default_indent: u16 = 0,
pub fn fromJS(formatOptions: *FormatOptions, globalThis: *JSC.JSGlobalObject, arguments: []const JSC.JSValue) !void {
pub fn fromJS(formatOptions: *FormatOptions, globalThis: *JSC.JSGlobalObject, arguments: []const JSC.JSValue) bun.JSError!void {
const arg1 = arguments[0];
if (arg1.isObject()) {
if (arg1.getTruthy(globalThis, "depth")) |opt| {
if (try arg1.getTruthy(globalThis, "depth")) |opt| {
if (opt.isInt32()) {
const arg = opt.toInt32();
if (arg < 0) {
@@ -705,14 +720,14 @@ pub const FormatOptions = struct {
}
}
}
if (try arg1.getOptional(globalThis, "colors", bool)) |opt| {
if (try arg1.getBooleanLoose(globalThis, "colors")) |opt| {
formatOptions.enable_colors = opt;
}
if (try arg1.getOptional(globalThis, "sorted", bool)) |opt| {
if (try arg1.getBooleanLoose(globalThis, "sorted")) |opt| {
formatOptions.ordered_properties = opt;
}
if (try arg1.getOptional(globalThis, "compact", bool)) |opt| {
if (try arg1.getBooleanLoose(globalThis, "compact")) |opt| {
formatOptions.single_line = opt;
}
} else {
@@ -972,7 +987,7 @@ pub const Formatter = struct {
// For detecting circular references
pub const Visited = struct {
const ObjectPool = @import("../pool.zig").ObjectPool;
pub const Map = std.AutoHashMap(JSValue.Type, void);
pub const Map = std.AutoHashMap(JSValue, void);
pub const Pool = ObjectPool(
Map,
struct {
@@ -1662,8 +1677,8 @@ pub const Formatter = struct {
this.writer.writeAll(" ") catch unreachable;
}
if (!is_iterator) {
const key = JSC.JSObject.getIndex(nextValue, globalObject, 0);
const value = JSC.JSObject.getIndex(nextValue, globalObject, 1);
const key = nextValue.getIndex(globalObject, 0);
const value = nextValue.getIndex(globalObject, 1);
if (!single_line) {
this.formatter.writeIndent(Writer, this.writer) catch unreachable;
@@ -1949,7 +1964,7 @@ pub const Formatter = struct {
this.map = this.map_node.?.data;
}
const entry = this.map.getOrPut(@intFromEnum(value)) catch unreachable;
const entry = this.map.getOrPut(value) catch unreachable;
if (entry.found_existing) {
writer.writeAll(comptime Output.prettyFmt("<r><cyan>[Circular]<r>", enable_ansi_colors));
return;
@@ -1958,7 +1973,7 @@ pub const Formatter = struct {
defer {
if (comptime Format.canHaveCircularReferences()) {
_ = this.map.remove(@intFromEnum(value));
_ = this.map.remove(value);
}
}
@@ -2259,7 +2274,7 @@ pub const Formatter = struct {
this.addForNewLine(2);
}
if (element.isEmpty()) {
if (element == .zero) {
empty_start = 0;
break :first;
}
@@ -2278,7 +2293,7 @@ pub const Formatter = struct {
while (i < len) : (i += 1) {
const element = value.getDirectIndex(this.globalThis, i);
if (element.isEmpty()) {
if (element == .zero) {
if (empty_start == null) {
empty_start = i;
}
@@ -2396,7 +2411,7 @@ pub const Formatter = struct {
blob.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
return;
} else if (value.as(JSC.FetchHeaders) != null) {
if (value.get(this.globalThis, "toJSON")) |toJSONFunction| {
if (value.get_unsafe(this.globalThis, "toJSON")) |toJSONFunction| {
this.addForNewLine("Headers ".len);
writer.writeAll(comptime Output.prettyFmt("<r>Headers ", enable_ansi_colors));
const prev_quote_keys = this.quote_keys;
@@ -2414,7 +2429,7 @@ pub const Formatter = struct {
);
}
} else if (value.as(JSC.DOMFormData) != null) {
if (value.get(this.globalThis, "toJSON")) |toJSONFunction| {
if (value.get_unsafe(this.globalThis, "toJSON")) |toJSONFunction| {
const prev_quote_keys = this.quote_keys;
this.quote_keys = true;
defer this.quote_keys = prev_quote_keys;
@@ -2516,7 +2531,7 @@ pub const Formatter = struct {
writer.writeAll(comptime Output.prettyFmt("<cyan>" ++ fmt ++ "<r>", enable_ansi_colors));
},
.Map => {
const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
const length_value = value.get_unsafe(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
const length = length_value.toInt32();
const prev_quote_strings = this.quote_strings;
@@ -2620,7 +2635,7 @@ pub const Formatter = struct {
writer.writeAll("}");
},
.Set => {
const length_value = value.get(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
const length_value = value.get_unsafe(this.globalThis, "size") orelse JSC.JSValue.jsNumberFromInt32(0);
const length = length_value.toInt32();
const prev_quote_strings = this.quote_strings;
@@ -2666,7 +2681,7 @@ pub const Formatter = struct {
writer.writeAll("}");
},
.toJSON => {
if (value.get(this.globalThis, "toJSON")) |func| brk: {
if (value.get_unsafe(this.globalThis, "toJSON")) |func| brk: {
const result = func.call(this.globalThis, value, &.{}) catch {
this.globalThis.clearException();
break :brk;
@@ -2706,7 +2721,7 @@ pub const Formatter = struct {
},
.Event => {
const event_type_value = brk: {
const value_ = value.get(this.globalThis, "type") orelse break :brk JSValue.undefined;
const value_ = value.get_unsafe(this.globalThis, "type") orelse break :brk JSValue.undefined;
if (value_.isString()) {
break :brk value_;
}
@@ -2827,7 +2842,7 @@ pub const Formatter = struct {
defer if (tag_name_slice.isAllocated()) tag_name_slice.deinit();
if (value.get(this.globalThis, "type")) |type_value| {
if (value.get_unsafe(this.globalThis, "type")) |type_value| {
const _tag = Tag.getAdvanced(type_value, this.globalThis, .{ .hide_global = true });
if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) {
@@ -2857,7 +2872,7 @@ pub const Formatter = struct {
writer.writeAll(tag_name_slice.slice());
if (enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", enable_ansi_colors));
if (value.get(this.globalThis, "key")) |key_value| {
if (value.get_unsafe(this.globalThis, "key")) |key_value| {
if (!key_value.isUndefinedOrNull()) {
if (needs_space)
writer.writeAll(" key=")
@@ -2874,7 +2889,7 @@ pub const Formatter = struct {
}
}
if (value.get(this.globalThis, "props")) |props| {
if (value.get_unsafe(this.globalThis, "props")) |props| {
const prev_quote_strings = this.quote_strings;
this.quote_strings = true;
defer this.quote_strings = prev_quote_strings;
@@ -2886,7 +2901,7 @@ pub const Formatter = struct {
}).init(this.globalThis, props);
defer props_iter.deinit();
const children_prop = props.get(this.globalThis, "children");
const children_prop = props.get_unsafe(this.globalThis, "children");
if (props_iter.len > 0) {
{
this.indent += 1;
@@ -3001,7 +3016,7 @@ pub const Formatter = struct {
var j: usize = 0;
while (j < length) : (j += 1) {
const child = JSC.JSObject.getIndex(children, this.globalThis, @as(u32, @intCast(j)));
const child = children.getIndex(this.globalThis, @as(u32, @intCast(j)));
this.format(Tag.getAdvanced(child, this.globalThis, .{ .hide_global = true }), Writer, writer_, child, this.globalThis, enable_ansi_colors);
if (j + 1 < length) {
writer.writeAll("\n");

View File

@@ -18,12 +18,8 @@ pub const ResolveMessage = struct {
pub usingnamespace JSC.Codegen.JSResolveMessage;
pub fn constructor(
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) ?*ResolveMessage {
globalThis.throw("ResolveMessage is not constructable", .{});
return null;
pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*ResolveMessage {
return globalThis.throw2("ResolveMessage is not constructable", .{});
}
pub fn getCode(this: *ResolveMessage, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
@@ -111,7 +107,7 @@ pub const ResolveMessage = struct {
this: *ResolveMessage,
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
return this.toStringFn(globalThis);
}
@@ -119,8 +115,8 @@ pub const ResolveMessage = struct {
this: *ResolveMessage,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
const args_ = callframe.arguments(1);
) bun.JSError!JSC.JSValue {
const args_ = callframe.arguments_old(1);
const args = args_.ptr[0..args_.len];
if (args.len > 0) {
if (!args[0].isString()) {
@@ -140,7 +136,7 @@ pub const ResolveMessage = struct {
this: *ResolveMessage,
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
var object = JSC.JSValue.createEmptyObject(globalThis, 7);
object.put(globalThis, ZigString.static("name"), bun.String.static("ResolveMessage").toJS(globalThis));
object.put(globalThis, ZigString.static("position"), this.getPosition(globalThis));

View File

@@ -6,7 +6,8 @@
/// Version 7: Several bundler changes that are likely to impact the runtime as well.
/// Version 8: Fix for generated symbols
/// Version 9: String printing changes
const expected_version = 9;
/// Version 10: Constant folding for ''.charCodeAt(n)
const expected_version = 10;
const bun = @import("root").bun;
const std = @import("std");

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,6 @@ pub const JSBundler = struct {
jsx: options.JSX.Pragma = .{},
code_splitting: bool = false,
minify: Minify = .{},
server_components: ServerComponents = ServerComponents{},
no_macros: bool = false,
ignore_dce_annotations: bool = false,
emit_dce_annotations: ?bool = null,
@@ -82,9 +81,7 @@ pub const JSBundler = struct {
pub const List = bun.StringArrayHashMapUnmanaged(Config);
const FromJSError = OOM || JSError;
pub fn fromJS(globalThis: *JSC.JSGlobalObject, config: JSC.JSValue, plugins: *?*Plugin, allocator: std.mem.Allocator) FromJSError!Config {
pub fn fromJS(globalThis: *JSC.JSGlobalObject, config: JSC.JSValue, plugins: *?*Plugin, allocator: std.mem.Allocator) JSError!Config {
var this = Config{
.entry_points = bun.StringSet.init(allocator),
.external = bun.StringSet.init(allocator),
@@ -101,11 +98,11 @@ pub const JSBundler = struct {
errdefer this.deinit(allocator);
errdefer if (plugins.*) |plugin| plugin.deinit();
if (config.getTruthy(globalThis, "experimentalCss")) |enable_css| {
if (try config.getTruthy(globalThis, "experimentalCss")) |enable_css| {
this.experimental_css = if (enable_css.isBoolean())
enable_css.toBoolean()
else if (enable_css.isObject()) true: {
if (enable_css.getTruthy(globalThis, "chunking")) |enable_chunking| {
if (try enable_css.getTruthy(globalThis, "chunking")) |enable_chunking| {
this.css_chunking = if (enable_chunking.isBoolean()) enable_css.toBoolean() else false;
}
@@ -121,24 +118,20 @@ pub const JSBundler = struct {
var i: usize = 0;
while (iter.next()) |plugin| : (i += 1) {
if (!plugin.isObject()) {
globalThis.throwInvalidArguments("Expected plugin to be an object", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected plugin to be an object", .{});
}
if (plugin.getOptional(globalThis, "name", ZigString.Slice) catch null) |slice| {
if (try plugin.getOptional(globalThis, "name", ZigString.Slice)) |slice| {
defer slice.deinit();
if (slice.len == 0) {
globalThis.throwInvalidArguments("Expected plugin to have a non-empty name", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected plugin to have a non-empty name", .{});
}
} else {
globalThis.throwInvalidArguments("Expected plugin to have a name", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected plugin to have a name", .{});
}
const function = (plugin.getFunction(globalThis, "setup") catch null) orelse {
globalThis.throwInvalidArguments("Expected plugin to have a setup() function", .{});
return error.JSError;
const function = try plugin.getFunction(globalThis, "setup") orelse {
return globalThis.throwInvalidArguments2("Expected plugin to have a setup() function", .{});
};
var bun_plugins: *Plugin = plugins.* orelse brk: {
@@ -166,16 +159,14 @@ pub const JSBundler = struct {
plugin_result = val;
},
.rejected => |err| {
globalThis.throwValue(err);
return error.JSError;
return globalThis.throwValue2(err);
},
}
}
}
if (plugin_result.toError()) |err| {
globalThis.throwValue(err);
return error.JSError;
return globalThis.throwValue2(err);
} else if (globalThis.hasException()) {
return error.JSError;
}
@@ -184,13 +175,11 @@ pub const JSBundler = struct {
}
}
if (config.getTruthy(globalThis, "macros")) |macros_flag| {
if (!macros_flag.coerce(bool, globalThis)) {
this.no_macros = true;
}
if (try config.getBooleanLoose(globalThis, "macros")) |macros_flag| {
this.no_macros = !macros_flag;
}
if (try config.getOptional(globalThis, "bytecode", bool)) |bytecode| {
if (try config.getBooleanLoose(globalThis, "bytecode")) |bytecode| {
this.bytecode = bytecode;
if (bytecode) {
@@ -204,8 +193,7 @@ pub const JSBundler = struct {
this.target = target;
if (target != .bun and this.bytecode) {
globalThis.throwInvalidArguments("target must be 'bun' when bytecode is true", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("target must be 'bun' when bytecode is true", .{});
}
}
@@ -226,7 +214,7 @@ pub const JSBundler = struct {
try this.footer.appendSliceExact(slice.slice());
}
if (config.getTruthy(globalThis, "sourcemap")) |source_map_js| {
if (try config.getTruthy(globalThis, "sourcemap")) |source_map_js| {
if (bun.FeatureFlags.breaking_changes_1_2 and config.isBoolean()) {
if (source_map_js == .true) {
this.source_map = if (has_out_dir)
@@ -251,89 +239,68 @@ pub const JSBundler = struct {
this.format = format;
if (this.bytecode and format != .cjs) {
globalThis.throwInvalidArguments("format must be 'cjs' when bytecode is true. Eventually we'll add esm support as well.", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("format must be 'cjs' when bytecode is true. Eventually we'll add esm support as well.", .{});
}
}
// if (try config.getOptional(globalThis, "hot", bool)) |hot| {
// this.hot = hot;
// }
if (try config.getOptional(globalThis, "splitting", bool)) |hot| {
if (try config.getBooleanLoose(globalThis, "splitting")) |hot| {
this.code_splitting = hot;
}
if (config.getTruthy(globalThis, "minify")) |hot| {
if (hot.isBoolean()) {
const value = hot.coerce(bool, globalThis);
if (try config.getTruthy(globalThis, "minify")) |minify| {
if (minify.isBoolean()) {
const value = minify.toBoolean();
this.minify.whitespace = value;
this.minify.syntax = value;
this.minify.identifiers = value;
} else if (hot.isObject()) {
if (try hot.getOptional(globalThis, "whitespace", bool)) |whitespace| {
} else if (minify.isObject()) {
if (try minify.getBooleanLoose(globalThis, "whitespace")) |whitespace| {
this.minify.whitespace = whitespace;
}
if (try hot.getOptional(globalThis, "syntax", bool)) |syntax| {
if (try minify.getBooleanLoose(globalThis, "syntax")) |syntax| {
this.minify.syntax = syntax;
}
if (try hot.getOptional(globalThis, "identifiers", bool)) |syntax| {
if (try minify.getBooleanLoose(globalThis, "identifiers")) |syntax| {
this.minify.identifiers = syntax;
}
} else {
globalThis.throwInvalidArguments("Expected minify to be a boolean or an object", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected minify to be a boolean or an object", .{});
}
}
if (try config.getArray(globalThis, "entrypoints") orelse try config.getArray(globalThis, "entryPoints")) |entry_points| {
var iter = entry_points.arrayIterator(globalThis);
while (iter.next()) |entry_point| {
var slice = entry_point.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected entrypoints to be an array of strings", .{});
return error.JSError;
};
var slice = try entry_point.toSliceOrNull(globalThis);
defer slice.deinit();
try this.entry_points.insert(slice.slice());
}
} else {
globalThis.throwInvalidArguments("Expected entrypoints to be an array of strings", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected entrypoints to be an array of strings", .{});
}
if (config.getTruthy(globalThis, "emitDCEAnnotations")) |flag| {
if (flag.coerce(bool, globalThis)) {
this.emit_dce_annotations = true;
}
if (try config.getBooleanLoose(globalThis, "emitDCEAnnotations")) |flag| {
this.emit_dce_annotations = flag;
}
if (config.getTruthy(globalThis, "ignoreDCEAnnotations")) |flag| {
if (flag.coerce(bool, globalThis)) {
this.ignore_dce_annotations = true;
}
if (try config.getBooleanLoose(globalThis, "ignoreDCEAnnotations")) |flag| {
this.ignore_dce_annotations = flag;
}
if (config.getTruthy(globalThis, "conditions")) |conditions_value| {
if (try config.getTruthy(globalThis, "conditions")) |conditions_value| {
if (conditions_value.isString()) {
var slice = conditions_value.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected conditions to be an array of strings", .{});
return error.JSError;
};
var slice = try conditions_value.toSliceOrNull(globalThis);
defer slice.deinit();
try this.conditions.insert(slice.slice());
} else if (conditions_value.jsType().isArray()) {
var iter = conditions_value.arrayIterator(globalThis);
while (iter.next()) |entry_point| {
var slice = entry_point.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected conditions to be an array of strings", .{});
return error.JSError;
};
var slice = try entry_point.toSliceOrNull(globalThis);
defer slice.deinit();
try this.conditions.insert(slice.slice());
}
} else {
globalThis.throwInvalidArguments("Expected conditions to be an array of strings", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected conditions to be an array of strings", .{});
}
}
@@ -371,10 +338,7 @@ pub const JSBundler = struct {
if (try config.getOwnArray(globalThis, "external")) |externals| {
var iter = externals.arrayIterator(globalThis);
while (iter.next()) |entry_point| {
var slice = entry_point.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected external to be an array of strings", .{});
return error.JSError;
};
var slice = try entry_point.toSliceOrNull(globalThis);
defer slice.deinit();
try this.external.insert(slice.slice());
}
@@ -383,10 +347,7 @@ pub const JSBundler = struct {
if (try config.getOwnArray(globalThis, "drop")) |drops| {
var iter = drops.arrayIterator(globalThis);
while (iter.next()) |entry| {
var slice = entry.toSliceOrNull(globalThis) orelse {
globalThis.throwInvalidArguments("Expected drop to be an array of strings", .{});
return error.JSError;
};
var slice = try entry.toSliceOrNull(globalThis);
defer slice.deinit();
try this.drop.insert(slice.slice());
}
@@ -404,7 +365,7 @@ pub const JSBundler = struct {
try this.public_path.appendSliceExact(slice.slice());
}
if (config.getTruthy(globalThis, "naming")) |naming| {
if (try config.getTruthy(globalThis, "naming")) |naming| {
if (naming.isString()) {
if (try config.getOptional(globalThis, "naming", ZigString.Slice)) |slice| {
defer slice.deinit();
@@ -442,15 +403,13 @@ pub const JSBundler = struct {
this.names.asset.data = this.names.owned_asset.list.items;
}
} else {
globalThis.throwInvalidArguments("Expected naming to be a string or an object", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("Expected naming to be a string or an object", .{});
}
}
if (try config.getOwnObject(globalThis, "define")) |define| {
if (!define.isObject()) {
globalThis.throwInvalidArguments("define must be an object", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("define must be an object", .{});
}
var define_iter = JSC.JSPropertyIterator(.{
@@ -464,8 +423,7 @@ pub const JSBundler = struct {
const value_type = property_value.jsType();
if (!value_type.isStringLike()) {
globalThis.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop});
return error.JSError;
return globalThis.throwInvalidArguments2("define \"{s}\" must be a JSON string", .{prop});
}
var val = JSC.ZigString.init("");
@@ -499,8 +457,7 @@ pub const JSBundler = struct {
while (loader_iter.next()) |prop| {
if (!prop.hasPrefixComptime(".") or prop.length() < 2) {
globalThis.throwInvalidArguments("loader property names must be file extensions, such as '.txt'", .{});
return error.JSError;
return globalThis.throwInvalidArguments2("loader property names must be file extensions, such as '.txt'", .{});
}
loader_values[loader_iter.i] = try loader_iter.value.toEnumFromMap(
@@ -537,18 +494,6 @@ pub const JSBundler = struct {
}
};
pub const ServerComponents = struct {
router: JSC.Strong = .{},
client: std.ArrayListUnmanaged(OwnedString) = .{},
server: std.ArrayListUnmanaged(OwnedString) = .{},
pub fn deinit(self: *ServerComponents, allocator: std.mem.Allocator) void {
self.router.deinit();
self.client.clearAndFree(allocator);
self.server.clearAndFree(allocator);
}
};
pub const Minify = struct {
whitespace: bool = false,
identifiers: bool = false,
@@ -572,7 +517,6 @@ pub const JSBundler = struct {
self.define.deinit();
self.dir.deinit();
self.serve.deinit(allocator);
self.server_components.deinit(allocator);
if (self.loaders) |loaders| {
for (loaders.extensions) |ext| {
bun.default_allocator.free(ext);
@@ -632,8 +576,8 @@ pub const JSBundler = struct {
pub fn buildFn(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
const arguments = callframe.arguments(1);
) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(1);
return build(globalThis, arguments.slice());
}
@@ -1191,7 +1135,7 @@ pub const BuildArtifact = struct {
this: *BuildArtifact,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
return @call(bun.callmod_inline, Blob.getText, .{ &this.blob, globalThis, callframe });
}
@@ -1199,21 +1143,21 @@ pub const BuildArtifact = struct {
this: *BuildArtifact,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
return @call(bun.callmod_inline, Blob.getJSON, .{ &this.blob, globalThis, callframe });
}
pub fn getArrayBuffer(
this: *BuildArtifact,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
return @call(bun.callmod_inline, Blob.getArrayBuffer, .{ &this.blob, globalThis, callframe });
}
pub fn getSlice(
this: *BuildArtifact,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
return @call(bun.callmod_inline, Blob.getSlice, .{ &this.blob, globalThis, callframe });
}
pub fn getType(
@@ -1227,7 +1171,7 @@ pub const BuildArtifact = struct {
this: *BuildArtifact,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
return @call(bun.callmod_inline, Blob.getStream, .{
&this.blob,
globalThis,

View File

@@ -310,7 +310,7 @@ fn exportReplacementValue(value: JSValue, globalThis: *JSGlobalObject) ?JSAst.Ex
return null;
}
fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) !TranspilerOptions {
fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice) (bun.JSError || bun.OOM)!TranspilerOptions {
const globalThis = globalObject;
const object = args.next() orelse return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
if (object.isUndefinedOrNull()) return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
@@ -325,19 +325,19 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
};
if (!object.isObject()) {
JSC.throwInvalidArguments("Expected an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("Expected an object", .{});
return error.JSError;
}
if (object.getTruthy(globalObject, "define")) |define| {
if (try object.getTruthy(globalObject, "define")) |define| {
define: {
if (define.isUndefinedOrNull()) {
break :define;
}
if (!define.isObject()) {
JSC.throwInvalidArguments("define must be an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("define must be an object", .{});
return error.JSError;
}
var define_iter = JSC.JSPropertyIterator(.{
@@ -358,8 +358,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
const value_type = property_value.jsType();
if (!value_type.isStringLike()) {
JSC.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("define \"{s}\" must be a JSON string", .{prop});
return error.JSError;
}
names[define_iter.i] = prop.toOwnedSlice(allocator) catch unreachable;
@@ -378,7 +378,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
}
if (object.get(globalThis, "external")) |external| {
if (try object.get(globalThis, "external")) |external| {
external: {
if (external.isUndefinedOrNull()) break :external;
@@ -399,8 +399,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
var i: usize = 0;
while (iter.next()) |entry| {
if (!entry.jsType().isStringLike()) {
JSC.throwInvalidArguments("external must be a string or string[]", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("external must be a string or string[]", .{});
return error.JSError;
}
var zig_str = JSC.ZigString.init("");
@@ -412,38 +412,30 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
transpiler.transform.external = externals[0..i];
} else {
JSC.throwInvalidArguments("external must be a string or string[]", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("external must be a string or string[]", .{});
return error.JSError;
}
}
}
if (object.get(globalThis, "loader")) |loader| {
if (Loader.fromJS(globalThis, loader, exception)) |resolved| {
if (try object.get(globalThis, "loader")) |loader| {
if (try Loader.fromJS(globalThis, loader)) |resolved| {
if (!resolved.isJavaScriptLike()) {
JSC.throwInvalidArguments("only JavaScript-like loaders supported for now", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("only JavaScript-like loaders supported for now", .{});
return error.JSError;
}
transpiler.default_loader = resolved;
}
if (exception.* != null) {
return transpiler;
}
}
if (object.get(globalThis, "target")) |target| {
if (Target.fromJS(globalThis, target, exception)) |resolved| {
if (try object.get(globalThis, "target")) |target| {
if (try Target.fromJS(globalThis, target)) |resolved| {
transpiler.transform.target = resolved.toAPI();
}
if (exception.* != null) {
return transpiler;
}
}
if (object.get(globalThis, "tsconfig")) |tsconfig| {
if (try object.get(globalThis, "tsconfig")) |tsconfig| {
tsconfig: {
if (tsconfig.isUndefinedOrNull()) break :tsconfig;
const kind = tsconfig.jsType();
@@ -451,8 +443,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
defer out.deref();
if (kind.isArray()) {
JSC.throwInvalidArguments("tsconfig must be a string or object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("tsconfig must be a string or object", .{});
return error.JSError;
}
if (!kind.isStringLike()) {
@@ -482,7 +474,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
else => false,
};
if (object.getTruthy(globalThis, "macro")) |macros| {
if (try object.getTruthy(globalThis, "macro")) |macros| {
macros: {
if (macros.isUndefinedOrNull()) break :macros;
if (macros.isBoolean()) {
@@ -492,8 +484,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
const kind = macros.jsType();
const is_object = kind.isObject();
if (!(kind.isStringLike() or is_object)) {
JSC.throwInvalidArguments("macro must be an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("macro must be an object", .{});
return error.JSError;
}
var out = bun.String.empty;
@@ -517,48 +509,48 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
}
if (object.getOptional(globalThis, "autoImportJSX", bool) catch return transpiler) |flag| {
if (try object.getBooleanLoose(globalThis, "autoImportJSX")) |flag| {
transpiler.runtime.auto_import_jsx = flag;
}
if (object.getOptional(globalThis, "allowBunRuntime", bool) catch return transpiler) |flag| {
if (try object.getBooleanLoose(globalThis, "allowBunRuntime")) |flag| {
transpiler.runtime.allow_runtime = flag;
}
if (object.getOptional(globalThis, "inline", bool) catch return transpiler) |flag| {
if (try object.getBooleanLoose(globalThis, "inline")) |flag| {
transpiler.runtime.inlining = flag;
}
if (object.getOptional(globalThis, "minifyWhitespace", bool) catch return transpiler) |flag| {
if (try object.getBooleanLoose(globalThis, "minifyWhitespace")) |flag| {
transpiler.minify_whitespace = flag;
}
if (object.getOptional(globalThis, "deadCodeElimination", bool) catch return transpiler) |flag| {
if (try object.getBooleanLoose(globalThis, "deadCodeElimination")) |flag| {
transpiler.dead_code_elimination = flag;
}
if (object.getTruthy(globalThis, "minify")) |hot| {
if (hot.isBoolean()) {
transpiler.minify_whitespace = hot.coerce(bool, globalThis);
if (try object.getTruthy(globalThis, "minify")) |minify| {
if (minify.isBoolean()) {
transpiler.minify_whitespace = minify.coerce(bool, globalThis);
transpiler.minify_syntax = transpiler.minify_whitespace;
transpiler.minify_identifiers = transpiler.minify_syntax;
} else if (hot.isObject()) {
if (try hot.getOptional(globalThis, "whitespace", bool)) |whitespace| {
} else if (minify.isObject()) {
if (try minify.getBooleanLoose(globalThis, "whitespace")) |whitespace| {
transpiler.minify_whitespace = whitespace;
}
if (try hot.getOptional(globalThis, "syntax", bool)) |syntax| {
if (try minify.getBooleanLoose(globalThis, "syntax")) |syntax| {
transpiler.minify_syntax = syntax;
}
if (try hot.getOptional(globalThis, "identifiers", bool)) |syntax| {
if (try minify.getBooleanLoose(globalThis, "identifiers")) |syntax| {
transpiler.minify_identifiers = syntax;
}
} else {
JSC.throwInvalidArguments("Expected minify to be a boolean or an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("Expected minify to be a boolean or an object", .{});
return error.JSError;
}
}
if (object.get(globalThis, "sourcemap")) |flag| {
if (try object.get(globalThis, "sourcemap")) |flag| {
if (flag.isBoolean() or flag.isUndefinedOrNull()) {
if (flag.toBoolean()) {
transpiler.transform.source_map = .@"inline";
@@ -569,8 +561,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
if (options.SourceMapOption.Map.fromJS(globalObject, flag)) |source| {
transpiler.transform.source_map = source.toAPI();
} else {
JSC.throwInvalidArguments("sourcemap must be one of \"inline\", \"linked\", \"external\", or \"none\"", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("sourcemap must be one of \"inline\", \"linked\", \"external\", or \"none\"", .{});
return error.JSError;
}
}
}
@@ -580,28 +572,28 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
var tree_shaking: ?bool = null;
if (object.getOptional(globalThis, "treeShaking", bool) catch return transpiler) |treeShaking| {
if (try object.getBooleanLoose(globalThis, "treeShaking")) |treeShaking| {
tree_shaking = treeShaking;
}
var trim_unused_imports: ?bool = null;
if (object.getOptional(globalThis, "trimUnusedImports", bool) catch return transpiler) |trimUnusedImports| {
if (try object.getBooleanLoose(globalThis, "trimUnusedImports")) |trimUnusedImports| {
trim_unused_imports = trimUnusedImports;
}
if (object.getTruthy(globalThis, "exports")) |exports| {
if (try object.getTruthy(globalThis, "exports")) |exports| {
if (!exports.isObject()) {
JSC.throwInvalidArguments("exports must be an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("exports must be an object", .{});
return error.JSError;
}
var replacements = Runtime.Features.ReplaceableExport.Map{};
errdefer replacements.clearAndFree(bun.default_allocator);
if (exports.getTruthy(globalThis, "eliminate")) |eliminate| {
if (try exports.getTruthy(globalThis, "eliminate")) |eliminate| {
if (!eliminate.jsType().isArray()) {
JSC.throwInvalidArguments("exports.eliminate must be an array", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("exports.eliminate must be an array", .{});
return error.JSError;
}
var total_name_buf_len: u32 = 0;
@@ -628,8 +620,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
const str = value.getZigString(globalThis);
if (str.len == 0) continue;
const name = std.fmt.bufPrint(buf.items.ptr[buf.items.len..buf.capacity], "{}", .{str}) catch {
JSC.throwInvalidArguments("Error reading exports.eliminate. TODO: utf-16", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("Error reading exports.eliminate. TODO: utf-16", .{});
return error.JSError;
};
buf.items.len += name.len;
if (name.len > 0) {
@@ -640,10 +632,10 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
}
if (exports.getTruthy(globalThis, "replace")) |replace| {
if (try exports.getTruthy(globalThis, "replace")) |replace| {
if (!replace.isObject()) {
JSC.throwInvalidArguments("replace must be an object", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("replace must be an object", .{});
return error.JSError;
}
var iter = JSC.JSPropertyIterator(.{
@@ -657,7 +649,7 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
// We cannot set the exception before `try` because it could be
// a double free with the `errdefer`.
defer if (exception.* != null) {
defer if (globalThis.hasException()) {
iter.deinit();
for (replacements.keys()) |key| {
bun.default_allocator.free(@constCast(key));
@@ -667,14 +659,14 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
while (iter.next()) |key_| {
const value = iter.value;
if (value.isEmpty()) continue;
if (value == .zero) continue;
const key = try key_.toOwnedSlice(bun.default_allocator);
if (!JSLexer.isIdentifier(key)) {
JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{key}, globalObject, exception);
globalObject.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{key});
bun.default_allocator.free(key);
return transpiler;
return error.JSError;
}
const entry = replacements.getOrPutAssumeCapacity(key);
@@ -692,9 +684,9 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
const replacement_name = slice.slice();
if (!JSLexer.isIdentifier(replacement_name)) {
JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{replacement_name}, globalObject, exception);
globalObject.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{replacement_name});
slice.deinit();
return transpiler;
return error.JSError;
}
entry.value_ptr.* = .{
@@ -707,8 +699,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
}
JSC.throwInvalidArguments("exports.replace values can only be string, null, undefined, number or boolean", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("exports.replace values can only be string, null, undefined, number or boolean", .{});
return error.JSError;
}
}
}
@@ -717,12 +709,12 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
transpiler.runtime.replace_exports = replacements;
}
if (object.getTruthy(globalThis, "logLevel")) |logLevel| {
if (try object.getTruthy(globalThis, "logLevel")) |logLevel| {
if (logger.Log.Level.Map.fromJS(globalObject, logLevel)) |level| {
transpiler.log.level = level;
} else {
JSC.throwInvalidArguments("logLevel must be one of \"verbose\", \"debug\", \"info\", \"warn\", or \"error\"", .{}, globalObject, exception);
return transpiler;
globalObject.throwInvalidArguments("logLevel must be one of \"verbose\", \"debug\", \"info\", \"warn\", or \"error\"", .{});
return error.JSError;
}
}
@@ -732,44 +724,31 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
return transpiler;
}
pub fn constructor(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) ?*Transpiler {
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*Transpiler {
var temp = bun.ArenaAllocator.init(getAllocator(globalThis));
const arguments = callframe.arguments(3);
const arguments = callframe.arguments_old(3);
var args = JSC.Node.ArgumentsSlice.init(
globalThis.bunVM(),
arguments.slice(),
);
defer temp.deinit();
var exception_ref = [_]JSC.C.JSValueRef{null};
const exception = &exception_ref[0];
const transpiler_options: TranspilerOptions = if (arguments.len > 0)
transformOptionsFromJSC(globalThis, temp.allocator(), &args, exception) catch {
JSC.throwInvalidArguments("Failed to create transpiler", .{}, globalThis, exception);
return null;
}
try transformOptionsFromJSC(globalThis, temp.allocator(), &args)
else
TranspilerOptions{ .log = logger.Log.init(getAllocator(globalThis)) };
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
return null;
if (globalThis.hasException()) {
return error.JSError;
}
const allocator = getAllocator(globalThis);
if ((transpiler_options.log.warnings + transpiler_options.log.errors) > 0) {
globalThis.throwValue(
transpiler_options.log.toJS(globalThis.ptr(), allocator, "Failed to create transpiler"),
);
return null;
return globalThis.throwValue2(transpiler_options.log.toJS(globalThis, allocator, "Failed to create transpiler"));
}
var log = allocator.create(logger.Log) catch unreachable;
var log = try allocator.create(logger.Log);
log.* = transpiler_options.log;
var bundler = Bundler.Bundler.init(
allocator,
@@ -778,30 +757,19 @@ pub fn constructor(
JavaScript.VirtualMachine.get().bundler.env,
) catch |err| {
if ((log.warnings + log.errors) > 0) {
globalThis.throwValue(
log.toJS(globalThis.ptr(), allocator, "Failed to create transpiler"),
);
return null;
return globalThis.throwValue2(log.toJS(globalThis, allocator, "Failed to create transpiler"));
}
globalThis.throwError(err, "Error creating transpiler");
return null;
return globalThis.throwError(err, "Error creating transpiler");
};
bundler.options.no_macros = transpiler_options.no_macros;
bundler.configureLinkerWithAutoJSX(false);
bundler.options.env.behavior = .disable;
bundler.configureDefines() catch |err| {
if ((log.warnings + log.errors) > 0) {
globalThis.throwValue(
log.toJS(globalThis.ptr(), allocator, "Failed to load define"),
);
return null;
return globalThis.throwValue2(log.toJS(globalThis, allocator, "Failed to load define"));
}
globalThis.throwError(err, "Failed to load define");
return null;
return globalThis.throwError(err, "Failed to load define");
};
if (transpiler_options.macro_map.count() > 0) {
@@ -828,7 +796,7 @@ pub fn constructor(
bundler.options.hot_module_reloading = transpiler_options.runtime.hot_module_reloading;
bundler.options.react_fast_refresh = false;
const transpiler = allocator.create(Transpiler) catch unreachable;
const transpiler = try allocator.create(Transpiler);
transpiler.* = Transpiler{
.transpiler_options = transpiler_options,
.bundler = bundler,
@@ -886,9 +854,9 @@ pub fn scan(
this: *Transpiler,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
const arguments = callframe.arguments(3);
const arguments = callframe.arguments_old(3);
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
defer args.deinit();
const code_arg = args.next() orelse {
@@ -903,20 +871,17 @@ pub fn scan(
defer code_holder.deinit();
const code = code_holder.slice();
args.eat();
var exception_ref = [_]JSC.C.JSValueRef{null};
const exception: JSC.C.ExceptionRef = &exception_ref;
const loader: ?Loader = brk: {
if (args.next()) |arg| {
args.eat();
break :brk Loader.fromJS(globalThis, arg, exception);
break :brk try Loader.fromJS(globalThis, arg);
}
break :brk null;
};
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
if (globalThis.hasException()) {
return .zero;
}
@@ -957,12 +922,7 @@ pub fn scan(
const named_imports_value = namedImportsToJS(
globalThis,
parse_result.ast.import_records.slice(),
exception,
);
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
return .zero;
}
const named_exports_value = namedExportsToJS(
globalThis,
@@ -971,52 +931,36 @@ pub fn scan(
return JSC.JSValue.createObject2(globalThis, imports_label, exports_label, named_imports_value, named_exports_value);
}
// pub fn build(
// this: *Transpiler,
// ctx: js.JSContextRef,
// _: js.JSObjectRef,
// _: js.JSObjectRef,
// arguments: []const js.JSValueRef,
// exception: js.ExceptionRef,
// ) JSC.C.JSObjectRef {}
pub fn transform(
this: *Transpiler,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
var exception_ref = [_]JSC.C.JSValueRef{null};
const exception: JSC.C.ExceptionRef = &exception_ref;
const arguments = callframe.arguments(3);
const arguments = callframe.arguments_old(3);
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
defer args.arena.deinit();
const code_arg = args.next() orelse {
globalThis.throwInvalidArgumentType("transform", "code", "string or Uint8Array");
return .zero;
return error.JSError;
};
var code = JSC.Node.StringOrBuffer.fromJSWithEncodingMaybeAsync(globalThis, bun.default_allocator, code_arg, .utf8, true) orelse {
var code = try JSC.Node.StringOrBuffer.fromJSWithEncodingMaybeAsync(globalThis, bun.default_allocator, code_arg, .utf8, true) orelse {
globalThis.throwInvalidArgumentType("transform", "code", "string or Uint8Array");
return .zero;
return error.JSError;
};
errdefer code.deinit();
args.eat();
const loader: ?Loader = brk: {
if (args.next()) |arg| {
args.eat();
break :brk Loader.fromJS(globalThis, arg, exception);
break :brk try Loader.fromJS(globalThis, arg);
}
break :brk null;
};
if (exception.* != null) {
code.deinit();
globalThis.throwValue(JSC.JSValue.c(exception.*));
return .zero;
}
if (code == .buffer) {
code_arg.protect();
}
@@ -1030,7 +974,7 @@ pub fn transform(
code_arg.unprotect();
}
globalThis.throwOutOfMemory();
return .zero;
return error.JSError;
};
task.schedule();
return task.promise.value();
@@ -1040,11 +984,9 @@ pub fn transformSync(
this: *Transpiler,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
var exception_value = [_]JSC.C.JSValueRef{null};
const exception: JSC.C.ExceptionRef = &exception_value;
const arguments = callframe.arguments(3);
const arguments = callframe.arguments_old(3);
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
defer args.arena.deinit();
@@ -1070,7 +1012,7 @@ pub fn transformSync(
if (args.next()) |arg| {
args.eat();
if (arg.isNumber() or arg.isString()) {
break :brk Loader.fromJS(globalThis, arg, exception);
break :brk try Loader.fromJS(globalThis, arg);
}
if (arg.isObject()) {
@@ -1090,21 +1032,16 @@ pub fn transformSync(
return .zero;
}
}
if (!js_ctx_value.isEmpty()) {
if (js_ctx_value != .zero) {
js_ctx_value.ensureStillAlive();
}
defer {
if (!js_ctx_value.isEmpty()) {
if (js_ctx_value != .zero) {
js_ctx_value.ensureStillAlive();
}
}
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
return .zero;
}
JSAst.Stmt.Data.Store.reset();
JSAst.Expr.Data.Store.reset();
defer {
@@ -1161,8 +1098,7 @@ pub fn transformSync(
buffer_writer.reset();
var printer = JSPrinter.BufferPrinter.init(buffer_writer);
_ = this.bundler.print(parse_result, @TypeOf(&printer), &printer, .esm_ascii) catch |err| {
globalThis.throwError(err, "Failed to print code");
return .zero;
return globalThis.throwError(err, "Failed to print code");
};
// TODO: benchmark if pooling this way is faster or moving is faster
@@ -1201,7 +1137,6 @@ const ImportRecord = @import("../../import_record.zig").ImportRecord;
fn namedImportsToJS(
global: *JSGlobalObject,
import_records: []const ImportRecord,
_: JSC.C.ExceptionRef,
) JSC.JSValue {
const path_label = JSC.ZigString.static("path");
const kind_label = JSC.ZigString.static("kind");
@@ -1225,10 +1160,8 @@ pub fn scanImports(
this: *Transpiler,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) JSC.JSValue {
const arguments = callframe.arguments(2);
var exception_val = [_]JSC.C.JSValueRef{null};
const exception: JSC.C.ExceptionRef = &exception_val;
) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
defer args.deinit();
@@ -1238,12 +1171,9 @@ pub fn scanImports(
};
const code_holder = JSC.Node.StringOrBuffer.fromJS(globalThis, args.arena.allocator(), code_arg) orelse {
if (exception.* == null) {
if (!globalThis.hasException()) {
globalThis.throwInvalidArgumentType("scanImports", "code", "string or Uint8Array");
} else {
globalThis.throwValue(JSC.JSValue.c(exception.*));
}
return .zero;
};
args.eat();
@@ -1252,7 +1182,7 @@ pub fn scanImports(
var loader: Loader = this.transpiler_options.default_loader;
if (args.next()) |arg| {
if (Loader.fromJS(globalThis, arg, exception)) |_loader| {
if (try Loader.fromJS(globalThis, arg)) |_loader| {
loader = _loader;
}
args.eat();
@@ -1263,11 +1193,6 @@ pub fn scanImports(
return .zero;
}
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
return .zero;
}
var arena = Mimalloc.Arena.init() catch unreachable;
const prev_allocator = this.bundler.allocator;
this.bundler.setAllocator(arena.allocator());
@@ -1315,8 +1240,7 @@ pub fn scanImports(
return .zero;
}
globalThis.throwError(err, "Failed to scan imports");
return .zero;
return globalThis.throwError(err, "Failed to scan imports");
};
defer this.scan_pass_result.reset();
@@ -1329,11 +1253,6 @@ pub fn scanImports(
const named_imports_value = namedImportsToJS(
globalThis,
this.scan_pass_result.import_records.items,
exception,
);
if (exception.* != null) {
globalThis.throwValue(JSC.JSValue.c(exception.*));
return .zero;
}
return named_imports_value;
}

View File

@@ -550,7 +550,7 @@ pub const TimerObject = struct {
return .{ timer, timer_js };
}
pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const this_value = callframe.this();
this_value.ensureStillAlive();
@@ -564,7 +564,7 @@ pub const TimerObject = struct {
return this_value;
}
pub fn doRefresh(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn doRefresh(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const this_value = callframe.this();
// setImmediate does not support refreshing and we do not support refreshing after cleanup
@@ -578,7 +578,7 @@ pub const TimerObject = struct {
return this_value;
}
pub fn doUnref(this: *TimerObject, _: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn doUnref(this: *TimerObject, _: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const this_value = callframe.this();
this_value.ensureStillAlive();
@@ -643,10 +643,10 @@ pub const TimerObject = struct {
}
}
pub fn hasRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn hasRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
return JSValue.jsBoolean(this.is_keeping_event_loop_alive);
}
pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
if (!this.has_accessed_primitive) {
this.has_accessed_primitive = true;
const vm = VirtualMachine.get();

View File

@@ -1562,7 +1562,7 @@ pub const InternalDNS = struct {
var dns_cache_errors: usize = 0;
var getaddrinfo_calls: usize = 0;
pub fn getDNSCacheStats(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
pub fn getDNSCacheStats(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const object = JSC.JSValue.createEmptyObject(globalObject, 6);
object.put(globalObject, JSC.ZigString.static("cacheHitsCompleted"), JSC.JSValue.jsNumber(@atomicLoad(usize, &dns_cache_hits_completed, .monotonic)));
object.put(globalObject, JSC.ZigString.static("cacheHitsInflight"), JSC.JSValue.jsNumber(@atomicLoad(usize, &dns_cache_hits_inflight, .monotonic)));
@@ -1633,12 +1633,11 @@ pub const InternalDNS = struct {
return req;
}
pub fn prefetchFromJS(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2).slice();
pub fn prefetchFromJS(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2).slice();
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("prefetch", 1, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("prefetch", 1, arguments.len);
}
const hostname_or_url = arguments[0];
@@ -2252,11 +2251,10 @@ pub const DNSResolver = struct {
});
};
pub fn resolve(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(3);
pub fn resolve(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(3);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolve", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolve", 2, arguments.len);
}
const record_type: RecordType = if (arguments.len == 1)
@@ -2337,11 +2335,10 @@ pub const DNSResolver = struct {
}
}
pub fn reverse(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn reverse(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("reverse", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("reverse", 2, arguments.len);
}
const ip_value = arguments.ptr[0];
@@ -2402,11 +2399,10 @@ pub const DNSResolver = struct {
return promise;
}
pub fn lookup(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn lookup(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("lookup", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("lookup", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2429,7 +2425,7 @@ pub const DNSResolver = struct {
var port: u16 = 0;
if (arguments.len > 1 and arguments.ptr[1].isCell()) {
if (arguments.ptr[1].get(globalThis, "port")) |port_value| {
if (try arguments.ptr[1].get(globalThis, "port")) |port_value| {
if (port_value.isNumber()) {
port = port_value.to(u16);
}
@@ -2472,11 +2468,10 @@ pub const DNSResolver = struct {
};
}
pub fn resolveSrv(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveSrv(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveSrv", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveSrv", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2503,11 +2498,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_ares_srv_reply, "srv", name.slice(), globalThis);
}
pub fn resolveSoa(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveSoa(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveSoa", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveSoa", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2529,11 +2523,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_ares_soa_reply, "soa", name.slice(), globalThis);
}
pub fn resolveCaa(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveCaa(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveCaa", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveCaa", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2560,11 +2553,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_ares_caa_reply, "caa", name.slice(), globalThis);
}
pub fn resolveNs(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveNs(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveNs", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveNs", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2586,11 +2578,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_hostent, "ns", name.slice(), globalThis);
}
pub fn resolvePtr(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolvePtr(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolvePtr", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolvePtr", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2617,11 +2608,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_hostent, "ptr", name.slice(), globalThis);
}
pub fn resolveCname(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveCname(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveCname", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveCname", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2648,11 +2638,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_hostent, "cname", name.slice(), globalThis);
}
pub fn resolveMx(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveMx(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveMx", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveMx", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2679,11 +2668,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_ares_mx_reply, "mx", name.slice(), globalThis);
}
pub fn resolveNaptr(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveNaptr(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveNaptr", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveNaptr", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2710,11 +2698,10 @@ pub const DNSResolver = struct {
return resolver.doResolveCAres(c_ares.struct_ares_naptr_reply, "naptr", name.slice(), globalThis);
}
pub fn resolveTxt(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(2);
pub fn resolveTxt(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(2);
if (arguments.len < 1) {
globalThis.throwNotEnoughArguments("resolveTxt", 2, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("resolveTxt", 2, arguments.len);
}
const name_value = arguments.ptr[0];
@@ -2831,7 +2818,7 @@ pub const DNSResolver = struct {
return promise;
}
pub fn getServers(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
pub fn getServers(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
_ = callframe;
var vm = globalThis.bunVM();
@@ -2917,11 +2904,10 @@ pub const DNSResolver = struct {
// Resolves the given address and port into a host name and service using the operating system's underlying getnameinfo implementation.
// If address is not a valid IP address, a TypeError will be thrown. The port will be coerced to a number.
// If it is not a legal port, a TypeError will be thrown.
pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(3);
pub fn lookupService(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(3);
if (arguments.len < 2) {
globalThis.throwNotEnoughArguments("lookupService", 3, arguments.len);
return .zero;
return globalThis.throwNotEnoughArguments("lookupService", 3, arguments.len);
}
const addr_value = arguments.ptr[0];
@@ -3004,98 +2990,37 @@ pub const DNSResolver = struct {
}
comptime {
@export(
resolve,
.{
.name = "Bun__DNSResolver__resolve",
},
);
@export(
lookup,
.{
.name = "Bun__DNSResolver__lookup",
},
);
@export(
resolveTxt,
.{
.name = "Bun__DNSResolver__resolveTxt",
},
);
@export(
resolveSoa,
.{
.name = "Bun__DNSResolver__resolveSoa",
},
);
@export(
resolveMx,
.{
.name = "Bun__DNSResolver__resolveMx",
},
);
@export(
resolveNaptr,
.{
.name = "Bun__DNSResolver__resolveNaptr",
},
);
@export(
resolveSrv,
.{
.name = "Bun__DNSResolver__resolveSrv",
},
);
@export(
resolveCaa,
.{
.name = "Bun__DNSResolver__resolveCaa",
},
);
@export(
resolveNs,
.{
.name = "Bun__DNSResolver__resolveNs",
},
);
@export(
resolvePtr,
.{
.name = "Bun__DNSResolver__resolvePtr",
},
);
@export(
resolveCname,
.{
.name = "Bun__DNSResolver__resolveCname",
},
);
@export(
getServers,
.{
.name = "Bun__DNSResolver__getServers",
},
);
@export(
reverse,
.{
.name = "Bun__DNSResolver__reverse",
},
);
@export(
lookupService,
.{
.name = "Bun__DNSResolver__lookupService",
},
);
@export(
InternalDNS.prefetchFromJS,
.{
.name = "Bun__DNSResolver__prefetch",
},
);
@export(InternalDNS.getDNSCacheStats, .{
.name = "Bun__DNSResolver__getCacheStats",
});
const js_resolve = JSC.toJSHostFunction(resolve);
@export(js_resolve, .{ .name = "Bun__DNSResolver__resolve" });
const js_lookup = JSC.toJSHostFunction(lookup);
@export(js_lookup, .{ .name = "Bun__DNSResolver__lookup" });
const js_resolveTxt = JSC.toJSHostFunction(resolveTxt);
@export(js_resolveTxt, .{ .name = "Bun__DNSResolver__resolveTxt" });
const js_resolveSoa = JSC.toJSHostFunction(resolveSoa);
@export(js_resolveSoa, .{ .name = "Bun__DNSResolver__resolveSoa" });
const js_resolveMx = JSC.toJSHostFunction(resolveMx);
@export(js_resolveMx, .{ .name = "Bun__DNSResolver__resolveMx" });
const js_resolveNaptr = JSC.toJSHostFunction(resolveNaptr);
@export(js_resolveNaptr, .{ .name = "Bun__DNSResolver__resolveNaptr" });
const js_resolveSrv = JSC.toJSHostFunction(resolveSrv);
@export(js_resolveSrv, .{ .name = "Bun__DNSResolver__resolveSrv" });
const js_resolveCaa = JSC.toJSHostFunction(resolveCaa);
@export(js_resolveCaa, .{ .name = "Bun__DNSResolver__resolveCaa" });
const js_resolveNs = JSC.toJSHostFunction(resolveNs);
@export(js_resolveNs, .{ .name = "Bun__DNSResolver__resolveNs" });
const js_resolvePtr = JSC.toJSHostFunction(resolvePtr);
@export(js_resolvePtr, .{ .name = "Bun__DNSResolver__resolvePtr" });
const js_resolveCname = JSC.toJSHostFunction(resolveCname);
@export(js_resolveCname, .{ .name = "Bun__DNSResolver__resolveCname" });
const js_getServers = JSC.toJSHostFunction(getServers);
@export(js_getServers, .{ .name = "Bun__DNSResolver__getServers" });
const js_reverse = JSC.toJSHostFunction(reverse);
@export(js_reverse, .{ .name = "Bun__DNSResolver__reverse" });
const js_lookupService = JSC.toJSHostFunction(lookupService);
@export(js_lookupService, .{ .name = "Bun__DNSResolver__lookupService" });
const js_prefetchFromJS = JSC.toJSHostFunction(InternalDNS.prefetchFromJS);
@export(js_prefetchFromJS, .{ .name = "Bun__DNSResolver__prefetch" });
const js_getDNSCacheStats = JSC.toJSHostFunction(InternalDNS.getDNSCacheStats);
@export(js_getDNSCacheStats, .{ .name = "Bun__DNSResolver__getCacheStats" });
}
};

View File

@@ -290,11 +290,11 @@ const SingleValueHeaders = bun.ComptimeStringMap(void, .{
.{"x-content-type-options"},
});
fn jsGetUnpackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
fn jsGetUnpackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
var settings: FullSettingsPayload = .{};
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
return settings.toJS(globalObject);
}
@@ -325,8 +325,8 @@ fn jsGetUnpackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.Call
}
}
fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSValue {
const args_list = callframe.arguments(1);
fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected settings to be a object", .{});
return .zero;
@@ -339,7 +339,7 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
return .zero;
}
if (options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (try options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (headerTableSize.isNumber()) {
const headerTableSizeValue = headerTableSize.toInt32();
if (headerTableSizeValue > MAX_HEADER_TABLE_SIZE or headerTableSizeValue < 0) {
@@ -352,14 +352,14 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
}
}
if (options.get(globalObject, "enablePush")) |enablePush| {
if (try options.get(globalObject, "enablePush")) |enablePush| {
if (!enablePush.isBoolean() and !enablePush.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected enablePush to be a boolean", .{});
return .zero;
}
}
if (options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (try options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (initialWindowSize.isNumber()) {
const initialWindowSizeValue = initialWindowSize.toInt32();
if (initialWindowSizeValue > MAX_HEADER_TABLE_SIZE or initialWindowSizeValue < 0) {
@@ -372,7 +372,7 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
}
}
if (options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (try options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (maxFrameSize.isNumber()) {
const maxFrameSizeValue = maxFrameSize.toInt32();
if (maxFrameSizeValue > MAX_FRAME_SIZE or maxFrameSizeValue < 16384) {
@@ -385,7 +385,7 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
}
}
if (options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (try options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (maxConcurrentStreams.isNumber()) {
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt32();
if (maxConcurrentStreamsValue > MAX_HEADER_TABLE_SIZE or maxConcurrentStreamsValue < 0) {
@@ -398,7 +398,7 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
}
}
if (options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (try options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (maxHeaderListSize.isNumber()) {
const maxHeaderListSizeValue = maxHeaderListSize.toInt32();
if (maxHeaderListSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderListSizeValue < 0) {
@@ -411,7 +411,7 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
}
}
if (options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (try options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (maxHeaderSize.isNumber()) {
const maxHeaderSizeValue = maxHeaderSize.toInt32();
if (maxHeaderSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderSizeValue < 0) {
@@ -427,9 +427,9 @@ fn jsAssertSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame
return .undefined;
}
fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSValue {
fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
var settings: FullSettingsPayload = .{};
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len > 0 and !args_list.ptr[0].isEmptyOrUndefinedOrNull()) {
const options = args_list.ptr[0];
@@ -439,7 +439,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
return .zero;
}
if (options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (try options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (headerTableSize.isNumber()) {
const headerTableSizeValue = headerTableSize.toInt32();
if (headerTableSizeValue > MAX_HEADER_TABLE_SIZE or headerTableSizeValue < 0) {
@@ -453,7 +453,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "enablePush")) |enablePush| {
if (try options.get(globalObject, "enablePush")) |enablePush| {
if (enablePush.isBoolean()) {
settings.enablePush = if (enablePush.asBoolean()) 1 else 0;
} else if (!enablePush.isEmptyOrUndefinedOrNull()) {
@@ -462,7 +462,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (try options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (initialWindowSize.isNumber()) {
const initialWindowSizeValue = initialWindowSize.toInt32();
if (initialWindowSizeValue > MAX_HEADER_TABLE_SIZE or initialWindowSizeValue < 0) {
@@ -476,7 +476,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (try options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (maxFrameSize.isNumber()) {
const maxFrameSizeValue = maxFrameSize.toInt32();
if (maxFrameSizeValue > MAX_FRAME_SIZE or maxFrameSizeValue < 16384) {
@@ -490,7 +490,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (try options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (maxConcurrentStreams.isNumber()) {
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt32();
if (maxConcurrentStreamsValue > MAX_HEADER_TABLE_SIZE or maxConcurrentStreamsValue < 0) {
@@ -504,7 +504,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (try options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (maxHeaderListSize.isNumber()) {
const maxHeaderListSizeValue = maxHeaderListSize.toInt32();
if (maxHeaderListSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderListSizeValue < 0) {
@@ -518,7 +518,7 @@ fn jsGetPackedSettings(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFr
}
}
if (options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (try options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (maxHeaderSize.isNumber()) {
const maxHeaderSizeValue = maxHeaderSize.toInt32();
if (maxHeaderSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderSizeValue < 0) {
@@ -586,15 +586,14 @@ const Handlers = struct {
return this.vm.eventLoop().runCallbackWithResult(callback, this.globalObject, thisValue, data);
}
pub fn fromJS(globalObject: *JSC.JSGlobalObject, opts: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Handlers {
pub fn fromJS(globalObject: *JSC.JSGlobalObject, opts: JSC.JSValue) bun.JSError!Handlers {
var handlers = Handlers{
.vm = globalObject.bunVM(),
.globalObject = globalObject,
};
if (opts.isEmptyOrUndefinedOrNull() or opts.isBoolean() or !opts.isObject()) {
exception.* = JSC.toInvalidArguments("Expected \"handlers\" to be an object", .{}, globalObject).asObjectRef();
return null;
return globalObject.throwInvalidArguments2("Expected \"handlers\" to be an object", .{});
}
const pairs = .{
@@ -615,10 +614,9 @@ const Handlers = struct {
};
inline for (pairs) |pair| {
if (opts.getTruthy(globalObject, pair.@"1")) |callback_value| {
if (try opts.getTruthy(globalObject, pair.@"1")) |callback_value| {
if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) {
exception.* = JSC.toInvalidArguments(comptime std.fmt.comptimePrint("Expected \"{s}\" callback to be a function", .{pair.@"1"}), .{}, globalObject).asObjectRef();
return null;
return globalObject.throwInvalidArguments2("Expected \"{s}\" callback to be a function", .{pair[1]});
}
@field(handlers, pair.@"0") = callback_value;
@@ -627,8 +625,7 @@ const Handlers = struct {
if (opts.fastGet(globalObject, .@"error")) |callback_value| {
if (!callback_value.isCell() or !callback_value.isCallable(globalObject.vm())) {
exception.* = JSC.toInvalidArguments("Expected \"error\" callback to be a function", .{}, globalObject).asObjectRef();
return null;
return globalObject.throwInvalidArguments2("Expected \"error\" callback to be a function", .{});
}
handlers.onError = callback_value;
@@ -636,19 +633,16 @@ const Handlers = struct {
// onWrite is required for duplex support or if more than 1 parser is attached to the same socket (unliked)
if (handlers.onWrite == .zero) {
exception.* = JSC.toInvalidArguments("Expected at least \"write\" callback", .{}, globalObject).asObjectRef();
return null;
return globalObject.throwInvalidArguments2("Expected at least \"write\" callback", .{});
}
if (opts.getTruthy(globalObject, "binaryType")) |binary_type_value| {
if (try opts.getTruthy(globalObject, "binaryType")) |binary_type_value| {
if (!binary_type_value.isString()) {
exception.* = JSC.toInvalidArguments("Expected \"binaryType\" to be a string", .{}, globalObject).asObjectRef();
return null;
return globalObject.throwInvalidArguments2("Expected \"binaryType\" to be a string", .{});
}
handlers.binary_type = BinaryType.fromJSValue(globalObject, binary_type_value) orelse {
exception.* = JSC.toInvalidArguments("Expected 'binaryType' to be 'arraybuffer', 'uint8array', 'buffer'", .{}, globalObject).asObjectRef();
return null;
handlers.binary_type = try BinaryType.fromJSValue(globalObject, binary_type_value) orelse {
return globalObject.throwInvalidArguments2("Expected 'binaryType' to be 'ArrayBuffer', 'Uint8Array', or 'Buffer'", .{});
};
}
@@ -2338,9 +2332,9 @@ pub const H2FrameParser = struct {
return DirectWriterStruct{ .writer = this };
}
pub fn setEncoding(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn setEncoding(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected encoding argument", .{});
return .zero;
@@ -2354,110 +2348,96 @@ pub const H2FrameParser = struct {
return .undefined;
}
pub fn loadSettingsFromJSValue(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, options: JSC.JSValue) bool {
pub fn loadSettingsFromJSValue(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, options: JSC.JSValue) bun.JSError!void {
if (options.isEmptyOrUndefinedOrNull() or !options.isObject()) {
globalObject.throw("Expected settings to be a object", .{});
return false;
return globalObject.throw2("Expected settings to be a object", .{});
}
if (options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (try options.get(globalObject, "headerTableSize")) |headerTableSize| {
if (headerTableSize.isNumber()) {
const headerTableSizeValue = headerTableSize.toInt32();
if (headerTableSizeValue > MAX_HEADER_TABLE_SIZE or headerTableSizeValue < 0) {
globalObject.throw("Expected headerTableSize to be a number between 0 and 2^32-1", .{});
return false;
return globalObject.throw2("Expected headerTableSize to be a number between 0 and 2^32-1", .{});
}
this.localSettings.headerTableSize = @intCast(headerTableSizeValue);
} else if (!headerTableSize.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected headerTableSize to be a number", .{});
return false;
return globalObject.throw2("Expected headerTableSize to be a number", .{});
}
}
if (options.get(globalObject, "enablePush")) |enablePush| {
if (try options.get(globalObject, "enablePush")) |enablePush| {
if (enablePush.isBoolean()) {
this.localSettings.enablePush = if (enablePush.asBoolean()) 1 else 0;
} else if (!enablePush.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected enablePush to be a boolean", .{});
return false;
return globalObject.throw2("Expected enablePush to be a boolean", .{});
}
}
if (options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (try options.get(globalObject, "initialWindowSize")) |initialWindowSize| {
if (initialWindowSize.isNumber()) {
const initialWindowSizeValue = initialWindowSize.toInt32();
if (initialWindowSizeValue > MAX_HEADER_TABLE_SIZE or initialWindowSizeValue < 0) {
globalObject.throw("Expected initialWindowSize to be a number between 0 and 2^32-1", .{});
return false;
return globalObject.throw2("Expected initialWindowSize to be a number between 0 and 2^32-1", .{});
}
this.localSettings.initialWindowSize = @intCast(initialWindowSizeValue);
} else if (!initialWindowSize.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected initialWindowSize to be a number", .{});
return false;
return globalObject.throw2("Expected initialWindowSize to be a number", .{});
}
}
if (options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (try options.get(globalObject, "maxFrameSize")) |maxFrameSize| {
if (maxFrameSize.isNumber()) {
const maxFrameSizeValue = maxFrameSize.toInt32();
if (maxFrameSizeValue > MAX_FRAME_SIZE or maxFrameSizeValue < 16384) {
globalObject.throw("Expected maxFrameSize to be a number between 16,384 and 2^24-1", .{});
return false;
return globalObject.throw2("Expected maxFrameSize to be a number between 16,384 and 2^24-1", .{});
}
this.localSettings.maxFrameSize = @intCast(maxFrameSizeValue);
} else if (!maxFrameSize.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected maxFrameSize to be a number", .{});
return false;
return globalObject.throw2("Expected maxFrameSize to be a number", .{});
}
}
if (options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (try options.get(globalObject, "maxConcurrentStreams")) |maxConcurrentStreams| {
if (maxConcurrentStreams.isNumber()) {
const maxConcurrentStreamsValue = maxConcurrentStreams.toInt32();
if (maxConcurrentStreamsValue > MAX_HEADER_TABLE_SIZE or maxConcurrentStreamsValue < 0) {
globalObject.throw("Expected maxConcurrentStreams to be a number between 0 and 2^32-1", .{});
return false;
return globalObject.throw2("Expected maxConcurrentStreams to be a number between 0 and 2^32-1", .{});
}
this.localSettings.maxConcurrentStreams = @intCast(maxConcurrentStreamsValue);
} else if (!maxConcurrentStreams.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected maxConcurrentStreams to be a number", .{});
return false;
return globalObject.throw2("Expected maxConcurrentStreams to be a number", .{});
}
}
if (options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (try options.get(globalObject, "maxHeaderListSize")) |maxHeaderListSize| {
if (maxHeaderListSize.isNumber()) {
const maxHeaderListSizeValue = maxHeaderListSize.toInt32();
if (maxHeaderListSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderListSizeValue < 0) {
globalObject.throw("Expected maxHeaderListSize to be a number between 0 and 2^32-1", .{});
return false;
return globalObject.throw2("Expected maxHeaderListSize to be a number between 0 and 2^32-1", .{});
}
this.localSettings.maxHeaderListSize = @intCast(maxHeaderListSizeValue);
} else if (!maxHeaderListSize.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected maxHeaderListSize to be a number", .{});
return false;
return globalObject.throw2("Expected maxHeaderListSize to be a number", .{});
}
}
if (options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (try options.get(globalObject, "maxHeaderSize")) |maxHeaderSize| {
if (maxHeaderSize.isNumber()) {
const maxHeaderSizeValue = maxHeaderSize.toInt32();
if (maxHeaderSizeValue > MAX_HEADER_TABLE_SIZE or maxHeaderSizeValue < 0) {
globalObject.throw("Expected maxHeaderSize to be a number between 0 and 2^32-1", .{});
return false;
return globalObject.throw2("Expected maxHeaderSize to be a number between 0 and 2^32-1", .{});
}
this.localSettings.maxHeaderListSize = @intCast(maxHeaderSizeValue);
} else if (!maxHeaderSize.isEmptyOrUndefinedOrNull()) {
globalObject.throw("Expected maxHeaderSize to be a number", .{});
return false;
return globalObject.throw2("Expected maxHeaderSize to be a number", .{});
}
}
return true;
return;
}
pub fn updateSettings(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn updateSettings(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected settings argument", .{});
return .zero;
@@ -2465,15 +2445,12 @@ pub const H2FrameParser = struct {
const options = args_list.ptr[0];
if (this.loadSettingsFromJSValue(globalObject, options)) {
this.setSettings(this.localSettings);
return .undefined;
}
return .zero;
try this.loadSettingsFromJSValue(globalObject, options);
this.setSettings(this.localSettings);
return .undefined;
}
pub fn getCurrentState(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn getCurrentState(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
var result = JSValue.createEmptyObject(globalObject, 9);
result.put(globalObject, JSC.ZigString.static("effectiveLocalWindowSize"), JSC.JSValue.jsNumber(this.windowSize));
@@ -2489,9 +2466,9 @@ pub const H2FrameParser = struct {
result.put(globalObject, JSC.ZigString.static("outboundQueueSize"), JSC.JSValue.jsNumber(this.outboundQueueSize));
return result;
}
pub fn goaway(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn goaway(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(3);
const args_list = callframe.arguments_old(3);
if (args_list.len < 1) {
globalObject.throw("Expected errorCode argument", .{});
return .zero;
@@ -2540,9 +2517,9 @@ pub const H2FrameParser = struct {
return .undefined;
}
pub fn ping(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn ping(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected payload argument", .{});
return .zero;
@@ -2564,9 +2541,9 @@ pub const H2FrameParser = struct {
return .zero;
}
pub fn getEndAfterHeaders(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn getEndAfterHeaders(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected stream argument", .{});
return .zero;
@@ -2592,9 +2569,9 @@ pub const H2FrameParser = struct {
return JSC.JSValue.jsBoolean(stream.endAfterHeaders);
}
pub fn isStreamAborted(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn isStreamAborted(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected stream argument", .{});
return .zero;
@@ -2623,9 +2600,9 @@ pub const H2FrameParser = struct {
// closed with cancel = aborted
return JSC.JSValue.jsBoolean(stream.state == .CLOSED and stream.rstCode == @intFromEnum(ErrorCode.CANCEL));
}
pub fn getStreamState(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn getStreamState(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected stream argument", .{});
return .zero;
@@ -2660,9 +2637,9 @@ pub const H2FrameParser = struct {
return state;
}
pub fn setStreamPriority(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn setStreamPriority(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(2);
const args_list = callframe.arguments_old(2);
if (args_list.len < 2) {
globalObject.throw("Expected stream and options arguments", .{});
return .zero;
@@ -2699,7 +2676,7 @@ pub const H2FrameParser = struct {
var exclusive = stream.exclusive;
var parent_id = stream.streamDependency;
var silent = false;
if (options.get(globalObject, "weight")) |js_weight| {
if (try options.get(globalObject, "weight")) |js_weight| {
if (js_weight.isNumber()) {
const weight_u32 = js_weight.toU32();
if (weight_u32 > 255) {
@@ -2710,7 +2687,7 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "parent")) |js_parent| {
if (try options.get(globalObject, "parent")) |js_parent| {
if (js_parent.isNumber()) {
parent_id = js_parent.toU32();
if (parent_id == 0 or parent_id > MAX_STREAM_ID) {
@@ -2720,11 +2697,11 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "exclusive")) |js_exclusive| {
if (try options.get(globalObject, "exclusive")) |js_exclusive| {
exclusive = js_exclusive.toBoolean();
}
if (options.get(globalObject, "silent")) |js_silent| {
if (try options.get(globalObject, "silent")) |js_silent| {
silent = js_silent.toBoolean();
}
if (parent_id == stream.id) {
@@ -2759,9 +2736,9 @@ pub const H2FrameParser = struct {
}
return JSC.JSValue.jsBoolean(true);
}
pub fn rstStream(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn rstStream(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(2);
const args_list = callframe.arguments_old(2);
if (args_list.len < 2) {
globalObject.throw("Expected stream and code arguments", .{});
return .zero;
@@ -2824,7 +2801,7 @@ pub const H2FrameParser = struct {
return (this.writeBuffer.len + this.queuedDataSize) / 1024 / 1024;
}
// get memory in bytes
pub fn getBufferSize(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn getBufferSize(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
return JSC.JSValue.jsNumber(this.writeBuffer.len + this.queuedDataSize);
}
@@ -2916,9 +2893,9 @@ pub const H2FrameParser = struct {
}
}
}
pub fn noTrailers(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn noTrailers(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected stream, headers and sensitiveHeaders arguments", .{});
return .zero;
@@ -2957,12 +2934,11 @@ pub const H2FrameParser = struct {
return .undefined;
}
pub fn sendTrailers(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn sendTrailers(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(3);
const args_list = callframe.arguments_old(3);
if (args_list.len < 3) {
globalObject.throw("Expected stream, headers and sensitiveHeaders arguments", .{});
return .zero;
return globalObject.throw2("Expected stream, headers and sensitiveHeaders arguments", .{});
}
const stream_arg = args_list.ptr[0];
@@ -2970,29 +2946,24 @@ pub const H2FrameParser = struct {
const sensitive_arg = args_list.ptr[2];
if (!stream_arg.isNumber()) {
globalObject.throw("Expected stream to be a number", .{});
return .zero;
return globalObject.throw2("Expected stream to be a number", .{});
}
const stream_id = stream_arg.toU32();
if (stream_id == 0 or stream_id > MAX_STREAM_ID) {
globalObject.throw("Invalid stream id", .{});
return .zero;
return globalObject.throw2("Invalid stream id", .{});
}
var stream = this.streams.getPtr(@intCast(stream_id)) orelse {
globalObject.throw("Invalid stream id", .{});
return .zero;
return globalObject.throw2("Invalid stream id", .{});
};
if (!headers_arg.isObject()) {
globalObject.throw("Expected headers to be an object", .{});
return .zero;
return globalObject.throw2("Expected headers to be an object", .{});
}
if (!sensitive_arg.isObject()) {
globalObject.throw("Expected sensitiveHeaders to be an object", .{});
return .zero;
return globalObject.throw2("Expected sensitiveHeaders to be an object", .{});
}
// max frame size will be always at least 16384
@@ -3015,14 +2986,12 @@ pub const H2FrameParser = struct {
if (header_name.charAt(0) == ':') {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_PSEUDOHEADER, "\"{s}\" is an invalid pseudoheader or is used incorrectly", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
}
var js_value = headers_arg.getTruthy(globalObject, name) orelse {
var js_value = try headers_arg.getTruthy(globalObject, name) orelse {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_HEADER_VALUE, "Invalid value for header \"{s}\"", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
};
if (js_value.jsType().isArray()) {
@@ -3031,24 +3000,21 @@ pub const H2FrameParser = struct {
if (SingleValueHeaders.has(name) and value_iter.len > 1) {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_SINGLE_VALUE_HEADER, "Header field \"{s}\" must only have a single value", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
}
while (value_iter.next()) |item| {
if (item.isEmptyOrUndefinedOrNull()) {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_HEADER_VALUE, "Invalid value for header \"{s}\"", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
}
const value_str = item.toStringOrNull(globalObject) orelse {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_HEADER_VALUE, "Invalid value for header \"{s}\"", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
};
const never_index = sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const never_index = try sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const value_slice = value_str.toSlice(globalObject, bun.default_allocator);
defer value_slice.deinit();
@@ -3067,11 +3033,10 @@ pub const H2FrameParser = struct {
} else {
const value_str = js_value.toStringOrNull(globalObject) orelse {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_HEADER_VALUE, "Invalid value for header \"{s}\"", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
return globalObject.throwValue2(exception);
};
const never_index = sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const never_index = try sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const value_slice = value_str.toSlice(globalObject, bun.default_allocator);
defer value_slice.deinit();
@@ -3111,26 +3076,23 @@ pub const H2FrameParser = struct {
this.dispatchWithExtra(.onStreamEnd, identifier, JSC.JSValue.jsNumber(@intFromEnum(stream.state)));
return .undefined;
}
pub fn writeStream(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn writeStream(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args = callframe.argumentsUndef(5);
const stream_arg, const data_arg, const encoding_arg, const close_arg, const callback_arg = args.ptr;
if (!stream_arg.isNumber()) {
globalObject.throw("Expected stream to be a number", .{});
return .zero;
return globalObject.throw2("Expected stream to be a number", .{});
}
const stream_id = stream_arg.toU32();
if (stream_id == 0 or stream_id > MAX_STREAM_ID) {
globalObject.throw("Invalid stream id", .{});
return .zero;
return globalObject.throw2("Invalid stream id", .{});
}
const close = close_arg.toBoolean();
var stream = this.streams.getPtr(@intCast(stream_id)) orelse {
globalObject.throw("Invalid stream id", .{});
return .zero;
return globalObject.throw2("Invalid stream id", .{});
};
if (!stream.canSendData()) {
this.dispatchWriteCallback(callback_arg);
@@ -3143,23 +3105,24 @@ pub const H2FrameParser = struct {
}
if (!encoding_arg.isString()) {
return globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg);
_ = globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg);
return error.JSError;
}
break :brk JSC.Node.Encoding.fromJS(encoding_arg, globalObject) orelse {
if (!globalObject.hasException()) return globalObject.throwInvalidArgumentTypeValue("write", "encoding", encoding_arg);
return .zero;
return error.JSError;
};
};
var buffer: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.fromJSWithEncoding(
var buffer: JSC.Node.StringOrBuffer = try JSC.Node.StringOrBuffer.fromJSWithEncoding(
globalObject,
bun.default_allocator,
data_arg,
encoding,
) orelse {
if (!globalObject.hasException()) return globalObject.throwInvalidArgumentTypeValue("write", "Buffer or String", data_arg);
return .zero;
return error.JSError;
};
defer buffer.deinit();
@@ -3188,11 +3151,11 @@ pub const H2FrameParser = struct {
return stream_id;
}
pub fn hasNativeRead(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn hasNativeRead(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
return JSC.JSValue.jsBoolean(this.native_socket == .tcp or this.native_socket == .tls);
}
pub fn getNextStream(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn getNextStream(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const id = this.getNextStreamID();
@@ -3203,9 +3166,9 @@ pub const H2FrameParser = struct {
return JSC.JSValue.jsNumber(id);
}
pub fn getStreamContext(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn getStreamContext(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected stream_id argument", .{});
return .zero;
@@ -3225,9 +3188,9 @@ pub const H2FrameParser = struct {
return stream.jsContext.get() orelse .undefined;
}
pub fn setStreamContext(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn setStreamContext(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(2);
const args_list = callframe.arguments_old(2);
if (args_list.len < 2) {
globalObject.throw("Expected stream_id and context arguments", .{});
return .zero;
@@ -3252,7 +3215,7 @@ pub const H2FrameParser = struct {
return .undefined;
}
pub fn getAllStreams(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSC.JSValue {
pub fn getAllStreams(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
const array = JSC.JSValue.createEmptyArray(globalObject, this.streams.count());
@@ -3265,7 +3228,7 @@ pub const H2FrameParser = struct {
}
return array;
}
pub fn emitAbortToAllStreams(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSC.JSValue {
pub fn emitAbortToAllStreams(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
var it = StreamResumableIterator.init(this);
while (it.next()) |stream| {
@@ -3285,10 +3248,10 @@ pub const H2FrameParser = struct {
}
return .undefined;
}
pub fn emitErrorToAllStreams(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn emitErrorToAllStreams(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected error argument", .{});
return .undefined;
@@ -3311,16 +3274,16 @@ pub const H2FrameParser = struct {
return .undefined;
}
pub fn flushFromJS(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn flushFromJS(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
return JSC.JSValue.jsNumber(this.flush());
}
pub fn request(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn request(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(5);
const args_list = callframe.arguments_old(5);
if (args_list.len < 4) {
globalObject.throw("Expected stream_id, stream_ctx, headers and sensitiveHeaders arguments", .{});
return .zero;
@@ -3405,7 +3368,7 @@ pub const H2FrameParser = struct {
continue;
}
var js_value = headers_arg.getTruthy(globalObject, name) orelse {
var js_value = try headers_arg.getTruthy(globalObject, name) orelse {
const exception = JSC.toTypeError(.ERR_HTTP2_INVALID_HEADER_VALUE, "Invalid value for header \"{s}\"", .{name}, globalObject);
globalObject.throwValue(exception);
return .zero;
@@ -3435,7 +3398,7 @@ pub const H2FrameParser = struct {
return .zero;
};
const never_index = sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const never_index = try sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const value_slice = value_str.toSlice(globalObject, bun.default_allocator);
defer value_slice.deinit();
@@ -3462,7 +3425,7 @@ pub const H2FrameParser = struct {
return .zero;
};
const never_index = sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const never_index = try sensitive_arg.getTruthy(globalObject, "neverIndex") != null;
const value_slice = value_str.toSlice(globalObject, bun.default_allocator);
defer value_slice.deinit();
@@ -3505,7 +3468,7 @@ pub const H2FrameParser = struct {
return JSC.JSValue.jsNumber(stream_id);
}
if (options.get(globalObject, "paddingStrategy")) |padding_js| {
if (try options.get(globalObject, "paddingStrategy")) |padding_js| {
if (padding_js.isNumber()) {
stream.paddingStrategy = switch (padding_js.to(u32)) {
1 => .aligned,
@@ -3515,14 +3478,14 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "waitForTrailers")) |trailes_js| {
if (try options.get(globalObject, "waitForTrailers")) |trailes_js| {
if (trailes_js.isBoolean()) {
waitForTrailers = trailes_js.asBoolean();
stream.waitForTrailers = waitForTrailers;
}
}
if (options.get(globalObject, "endStream")) |end_stream_js| {
if (try options.get(globalObject, "endStream")) |end_stream_js| {
if (end_stream_js.isBoolean()) {
if (end_stream_js.asBoolean()) {
end_stream = true;
@@ -3534,7 +3497,7 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "exclusive")) |exclusive_js| {
if (try options.get(globalObject, "exclusive")) |exclusive_js| {
if (exclusive_js.isBoolean()) {
if (exclusive_js.asBoolean()) {
exclusive = true;
@@ -3544,7 +3507,7 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "parent")) |parent_js| {
if (try options.get(globalObject, "parent")) |parent_js| {
if (parent_js.isNumber() or parent_js.isInt32()) {
has_priority = true;
parent = parent_js.toInt32();
@@ -3558,7 +3521,7 @@ pub const H2FrameParser = struct {
}
}
if (options.get(globalObject, "weight")) |weight_js| {
if (try options.get(globalObject, "weight")) |weight_js| {
if (weight_js.isNumber() or weight_js.isInt32()) {
has_priority = true;
weight = weight_js.toInt32();
@@ -3580,7 +3543,7 @@ pub const H2FrameParser = struct {
stream.weight = @intCast(weight);
}
if (options.get(globalObject, "signal")) |signal_arg| {
if (try options.get(globalObject, "signal")) |signal_arg| {
if (signal_arg.as(JSC.WebCore.AbortSignal)) |signal_| {
if (signal_.aborted()) {
stream.state = .IDLE;
@@ -3658,9 +3621,9 @@ pub const H2FrameParser = struct {
return JSC.JSValue.jsNumber(stream_id);
}
pub fn read(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn read(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected 1 argument", .{});
return .zero;
@@ -3700,9 +3663,9 @@ pub const H2FrameParser = struct {
this.detachNativeSocket();
}
pub fn setNativeSocketFromJS(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn setNativeSocketFromJS(this: *H2FrameParser, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
JSC.markBinding(@src());
const args_list = callframe.arguments(1);
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected socket argument", .{});
return .zero;
@@ -3752,32 +3715,26 @@ pub const H2FrameParser = struct {
}
}
pub fn constructor(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*H2FrameParser {
const args_list = callframe.arguments(1);
pub fn constructor(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*H2FrameParser {
const args_list = callframe.arguments_old(1);
if (args_list.len < 1) {
globalObject.throw("Expected 1 argument", .{});
return null;
return globalObject.throw2("Expected 1 argument", .{});
}
const options = args_list.ptr[0];
if (options.isEmptyOrUndefinedOrNull() or options.isBoolean() or !options.isObject()) {
globalObject.throwInvalidArguments("expected options as argument", .{});
return null;
return globalObject.throwInvalidArguments2("expected options as argument", .{});
}
var exception: JSC.C.JSValueRef = null;
const context_obj = options.get(globalObject, "context") orelse {
globalObject.throw("Expected \"context\" option", .{});
return null;
const context_obj = try options.get(globalObject, "context") orelse {
return globalObject.throw2("Expected \"context\" option", .{});
};
var handler_js = JSC.JSValue.zero;
if (options.get(globalObject, "handlers")) |handlers_| {
if (try options.get(globalObject, "handlers")) |handlers_| {
handler_js = handlers_;
}
var handlers = Handlers.fromJS(globalObject, handler_js, &exception) orelse {
globalObject.throwValue(exception.?.value());
return null;
};
var handlers = try Handlers.fromJS(globalObject, handler_js);
errdefer handlers.deinit();
var this = brk: {
if (ENABLE_ALLOCATOR_POOL) {
@@ -3817,8 +3774,10 @@ pub const H2FrameParser = struct {
});
}
};
errdefer this.deinit();
// check if socket is provided, and if it is a valid native socket
if (options.get(globalObject, "native")) |socket_js| {
if (try options.get(globalObject, "native")) |socket_js| {
if (JSTLSSocket.fromJS(socket_js)) |socket| {
log("TLSSocket attached", .{});
if (socket.attachNativeCallback(.{ .h2 = this })) {
@@ -3839,20 +3798,16 @@ pub const H2FrameParser = struct {
}
}
}
if (options.get(globalObject, "settings")) |settings_js| {
if (try options.get(globalObject, "settings")) |settings_js| {
if (!settings_js.isEmptyOrUndefinedOrNull()) {
if (!this.loadSettingsFromJSValue(globalObject, settings_js)) {
this.deinit();
handlers.deinit();
return null;
}
try this.loadSettingsFromJSValue(globalObject, settings_js);
if (settings_js.get(globalObject, "maxOutstandingPings")) |max_pings| {
if (try settings_js.get(globalObject, "maxOutstandingPings")) |max_pings| {
if (max_pings.isNumber()) {
this.maxOutstandingPings = max_pings.to(u64);
}
}
if (settings_js.get(globalObject, "maxSessionMemory")) |max_memory| {
if (try settings_js.get(globalObject, "maxSessionMemory")) |max_memory| {
if (max_memory.isNumber()) {
this.maxSessionMemory = @truncate(max_memory.to(u64));
if (this.maxSessionMemory < 1) {
@@ -3860,7 +3815,7 @@ pub const H2FrameParser = struct {
}
}
}
if (settings_js.get(globalObject, "maxHeaderListPairs")) |max_header_list_pairs| {
if (try settings_js.get(globalObject, "maxHeaderListPairs")) |max_header_list_pairs| {
if (max_header_list_pairs.isNumber()) {
this.maxHeaderListPairs = @truncate(max_header_list_pairs.to(u64));
if (this.maxHeaderListPairs < 4) {
@@ -3868,7 +3823,7 @@ pub const H2FrameParser = struct {
}
}
}
if (settings_js.get(globalObject, "maxSessionRejectedStreams")) |max_rejected_streams| {
if (try settings_js.get(globalObject, "maxSessionRejectedStreams")) |max_rejected_streams| {
if (max_rejected_streams.isNumber()) {
this.maxRejectedStreams = @truncate(max_rejected_streams.to(u64));
}
@@ -3876,7 +3831,7 @@ pub const H2FrameParser = struct {
}
}
var is_server = false;
if (options.get(globalObject, "type")) |type_js| {
if (try options.get(globalObject, "type")) |type_js| {
is_server = type_js.isNumber() and type_js.to(u32) == 0;
}
@@ -3894,7 +3849,7 @@ pub const H2FrameParser = struct {
}
return this;
}
pub fn detachFromJS(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn detachFromJS(this: *H2FrameParser, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
JSC.markBinding(@src());
this.detach(false);
return .undefined;

File diff suppressed because it is too large Load Diff

View File

@@ -387,12 +387,12 @@ pub const Stdio = union(enum) {
switch (req.ptr) {
.File, .Blob => {
globalThis.throwTODO("Support fd/blob backed ReadableStream in spawn stdin. See https://github.com/oven-sh/bun/issues/8049");
globalThis.throwTODO("Support fd/blob backed ReadableStream in spawn stdin. See https://github.com/oven-sh/bun/issues/8049") catch {};
return false;
},
.Direct, .JavaScript, .Bytes => {
// out_stdio.* = .{ .connect = req };
globalThis.throwTODO("Re-enable ReadableStream support in spawn stdin. ");
globalThis.throwTODO("Re-enable ReadableStream support in spawn stdin. ") catch {};
return false;
},
.Invalid => {

View File

@@ -41,13 +41,6 @@ pub const ResourceUsage = struct {
pub usingnamespace JSC.Codegen.JSResourceUsage;
rusage: Rusage,
pub fn constructor(
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) ?*Subprocess {
return null;
}
pub fn getCPUTime(
this: *ResourceUsage,
globalObject: *JSGlobalObject,
@@ -237,7 +230,7 @@ pub const Subprocess = struct {
this: *Subprocess,
globalObject: *JSGlobalObject,
_: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
return this.createResourceUsageObject(globalObject);
}
@@ -393,11 +386,8 @@ pub const Subprocess = struct {
this.updateHasPendingActivity();
}
pub fn constructor(
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) ?*Subprocess {
return null;
pub fn constructor(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*Subprocess {
return globalObject.throw2("Cannot construct Subprocess", .{});
}
const Readable = union(enum) {
@@ -592,7 +582,7 @@ pub const Subprocess = struct {
this: *Subprocess,
global: *JSGlobalObject,
_: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.process.hasExited()) {
// rely on GC to clean everything up in this case
return .undefined;
@@ -620,10 +610,10 @@ pub const Subprocess = struct {
this: *Subprocess,
globalThis: *JSGlobalObject,
callframe: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
this.this_jsvalue = callframe.this();
var arguments = callframe.arguments(1);
var arguments = callframe.arguments_old(1);
// If signal is 0, then no actual signal is sent, but error checking
// is still performed.
const sig: i32 = brk: {
@@ -654,7 +644,7 @@ pub const Subprocess = struct {
if (arguments.ptr[0].asString().length() == 0) {
break :brk SignalCode.default;
}
const signal_code = arguments.ptr[0].toEnum(globalThis, "signal", SignalCode) catch return .zero;
const signal_code = try arguments.ptr[0].toEnum(globalThis, "signal", SignalCode);
break :brk @intFromEnum(signal_code);
} else if (!arguments.ptr[0].isEmptyOrUndefinedOrNull()) {
globalThis.throwInvalidArguments("Invalid signal: must be a string or an integer", .{});
@@ -700,12 +690,12 @@ pub const Subprocess = struct {
this.process.close();
}
pub fn doRef(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn doRef(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
this.jsRef();
return .undefined;
}
pub fn doUnref(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn doUnref(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
this.jsUnref();
return .undefined;
}
@@ -724,7 +714,7 @@ pub const Subprocess = struct {
}
}
pub fn doSend(this: *Subprocess, global: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) JSValue {
pub fn doSend(this: *Subprocess, global: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSValue {
IPClog("Subprocess#doSend", .{});
const ipc_data = &(this.ipc_data orelse {
if (this.hasExited()) {
@@ -751,7 +741,7 @@ pub const Subprocess = struct {
const ipc_data = this.ipc() orelse return;
ipc_data.close(nextTick);
}
pub fn disconnect(this: *Subprocess, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn disconnect(this: *Subprocess, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
_ = globalThis;
_ = callframe;
this.disconnectIPC(true);
@@ -1674,11 +1664,11 @@ pub const Subprocess = struct {
return JSC.JSValue.jsNull();
}
pub fn spawn(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) JSValue {
pub fn spawn(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) bun.JSError!JSValue {
return spawnMaybeSync(globalThis, args, secondaryArgsValue, false);
}
pub fn spawnSync(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) JSValue {
pub fn spawnSync(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) bun.JSError!JSValue {
return spawnMaybeSync(globalThis, args, secondaryArgsValue, true);
}
@@ -1687,7 +1677,7 @@ pub const Subprocess = struct {
args_: JSValue,
secondaryArgsValue: ?JSValue,
comptime is_sync: bool,
) JSValue {
) bun.JSError!JSValue {
var arena = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();
var allocator = arena.allocator();
@@ -1745,7 +1735,7 @@ pub const Subprocess = struct {
} else if (!args.isObject()) {
globalThis.throwInvalidArguments("cmd must be an array", .{});
return .zero;
} else if (args.getTruthy(globalThis, "cmd")) |cmd_value_| {
} else if (try args.getTruthy(globalThis, "cmd")) |cmd_value_| {
cmd_value = cmd_value_;
} else {
globalThis.throwInvalidArguments("cmd must be an array", .{});
@@ -1753,7 +1743,7 @@ pub const Subprocess = struct {
}
if (args.isObject()) {
if (args.getTruthy(globalThis, "argv0")) |argv0_| {
if (try args.getTruthy(globalThis, "argv0")) |argv0_| {
const argv0_str = argv0_.getZigString(globalThis);
if (argv0_str.len > 0) {
argv0 = argv0_str.toOwnedSliceZ(allocator) catch {
@@ -1764,7 +1754,7 @@ pub const Subprocess = struct {
}
// need to update `cwd` before searching for executable with `Which.which`
if (args.getTruthy(globalThis, "cwd")) |cwd_| {
if (try args.getTruthy(globalThis, "cwd")) |cwd_| {
const cwd_str = cwd_.getZigString(globalThis);
if (cwd_str.len > 0) {
cwd = cwd_str.toOwnedSliceZ(allocator) catch {
@@ -1796,7 +1786,7 @@ pub const Subprocess = struct {
{
var first_cmd = cmds_array.next().?;
var arg0 = first_cmd.toSliceOrNullWithAllocator(globalThis, allocator) orelse return .zero;
var arg0 = try first_cmd.toSliceOrNullWithAllocator(globalThis, allocator);
defer arg0.deinit();
if (argv0 == null) {
@@ -1849,10 +1839,10 @@ pub const Subprocess = struct {
if (args != .zero and args.isObject()) {
// This must run before the stdio parsing happens
if (!is_sync) {
if (args.getTruthy(globalThis, "ipc")) |val| {
if (try args.getTruthy(globalThis, "ipc")) |val| {
if (val.isCell() and val.isCallable(globalThis.vm())) {
maybe_ipc_mode = ipc_mode: {
if (args.getTruthy(globalThis, "serialization")) |mode_val| {
if (try args.getTruthy(globalThis, "serialization")) |mode_val| {
if (mode_val.isString()) {
break :ipc_mode IPC.Mode.fromJS(globalThis, mode_val) orelse {
if (!globalThis.hasException()) {
@@ -1875,7 +1865,7 @@ pub const Subprocess = struct {
}
}
if (args.getTruthy(globalThis, "signal")) |signal_val| {
if (try args.getTruthy(globalThis, "signal")) |signal_val| {
if (signal_val.as(JSC.WebCore.AbortSignal)) |signal| {
abort_signal = signal.ref();
} else {
@@ -1883,7 +1873,7 @@ pub const Subprocess = struct {
}
}
if (args.getTruthy(globalThis, "onDisconnect")) |onDisconnect_| {
if (try args.getTruthy(globalThis, "onDisconnect")) |onDisconnect_| {
if (!onDisconnect_.isCell() or !onDisconnect_.isCallable(globalThis.vm())) {
globalThis.throwInvalidArguments("onDisconnect must be a function or undefined", .{});
return .zero;
@@ -1895,7 +1885,7 @@ pub const Subprocess = struct {
onDisconnect_.withAsyncContextIfNeeded(globalThis);
}
if (args.getTruthy(globalThis, "onExit")) |onExit_| {
if (try args.getTruthy(globalThis, "onExit")) |onExit_| {
if (!onExit_.isCell() or !onExit_.isCallable(globalThis.vm())) {
globalThis.throwInvalidArguments("onExit must be a function or undefined", .{});
return .zero;
@@ -1907,7 +1897,7 @@ pub const Subprocess = struct {
onExit_.withAsyncContextIfNeeded(globalThis);
}
if (args.getTruthy(globalThis, "env")) |object| {
if (try args.getTruthy(globalThis, "env")) |object| {
if (!object.isObject()) {
globalThis.throwInvalidArguments("env must be an object", .{});
return .zero;
@@ -1923,7 +1913,7 @@ pub const Subprocess = struct {
};
env_array = envp_managed.moveToUnmanaged();
}
if (args.get(globalThis, "stdio")) |stdio_val| {
if (try args.get(globalThis, "stdio")) |stdio_val| {
if (!stdio_val.isEmptyOrUndefinedOrNull()) {
if (stdio_val.jsType().isArray()) {
var stdio_iter = stdio_val.arrayIterator(globalThis);
@@ -1962,44 +1952,44 @@ pub const Subprocess = struct {
}
}
} else {
if (args.get(globalThis, "stdin")) |value| {
if (try args.get(globalThis, "stdin")) |value| {
if (!stdio[0].extract(globalThis, 0, value))
return .zero;
}
if (args.get(globalThis, "stderr")) |value| {
if (try args.get(globalThis, "stderr")) |value| {
if (!stdio[2].extract(globalThis, 2, value))
return .zero;
}
if (args.get(globalThis, "stdout")) |value| {
if (try args.get(globalThis, "stdout")) |value| {
if (!stdio[1].extract(globalThis, 1, value))
return .zero;
}
}
if (comptime !is_sync) {
if (args.get(globalThis, "lazy")) |lazy_val| {
if (try args.get(globalThis, "lazy")) |lazy_val| {
if (lazy_val.isBoolean()) {
lazy = lazy_val.toBoolean();
}
}
}
if (args.get(globalThis, "detached")) |detached_val| {
if (try args.get(globalThis, "detached")) |detached_val| {
if (detached_val.isBoolean()) {
detached = detached_val.toBoolean();
}
}
if (Environment.isWindows) {
if (args.get(globalThis, "windowsHide")) |val| {
if (try args.get(globalThis, "windowsHide")) |val| {
if (val.isBoolean()) {
windows_hide = val.asBoolean();
}
}
if (args.get(globalThis, "windowsVerbatimArguments")) |val| {
if (try args.get(globalThis, "windowsVerbatimArguments")) |val| {
if (val.isBoolean()) {
windows_verbatim_arguments = val.asBoolean();
}
@@ -2009,8 +1999,7 @@ pub const Subprocess = struct {
}
if (!override_env and env_array.items.len == 0) {
env_array.items = jsc_vm.bundler.env.map.createNullDelimitedEnvMap(allocator) catch |err|
return globalThis.handleError(err, "in Bun.spawn");
env_array.items = jsc_vm.bundler.env.map.createNullDelimitedEnvMap(allocator) catch |err| return globalThis.throwError(err, "in Bun.spawn") catch return .zero;
env_array.capacity = env_array.items.len;
}
@@ -2044,7 +2033,7 @@ pub const Subprocess = struct {
// And then one fd is assigned specifically and only for IPC. If the user dont specify it, we add one (default: 3).
//
// When Bun.spawn() is given an `.ipc` callback, it enables IPC as follows:
env_array.ensureUnusedCapacity(allocator, 3) catch |err| return globalThis.handleError(err, "in Bun.spawn");
env_array.ensureUnusedCapacity(allocator, 3) catch |err| return globalThis.throwError(err, "in Bun.spawn") catch return .zero;
const ipc_fd: u32 = brk: {
if (ipc_channel == -1) {
// If the user didn't specify an IPC channel, we need to add one
@@ -2129,9 +2118,7 @@ pub const Subprocess = struct {
@ptrCast(env_array.items.ptr),
) catch |err| {
spawn_options.deinit();
globalThis.throwError(err, ": failed to spawn process");
return .zero;
return globalThis.throwError(err, ": failed to spawn process") catch return .zero;
}) {
.err => |err| {
spawn_options.deinit();
@@ -2423,7 +2410,7 @@ pub const Subprocess = struct {
},
.internal => |data| {
IPC.log("Received IPC internal message from child", .{});
node_cluster_binding.handleInternalMessagePrimary(this.globalThis, this, data);
node_cluster_binding.handleInternalMessagePrimary(this.globalThis, this, data) catch {};
},
}
}

View File

@@ -122,17 +122,15 @@ pub const UDPSocketConfig = struct {
on_drain: JSValue = .zero,
on_error: JSValue = .zero,
pub fn fromJS(globalThis: *JSGlobalObject, options: JSValue) ?This {
pub fn fromJS(globalThis: *JSGlobalObject, options: JSValue) bun.JSError!This {
if (options.isEmptyOrUndefinedOrNull() or !options.isObject()) {
globalThis.throwInvalidArguments("Expected an object", .{});
return null;
return globalThis.throwInvalidArguments2("Expected an object", .{});
}
const hostname = brk: {
if (options.getTruthy(globalThis, "hostname")) |value| {
if (try options.getTruthy(globalThis, "hostname")) |value| {
if (!value.isString()) {
globalThis.throwInvalidArguments("Expected \"hostname\" to be a string", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"hostname\" to be a string", .{});
}
const str = value.toBunString(globalThis);
defer str.deref();
@@ -144,11 +142,10 @@ pub const UDPSocketConfig = struct {
defer if (globalThis.hasException()) default_allocator.free(hostname);
const port: u16 = brk: {
if (options.getTruthy(globalThis, "port")) |value| {
if (try options.getTruthy(globalThis, "port")) |value| {
const number = value.coerceToInt32(globalThis);
if (number < 0 or number > 0xffff) {
globalThis.throwInvalidArguments("Expected \"port\" to be an integer between 0 and 65535", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"port\" to be an integer between 0 and 65535", .{});
}
break :brk @intCast(number);
} else {
@@ -161,29 +158,25 @@ pub const UDPSocketConfig = struct {
.port = port,
};
if (options.getTruthy(globalThis, "socket")) |socket| {
if (try options.getTruthy(globalThis, "socket")) |socket| {
if (!socket.isObject()) {
globalThis.throwInvalidArguments("Expected \"socket\" to be an object", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"socket\" to be an object", .{});
}
if (options.getTruthy(globalThis, "binaryType")) |value| {
if (try options.getTruthy(globalThis, "binaryType")) |value| {
if (!value.isString()) {
globalThis.throwInvalidArguments("Expected \"socket.binaryType\" to be a string", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"socket.binaryType\" to be a string", .{});
}
config.binary_type = JSC.BinaryType.fromJSValue(globalThis, value) orelse {
globalThis.throwInvalidArguments("Expected \"socket.binaryType\" to be 'arraybuffer', 'uint8array', or 'buffer'", .{});
return null;
config.binary_type = try JSC.BinaryType.fromJSValue(globalThis, value) orelse {
return globalThis.throwInvalidArguments2("Expected \"socket.binaryType\" to be 'arraybuffer', 'uint8array', or 'buffer'", .{});
};
}
inline for (handlers) |handler| {
if (socket.getTruthyComptime(globalThis, handler.@"0")) |value| {
if (try socket.getTruthyComptime(globalThis, handler.@"0")) |value| {
if (!value.isCell() or !value.isCallable(globalThis.vm())) {
globalThis.throwInvalidArguments("Expected \"socket.{s}\" to be a function", .{handler.@"0"});
return null;
return globalThis.throwInvalidArguments2("Expected \"socket.{s}\" to be a function", .{handler.@"0"});
}
@field(config, handler.@"1") = value;
}
@@ -198,25 +191,21 @@ pub const UDPSocketConfig = struct {
}
}
if (options.getTruthy(globalThis, "connect")) |connect| {
if (try options.getTruthy(globalThis, "connect")) |connect| {
if (!connect.isObject()) {
globalThis.throwInvalidArguments("Expected \"connect\" to be an object", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"connect\" to be an object", .{});
}
const connect_host_js = connect.getTruthy(globalThis, "hostname") orelse {
globalThis.throwInvalidArguments("Expected \"connect.hostname\" to be a string", .{});
return null;
const connect_host_js = try connect.getTruthy(globalThis, "hostname") orelse {
return globalThis.throwInvalidArguments2("Expected \"connect.hostname\" to be a string", .{});
};
if (!connect_host_js.isString()) {
globalThis.throwInvalidArguments("Expected \"connect.hostname\" to be a string", .{});
return null;
return globalThis.throwInvalidArguments2("Expected \"connect.hostname\" to be a string", .{});
}
const connect_port_js = connect.getTruthy(globalThis, "port") orelse {
globalThis.throwInvalidArguments("Expected \"connect.port\" to be an integer", .{});
return null;
const connect_port_js = try connect.getTruthy(globalThis, "port") orelse {
return globalThis.throwInvalidArguments2("Expected \"connect.port\" to be an integer", .{});
};
const connect_port = connect_port_js.coerceToInt32(globalThis);
@@ -292,12 +281,10 @@ pub const UDPSocket = struct {
pub usingnamespace bun.New(@This());
pub fn udpSocket(globalThis: *JSGlobalObject, options: JSValue) JSValue {
pub fn udpSocket(globalThis: *JSGlobalObject, options: JSValue) bun.JSError!JSValue {
log("udpSocket", .{});
const config = UDPSocketConfig.fromJS(globalThis, options) orelse {
return .zero;
};
const config = try UDPSocketConfig.fromJS(globalThis, options);
const vm = globalThis.bunVM();
var this = This.new(.{
@@ -327,16 +314,14 @@ pub const UDPSocket = struct {
)) |socket| {
this.socket = socket;
} else {
globalThis.throw("Failed to bind socket", .{});
return .zero;
return globalThis.throw2("Failed to bind socket", .{});
}
if (config.connect) |connect| {
const ret = this.socket.connect(connect.address, connect.port);
if (ret != 0) {
if (JSC.Maybe(void).errnoSys(ret, .connect)) |err| {
globalThis.throwValue(err.toJS(globalThis));
return .zero;
return globalThis.throwValue2(err.toJS(globalThis));
}
}
this.connect_info = .{ .port = connect.port };
@@ -370,12 +355,12 @@ pub const UDPSocket = struct {
return true;
}
pub fn sendMany(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) JSValue {
pub fn sendMany(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
if (this.closed) {
globalThis.throw("Socket is closed", .{});
return .zero;
}
const arguments = callframe.arguments(1);
const arguments = callframe.arguments_old(1);
if (arguments.len != 1) {
globalThis.throwInvalidArguments("Expected 1 argument, got {}", .{arguments.len});
return .zero;
@@ -460,12 +445,12 @@ pub const UDPSocket = struct {
this: *This,
globalThis: *JSGlobalObject,
callframe: *CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.closed) {
globalThis.throw("Socket is closed", .{});
return .zero;
}
const arguments = callframe.arguments(3);
const arguments = callframe.arguments_old(3);
const dst: ?Destination = brk: {
if (this.connect_info != null) {
if (arguments.len == 1) {
@@ -563,7 +548,7 @@ pub const UDPSocket = struct {
address: JSValue,
};
pub fn ref(this: *This, globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn ref(this: *This, globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
if (!this.closed) {
this.poll_ref.ref(globalThis.bunVM());
}
@@ -571,7 +556,7 @@ pub const UDPSocket = struct {
return .undefined;
}
pub fn unref(this: *This, globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn unref(this: *This, globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
this.poll_ref.unref(globalThis.bunVM());
return .undefined;
@@ -581,24 +566,21 @@ pub const UDPSocket = struct {
this: *This,
_: *JSGlobalObject,
_: *CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (!this.closed) this.socket.close();
return .undefined;
}
pub fn reload(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) JSValue {
const args = callframe.arguments(1);
pub fn reload(this: *This, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
const args = callframe.arguments_old(1);
if (args.len < 1) {
globalThis.throwInvalidArguments("Expected 1 argument", .{});
return .zero;
return globalThis.throwInvalidArguments2("Expected 1 argument", .{});
}
const options = args.ptr[0];
const config = UDPSocketConfig.fromJS(globalThis, options) orelse {
return .zero;
};
const config = try UDPSocketConfig.fromJS(globalThis, options);
config.protect();
var previous_config = this.config;
@@ -693,8 +675,8 @@ pub const UDPSocket = struct {
this.destroy();
}
pub fn jsConnect(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) JSC.JSValue {
const args = callFrame.arguments(2);
pub fn jsConnect(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const args = callFrame.arguments_old(2);
const this = callFrame.this().as(UDPSocket) orelse {
globalThis.throwInvalidArguments("Expected UDPSocket as 'this'", .{});
@@ -743,7 +725,7 @@ pub const UDPSocket = struct {
return .undefined;
}
pub fn jsDisconnect(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) JSC.JSValue {
pub fn jsDisconnect(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const this = callFrame.this().as(UDPSocket) orelse {
globalObject.throwInvalidArguments("Expected UDPSocket as 'this'", .{});
return .zero;

View File

@@ -6,7 +6,7 @@ const JSC = bun.JSC;
const JSValue = JSC.JSValue;
const JSGlobalObject = JSC.JSGlobalObject;
fn x509GetNameObject(globalObject: *JSGlobalObject, name: ?*BoringSSL.X509_NAME) JSValue {
fn x509GetNameObject(globalObject: *JSGlobalObject, name: ?*BoringSSL.X509_NAME) bun.JSError!JSValue {
const cnt = BoringSSL.X509_NAME_entry_count(name);
if (cnt <= 0) {
return JSValue.jsUndefined();
@@ -54,7 +54,7 @@ fn x509GetNameObject(globalObject: *JSGlobalObject, name: ?*BoringSSL.X509_NAME)
// change here without breaking things. Note that this creates nested data
// structures, yet still does not allow representing Distinguished Names
// accurately.
if (result.getTruthy(globalObject, name_slice)) |value| {
if (try result.getTruthy(globalObject, name_slice)) |value| {
if (value.jsType().isArray()) {
value.push(globalObject, JSC.ZigString.fromUTF8(value_slice).toJS(globalObject));
} else {
@@ -434,7 +434,7 @@ fn toUpper(slice: []u8) void {
}
}
pub fn toJS(cert: *BoringSSL.X509, globalObject: *JSGlobalObject) JSValue {
pub fn toJS(cert: *BoringSSL.X509, globalObject: *JSGlobalObject) bun.JSError!JSValue {
const bio = BoringSSL.BIO_new(BoringSSL.BIO_s_mem()) orelse {
globalObject.throw("Failed to create BIO", .{});
return .zero;
@@ -444,9 +444,9 @@ pub fn toJS(cert: *BoringSSL.X509, globalObject: *JSGlobalObject) JSValue {
// X509_check_ca() returns a range of values. Only 1 means "is a CA"
const is_ca = BoringSSL.X509_check_ca(cert) == 1;
const subject = BoringSSL.X509_get_subject_name(cert);
result.put(globalObject, ZigString.static("subject"), x509GetNameObject(globalObject, subject));
result.put(globalObject, ZigString.static("subject"), try x509GetNameObject(globalObject, subject));
const issuer = BoringSSL.X509_get_issuer_name(cert);
result.put(globalObject, ZigString.static("issuer"), x509GetNameObject(globalObject, issuer));
result.put(globalObject, ZigString.static("issuer"), try x509GetNameObject(globalObject, issuer));
result.put(globalObject, ZigString.static("subjectaltname"), x509GetSubjectAltNameString(globalObject, bio, cert));
result.put(globalObject, ZigString.static("infoAccess"), x509GetInfoAccessString(globalObject, bio, cert));
result.put(globalObject, ZigString.static("ca"), JSValue.jsBoolean(is_ca));

View File

@@ -288,21 +288,33 @@ pub const FFI = struct {
}
}
} else if (Environment.isLinux) {
// On Debian/Ubuntu, the lib and include paths are suffixed with {arch}-linux-gnu
// e.g. x86_64-linux-gnu or aarch64-linux-gnu
// On Alpine and RHEL-based distros, the paths are not suffixed
if (Environment.isX64) {
if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/include/x86_64-linux-gnu").isTrue()) {
cached_default_system_include_dir = "/usr/include/x86_64-linux-gnu";
} else if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/include").isTrue()) {
cached_default_system_include_dir = "/usr/include";
}
if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/lib/x86_64-linux-gnu").isTrue()) {
cached_default_system_library_dir = "/usr/lib/x86_64-linux-gnu";
} else if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/lib64").isTrue()) {
cached_default_system_library_dir = "/usr/lib64";
}
} else if (Environment.isAarch64) {
if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/include/aarch64-linux-gnu").isTrue()) {
cached_default_system_include_dir = "/usr/include/aarch64-linux-gnu";
} else if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/include").isTrue()) {
cached_default_system_include_dir = "/usr/include";
}
if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/lib/aarch64-linux-gnu").isTrue()) {
cached_default_system_library_dir = "/usr/lib/aarch64-linux-gnu";
} else if (bun.sys.directoryExistsAt(std.fs.cwd(), "/usr/lib64").isTrue()) {
cached_default_system_library_dir = "/usr/lib64";
}
}
}
@@ -568,7 +580,7 @@ pub const FFI = struct {
bun.default_allocator.free(this.items);
}
pub fn fromJSArray(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) StringArray {
pub fn fromJSArray(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) bun.JSError!StringArray {
var iter = value.arrayIterator(globalThis);
var items = std.ArrayList([:0]const u8).init(bun.default_allocator);
@@ -579,7 +591,7 @@ pub const FFI = struct {
}
items.deinit();
_ = globalThis.throwInvalidArgumentTypeValue(property, "array of strings", val);
return .{};
return error.JSError;
}
const str = val.getZigString(globalThis);
if (str.isEmpty()) continue;
@@ -589,11 +601,11 @@ pub const FFI = struct {
return .{ .items = items.items };
}
pub fn fromJSString(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) StringArray {
pub fn fromJSString(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) bun.JSError!StringArray {
if (value == .undefined) return .{};
if (!value.isString()) {
_ = globalThis.throwInvalidArgumentTypeValue(property, "array of strings", value);
return .{};
return error.JSError;
}
const str = value.getZigString(globalThis);
if (str.isEmpty()) return .{};
@@ -602,7 +614,7 @@ pub const FFI = struct {
return .{ .items = items.items };
}
pub fn fromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) StringArray {
pub fn fromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, comptime property: []const u8) bun.JSError!StringArray {
if (value.isArray()) {
return fromJSArray(globalThis, value, property);
}
@@ -610,10 +622,10 @@ pub const FFI = struct {
}
};
pub fn Bun__FFI__cc(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(1).slice();
pub fn Bun__FFI__cc(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(1).slice();
if (arguments.len == 0 or !arguments[0].isObject()) {
return JSC.toInvalidArguments("Expected object", .{}, globalThis);
return globalThis.throwInvalidArguments2("Expected object", .{});
}
// Step 1. compile the user's code
@@ -630,32 +642,33 @@ pub const FFI = struct {
const symbols_object = object.getOwn(globalThis, "symbols") orelse .undefined;
if (!globalThis.hasException() and (symbols_object == .zero or !symbols_object.isObject())) {
_ = globalThis.throwInvalidArgumentTypeValue("symbols", "object", symbols_object);
return error.JSError;
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
if (generateSymbols(globalThis, &compile_c.symbols.map, symbols_object) catch JSC.JSValue.zero) |val| {
if (try generateSymbols(globalThis, &compile_c.symbols.map, symbols_object)) |val| {
if (val != .zero and !globalThis.hasException())
globalThis.throwValue(val);
return .zero;
return error.JSError;
}
if (compile_c.symbols.map.count() == 0) {
globalThis.throw("Expected at least one exported symbol", .{});
return .zero;
return error.JSError;
}
if (object.getOwn(globalThis, "library")) |library_value| {
compile_c.libraries = StringArray.fromJS(globalThis, library_value, "library");
compile_c.libraries = try StringArray.fromJS(globalThis, library_value, "library");
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
if (object.getTruthy(globalThis, "flags")) |flags_value| {
if (try object.getTruthy(globalThis, "flags")) |flags_value| {
if (flags_value.isArray()) {
var iter = flags_value.arrayIterator(globalThis);
@@ -689,10 +702,10 @@ pub const FFI = struct {
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
if (object.getTruthy(globalThis, "define")) |define_value| {
if (try object.getTruthy(globalThis, "define")) |define_value| {
if (define_value.isObject()) {
const Iter = JSC.JSPropertyIterator(.{ .include_value = true, .skip_empty_name = true });
var iter = Iter.init(globalThis, define_value);
@@ -710,7 +723,7 @@ pub const FFI = struct {
}
if (globalThis.hasException()) {
bun.default_allocator.free(key);
return .zero;
return error.JSError;
}
compile_c.define.append(bun.default_allocator, .{ key, owned_value }) catch bun.outOfMemory();
@@ -719,15 +732,15 @@ pub const FFI = struct {
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
if (object.getTruthy(globalThis, "include")) |include_value| {
compile_c.include_dirs = StringArray.fromJS(globalThis, include_value, "include");
if (try object.getTruthy(globalThis, "include")) |include_value| {
compile_c.include_dirs = try StringArray.fromJS(globalThis, include_value, "include");
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
if (object.getOwn(globalThis, "source")) |source_value| {
@@ -749,7 +762,7 @@ pub const FFI = struct {
}
if (globalThis.hasException()) {
return .zero;
return error.JSError;
}
// Now we compile the code with tinycc.
@@ -766,14 +779,14 @@ pub const FFI = struct {
}
globalThis.throw("{s}", .{combined.items});
return .zero;
return error.JSError;
},
error.JSException => {
return .zero;
return error.JSError;
},
error.OutOfMemory => {
globalThis.throwOutOfMemory();
return .zero;
return error.JSError;
},
}
};
@@ -800,17 +813,17 @@ pub const FFI = struct {
globalThis.throwValue(ret);
}
return .zero;
return error.JSError;
};
switch (function.step) {
.failed => |err| {
const res = ZigString.init(err.msg).toErrorInstance(globalThis);
globalThis.throwValue(res);
return .zero;
return error.JSError;
},
.pending => {
globalThis.throw("Failed to compile (nothing happend!)", .{});
return .zero;
return error.JSError;
},
.compiled => |*compiled| {
const str = ZigString.init(bun.asByteSlice(function_name));
@@ -903,7 +916,7 @@ pub const FFI = struct {
this: *FFI,
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSValue {
) bun.JSError!JSValue {
JSC.markBinding(@src());
if (this.closed) {
return .undefined;
@@ -1296,7 +1309,7 @@ pub const FFI = struct {
JSC.Codegen.JSFFI.symbolsValueSetCached(js_object, global, obj);
return js_object;
}
pub fn generateSymbolForFunction(global: *JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, function: *Function) !?JSValue {
pub fn generateSymbolForFunction(global: *JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, function: *Function) bun.JSError!?JSValue {
JSC.markBinding(@src());
var abi_types = std.ArrayListUnmanaged(ABIType){};
@@ -1347,11 +1360,11 @@ pub const FFI = struct {
var threadsafe = false;
if (value.getTruthy(global, "threadsafe")) |threadsafe_value| {
if (try value.getTruthy(global, "threadsafe")) |threadsafe_value| {
threadsafe = threadsafe_value.toBoolean();
}
if (value.getTruthy(global, "returns")) |ret_value| brk: {
if (try value.getTruthy(global, "returns")) |ret_value| brk: {
if (ret_value.isAnyInt()) {
const int = ret_value.toInt32();
switch (int) {
@@ -1396,7 +1409,7 @@ pub const FFI = struct {
.threadsafe = threadsafe,
};
if (value.get(global, "ptr")) |ptr| {
if (try value.get(global, "ptr")) |ptr| {
if (ptr.isNumber()) {
const num = ptr.asPtrAddress();
if (num > 0)
@@ -1411,7 +1424,8 @@ pub const FFI = struct {
return null;
}
pub fn generateSymbols(global: *JSGlobalObject, symbols: *bun.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) !?JSValue {
pub fn generateSymbols(global: *JSGlobalObject, symbols: *bun.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) bun.JSError!?JSValue {
JSC.markBinding(@src());
const allocator = VirtualMachine.get().allocator;

View File

@@ -32,6 +32,25 @@ export default [
klass: {},
}),
define({
name: "FrameworkFileSystemRouter",
construct: true,
finalize: true,
JSType: "0b11101110",
configurable: false,
proto: {
toJSON: {
fn: "toJSON",
length: 0,
},
match: {
fn: "match",
length: 1,
},
},
klass: {},
}),
define({
name: "MatchedRoute",
noConstructor: true,

View File

@@ -49,17 +49,15 @@ pub const FileSystemRouter = struct {
pub usingnamespace JSC.Codegen.JSFileSystemRouter;
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*FileSystemRouter {
const argument_ = callframe.arguments(1);
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*FileSystemRouter {
const argument_ = callframe.arguments_old(1);
if (argument_.len == 0) {
globalThis.throwInvalidArguments("Expected object", .{});
return null;
return globalThis.throwInvalidArguments2("Expected object", .{});
}
const argument = argument_.ptr[0];
if (argument.isEmptyOrUndefinedOrNull() or !argument.isObject()) {
globalThis.throwInvalidArguments("Expected object", .{});
return null;
return globalThis.throwInvalidArguments2("Expected object", .{});
}
var vm = globalThis.bunVM();
@@ -69,20 +67,17 @@ pub const FileSystemRouter = struct {
var asset_prefix_slice: ZigString.Slice = .{};
var out_buf: [bun.MAX_PATH_BYTES * 2]u8 = undefined;
if (argument.get(globalThis, "style")) |style_val| {
if (try argument.get(globalThis, "style")) |style_val| {
if (!style_val.getZigString(globalThis).eqlComptime("nextjs")) {
globalThis.throwInvalidArguments("Only 'nextjs' style is currently implemented", .{});
return null;
return globalThis.throwInvalidArguments2("Only 'nextjs' style is currently implemented", .{});
}
} else {
globalThis.throwInvalidArguments("Expected 'style' option (ex: \"style\": \"nextjs\")", .{});
return null;
return globalThis.throwInvalidArguments2("Expected 'style' option (ex: \"style\": \"nextjs\")", .{});
}
if (argument.get(globalThis, "dir")) |dir| {
if (try argument.get(globalThis, "dir")) |dir| {
if (!dir.isString()) {
globalThis.throwInvalidArguments("Expected dir to be a string", .{});
return null;
return globalThis.throwInvalidArguments2("Expected dir to be a string", .{});
}
const root_dir_path_ = dir.toSlice(globalThis, globalThis.allocator());
if (!(root_dir_path_.len == 0 or strings.eqlComptime(root_dir_path_.slice(), "."))) {
@@ -97,44 +92,40 @@ pub const FileSystemRouter = struct {
}
} else {
// dir is not optional
globalThis.throwInvalidArguments("Expected dir to be a string", .{});
return null;
return globalThis.throwInvalidArguments2("Expected dir to be a string", .{});
}
var arena = globalThis.allocator().create(bun.ArenaAllocator) catch unreachable;
arena.* = bun.ArenaAllocator.init(globalThis.allocator());
const allocator = arena.allocator();
var extensions = std.ArrayList(string).init(allocator);
if (argument.get(globalThis, "fileExtensions")) |file_extensions| {
if (try argument.get(globalThis, "fileExtensions")) |file_extensions| {
if (!file_extensions.jsType().isArray()) {
globalThis.throwInvalidArguments("Expected fileExtensions to be an Array", .{});
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwInvalidArguments2("Expected fileExtensions to be an Array", .{});
}
var iter = file_extensions.arrayIterator(globalThis);
extensions.ensureTotalCapacityPrecise(iter.len) catch unreachable;
while (iter.next()) |val| {
if (!val.isString()) {
globalThis.throwInvalidArguments("Expected fileExtensions to be an Array of strings", .{});
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwInvalidArguments2("Expected fileExtensions to be an Array of strings", .{});
}
if (val.getLength(globalThis) == 0) continue;
extensions.appendAssumeCapacity((val.toSlice(globalThis, allocator).clone(allocator) catch unreachable).slice()[1..]);
}
}
if (argument.getTruthy(globalThis, "assetPrefix")) |asset_prefix| {
if (try argument.getTruthy(globalThis, "assetPrefix")) |asset_prefix| {
if (!asset_prefix.isString()) {
globalThis.throwInvalidArguments("Expected assetPrefix to be a string", .{});
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwInvalidArguments2("Expected assetPrefix to be a string", .{});
}
asset_prefix_slice = asset_prefix.toSlice(globalThis, allocator).clone(allocator) catch unreachable;
@@ -147,17 +138,15 @@ pub const FileSystemRouter = struct {
const path_to_use = (root_dir_path.cloneWithTrailingSlash(allocator) catch unreachable).slice();
const root_dir_info = vm.bundler.resolver.readDirInfo(path_to_use) catch {
globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "reading root directory"));
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwValue2(log.toJS(globalThis, globalThis.allocator(), "reading root directory"));
} orelse {
globalThis.throw("Unable to find directory: {s}", .{root_dir_path.slice()});
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throw2("Unable to find directory: {s}", .{root_dir_path.slice()});
};
var router = Router.init(vm.bundler.fs, allocator, .{
@@ -167,29 +156,26 @@ pub const FileSystemRouter = struct {
}) catch unreachable;
router.loadRoutes(&log, root_dir_info, Resolver, &vm.bundler.resolver, router.config.dir) catch {
globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "loading routes"));
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwValue2(log.toJS(globalThis, globalThis.allocator(), "loading routes"));
};
if (argument.get(globalThis, "origin")) |origin| {
if (try argument.get(globalThis, "origin")) |origin| {
if (!origin.isString()) {
globalThis.throwInvalidArguments("Expected origin to be a string", .{});
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwInvalidArguments2("Expected origin to be a string", .{});
}
origin_str = origin.toSlice(globalThis, globalThis.allocator());
}
if (log.errors + log.warnings > 0) {
globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "loading routes"));
origin_str.deinit();
arena.deinit();
globalThis.allocator().destroy(arena);
return null;
return globalThis.throwValue2(log.toJS(globalThis, globalThis.allocator(), "loading routes"));
}
var fs_router = globalThis.allocator().create(FileSystemRouter) catch unreachable;
@@ -211,7 +197,7 @@ pub const FileSystemRouter = struct {
return fs_router;
}
pub fn reload(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
pub fn reload(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const this_value = callframe.this();
var arena = globalThis.allocator().create(bun.ArenaAllocator) catch unreachable;
@@ -259,8 +245,8 @@ pub const FileSystemRouter = struct {
return this_value;
}
pub fn match(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSValue {
const argument_ = callframe.arguments(2);
pub fn match(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSValue {
const argument_ = callframe.arguments_old(2);
if (argument_.len == 0) {
globalThis.throwInvalidArguments("Expected string, Request or Response", .{});
return JSValue.zero;
@@ -531,11 +517,10 @@ pub const MatchedRoute = struct {
for (entry.values, 0..) |value, i| {
values[i] = ZigString.init(value).withEncoding();
}
obj.putRecord(global, &str, values.ptr, values.len);
obj.putRecord(global, &str, values);
} else {
query_string_value_refs_buf[0] = ZigString.init(entry.values[0]).withEncoding();
obj.putRecord(global, &str, &query_string_value_refs_buf, 1);
obj.putRecord(global, &str, query_string_value_refs_buf[0..1]);
}
}
}

View File

@@ -39,7 +39,7 @@ const ScanOpts = struct {
follow_symlinks: bool,
error_on_broken_symlinks: bool,
fn parseCWD(globalThis: *JSGlobalObject, allocator: std.mem.Allocator, cwdVal: JSC.JSValue, absolute: bool, comptime fnName: string) ?[]const u8 {
fn parseCWD(globalThis: *JSGlobalObject, allocator: std.mem.Allocator, cwdVal: JSC.JSValue, absolute: bool, comptime fnName: string) bun.JSError![]const u8 {
const cwd_str_raw = cwdVal.toSlice(globalThis, allocator);
if (cwd_str_raw.len == 0) return "";
@@ -48,7 +48,7 @@ const ScanOpts = struct {
if (ResolvePath.Platform.auto.isAbsolute(cwd_str_raw.slice())) {
const cwd_str = cwd_str_raw.clone(allocator) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
};
break :cwd_str cwd_str.ptr[0..cwd_str.len];
}
@@ -59,7 +59,7 @@ const ScanOpts = struct {
const cwd_str = ResolvePath.joinStringBuf(&path_buf2, &[_][]const u8{cwd_str_raw.slice()}, .auto);
break :cwd_str allocator.dupe(u8, cwd_str) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
};
}
@@ -69,8 +69,7 @@ const ScanOpts = struct {
.result => |cwd| cwd,
.err => |err| {
const errJs = err.toJSC(globalThis);
globalThis.throwValue(errJs);
return null;
return globalThis.throwValue2(errJs);
},
};
@@ -81,19 +80,18 @@ const ScanOpts = struct {
break :cwd_str allocator.dupe(u8, cwd_str) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
};
};
if (cwd_str.len > bun.MAX_PATH_BYTES) {
globalThis.throw("{s}: invalid `cwd`, longer than {d} bytes", .{ fnName, bun.MAX_PATH_BYTES });
return null;
return globalThis.throw2("{s}: invalid `cwd`, longer than {d} bytes", .{ fnName, bun.MAX_PATH_BYTES });
}
return cwd_str;
}
fn fromJS(globalThis: *JSGlobalObject, arguments: *ArgumentsSlice, comptime fnName: []const u8, arena: *Arena) ?ScanOpts {
fn fromJS(globalThis: *JSGlobalObject, arguments: *ArgumentsSlice, comptime fnName: []const u8, arena: *Arena) bun.JSError!?ScanOpts {
const optsObj: JSValue = arguments.nextEat() orelse return null;
var out: ScanOpts = .{
.cwd = null,
@@ -106,13 +104,11 @@ const ScanOpts = struct {
if (optsObj.isUndefinedOrNull()) return out;
if (!optsObj.isObject()) {
if (optsObj.isString()) {
if (parseCWD(globalThis, arena.allocator(), optsObj, out.absolute, fnName)) |result| {
{
const result = try parseCWD(globalThis, arena.allocator(), optsObj, out.absolute, fnName);
if (result.len > 0) {
out.cwd = result;
}
} else {
// error
return null;
}
return out;
}
@@ -120,39 +116,37 @@ const ScanOpts = struct {
return null;
}
if (optsObj.getTruthy(globalThis, "onlyFiles")) |only_files| {
if (try optsObj.getTruthy(globalThis, "onlyFiles")) |only_files| {
out.only_files = if (only_files.isBoolean()) only_files.asBoolean() else false;
}
if (optsObj.getTruthy(globalThis, "throwErrorOnBrokenSymlink")) |error_on_broken| {
if (try optsObj.getTruthy(globalThis, "throwErrorOnBrokenSymlink")) |error_on_broken| {
out.error_on_broken_symlinks = if (error_on_broken.isBoolean()) error_on_broken.asBoolean() else false;
}
if (optsObj.getTruthy(globalThis, "followSymlinks")) |followSymlinksVal| {
if (try optsObj.getTruthy(globalThis, "followSymlinks")) |followSymlinksVal| {
out.follow_symlinks = if (followSymlinksVal.isBoolean()) followSymlinksVal.asBoolean() else false;
}
if (optsObj.getTruthy(globalThis, "absolute")) |absoluteVal| {
if (try optsObj.getTruthy(globalThis, "absolute")) |absoluteVal| {
out.absolute = if (absoluteVal.isBoolean()) absoluteVal.asBoolean() else false;
}
if (optsObj.getTruthy(globalThis, "cwd")) |cwdVal| {
if (try optsObj.getTruthy(globalThis, "cwd")) |cwdVal| {
if (!cwdVal.isString()) {
globalThis.throw("{s}: invalid `cwd`, not a string", .{fnName});
return null;
}
if (parseCWD(globalThis, arena.allocator(), cwdVal, out.absolute, fnName)) |result| {
{
const result = try parseCWD(globalThis, arena.allocator(), cwdVal, out.absolute, fnName);
if (result.len > 0) {
out.cwd = result;
}
} else {
// error
return null;
}
}
if (optsObj.getTruthy(globalThis, "dot")) |dot| {
if (try optsObj.getTruthy(globalThis, "dot")) |dot| {
out.dot = if (dot.isBoolean()) dot.asBoolean() else false;
}
@@ -248,8 +242,8 @@ fn makeGlobWalker(
comptime fnName: []const u8,
alloc: Allocator,
arena: *Arena,
) ?*GlobWalker {
const matchOpts = ScanOpts.fromJS(globalThis, arguments, fnName, arena) orelse return null;
) bun.JSError!?*GlobWalker {
const matchOpts = try ScanOpts.fromJS(globalThis, arguments, fnName, arena) orelse return null;
const cwd = matchOpts.cwd;
const dot = matchOpts.dot;
const absolute = matchOpts.absolute;
@@ -260,7 +254,7 @@ fn makeGlobWalker(
if (cwd != null) {
var globWalker = alloc.create(GlobWalker) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
};
globWalker.* = .{};
@@ -276,11 +270,10 @@ fn makeGlobWalker(
only_files,
) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
}) {
.err => |err| {
globalThis.throwValue(err.toJSC(globalThis));
return null;
return globalThis.throwValue2(err.toJSC(globalThis));
},
else => {},
}
@@ -288,7 +281,7 @@ fn makeGlobWalker(
}
var globWalker = alloc.create(GlobWalker) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
};
globWalker.* = .{};
@@ -302,11 +295,10 @@ fn makeGlobWalker(
only_files,
) catch {
globalThis.throwOutOfMemory();
return null;
return error.JSError;
}) {
.err => |err| {
globalThis.throwValue(err.toJSC(globalThis));
return null;
return globalThis.throwValue2(err.toJSC(globalThis));
},
else => {},
}
@@ -314,26 +306,21 @@ fn makeGlobWalker(
return globWalker;
}
pub fn constructor(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) ?*Glob {
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*Glob {
const alloc = getAllocator(globalThis);
const arguments_ = callframe.arguments(1);
const arguments_ = callframe.arguments_old(1);
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer arguments.deinit();
const pat_arg = arguments.nextEat() orelse {
globalThis.throw("Glob.constructor: expected 1 arguments, got 0", .{});
return null;
const pat_arg: JSValue = arguments.nextEat() orelse {
return globalThis.throw2("Glob.constructor: expected 1 arguments, got 0", .{});
};
if (!pat_arg.isString()) {
globalThis.throw("Glob.constructor: first argument is not a string", .{});
return null;
return globalThis.throw2("Glob.constructor: first argument is not a string", .{});
}
const pat_str: []u8 = @constCast((pat_arg.toSliceClone(globalThis) orelse return null).slice());
const pat_str: []u8 = @constCast((pat_arg.toSliceClone(globalThis) orelse return error.JSError).slice());
const all_ascii = isAllAscii(pat_str);
@@ -341,16 +328,10 @@ pub fn constructor(
glob.* = .{ .pattern = pat_str, .is_ascii = all_ascii };
if (!all_ascii) {
var codepoints = std.ArrayList(u32).initCapacity(alloc, glob.pattern.len * 2) catch {
globalThis.throwOutOfMemory();
return null;
};
var codepoints = try std.ArrayList(u32).initCapacity(alloc, glob.pattern.len * 2);
errdefer codepoints.deinit();
convertUtf8(&codepoints, glob.pattern) catch {
globalThis.throwOutOfMemory();
return null;
};
try convertUtf8(&codepoints, glob.pattern);
glob.pattern_codepoints = codepoints;
}
@@ -384,15 +365,15 @@ fn decrPendingActivityFlag(has_pending_activity: *std.atomic.Value(usize)) void
_ = has_pending_activity.fetchSub(1, .seq_cst);
}
pub fn __scan(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn __scan(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const alloc = getAllocator(globalThis);
const arguments_ = callframe.arguments(1);
const arguments_ = callframe.arguments_old(1);
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer arguments.deinit();
var arena = std.heap.ArenaAllocator.init(alloc);
const globWalker = this.makeGlobWalker(globalThis, &arguments, "scan", alloc, &arena) orelse {
const globWalker = try this.makeGlobWalker(globalThis, &arguments, "scan", alloc, &arena) orelse {
arena.deinit();
return .undefined;
};
@@ -401,22 +382,22 @@ pub fn __scan(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFram
var task = WalkTask.create(globalThis, alloc, globWalker, &this.has_pending_activity) catch {
decrPendingActivityFlag(&this.has_pending_activity);
globalThis.throwOutOfMemory();
return .undefined;
return error.JSError;
};
task.schedule();
return task.promise.value();
}
pub fn __scanSync(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn __scanSync(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const alloc = getAllocator(globalThis);
const arguments_ = callframe.arguments(1);
const arguments_ = callframe.arguments_old(1);
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer arguments.deinit();
var arena = std.heap.ArenaAllocator.init(alloc);
var globWalker = this.makeGlobWalker(globalThis, &arguments, "scanSync", alloc, &arena) orelse {
var globWalker = try this.makeGlobWalker(globalThis, &arguments, "scanSync", alloc, &arena) orelse {
arena.deinit();
return .undefined;
};
@@ -424,11 +405,10 @@ pub fn __scanSync(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.Call
switch (globWalker.walk() catch {
globalThis.throwOutOfMemory();
return .undefined;
return error.JSError;
}) {
.err => |err| {
globalThis.throwValue(err.toJSC(globalThis));
return JSValue.undefined;
return globalThis.throwValue2(err.toJSC(globalThis));
},
.result => {},
}
@@ -438,12 +418,12 @@ pub fn __scanSync(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.Call
return matchedPaths;
}
pub fn match(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue {
pub fn match(this: *Glob, globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const alloc = getAllocator(globalThis);
var arena = Arena.init(alloc);
defer arena.deinit();
const arguments_ = callframe.arguments(1);
const arguments_ = callframe.arguments_old(1);
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer arguments.deinit();
const str_arg = arguments.nextEat() orelse {

View File

@@ -48,7 +48,7 @@ pub const HTMLRewriter = struct {
pub usingnamespace JSC.Codegen.JSHTMLRewriter;
pub fn constructor(_: *JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*HTMLRewriter {
pub fn constructor(_: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*HTMLRewriter {
const rewriter = bun.default_allocator.create(HTMLRewriter) catch bun.outOfMemory();
rewriter.* = HTMLRewriter{
.builder = LOLHTML.HTMLRewriter.Builder.init(),
@@ -64,12 +64,12 @@ pub const HTMLRewriter = struct {
selector_name: ZigString,
callFrame: *JSC.CallFrame,
listener: JSValue,
) JSValue {
) bun.JSError!JSValue {
const selector_slice = std.fmt.allocPrint(bun.default_allocator, "{}", .{selector_name}) catch bun.outOfMemory();
var selector = LOLHTML.HTMLSelector.parse(selector_slice) catch
return throwLOLHTMLError(global);
const handler_ = ElementHandler.init(global, listener) catch return .zero;
const handler_ = try ElementHandler.init(global, listener);
const handler = getAllocator(global).create(ElementHandler) catch bun.outOfMemory();
handler.* = handler_;
@@ -111,8 +111,8 @@ pub const HTMLRewriter = struct {
global: *JSGlobalObject,
listener: JSValue,
callFrame: *JSC.CallFrame,
) JSValue {
const handler_ = DocumentHandler.init(global, listener) catch return .zero;
) bun.JSError!JSValue {
const handler_ = try DocumentHandler.init(global, listener);
const handler = getAllocator(global).create(DocumentHandler) catch bun.outOfMemory();
handler.* = handler_;
@@ -198,7 +198,8 @@ pub const HTMLRewriter = struct {
};
if (kind != .other) {
if (JSC.WebCore.Body.extract(global, response_value)) |body_value| {
{
const body_value = JSC.WebCore.Body.extract(global, response_value) catch return .zero;
const resp = bun.new(Response, Response{
.init = .{
.status_code = 200,
@@ -776,11 +777,8 @@ const DocumentHandler = struct {
};
if (!thisObject.isObject()) {
global.throwInvalidArguments(
"Expected object",
.{},
);
return error.InvalidArguments;
global.throwInvalidArguments("Expected object", .{});
return error.JSError;
}
errdefer {
@@ -801,37 +799,37 @@ const DocumentHandler = struct {
}
}
if (thisObject.get(global, "doctype")) |val| {
if (try thisObject.get(global, "doctype")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("doctype must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onDocTypeCallback = val;
}
if (thisObject.get(global, "comments")) |val| {
if (try thisObject.get(global, "comments")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("comments must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onCommentCallback = val;
}
if (thisObject.get(global, "text")) |val| {
if (try thisObject.get(global, "text")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("text must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onTextCallback = val;
}
if (thisObject.get(global, "end")) |val| {
if (try thisObject.get(global, "end")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("end must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onEndCallback = val;
@@ -935,35 +933,32 @@ const ElementHandler = struct {
}
if (!thisObject.isObject()) {
global.throwInvalidArguments(
"Expected object",
.{},
);
return error.InvalidArguments;
global.throwInvalidArguments("Expected object", .{});
return error.JSError;
}
if (thisObject.get(global, "element")) |val| {
if (try thisObject.get(global, "element")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("element must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onElementCallback = val;
}
if (thisObject.get(global, "comments")) |val| {
if (try thisObject.get(global, "comments")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("comments must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onCommentCallback = val;
}
if (thisObject.get(global, "text")) |val| {
if (try thisObject.get(global, "text")) |val| {
if (val.isUndefinedOrNull() or !val.isCell() or !val.isCallable(global.vm())) {
global.throwInvalidArguments("text must be a function", .{});
return error.InvalidArguments;
return error.JSError;
}
val.protect();
handler.onTextCallback = val;
@@ -1096,7 +1091,7 @@ pub const TextChunk = struct {
this: *TextChunk,
_: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.text_chunk == null)
return JSValue.jsUndefined();
this.text_chunk.?.remove();
@@ -1278,7 +1273,7 @@ pub const Comment = struct {
this: *Comment,
_: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.comment == null)
return JSValue.jsNull();
this.comment.?.remove();
@@ -1400,7 +1395,7 @@ pub const EndTag = struct {
this: *EndTag,
_: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.end_tag == null)
return JSValue.jsUndefined();
@@ -1449,7 +1444,7 @@ pub const AttributeIterator = struct {
pub usingnamespace JSC.Codegen.JSAttributeIterator;
pub fn next(this: *AttributeIterator, globalObject: *JSGlobalObject, _: *JSC.CallFrame) JSValue {
pub fn next(this: *AttributeIterator, globalObject: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue {
const done_label = JSC.ZigString.static("done");
const value_label = JSC.ZigString.static("value");
@@ -1475,7 +1470,7 @@ pub const AttributeIterator = struct {
));
}
pub fn getThis(_: *AttributeIterator, _: *JSGlobalObject, callFrame: *JSC.CallFrame) JSValue {
pub fn getThis(_: *AttributeIterator, _: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSValue {
return callFrame.this();
}
};
@@ -1494,7 +1489,7 @@ pub const Element = struct {
globalObject: *JSGlobalObject,
function: JSValue,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.element == null)
return JSValue.jsNull();
if (function.isUndefinedOrNull() or !function.isCallable(globalObject.vm())) {
@@ -1674,7 +1669,7 @@ pub const Element = struct {
this: *Element,
_: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.element == null)
return JSValue.jsUndefined();
@@ -1687,7 +1682,7 @@ pub const Element = struct {
this: *Element,
_: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) JSValue {
) bun.JSError!JSValue {
if (this.element == null)
return JSValue.jsUndefined();

File diff suppressed because it is too large Load Diff

View File

@@ -193,7 +193,7 @@ fn toTypeErrorWithCode(
zig_str.mark();
}
const code_str = ZigString.init(code);
return JSC.JSValue.createTypeError(&zig_str, &code_str, ctx.ptr());
return JSC.JSValue.createTypeError(&zig_str, &code_str, ctx);
}
pub fn toTypeError(
@@ -413,7 +413,8 @@ pub const ArrayBuffer = extern struct {
pub fn fromTypedArray(ctx: JSC.C.JSContextRef, value: JSC.JSValue) ArrayBuffer {
var out = std.mem.zeroes(ArrayBuffer);
bun.assert(value.asArrayBuffer_(ctx.ptr(), &out));
const was = value.asArrayBuffer_(ctx, &out);
bun.assert(was);
out.value = value;
return out;
}
@@ -479,7 +480,7 @@ pub const ArrayBuffer = extern struct {
const log = Output.scoped(.ArrayBuffer, false);
pub fn toJS(this: ArrayBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.JSValue {
if (!this.value.isEmpty()) {
if (this.value != .zero) {
return this.value;
}
@@ -519,7 +520,7 @@ pub const ArrayBuffer = extern struct {
callback: JSC.C.JSTypedArrayBytesDeallocator,
exception: JSC.C.ExceptionRef,
) JSC.JSValue {
if (!this.value.isEmpty()) {
if (this.value != .zero) {
return this.value;
}
@@ -605,7 +606,7 @@ pub const MarkedArrayBuffer = struct {
return MarkedArrayBuffer.fromBytes(buf, allocator, JSC.JSValue.JSType.Uint8Array);
}
pub fn fromJS(global: *JSC.JSGlobalObject, value: JSC.JSValue, _: JSC.C.ExceptionRef) ?MarkedArrayBuffer {
pub fn fromJS(global: *JSC.JSGlobalObject, value: JSC.JSValue) ?MarkedArrayBuffer {
const array_buffer = value.asArrayBuffer(global) orelse return null;
return MarkedArrayBuffer{ .buffer = array_buffer, .allocator = null };
}
@@ -979,16 +980,13 @@ pub fn DOMCall(
if (!JSC.is_bindgen) {
@export(slowpath, .{ .name = shim.symbolName("slowpath") });
@export(fastpath, .{ .name = shim.symbolName("fastpath") });
} else {
_ = slowpath;
_ = fastpath;
}
}
};
}
pub fn InstanceMethodType(comptime Container: type) type {
return fn (instance: *Container, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue;
return fn (instance: *Container, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue;
}
pub fn wrapInstanceMethod(
@@ -1006,8 +1004,8 @@ pub fn wrapInstanceMethod(
this: *Container,
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(FunctionTypeInfo.params.len);
) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(FunctionTypeInfo.params.len);
var iter = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
var args: Args = undefined;
@@ -1030,7 +1028,7 @@ pub fn wrapInstanceMethod(
args[i] = this;
},
*JSC.JSGlobalObject => {
args[i] = globalThis.ptr();
args[i] = globalThis;
},
*JSC.CallFrame => {
args[i] = callframe;
@@ -1041,7 +1039,7 @@ pub fn wrapInstanceMethod(
iter.deinit();
return JSC.JSValue.zero;
};
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis.ptr(), iter.arena.allocator(), arg) orelse {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse {
globalThis.throwInvalidArguments("expected string or buffer", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1050,7 +1048,7 @@ pub fn wrapInstanceMethod(
?JSC.Node.StringOrBuffer => {
if (iter.nextEat()) |arg| {
if (!arg.isEmptyOrUndefinedOrNull()) {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis.ptr(), iter.arena.allocator(), arg) orelse {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse {
globalThis.throwInvalidArguments("expected string or buffer", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1064,7 +1062,7 @@ pub fn wrapInstanceMethod(
},
JSC.ArrayBuffer => {
if (iter.nextEat()) |arg| {
args[i] = arg.asArrayBuffer(globalThis.ptr()) orelse {
args[i] = arg.asArrayBuffer(globalThis) orelse {
globalThis.throwInvalidArguments("expected TypedArray", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1077,7 +1075,7 @@ pub fn wrapInstanceMethod(
},
?JSC.ArrayBuffer => {
if (iter.nextEat()) |arg| {
args[i] = arg.asArrayBuffer(globalThis.ptr()) orelse {
args[i] = arg.asArrayBuffer(globalThis) orelse {
globalThis.throwInvalidArguments("expected TypedArray", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1099,11 +1097,11 @@ pub fn wrapInstanceMethod(
return JSC.JSValue.zero;
}
args[i] = string_value.getZigString(globalThis.ptr());
args[i] = string_value.getZigString(globalThis);
},
?JSC.Cloudflare.ContentOptions => {
if (iter.nextEat()) |content_arg| {
if (content_arg.get(globalThis.ptr(), "html")) |html_val| {
if (try content_arg.get(globalThis, "html")) |html_val| {
args[i] = .{ .html = html_val.toBoolean() };
}
} else {
@@ -1169,7 +1167,7 @@ pub fn wrapStaticMethod(
comptime Container: type,
comptime name: string,
comptime auto_protect: bool,
) JSC.Codegen.StaticCallbackType {
) JSC.JSHostZigFunction {
return struct {
const FunctionType = @TypeOf(@field(Container, name));
const FunctionTypeInfo: std.builtin.Type.Fn = @typeInfo(FunctionType).Fn;
@@ -1179,8 +1177,8 @@ pub fn wrapStaticMethod(
pub fn method(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(JSC.conv) JSC.JSValue {
const arguments = callframe.arguments(FunctionTypeInfo.params.len);
) bun.JSError!JSC.JSValue {
const arguments = callframe.arguments_old(FunctionTypeInfo.params.len);
var iter = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments.slice());
var args: Args = undefined;
@@ -1188,7 +1186,7 @@ pub fn wrapStaticMethod(
const ArgType = param.type.?;
switch (param.type.?) {
*JSC.JSGlobalObject => {
args[i] = globalThis.ptr();
args[i] = globalThis;
},
JSC.Node.StringOrBuffer => {
const arg = iter.nextEat() orelse {
@@ -1196,7 +1194,7 @@ pub fn wrapStaticMethod(
iter.deinit();
return JSC.JSValue.zero;
};
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis.ptr(), iter.arena.allocator(), arg) orelse {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse {
globalThis.throwInvalidArguments("expected string or buffer", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1204,7 +1202,7 @@ pub fn wrapStaticMethod(
},
?JSC.Node.StringOrBuffer => {
if (iter.nextEat()) |arg| {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis.ptr(), iter.arena.allocator(), arg) orelse brk: {
args[i] = JSC.Node.StringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse brk: {
if (arg == .undefined) {
break :brk null;
}
@@ -1219,7 +1217,7 @@ pub fn wrapStaticMethod(
},
JSC.Node.BlobOrStringOrBuffer => {
if (iter.nextEat()) |arg| {
args[i] = JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis.ptr(), iter.arena.allocator(), arg) orelse {
args[i] = JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, iter.arena.allocator(), arg) orelse {
globalThis.throwInvalidArguments("expected blob, string or buffer", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1232,7 +1230,7 @@ pub fn wrapStaticMethod(
},
JSC.ArrayBuffer => {
if (iter.nextEat()) |arg| {
args[i] = arg.asArrayBuffer(globalThis.ptr()) orelse {
args[i] = arg.asArrayBuffer(globalThis) orelse {
globalThis.throwInvalidArguments("expected TypedArray", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1245,7 +1243,7 @@ pub fn wrapStaticMethod(
},
?JSC.ArrayBuffer => {
if (iter.nextEat()) |arg| {
args[i] = arg.asArrayBuffer(globalThis.ptr()) orelse {
args[i] = arg.asArrayBuffer(globalThis) orelse {
globalThis.throwInvalidArguments("expected TypedArray", .{});
iter.deinit();
return JSC.JSValue.zero;
@@ -1267,11 +1265,11 @@ pub fn wrapStaticMethod(
return JSC.JSValue.zero;
}
args[i] = string_value.getZigString(globalThis.ptr());
args[i] = string_value.getZigString(globalThis);
},
?JSC.Cloudflare.ContentOptions => {
if (iter.nextEat()) |content_arg| {
if (content_arg.get(globalThis.ptr(), "html")) |html_val| {
if (try content_arg.get(globalThis, "html")) |html_val| {
args[i] = .{ .html = html_val.toBoolean() };
}
} else {
@@ -1419,9 +1417,9 @@ pub const BinaryType = enum(u4) {
return Map.get(input);
}
pub fn fromJSValue(globalThis: *JSC.JSGlobalObject, input: JSValue) ?BinaryType {
pub fn fromJSValue(globalThis: *JSC.JSGlobalObject, input: JSValue) bun.JSError!?BinaryType {
if (input.isString()) {
return Map.getWithEql(input.getZigString(globalThis), ZigString.eqlComptime);
return Map.getWithEql(try input.toBunString2(globalThis), bun.String.eqlComptime);
}
return null;

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