Compare commits

...

57 Commits

Author SHA1 Message Date
Ashcon Partovi
eeeef5aaf0 Terminal works, launch is being reworked 2023-08-26 18:26:50 -07:00
Ashcon Partovi
f9b966c13f Bun Terminal 2023-08-26 01:38:31 -07:00
Ashcon Partovi
d2ad4da1a0 Fix debugger.ts 2023-08-26 01:16:19 -07:00
Ashcon Partovi
eb4ef364f2 More fixes 2023-08-25 23:29:46 -07:00
Ashcon Partovi
d0e2679fb5 More changes 2 2023-08-25 22:20:22 -07:00
Ashcon Partovi
764437eb6d More changes 2023-08-25 21:10:53 -07:00
Ashcon Partovi
824655e1cb More fixes for dap 2023-08-25 12:03:00 -07:00
Jarred Sumner
21b2d5c3a5 Update executables.md 2023-08-25 04:45:41 -07:00
Jarred Sumner
10815a7d43 Update executables.md 2023-08-25 04:33:54 -07:00
Jarred Sumner
f839640c17 Update executables.md 2023-08-25 04:32:20 -07:00
Jarred Sumner
557e912d9a Fix assertion failure with sourcemaps in multi-threaded transpiler (#4321)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-25 04:00:54 -07:00
Jarred Sumner
1e75a978e5 Skip disabled test 2023-08-25 04:00:23 -07:00
Jarred Sumner
95e8c24db1 Update WebKit 2023-08-25 03:56:44 -07:00
Jarred Sumner
755f41fe2a [Inspector] Get firefox to work 2023-08-25 03:56:39 -07:00
Jarred Sumner
9aabe4eea1 Upgrade peechy 2023-08-25 01:43:25 -07:00
Jarred Sumner
90f3bf2796 Update http.md 2023-08-24 23:05:27 -07:00
Jarred Sumner
16b4bf341a Disable minifying "str".length until https://github.com/oven-sh/bun/issues/4217 is fixed 2023-08-24 23:00:53 -07:00
Ashcon Partovi
1480889205 Improved support for debug-adapter-protocol (#4186)
* Improve support for \`debug-adapter-protocol\`

* More improvements, fix formatting in debug console

* Fix attaching

* Prepare for source maps

* Start of source map support, breakpoints work

* Source map support

* add some package.jsons

* wip

* Update package.json

* More fixes

* Make source maps safer if exception occurs

* Check bun version if it fails

* Fix console.log formatting

* Fix source maps partly

* More source map fixes

* Prepare for extension

* watch mode with dap

* Improve preview code

* Prepare for extension 2

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-24 22:53:34 -07:00
Jarred Sumner
f269432d90 Listen on a unix domain socket with Bun.serve() (#4311)
* Update response.zig

* Comment this out for now

* Support unix domain socket in Bun.serve()

* Add test

* add types

* Update JSFetchHeaders.cpp

* comment this test out

* tls unix web socket serve options

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
2023-08-24 22:49:58 -07:00
Colin McDonnell
73b3fb7b0f Add guides for test runner (#4308) 2023-08-24 22:28:07 -07:00
Ashcon Partovi
2bcbafe7d3 Fix debugger not updating after reload with --hot 2023-08-24 20:09:32 -07:00
Colin McDonnell
f7f734788c Update tsconfig.base.json 2023-08-24 19:54:03 -07:00
Colin McDonnell
2cd1d59387 Update toml import type test 2023-08-24 19:52:53 -07:00
Colin McDonnell
b70210a005 Use noEmit 2023-08-24 19:51:14 -07:00
Code Hz
b9c2309c8a Remove conflict option in tsconfig-for-init.json (#4284)
fix https://github.com/oven-sh/bun/issues/4283
2023-08-24 19:41:23 -07:00
Jarred Sumner
43c4da8c9a Update build-id 2023-08-24 19:39:54 -07:00
Jarred Sumner
8a48e8bb0b Report extra memory more (#4289)
* Report memory allocated in fetch

* Memory size reporting to `Headers`

* Fixup memory reporting allocator

* Make these tests do more

* cleanup some of this

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-24 19:39:00 -07:00
Dylan Conway
097ae4e982 fix build 2023-08-24 19:05:40 -07:00
Alex Lam S.L
213f5bef9d [install] fix stale life-cycle scripts from lockfile (#4307)
fixes #4269
2023-08-24 17:18:51 -07:00
Alex Lam S.L
e115638cba [install] fix crash when installing package that uses loose semver pre-release (#4302)
- also fix parsing of `1.2.3pre+build`

fixes #4266
2023-08-24 17:17:48 -07:00
Ai Hoshino
6e57556fad Fix(node:http): fix URL formatting when using a proxy. (#4297)
Close: #4295
2023-08-24 17:17:18 -07:00
Ai Hoshino
339d2c7f19 Make the server not crash if an error occurs in dev build. (#4300)
Close: #4298
2023-08-24 17:16:51 -07:00
Jarred Sumner
d2bef4fbea Don't inline require/import errors at runtime (#4306)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-24 17:03:05 -07:00
Jason
19aa9d93de update zig to 0.11.0 (#4233)
* WIP

* backup

* more change

* json related error sovled

* number related issue solved

* revert WriterType changed before

* destroy -> free

* jsonStringify related issues solved

* fix mem.free expected []T or *[_]T, passed [*]const u8

* fix expected []T or *[_]T, passed [*:0]const u8

* fix build script

* fix build script, for real

* replace 0.11.0-dev.4006+bf827d0b5 to 0.12.0-dev.161+6a5463951

* fix build on macOS, COPYFILE.DATA -> COPYFILE_DATA

* fix the last destroy on [*]ptr issue

---------

Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2023-08-24 16:13:14 -07:00
jhmaster
55eb4ffe8f Update bun-polyfills & bun-wasm (#4246)
* automate Bun.version & revision polyfills

* polyfill Bun.gc

* bun:jsc module initial polyfills

* update peechy schema

* bun-polyfills: fix some project configs

* bun-wasm: lots of fixes

* bun-polyfills: Bun.Transpiler impl.

* revision hash update
2023-08-24 14:39:28 -07:00
Jarred Sumner
a051a6f620 Fix performance regression in reading from the request body (#4291)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-24 14:36:39 -07:00
Jarred Sumner
9c68abdb8d wip (#4282)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-24 01:32:22 -07:00
Jarred Sumner
ad326b7734 [Inspector] Fix bug with sourcemaps including internal metadata bytes 2023-08-23 22:22:55 -07:00
Colin McDonnell
aa08c35c06 Add Debugger docs and a couple guides (#4281)
* Add debugger docs. Add guides.

* Add files
2023-08-23 18:15:21 -07:00
dave caruso
20d42dfaa3 Fix <const T>() => (#4278) 2023-08-23 17:22:54 -07:00
VietnamecDevelopment
3556fa3b1e Update globals.d.ts (#4276) 2023-08-23 16:25:05 -07:00
Code Hz
5e07fd4fbc Fix ffi type (#4265)
* add readonly so it works with as const

* split ffi type infer to args and returns

* add JSCallback to FFITypeToArgsType

* add read functions

* simplify FFITypeOrString

* fix cstring type

* fix pointer type test

* fix readonly

* add unknown handling

* trigger action

* use Parameters

* add read type test

* add other read function to tests
2023-08-23 15:56:46 -07:00
Jarred Sumner
c60385716b Bunch of streams fixes (#4251)
* Update WebKit

* Don't do async hooks things when async hooks are not enabled

* Smarter scheduling of event loop tasks with the http server

* less exciting approach

* Bump WebKit

* Another approach

* Fix body-stream tests

* Fixes #1886

* Fix UAF in fetch body streaming

* Missing from commit

* Fix leak

* Fix the other leak

* Fix test

* Fix crash

* missing duperef

* Make this code clearer

* Ignore empty chunks

* Fixes #3969

* Delete flaky test

* Update bun-linux-build.yml

* Fix memory issue

* fix result body, and .done status before the last callback, dont touch headers after sent once

* refactor HTTPClientResult

* less flasky corrupted test

* oops

* fix mutex invalid state

* fix onProgressUpdate deinit/unlock

* fix onProgressUpdate deinit/unlock

* oops

* remove verbose

* fix posible null use

* avoid http null

* metadata can still be used onReject after toResponse

* dont leak task.http

* fix flask tests

* less flask close tests

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: cirospaciari <ciro.spaciari@gmail.com>
2023-08-23 14:05:05 -07:00
Quentin
f3266ff436 docs: remove broken DNS link which is also not present in the official docs (#4268) 2023-08-23 10:36:26 -07:00
xxxhussein
b01764b31e Fix more types. (#4273) 2023-08-23 10:35:51 -07:00
Jozef Steinhübl
851763174e ask for bun --revision instead bun -v (#4256) 2023-08-23 00:42:33 -07:00
dave caruso
8518fbb573 fix yield (#4264) 2023-08-22 21:54:59 -07:00
Colin McDonnell
d86084dd8e Fix Bun.inspect types 2023-08-22 14:28:01 -07:00
dave caruso
52802a4c55 fix fsevents and stub for qwikcity (#4247)
* fix test

* ok

* cm

* EE

* remove the hack we didnt need
2023-08-21 23:39:56 -07:00
Dylan Conway
44e4d5852a fix stdin stream unref and resuming (#4250)
* fix stream unref and resuming stream

* fix `child-process-stdio` test
2023-08-21 23:39:41 -07:00
Colin McDonnell
3a45f2c71b Docs and types for v0.8.0 (#4199)
* Improve test documentation

* Update nodejs compat docs with tty

* Add debugger guide

* Document Bun.inspect.custom, improve bun test nav

* Address reviews

* Update Bun.file types

* Add Nuxt guide

* Add tty types
2023-08-21 21:34:03 -07:00
Jarred Sumner
9eeb7bdbff Bun v0.8 2023-08-21 21:26:45 -07:00
Jarred Sumner
ed14b64e65 Make the code generator less duplicative 2023-08-21 21:24:49 -07:00
Jarred Sumner
bca1bcf29c import errors have code set to ERR_MODULE_NOT_FOUND and require errors have code set to MODULE_NOT_FOUND (#4244)
* ResolveMessage

* Fix it

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-21 21:11:27 -07:00
Ciro Spaciari
9027484ae1 fetch(stream) add stream support for compressed and uncompressed data (#4127)
* streams non compressed data in 64kb chunks (at least)

* fmt

* wip remove pause

* fix default streaming and buffering

* fix atomic lags

* fix size

* make chunked encoding work again (WIP streaming chunked)

* WIP: chunked encoding streaming

* fix end of streamings

* working streaming + compression

* add fixes + tests

* fmt + fix proxy

* fix oopsies

* codegen after merge

* fmt + fixes

* more fixes

* more fixes and logs

* avoid double free

* check empty before pop

* check empty on pop

* fix copy to real when complete

* remove unnecessary logs

* better has_schedule_callback swap, body locked size helper, remove isEmpty from unbounded_queue pop

* fix response ref, fix body_size

* add deflate support, fix error throw, add more tests

* codegen after merge

* remove logs, add connection close test

* fix macOS build

* fix redirect error option

* make body_size more clear

* support new Reponse(response)

* toString DOMWrapper objects properly instead of supporting response in Response constructor

* ignore headers with no name, add more tests

* oops

* handle transform with fetch

* add gz image stream test

* remove duplicate test

* fix missing chunk on macOS under pressure

* oops include all OS

* some fixes

* compare buffers instead of sizes

* refactor err.err and protect it
2023-08-21 20:30:34 -07:00
dave caruso
91eacade97 Fix inquirer (#4245) 2023-08-21 19:32:41 -07:00
Jarred Sumner
6a02edef5d Update module_loader.zig 2023-08-21 19:04:18 -07:00
265 changed files with 55601 additions and 11019 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

BIN
bun.lockb

Binary file not shown.

View File

@@ -35,7 +35,7 @@ To configure which port and hostname the server will listen on:
```ts
Bun.serve({
port: 8080, // defaults to $PORT, then 3000
port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
hostname: "mydomain.com", // defaults to "0.0.0.0"
fetch(req) {
return new Response(`404!`);
@@ -43,6 +43,17 @@ Bun.serve({
});
```
To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket):
```ts
Bun.serve({
unix: "/tmp/my-socket.sock", // path to socket
fetch(req) {
return new Response(`404!`);
},
});
```
## Error handling
To activate development mode, set `development: true`. By default, development mode is _enabled_ unless `NODE_ENV` is `production`.
@@ -78,7 +89,7 @@ Bun.serve({
```
{% callout %}
**Note** — Full debugger support is planned.
[Learn more about debugging in Bun](https://bun.sh/docs/runtime/debugger)
{% /callout %}
The call to `Bun.serve` returns a `Server` object. To stop the server, call the `.stop()` method.

View File

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

View File

@@ -31,3 +31,30 @@ All imported files and packages are bundled into the executable, along with a co
- `--publicPath`
{% /callout %}
## Embedding files
Standalone executables support embedding files.
To embed files into an executable with `bun build --compile`, import the file in your code
```js
// this becomes an internal file path
import icon from "./icon.png";
import { file } from "bun";
export default {
fetch(req) {
return new Response(file(icon));
},
};
```
You may need to specify a `--loader` for it to be treated as a `"file"` loader (so you get back a file path).
Embedded files can be read using `Bun.file`'s functions or the Node.js `fs.readFile` function (in `"node:fs"`).
## Minification
To trim down the size of the executable a little, pass `--minify` to `bun build --compile`. This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller.

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
---
name: Set a time zone in Bun
---
Bun supports programmatically setting a default time zone for the lifetime of the `bun` process. To do set, set the value of the `TZ` environment variable to a [valid timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
{% callout %}
When running a file with `bun`, the timezone defaults to your system's configured local time zone.
When running tests with `bun test`, the timezone is set to `UTC` to make tests more deterministic.
{% /callout %}
```ts
process.env.TZ = "America/New_York";
```
---
Alternatively, this can be set from the command line when running a Bun command.
```sh
$ TZ=America/New_York bun run dev
```
---
Once `TZ` is set, any `Date` instances will have that time zone. By default all dates use your system's configured time zone.
```ts
new Date().getHours(); // => 18
process.env.TZ = "America/New_York";
new Date().getHours(); // => 21
```

View File

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

23
docs/guides/test/bail.md Normal file
View File

@@ -0,0 +1,23 @@
---
name: Bail early with the Bun test runner
---
Use the `--bail` flag to bail on a test run after a single failure. This is useful for aborting as soon as possible in a continuous integration environment.
```sh
# re-run each test 10 times
$ bun test --bail
```
---
To bail after a certain threshold of failures, optionally specify a number after the flag.
```sh
# bail after 10 failures
$ bun test --bail 10
```
---
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.

View File

@@ -0,0 +1,58 @@
---
name: Set a code coverage threshold with the Bun test runner
---
Bun's test runner supports built-in code coverage reporting via the `--coverage` flag.
```sh
$ bun test --coverage
test.test.ts:
✓ math > add [0.71ms]
✓ math > multiply [0.03ms]
✓ random [0.13ms]
-------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files | 66.67 | 77.78 |
math.ts | 50.00 | 66.67 |
random.ts | 50.00 | 66.67 |
-------------|---------|---------|-------------------
3 pass
0 fail
3 expect() calls
```
---
To set a minimum coverage threshold, add the following line to your `bunfig.toml`. This requires that 90% of your codebase is covered by tests.
```toml
[test]
# to require 90% line-level and function-level coverage
coverageThreshold = 0.9
```
---
If your test suite does not meet this threshold, `bun test` will exit with a non-zero exit code to signal a failure.
```sh
$ bun test --coverage
<test output>
$ echo $?
1 # this is the exit code of the previous command
```
Different thresholds can be set for line-level and function-level coverage.
```toml
[test]
# to set different thresholds for lines and functions
coverageThreshold = { line = 0.5, function = 0.7 }
```
---
See [Docs > Test runner > Coverage](/docs/test/coverage) for complete documentation on code coverage reporting in Bun.

View File

@@ -0,0 +1,44 @@
---
name: Generate code coverage reports with the Bun test runner
---
Bun's test runner supports built-in _code coverage reporting_. This makes it easy to see how much of the codebase is covered by tests, and find areas that are not currently well-tested.
---
Pass the `--coverage` flag to `bun test` to enable this feature. This will print a coverage report after the test run.
The coverage report lists the source files that were executed during the test run, the percentage of functions and lines that were executed, and the line ranges that were not executed during the run.
```sh
$ bun test --coverage
test.test.ts:
✓ math > add [0.71ms]
✓ math > multiply [0.03ms]
✓ random [0.13ms]
-------------|---------|---------|-------------------
File | % Funcs | % Lines | Uncovered Line #s
-------------|---------|---------|-------------------
All files | 66.67 | 77.78 |
math.ts | 50.00 | 66.67 |
random.ts | 50.00 | 66.67 |
-------------|---------|---------|-------------------
3 pass
0 fail
3 expect() calls
```
---
To always enable coverage reporting by default, add the following line to your `bunfig.toml`:
```toml
[test]
coverage = true # always enable coverage
```
---
Refer to [Docs > Test runner > Coverage](/docs/test/coverage) for complete documentation on code coverage reporting in Bun.

View File

@@ -0,0 +1,68 @@
---
name: Write browser DOM tests with Bun and happy-dom
---
You can write and run browser tests with Bun's test runner in conjunction with [Happy DOM](https://github.com/capricorn86/happy-dom). Happy DOM implements mocked versions of browser APIs like `document` and `location`.
---
To get started, install `happy-dom`.
```sh
$ bun add -d @happy-dom/global-registrator
```
---
This module exports a "registrator" that will adds the mocked browser APIs to the global scope.
```ts#happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();
```
---
We need to make sure this file is executed before any of our test files. That's a job for Bun's built-in _preload_ functionality. Create a `bunfig.toml` file in the root of your project (if it doesn't already exist) and add the following lines.
The `./happydom.ts` file should contain the registration code above.
```toml#bunfig.toml
[test]
preload = "./happydom.ts"
```
---
Now running `bun test` inside our project will automatically execute `happydom.ts` first. We can start writing tests that use browser APIs.
```ts
import { test, expect } from "bun:test";
test("set button text", () => {
document.body.innerHTML = `<button>My button</button>`;
const button = document.querySelector("button");
expect(button?.innerText).toEqual("My button");
});
```
---
With Happy DOM propertly configured, this test runs as expected.
```sh
$ bun test
dom.test.ts:
✓ set button text [0.82ms]
1 pass
0 fail
1 expect() calls
Ran 1 tests across 1 files. 1 total [125.00ms]
```
---
Refer to the [Happy DOM repo](https://github.com/capricorn86/happy-dom) and [Docs > Test runner > DOM](/docs/test/dom) for complete documentation on writing browser tests with Bun.

View File

@@ -0,0 +1,4 @@
{
"name": "Test runner",
"description": "A collection of guides for writing, running, and configuring tests in Bun"
}

View File

@@ -0,0 +1,48 @@
---
name: Set the system time in Bun's test runner
---
Bun's test runner supports setting the system time programmatically with the `setSystemTime` function.
```ts
import { test, expect, beforeAll, setSystemTime } from "bun:test";
test("party like it's 1999", () => {
const date = new Date("1999-01-01T00:00:00.000Z");
setSystemTime(date); // it's now January 1, 1999
const now = new Date();
expect(now.getFullYear()).toBe(1999);
expect(now.getMonth()).toBe(0);
expect(now.getDate()).toBe(1);
});
```
---
The `setSystemTime` function is commonly used on conjunction with [Lifecycle Hooks](/docs/test/lifecycle) to configure a testing environment with a determinstic "fake clock".
```ts
import { test, expect, beforeAll, setSystemTime } from "bun:test";
beforeAll(() => {
const date = new Date("1999-01-01T00:00:00.000Z");
setSystemTime(date); // it's now January 1, 1999
});
// tests...
```
---
To reset the system clock to the actual time, call `setSystemTime` with no arguments.
```ts
import { test, expect, beforeAll, setSystemTime } from "bun:test";
setSystemTime(); // reset to actual time
```
---
See [Docs > Test Runner > Date and time](/docs/test/time) for complete documentation on mocking with the Bun test runner.

View File

@@ -0,0 +1,68 @@
---
name: Mock functions in `bun test`
---
Create mocks with the `mock` function from `bun:test`.
```ts
import { test, expect, mock } from "bun:test";
const random = mock(() => Math.random());
```
---
The mock function can accept arguments.
```ts
import { test, expect, mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());
```
---
The result of `mock()` is a new function that's been decorated with some additional properties.
```ts
import { mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());
random(2);
random(10);
random.mock.calls;
// [[ 2 ], [ 10 ]]
random.mock.results;
// [
// { type: "return", value: 0.6533907460954099 },
// { type: "return", value: 0.6452713933037312 }
// ]
```
---
These extra properties make it possible to write `expect` assertions about usage of the mock function, including how many times it was called, the arguments, and the return values.
```ts
import { test, mock } from "bun:test";
const random = mock((multiplier: number) => multiplier * Math.random());
test("random", async () => {
const a = random(1);
const b = random(2);
const c = random(3);
expect(random).toHaveBeenCalled();
expect(random).toHaveBeenCalledTimes(3);
expect(random.mock.args).toEqual([[1], [2], [3]]);
expect(random.mock.results[0]).toEqual({ type: "return", value: a });
});
```
---
See [Docs > Test Runner > Mocks](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.

View File

@@ -0,0 +1,14 @@
---
name: Re-run tests multiple times with the Bun test runner
---
Use the `--rerun-each` flag to re-run every test multiple times with the Bun test runner. This is useful for finding flaky or non-deterministic tests.
```sh
# re-run each test 10 times
$ bun test --rerun-each 10
```
---
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.

View File

@@ -0,0 +1,99 @@
---
name: Run your tests with the Bun test runner
---
Bun has a built-in test runner with a Jest-like `expect` API. To use it, run `bun test` from your project directory. The test runner will search for all files in the directory that match the following patterns:
- `*.test.{js|jsx|ts|tsx}`
- `*_test.{js|jsx|ts|tsx}`
- `*.spec.{js|jsx|ts|tsx}`
- `*_spec.{js|jsx|ts|tsx}`
```sh
$ bun test
bun test v0.8.0 (9c68abdb)
test.test.js:
✓ add [0.87ms]
✓ multiply [0.02ms]
test2.test.js:
✓ add [0.72ms]
✓ multiply [0.01ms]
test3.test.js:
✓ add [0.54ms]
✓ multiply [0.01ms]
6 pass
0 fail
6 expect() calls
Ran 6 tests across 3 files. [9.00ms]
```
---
To only run certain test files, pass a positional argument to `bun test`. The runner will only execute files that contain that argument in their path.
```sh
$ bun test test3
bun test v0.8.0 (9c68abdb)
test3.test.js:
✓ add [1.40ms]
✓ multiply [0.03ms]
2 pass
0 fail
2 expect() calls
Ran 2 tests across 1 files. [15.00ms]
```
---
All tests have a name, defined as the first parameter to the `test` function. Tests can also be inside a `describe` block.
```ts
import { test, expect } from "bun:test";
test("add", () => {
expect(2 + 2).toEqual(4);
});
test("multiply", () => {
expect(2 * 2).toEqual(4);
});
```
---
To filter which tests are executed by name, use the `-t`/`--test-name-pattern` flag.
Adding `-t add` will only run tests with "add" in the name. This flag also checks the name of the test suite (the first parameter to `describe`).
```sh
$ bun test -t add
bun test v0.8.0 (9c68abdb)
test.test.js:
✓ add [1.79ms]
» multiply
test2.test.js:
✓ add [2.30ms]
» multiply
test3.test.js:
✓ add [0.32ms]
» multiply
3 pass
3 skip
0 fail
3 expect() calls
Ran 6 tests across 3 files. [59.00ms]
```
---
See [Docs > Test Runner](/docs/cli/test) for complete documentation on the test runner.

View File

@@ -0,0 +1,37 @@
---
name: Skip tests with the Bun test runner
---
To skip a test with the Bun test runner, use the `test.skip` function.
```ts-diff
test.skip("unimplemented feature", ()=>{
expect(Bun.isAwesome()).toBe(true);
});
```
---
Running `bun test` will not execute this test. It will be marked as skipped in the terminal output.
```sh
$ bun test
test.test.ts:
✓ add [0.03ms]
✓ multiply [0.02ms]
» unimplemented feature
2 pass
1 skip
0 fail
2 expect() calls
Ran 3 tests across 1 files. [74.00ms]
```
---
See also:
- [Mark a test as a todo](/guides/test/todo-tests)
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)

View File

@@ -0,0 +1,99 @@
---
name: Use snapshot testing in `bun test`
---
Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.
{% callout %}
The `.toMatchInlineSnapshot()` method is not yet supported.
{% /callout %}
```ts#snap.test.ts
import { test, expect } from "bun:test";
test("snapshot", () => {
expect({ foo: "bar" }).toMatchSnapshot();
});
```
---
The first time this test is executed, Bun will evaluate the value passed into `expect()` (`{ foo: "bar" }`) and write it to disk in a directory called `__snapshots__` that lives alongside the test file.
```sh
$ bun test test/snap
bun test v0.8.0 (9c68abdb)
test/snap.test.ts:
✓ snapshot [1.48ms]
1 pass
0 fail
snapshots: +1 added # note: the snapshot is created automatically the first run
1 expect() calls
Ran 1 tests across 1 files. [82.00ms]
```
---
The `__snapshots__` directory contains a `.snap` file for each test file in the directory.
```txt
test
├── __snapshots__
│   └── snap.test.ts.snap
└── snap.test.ts
```
---
The `snap.test.ts.snap` file is a JavaScript file that exports a serialized version of the value passed into `expect()`. The `{foo: "bar"}` object has been serialized to JSON.
```js
// Bun Snapshot v1, https://goo.gl/fbAQLP
exports[`snapshot 1`] = `
{
"foo": "bar",
}
`;
```
---
Later, when this test file is executed again, Bun will read the snapshot file and compare it to the value passed into `expect()`. If the values are different, the test will fail.
```sh
$ bun test
bun test v0.8.0 (9c68abdb)
test/snap.test.ts:
✓ snapshot [1.05ms]
1 pass
0 fail
1 snapshots, 1 expect() calls
Ran 1 tests across 1 files. [101.00ms]
```
---
To update snapshots, use the `--update-snapshots` flag.
```sh
$ bun test --update-snapshots
bun test v0.8.0 (9c68abdb)
test/snap.test.ts:
✓ snapshot [0.86ms]
1 pass
0 fail
snapshots: +1 added # the snapshot was regenerated
1 expect() calls
Ran 1 tests across 1 files. [102.00ms]
```
---
See [Docs > Test Runner > Snapshots](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.

View File

@@ -0,0 +1,46 @@
---
name: Spy on methods in `bun test`
---
Use the `spyOn` utility to track method calls with Bun's test runner.
```ts
import { test, expect, spyOn } from "bun:test";
const leo = {
name: "Leonard",
sayHi(thing: string) {
console.log(`Sup I'm ${this.name} and I like ${thing}`);
},
};
const spy = spyOn(leo, "sayHi");
```
---
Once the spy is created, it can be used to write `expect` assertions relating to method calls.
```ts-diff
import { test, expect, spyOn } from "bun:test";
const leo = {
name: "Leonardo",
sayHi(thing: string) {
console.log(`Sup, I'm ${this.name} and I like ${thing}`);
},
};
const spy = spyOn(leo, "sayHi");
+ test("turtles", ()=>{
+ expect(spy).toHaveBeenCalledTimes(0);
+ leo.sayHi("pizza");
+ expect(spy).toHaveBeenCalledTimes(0);
+ expect(spy.mock.calls).toEqual([[ "pizza" ]]);
+ })
```
---
See [Docs > Test Runner > Mocks](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.

View File

@@ -0,0 +1,15 @@
---
name: Set a per-test timeout with the Bun test runner
---
Use the `--timeout` flag to set a timeout for each test in milliseconds. If any test exceeds this timeout, it will be marked as failed.
The default timeout is `5000` (5 seconds).
```sh
$ bun test --timeout 3000 # 3 seconds
```
---
See [Docs > Test runner](/docs/cli/test) for complete documentation of `bun test`.

View File

@@ -0,0 +1,60 @@
---
name: Mark a test as a "todo" with the Bun test runner
---
To remind yourself to write a test later, use the `test.todo` function. There's no need to provide a test implementation.
```ts
import { test, expect } from "bun:test";
// write this later
test.todo("unimplemented feature");
```
---
Optionally, you can provide a test implementation.
```ts
import { test, expect } from "bun:test";
test.todo("unimplemented feature", () => {
expect(Bun.isAwesome()).toBe(true);
});
```
---
The output of `bun test` indicates how many `todo` tests were encountered.
```sh
$ bun test
test.test.ts:
✓ add [0.03ms]
✓ multiply [0.02ms]
✎ unimplemented feature
2 pass
1 todo
0 fail
2 expect() calls
Ran 3 tests across 1 files. [74.00ms]
```
---
Note that `todo` tests _are executed_ by the test runner! They are _expected to fail_; if a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
```sh
$ bun test
$ echo $?
1 # this is the exit code of the previous command
```
---
See also:
- [Skip a test](/guides/test/skip-tests)
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)

View File

@@ -0,0 +1,50 @@
---
name: Update snapshots in `bun test`
---
Bun's test runner supports Jest-style snapshot testing via `.toMatchSnapshot()`.
{% callout %}
The `.toMatchInlineSnapshot()` method is not yet supported.
{% /callout %}
```ts#snap.test.ts
import { test, expect } from "bun:test";
test("snapshot", () => {
expect({ foo: "bar" }).toMatchSnapshot();
});
```
---
The first time this test is executed, Bun will write a snapshot file to disk in a directory called `__snapshots__` that lives alongside the test file.
```txt
test
├── __snapshots__
│   └── snap.test.ts.snap
└── snap.test.ts
```
---
To regenerate snapshots, use the `--update-snapshots` flag.
```sh
$ bun test --update-snapshots
bun test v0.8.0 (9c68abdb)
test/snap.test.ts:
✓ snapshot [0.86ms]
1 pass
0 fail
snapshots: +1 added # the snapshot was regenerated
1 expect() calls
Ran 1 tests across 1 files. [102.00ms]
```
---
See [Docs > Test Runner > Snapshots](/docs/test/mocks) for complete documentation on mocking with the Bun test runner.

View File

@@ -0,0 +1,19 @@
---
name: Run tests in watch mode with Bun
---
Use the `--watch` flag to run your tests in watch mode.
```sh
$ bun test --watch
```
---
This will restart the running Bun process whenever a file change is detected. It's fast. In this example, the editor is configured to save the file on every keystroke.
{% image src="https://github.com/oven-sh/bun/assets/3084745/dc49a36e-ba82-416f-b960-1c883a924248" caption="Running tests in watch mode in Bun" /%}
---
See [Docs > Test Runner](/docs/cli/test) for complete documentation on the test runner.

View File

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

View File

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

View File

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

View File

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

90
docs/runtime/debugger.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,87 @@
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Server } from "node:net";
import { createServer } from "node:net";
import { EventEmitter } from "node:events";
const isDebug = process.env.NODE_ENV === "development";
export type UnixSignalEventMap = {
"Signal.listening": [string];
"Signal.error": [Error];
"Signal.received": [string];
"Signal.closed": [];
};
/**
* Starts a server that listens for signals on a UNIX domain socket.
*/
export class UnixSignal extends EventEmitter<UnixSignalEventMap> {
#path: string;
#server: Server;
#ready: Promise<void>;
constructor(path?: string) {
super();
this.#path = path ? parseUnixPath(path) : randomUnixPath();
this.#server = createServer();
this.#server.on("listening", () => this.emit("Signal.listening", this.#path));
this.#server.on("error", error => this.emit("Signal.error", error));
this.#server.on("close", () => this.emit("Signal.closed"));
this.#server.on("connection", socket => {
socket.on("data", data => {
this.emit("Signal.received", data.toString());
});
});
this.#ready = new Promise((resolve, reject) => {
this.#server.on("listening", resolve);
this.#server.on("error", reject);
});
this.#server.listen(this.#path);
}
emit<E extends keyof UnixSignalEventMap>(event: E, ...args: UnixSignalEventMap[E]): boolean {
if (isDebug) {
console.log(event, ...args);
}
return super.emit(event, ...args);
}
/**
* The path to the UNIX domain socket.
*/
get url(): string {
return `unix://${this.#path}`;
}
/**
* Resolves when the server is listening or rejects if an error occurs.
*/
get ready(): Promise<void> {
return this.#ready;
}
/**
* Closes the server.
*/
close(): void {
this.#server.close();
}
}
function randomUnixPath(): string {
return join(tmpdir(), `${Math.random().toString(36).slice(2)}.sock`);
}
function parseUnixPath(path: string): string {
if (path.startsWith("/")) {
return path;
}
try {
const { pathname } = new URL(path);
return pathname;
} catch {
throw new Error(`Invalid UNIX path: ${path}`);
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "nodenext",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"inlineSourceMap": true,
"allowJs": true,
"outDir": "dist",
},
"include": ["src", "scripts", "../bun-types/index.d.ts", "../bun-inspector-protocol/src"]
}

View File

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

View File

@@ -1,4 +1,4 @@
# web-inspector-bun
# bun-devtools-frontend
This is the WebKit Web Inspector bundled as standalone assets.

View File

@@ -17,7 +17,12 @@ try {
.on("script", {
element(element) {
const src = element.getAttribute("src");
if (src && !src?.includes("External") && !src?.includes("WebKitAdditions")) {
if (
src &&
!src?.includes("External") &&
!src?.includes("WebKitAdditions") &&
!src.includes("DOMUtilities.js")
) {
if (scriptsToBundle.length === 0) {
element.replace("<script>var WI = {};\n</script>", { html: true });
} else {
@@ -35,7 +40,40 @@ try {
})
.on("head", {
element(element) {
element.prepend(` <base href="/" /> `, { html: true });
element.prepend(
`
<script type="text/javascript">
if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
};
}
</script>
<base href="/" /> `,
{ html: true },
);
element.append(
`
@@ -46,7 +84,7 @@ try {
</style>
<script src="${jsReplacementId}"></script>
<script>
<script type="text/javascript">
WI.sharedApp = new WI.AppController;
WI.sharedApp.initialize();
</script>`,
@@ -71,6 +109,9 @@ try {
const javascript = scriptsToBundle.map(a => `import '${join(basePath, a)}';`).join("\n") + "\n";
// const css = stylesToBundle.map(a => `@import "${join(basePath, a)}";`).join("\n") + "\n";
await Bun.write(join(import.meta.dir, "out/manifest.js"), javascript);
mkdirSync("out/WebKitAdditions/WebInspectorUI/", { recursive: true });
await Bun.write(join(import.meta.dir, "out/WebKitAdditions/WebInspectorUI/WebInspectorUIAdditions.js"), "");
await Bun.write(join(import.meta.dir, "out/WebKitAdditions/WebInspectorUI/WebInspectorUIAdditions.css"), "");
// await Bun.write(join(import.meta.dir, "manifest.css"), css);
const jsBundle = await Bun.build({
entrypoints: [join(import.meta.dir, "out/manifest.js")],

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
import type { EventEmitter } from "node:events";
import type { JSC } from "../protocol";
export type InspectorEventMap = {
[E in keyof JSC.EventMap]: [JSC.EventMap[E]];
} & {
"Inspector.connecting": [string];
"Inspector.connected": [];
"Inspector.disconnected": [Error | undefined];
"Inspector.error": [Error];
"Inspector.pendingRequest": [JSC.Request];
"Inspector.request": [JSC.Request];
"Inspector.response": [JSC.Response];
"Inspector.event": [JSC.Event];
};
/**
* A client that can send and receive messages to/from a debugger.
*/
export interface Inspector extends EventEmitter<InspectorEventMap> {
/**
* Starts the inspector.
*/
start(...args: unknown[]): Promise<boolean>;
/**
* Sends a request to the debugger.
*/
send<M extends keyof JSC.RequestMap & keyof JSC.ResponseMap>(
method: M,
params?: JSC.RequestMap[M],
): Promise<JSC.ResponseMap[M]>;
/**
* If the inspector is closed.
*/
get closed(): boolean;
/**
* Closes the inspector.
*/
close(...args: unknown[]): void;
}

View File

@@ -0,0 +1,239 @@
import type { Inspector, InspectorEventMap } from ".";
import type { JSC } from "../protocol";
import { EventEmitter } from "node:events";
import { WebSocket } from "ws";
/**
* An inspector that communicates with a debugger over a WebSocket.
*/
export class WebSocketInspector extends EventEmitter<InspectorEventMap> implements Inspector {
#url?: string;
#webSocket?: WebSocket;
#ready: Promise<boolean> | undefined;
#requestId: number;
#pendingRequests: JSC.Request[];
#pendingResponses: Map<number, (result: unknown) => void>;
constructor(url?: string | URL) {
super();
this.#url = url ? String(url) : undefined;
this.#requestId = 1;
this.#pendingRequests = [];
this.#pendingResponses = new Map();
}
get url(): string {
return this.#url!;
}
async start(url?: string | URL): Promise<boolean> {
if (url) {
this.#url = String(url);
}
if (!this.#url) {
this.emit("Inspector.error", new Error("Inspector needs a URL, but none was provided"));
return false;
}
return this.#connect(this.#url);
}
async #connect(url: string): Promise<boolean> {
if (this.#ready) {
return this.#ready;
}
this.close(1001, "Restarting...");
this.emit("Inspector.connecting", url);
let webSocket: WebSocket;
try {
// @ts-expect-error: Support both Bun and Node.js version of `headers`.
webSocket = new WebSocket(url, {
headers: {
"Ref-Event-Loop": "1",
},
finishRequest: (request: import("http").ClientRequest) => {
request.setHeader("Ref-Event-Loop", "1");
request.end();
},
});
} catch (cause) {
this.#close(unknownToError(cause));
return false;
}
webSocket.addEventListener("open", () => {
this.emit("Inspector.connected");
for (const request of this.#pendingRequests) {
if (this.#send(request)) {
this.emit("Inspector.request", request);
}
}
this.#pendingRequests.length = 0;
});
webSocket.addEventListener("message", ({ data }) => {
if (typeof data === "string") {
this.#accept(data);
}
});
webSocket.addEventListener("error", event => {
this.#close(unknownToError(event));
});
webSocket.addEventListener("unexpected-response", () => {
this.#close(new Error("WebSocket upgrade failed"));
});
webSocket.addEventListener("close", ({ code, reason }) => {
if (code === 1001 || code === 1006) {
this.#close();
return;
}
this.#close(new Error(`WebSocket closed: ${code} ${reason}`.trimEnd()));
});
this.#webSocket = webSocket;
const ready = new Promise<boolean>(resolve => {
webSocket.addEventListener("open", () => resolve(true));
webSocket.addEventListener("close", () => resolve(false));
webSocket.addEventListener("error", () => resolve(false));
}).finally(() => {
this.#ready = undefined;
});
this.#ready = ready;
return ready;
}
send<M extends keyof JSC.RequestMap & keyof JSC.ResponseMap>(
method: M,
params?: JSC.RequestMap[M] | undefined,
): Promise<JSC.ResponseMap[M]> {
const id = this.#requestId++;
const request = {
id,
method,
params: params ?? {},
};
return new Promise((resolve, reject) => {
const done = (result: any) => {
this.#pendingResponses.delete(id);
if (result instanceof Error) {
reject(result);
} else {
resolve(result);
}
};
this.#pendingResponses.set(id, done);
if (this.#send(request)) {
this.emit("Inspector.request", request);
} else {
this.emit("Inspector.pendingRequest", request);
}
});
}
#send(request: JSC.Request): boolean {
if (this.#webSocket) {
const { readyState } = this.#webSocket!;
if (readyState === WebSocket.OPEN) {
this.#webSocket.send(JSON.stringify(request));
return true;
}
}
if (!this.#pendingRequests.includes(request)) {
this.#pendingRequests.push(request);
}
return false;
}
#accept(message: string): void {
let data: JSC.Event | JSC.Response;
try {
data = JSON.parse(message);
} catch (cause) {
this.emit("Inspector.error", new Error(`Failed to parse message: ${message}`, { cause }));
return;
}
if (!("id" in data)) {
this.emit("Inspector.event", data);
const { method, params } = data;
this.emit(method, params);
return;
}
this.emit("Inspector.response", data);
const { id } = data;
const resolve = this.#pendingResponses.get(id);
if (!resolve) {
this.emit("Inspector.error", new Error(`Failed to find matching request for ID: ${id}`));
return;
}
this.#pendingResponses.delete(id);
if ("error" in data) {
const { error } = data;
const { message } = error;
resolve(new Error(message));
} else {
const { result } = data;
resolve(result);
}
}
get closed(): boolean {
if (!this.#webSocket) {
return true;
}
const { readyState } = this.#webSocket;
switch (readyState) {
case WebSocket.CLOSED:
case WebSocket.CLOSING:
return true;
}
return false;
}
close(code?: number, reason?: string): void {
this.#webSocket?.close(code ?? 1001, reason);
}
#close(error?: Error): void {
for (const resolve of this.#pendingResponses.values()) {
resolve(error ?? new Error("WebSocket closed"));
}
this.#pendingResponses.clear();
if (error) {
this.emit("Inspector.error", error);
}
this.emit("Inspector.disconnected", error);
}
}
function unknownToError(input: unknown): Error {
if (input instanceof Error) {
return input;
}
if (typeof input === "object" && input !== null && "message" in input) {
const { message } = input;
return new Error(`${message}`);
}
return new Error(`${input}`);
}

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
import type { JSC } from "../protocol";
export function remoteObjectToString(remoteObject: JSC.Runtime.RemoteObject, topLevel?: boolean): string {
const { type, subtype, value, description, className, preview } = remoteObject;
switch (type) {
case "undefined":
return "undefined";
case "boolean":
case "number":
return description ?? JSON.stringify(value);
case "string":
if (topLevel) {
return String(value ?? description);
}
return JSON.stringify(value ?? description);
case "symbol":
case "bigint":
return description!;
case "function":
return description!.replace("function", "ƒ") || "ƒ";
}
switch (subtype) {
case "null":
return "null";
case "regexp":
case "date":
case "error":
return description!;
}
if (preview) {
return objectPreviewToString(preview);
}
if (className) {
return className;
}
return description || "Object";
}
export function objectPreviewToString(objectPreview: JSC.Runtime.ObjectPreview): string {
const { type, subtype, entries, properties, overflow, description, size } = objectPreview;
if (type !== "object") {
return remoteObjectToString(objectPreview);
}
let items: string[];
if (entries) {
items = entries.map(entryPreviewToString).sort();
} else if (properties) {
if (isIndexed(subtype)) {
items = properties.map(indexedPropertyPreviewToString).sort();
} else {
items = properties.map(namedPropertyPreviewToString).sort();
}
} else {
items = ["…"];
}
if (overflow) {
items.push("…");
}
let label: string;
if (description === "Object") {
label = "";
} else if (size === undefined) {
label = description!;
} else {
label = `${description}(${size})`;
}
if (!items.length) {
return label || "{}";
}
if (label) {
label += " ";
}
if (isIndexed(subtype)) {
return `${label}[${items.join(", ")}]`;
}
return `${label}{${items.join(", ")}}`;
}
function propertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
const { type, value, ...preview } = propertyPreview;
if (type === "accessor") {
return "ƒ";
}
return remoteObjectToString({ ...preview, type, description: value });
}
function entryPreviewToString(entryPreview: JSC.Runtime.EntryPreview): string {
const { key, value } = entryPreview;
if (key) {
return `${objectPreviewToString(key)} => ${objectPreviewToString(value)}`;
}
return objectPreviewToString(value);
}
function namedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
const { name, valuePreview } = propertyPreview;
if (valuePreview) {
return `${name}: ${objectPreviewToString(valuePreview)}`;
}
return `${name}: ${propertyPreviewToString(propertyPreview)}`;
}
function indexedPropertyPreviewToString(propertyPreview: JSC.Runtime.PropertyPreview): string {
const { valuePreview } = propertyPreview;
if (valuePreview) {
return objectPreviewToString(valuePreview);
}
return propertyPreviewToString(propertyPreview);
}
function isIndexed(type?: JSC.Runtime.RemoteObject["subtype"]): boolean {
return type === "array" || type === "set" || type === "weakset";
}

View File

@@ -0,0 +1,190 @@
import { describe, test, expect, mock, beforeAll, afterAll } from "bun:test";
import { WebSocketInspector } from "../../src/inspector/websocket";
import type { Server } from "bun";
import { serve } from "bun";
let server: Server;
let url: URL;
describe("WebSocketInspector", () => {
test("fails without a URL", () => {
const ws = new WebSocketInspector();
const fn = mock(error => {
expect(error).toBeInstanceOf(Error);
});
ws.on("Inspector.error", fn);
expect(ws.start()).resolves.toBeFalse();
expect(fn).toHaveBeenCalled();
});
test("fails with invalid URL", () => {
const ws = new WebSocketInspector("notaurl");
const fn = mock(error => {
expect(error).toBeInstanceOf(Error);
});
ws.on("Inspector.error", fn);
expect(ws.start()).resolves.toBeFalse();
expect(fn).toHaveBeenCalled();
});
test("fails with valid URL but no server", () => {
const ws = new WebSocketInspector("ws://localhost:0/doesnotexist/");
const fn = mock(error => {
expect(error).toBeInstanceOf(Error);
});
ws.on("Inspector.error", fn);
expect(ws.start()).resolves.toBeFalse();
expect(fn).toHaveBeenCalled();
});
test("fails with invalid upgrade response", () => {
const ws = new WebSocketInspector(new URL("/", url));
const fn = mock(error => {
expect(error).toBeInstanceOf(Error);
});
ws.on("Inspector.error", fn);
expect(ws.start()).resolves.toBeFalse();
expect(fn).toHaveBeenCalled();
});
test("can connect to a server", () => {
const ws = new WebSocketInspector(url);
const fn = mock(() => {
expect(ws.closed).toBe(false);
});
ws.on("Inspector.connected", fn);
expect(ws.start()).resolves.toBeTrue();
expect(fn).toHaveBeenCalled();
ws.close();
});
test("can disconnect from a server", () => {
const ws = new WebSocketInspector(url);
const fn = mock(() => {
expect(ws.closed).toBeTrue();
});
ws.on("Inspector.disconnected", fn);
expect(ws.start()).resolves.toBeTrue();
ws.close();
expect(fn).toHaveBeenCalled();
});
test("can connect to a server multiple times", () => {
const ws = new WebSocketInspector(url);
const fn0 = mock(() => {
expect(ws.closed).toBeFalse();
});
ws.on("Inspector.connected", fn0);
const fn1 = mock(() => {
expect(ws.closed).toBeTrue();
});
ws.on("Inspector.disconnected", fn1);
for (let i = 0; i < 3; i++) {
expect(ws.start()).resolves.toBeTrue();
ws.close();
}
expect(fn0).toHaveBeenCalledTimes(3);
expect(fn1).toHaveBeenCalledTimes(3);
});
test("can send a request", () => {
const ws = new WebSocketInspector(url);
const fn0 = mock(request => {
expect(request).toStrictEqual({
id: 1,
method: "Debugger.setPauseOnAssertions",
params: {
enabled: true,
},
});
});
ws.on("Inspector.request", fn0);
const fn1 = mock(response => {
expect(response).toStrictEqual({
id: 1,
result: {
ok: true,
},
});
});
ws.on("Inspector.response", fn1);
expect(ws.start()).resolves.toBeTrue();
expect(ws.send("Debugger.setPauseOnAssertions", { enabled: true })).resolves.toMatchObject({ ok: true });
expect(fn0).toHaveBeenCalled();
expect(fn1).toHaveBeenCalled();
ws.close();
});
test("can send a request before connecting", () => {
const ws = new WebSocketInspector(url);
const fn0 = mock(request => {
expect(request).toStrictEqual({
id: 1,
method: "Runtime.enable",
params: {},
});
});
ws.on("Inspector.pendingRequest", fn0);
ws.on("Inspector.request", fn0);
const fn1 = mock(response => {
expect(response).toStrictEqual({
id: 1,
result: {
ok: true,
},
});
});
ws.on("Inspector.response", fn1);
const request = ws.send("Runtime.enable");
expect(ws.start()).resolves.toBe(true);
expect(request).resolves.toMatchObject({ ok: true });
expect(fn0).toHaveBeenCalledTimes(2);
expect(fn1).toHaveBeenCalled();
ws.close();
});
test("can receive an event", () => {
const ws = new WebSocketInspector(url);
const fn = mock(event => {
expect(event).toStrictEqual({
method: "Debugger.scriptParsed",
params: {
scriptId: "1",
},
});
});
ws.on("Inspector.event", fn);
expect(ws.start()).resolves.toBeTrue();
expect(ws.send("Debugger.enable")).resolves.toMatchObject({ ok: true });
expect(fn).toHaveBeenCalled();
ws.close();
});
});
beforeAll(() => {
server = serve({
port: 0,
fetch(request, server) {
if (request.url.endsWith("/ws") && server.upgrade(request)) {
return;
}
return new Response();
},
websocket: {
message(ws, message) {
const { id, method } = JSON.parse(String(message));
ws.send(JSON.stringify({ id, result: { ok: true } }));
if (method === "Debugger.enable") {
ws.send(JSON.stringify({ method: "Debugger.scriptParsed", params: { scriptId: "1" } }));
}
},
},
});
const { hostname, port } = server;
url = new URL(`ws://${hostname}:${port}/ws`);
});
afterAll(() => {
server?.stop(true);
});

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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