mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 15:38:46 +00:00
Compare commits
141 Commits
don/fix/na
...
ben/shell-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac11340a3 | ||
|
|
f4937678e4 | ||
|
|
b124ba056c | ||
|
|
0237baee92 | ||
|
|
cc481465b5 | ||
|
|
7a033e49c5 | ||
|
|
59551ebc79 | ||
|
|
ad766f2402 | ||
|
|
efabdcbe1f | ||
|
|
174a0f70df | ||
|
|
deeebf0538 | ||
|
|
63bc08e3ca | ||
|
|
d09daca867 | ||
|
|
b3edef5989 | ||
|
|
baca1f4634 | ||
|
|
8bb6dd3cee | ||
|
|
215da32660 | ||
|
|
5c6e20aeb4 | ||
|
|
1060558456 | ||
|
|
a2d028462b | ||
|
|
ec4b9d198b | ||
|
|
fd9a5ea668 | ||
|
|
e8249d885c | ||
|
|
ac8fb0e1f5 | ||
|
|
39fdabc364 | ||
|
|
144a9c2f6d | ||
|
|
caff4e6008 | ||
|
|
1322adbb16 | ||
|
|
11e5a6a2c7 | ||
|
|
a130816004 | ||
|
|
f4e0684603 | ||
|
|
2db9ab4c72 | ||
|
|
2f48282cbd | ||
|
|
1574df835e | ||
|
|
04973a1520 | ||
|
|
4e3c9bc1d1 | ||
|
|
d4c1114f9d | ||
|
|
b829590356 | ||
|
|
04f985523b | ||
|
|
32e6049be0 | ||
|
|
94274b7198 | ||
|
|
46246bb526 | ||
|
|
09ab840114 | ||
|
|
211824bb3e | ||
|
|
b7e5a38975 | ||
|
|
cbeffe1b48 | ||
|
|
a8c8fa15b9 | ||
|
|
032f99285c | ||
|
|
db5b915559 | ||
|
|
445fe2ac4a | ||
|
|
82c26f0a58 | ||
|
|
943dc53a3b | ||
|
|
61edc58362 | ||
|
|
7a35567b45 | ||
|
|
47f9bb84e8 | ||
|
|
b02156e793 | ||
|
|
6aa62fe4bf | ||
|
|
2206c14314 | ||
|
|
5167ed20f9 | ||
|
|
0bb0bf7c08 | ||
|
|
4978fb8baf | ||
|
|
c2a9cf5bbd | ||
|
|
d474b54ad1 | ||
|
|
9503f7b0b9 | ||
|
|
01d932d7e4 | ||
|
|
5c8da4436c | ||
|
|
d862966631 | ||
|
|
f6c3b92f73 | ||
|
|
363bdf5c6c | ||
|
|
04703bd3cc | ||
|
|
8c4d3ff801 | ||
|
|
fb6f7e43d8 | ||
|
|
78f4b20600 | ||
|
|
bda1ad192d | ||
|
|
8f7143882e | ||
|
|
8f888be7d5 | ||
|
|
84ad89cc95 | ||
|
|
18440d4b11 | ||
|
|
7f0b117496 | ||
|
|
94e5071947 | ||
|
|
8c32eb8354 | ||
|
|
3b956757d9 | ||
|
|
fbc4aa480b | ||
|
|
dc5fae461d | ||
|
|
a3ea521c98 | ||
|
|
27c90786ca | ||
|
|
226275c26d | ||
|
|
b082572dcb | ||
|
|
275a34b014 | ||
|
|
aef6a173ee | ||
|
|
1b271fd45e | ||
|
|
6e45e3bf1e | ||
|
|
1e6fdc9f15 | ||
|
|
9b515d74aa | ||
|
|
7c6d9cac50 | ||
|
|
4811899bc5 | ||
|
|
e284c500a4 | ||
|
|
86a4f306ee | ||
|
|
92a91ef2fd | ||
|
|
6b2486a95d | ||
|
|
0efc4eaf97 | ||
|
|
f3d18fc587 | ||
|
|
9cf9a26330 | ||
|
|
5ae28d27a0 | ||
|
|
1de31292fb | ||
|
|
fcdddf6425 | ||
|
|
0622ad57b4 | ||
|
|
a5fb10981b | ||
|
|
cc04d51dc3 | ||
|
|
4d0e9a968b | ||
|
|
99a3b01bd0 | ||
|
|
32c17d8656 | ||
|
|
527412626a | ||
|
|
0d1a00fa0f | ||
|
|
5e4ebf4381 | ||
|
|
31bd9a3ac0 | ||
|
|
636d2459bb | ||
|
|
b89b5d5710 | ||
|
|
f0e7251b61 | ||
|
|
f29e912a91 | ||
|
|
ef8bd44e98 | ||
|
|
cdf62b35ff | ||
|
|
59f3d1ca31 | ||
|
|
f1a5e78033 | ||
|
|
e3e4264208 | ||
|
|
0b6aa96672 | ||
|
|
e01548c6e9 | ||
|
|
3711280d44 | ||
|
|
78e52006c5 | ||
|
|
905fbee768 | ||
|
|
0405e1451c | ||
|
|
b418b7794a | ||
|
|
600343fff7 | ||
|
|
43367817a4 | ||
|
|
7b65ca2a71 | ||
|
|
29c737b2b9 | ||
|
|
b4f34b03d6 | ||
|
|
b44769c751 | ||
|
|
10663d7912 | ||
|
|
a2c64ad706 | ||
|
|
79afefa488 |
@@ -621,6 +621,21 @@ function getReleaseStep(buildPlatforms, options) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Step}
|
||||
*/
|
||||
function getBenchmarkStep() {
|
||||
return {
|
||||
key: "benchmark",
|
||||
label: "📊",
|
||||
agents: {
|
||||
queue: "build-zig",
|
||||
},
|
||||
command: "bun .buildkite/scripts/upload-benchmark.ts",
|
||||
depends_on: [`linux-x64-build-bun`],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} Pipeline
|
||||
* @property {Step[]} [steps]
|
||||
@@ -1099,6 +1114,8 @@ async function getPipeline(options = {}) {
|
||||
steps.push(getReleaseStep(buildPlatforms, options));
|
||||
}
|
||||
|
||||
steps.push(getBenchmarkStep());
|
||||
|
||||
/** @type {Map<string, GroupStep>} */
|
||||
const stepsByGroup = new Map();
|
||||
|
||||
|
||||
7
.buildkite/scripts/upload-benchmark.ts
Normal file
7
.buildkite/scripts/upload-benchmark.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { getCommit, getSecret } from "../../scripts/utils.mjs";
|
||||
|
||||
console.log("Submitting...");
|
||||
const response = await fetch(getSecret("BENCHMARK_URL") + "?tag=_&commit=" + getCommit() + "&artifact_url=_", {
|
||||
method: "POST",
|
||||
});
|
||||
console.log("Got status " + response.status);
|
||||
@@ -158,25 +158,36 @@ function upload_s3_file() {
|
||||
run_command aws --endpoint-url="$AWS_ENDPOINT" s3 cp "$file" "s3://$AWS_BUCKET/$folder/$file"
|
||||
}
|
||||
|
||||
function send_bench_webhook() {
|
||||
if [ -z "$BENCHMARK_URL" ]; then
|
||||
echo "error: \$BENCHMARK_URL is not set"
|
||||
# exit 1 # TODO: this isn't live yet
|
||||
function send_discord_announcement() {
|
||||
local value=$(buildkite-agent secret get "BUN_ANNOUNCE_CANARY_WEBHOOK_URL")
|
||||
if [ -z "$value" ]; then
|
||||
echo "warn: BUN_ANNOUNCE_CANARY_WEBHOOK_URL not set, skipping Discord announcement"
|
||||
return
|
||||
fi
|
||||
|
||||
local tag="$1"
|
||||
local version="$1"
|
||||
local commit="$BUILDKITE_COMMIT"
|
||||
local artifact_path="${commit}"
|
||||
local short_sha="${commit:0:7}"
|
||||
local commit_url="https://github.com/oven-sh/bun/commit/$commit"
|
||||
|
||||
if [ "$tag" == "canary" ]; then
|
||||
artifact_path="${commit}-canary"
|
||||
if [ "$version" == "canary" ]; then
|
||||
local json_payload=$(cat <<EOF
|
||||
{
|
||||
"embeds": [{
|
||||
"title": "New Bun Canary now available",
|
||||
"description": "A new canary build of Bun has been automatically uploaded ([${short_sha}](${commit_url})). To upgrade, run:\n\n\`\`\`shell\nbun upgrade --canary\n\`\`\`\nCommit: \`${commit}\`",
|
||||
"color": 16023551,
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
}]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
curl -H "Content-Type: application/json" \
|
||||
-d "$json_payload" \
|
||||
-sf \
|
||||
"$value" >/dev/null
|
||||
fi
|
||||
|
||||
local artifact_url="https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/$artifact_path/bun-linux-x64.zip"
|
||||
local webhook_url="$BENCHMARK_URL?tag=$tag&commit=$commit&artifact_url=$artifact_url"
|
||||
|
||||
curl -X POST "$webhook_url"
|
||||
}
|
||||
|
||||
function create_release() {
|
||||
@@ -227,7 +238,7 @@ function create_release() {
|
||||
|
||||
update_github_release "$tag"
|
||||
create_sentry_release "$tag"
|
||||
send_bench_webhook "$tag"
|
||||
send_discord_announcement "$tag"
|
||||
}
|
||||
|
||||
function assert_canary() {
|
||||
|
||||
@@ -268,16 +268,16 @@ If there's a class, prototype, and constructor:
|
||||
2. Initialize the class structure in [ZigGlobalObject.cpp](mdc:src/bun.js/bindings/ZigGlobalObject.cpp) in `void GlobalObject::finishCreation(VM& vm)`
|
||||
3. Visit the class structure in visitChildren in [ZigGlobalObject.cpp](mdc:src/bun.js/bindings/ZigGlobalObject.cpp) in `void GlobalObject::visitChildrenImpl`
|
||||
|
||||
```c++
|
||||
|
||||
```c++#ZigGlobalObject.cpp
|
||||
void GlobalObject::finishCreation(VM& vm) {
|
||||
// ...
|
||||
m_JSStatsBigIntClassStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
// Call the function to initialize our class structure.
|
||||
Bun::initJSBigIntStatsClassStructure(init);
|
||||
});
|
||||
```
|
||||
|
||||
If there's only a class, use `JSC::LazyProperty<JSGlobalObject, Structure>` instead of `JSC::LazyClassStructure`.
|
||||
|
||||
Then, implement the function that creates the structure:
|
||||
```c++
|
||||
void setupX509CertificateClassStructure(LazyClassStructure::Initializer& init)
|
||||
@@ -296,6 +296,36 @@ void setupX509CertificateClassStructure(LazyClassStructure::Initializer& init)
|
||||
}
|
||||
```
|
||||
|
||||
If there's only a class, use `JSC::LazyProperty<JSGlobalObject, Structure>` instead of `JSC::LazyClassStructure`:
|
||||
|
||||
1. Add the `JSC::LazyProperty<JSGlobalObject, Structure>` to @ZigGlobalObject.h
|
||||
2. Initialize the class structure in @ZigGlobalObject.cpp in `void GlobalObject::finishCreation(VM& vm)`
|
||||
3. Visit the lazy property in visitChildren in @ZigGlobalObject.cpp in `void GlobalObject::visitChildrenImpl`
|
||||
void GlobalObject::finishCreation(VM& vm) {
|
||||
// ...
|
||||
this.m_myLazyProperty.initLater([](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
|
||||
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
|
||||
});
|
||||
```
|
||||
|
||||
Then, implement the function that creates the structure:
|
||||
```c++
|
||||
Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalObject)
|
||||
{
|
||||
// If there is a prototype:
|
||||
auto* prototypeStructure = JSX509CertificatePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
|
||||
auto* prototype = JSX509CertificatePrototype::create(init.vm, init.global, prototypeStructure);
|
||||
|
||||
// If there is no prototype or it only has
|
||||
|
||||
auto* structure = JSX509Certificate::createStructure(init.vm, init.global, prototype);
|
||||
init.setPrototype(prototype);
|
||||
init.setStructure(structure);
|
||||
init.setConstructor(constructor);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Then, use the structure by calling `globalObject.m_myStructureName.get(globalObject)`
|
||||
|
||||
```C++
|
||||
|
||||
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: "1.2.0"
|
||||
bun-version: "1.2.3"
|
||||
- name: Install Dependencies
|
||||
run: bun install
|
||||
- name: Sign Release
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: "1.2.0"
|
||||
bun-version: "1.2.3"
|
||||
- name: Install Dependencies
|
||||
run: bun install
|
||||
- name: Release
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
if: ${{ env.BUN_VERSION != 'canary' }}
|
||||
uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: "1.2.0"
|
||||
bun-version: "1.2.3"
|
||||
- name: Setup Bun
|
||||
if: ${{ env.BUN_VERSION == 'canary' }}
|
||||
uses: ./.github/actions/setup-bun
|
||||
@@ -167,12 +167,16 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout (DefinitelyTyped)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DefinitelyTyped/DefinitelyTyped
|
||||
- name: Checkout (bun)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: bun
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
uses: ./bun/.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: "1.2.0"
|
||||
- id: bun-version
|
||||
@@ -227,7 +231,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Docker emulator
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- id: buildx
|
||||
name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -235,7 +239,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- id: metadata
|
||||
name: Setup Docker metadata
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: oven/bun
|
||||
flavor: |
|
||||
@@ -252,7 +256,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Push to Docker
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./dockerhub/${{ matrix.dir || matrix.variant }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -181,3 +181,5 @@ tmp
|
||||
.buildkite/ci.yml
|
||||
*.sock
|
||||
scratch*.{js,ts,tsx,cjs,mjs}
|
||||
|
||||
*.bun-build
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -36,6 +36,7 @@
|
||||
// "zig.buildOnSave": true,
|
||||
"zig.buildFilePath": "${workspaceFolder}/build.zig",
|
||||
"zig.path": "${workspaceFolder}/vendor/zig/zig.exe",
|
||||
"zig.zls.path": "${workspaceFolder}/vendor/zig/zls.exe",
|
||||
"zig.formattingProvider": "zls",
|
||||
"zig.zls.enableInlayHints": false,
|
||||
"[zig]": {
|
||||
|
||||
@@ -67,7 +67,7 @@ $ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 18 all
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
$ sudo pacman -S llvm clang lld
|
||||
$ sudo pacman -S llvm clang18 lld
|
||||
```
|
||||
|
||||
```bash#Fedora
|
||||
|
||||
4
Makefile
4
Makefile
@@ -1154,7 +1154,7 @@ jsc-copy-headers:
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/StubInfoSummary.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/StubInfoSummary.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/CommonSlowPaths.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/CommonSlowPaths.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/DirectArguments.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/DirectArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/GenericArguments.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GenericArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/GenericArgumentsImpl.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GenericArgumentsImpl.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/SamplingProfiler.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/SamplingProfiler.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/ScopedArguments.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ScopedArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/JSLexicalEnvironment.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSLexicalEnvironment.h
|
||||
@@ -1205,7 +1205,7 @@ jsc-copy-headers-debug:
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/StubInfoSummary.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/StubInfoSummary.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/CommonSlowPaths.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/CommonSlowPaths.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/DirectArguments.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/DirectArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/GenericArguments.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GenericArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/GenericArgumentsImpl.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GenericArgumentsImpl.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/SamplingProfiler.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/SamplingProfiler.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/ScopedArguments.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ScopedArguments.h
|
||||
cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/JSLexicalEnvironment.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSLexicalEnvironment.h
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import micromatch from "micromatch";
|
||||
import { Glob } from "bun";
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
const Glob = typeof Bun !== "undefined" ? Bun.Glob : undefined;
|
||||
const doMatch = typeof Bun === "undefined" ? micromatch.isMatch : (a, b) => new Glob(b).match(a);
|
||||
|
||||
bench((Glob ? "Bun.Glob - " : "micromatch - ") + "**/*.js", () => {
|
||||
doMatch("foo/bar.js", "**/*.js");
|
||||
});
|
||||
function benchPattern(name, glob, pattern) {
|
||||
bench(name, () => {
|
||||
new Glob(glob).match(pattern);
|
||||
})
|
||||
}
|
||||
|
||||
benchPattern("max-depth" , "1{2,3{4,5{6,7{8,9{a,b{c,d{e,f{g,h{i,j{k,l}}}}}}}}}}m", "13579bdfhjlm");
|
||||
benchPattern("non-ascii", "😎/¢£.{ts,tsx,js,jsx}", "😎/¢£.jsx");
|
||||
benchPattern("utf8", "フォルダ/**/*", "フォルダ/aaa.js");
|
||||
benchPattern("non-ascii+max-depth" , "1{2,3{4,5{6,7{8,😎{a,b{c,d{e,f{g,h{i,j{k,l}}}}}}}}}}m", "1357😎bdfhjlm");
|
||||
benchPattern("pretty-average", "test/{foo/**,bar}/baz", "test/bar/baz");
|
||||
benchPattern("pretty-average-2", "a/**/c/*.md", "a/bb.bb/aa/b.b/aa/c/xyz.md");
|
||||
benchPattern("pretty-average-3", "a/b/**/c{d,e}/**/xyz.md", "a/b/cd/xyz.md");
|
||||
benchPattern("pretty-average-4", "foo/bar/**/one/**/*.*", "foo/bar/baz/one/two/three/image.png");
|
||||
benchPattern("long-pretty-average", "some/**/needle.{js,tsx,mdx,ts,jsx,txt}", "some/a/bigger/path/to/the/crazy/needle.txt");
|
||||
benchPattern("brackets-lots", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", "foo-bar");
|
||||
|
||||
bench((Glob ? "Bun.Glob - " : "micromatch - ") + "*.js", () => {
|
||||
doMatch("bar.js", "*.js");
|
||||
});
|
||||
|
||||
await run({
|
||||
avg: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
avg: true,
|
||||
})
|
||||
|
||||
19
bench/glob/micromatch.mjs
Normal file
19
bench/glob/micromatch.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import micromatch from "micromatch";
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
const Glob = typeof Bun !== "undefined" ? Bun.Glob : undefined;
|
||||
const doMatch = typeof Bun === "undefined" ? micromatch.isMatch : (a, b) => new Glob(b).match(a);
|
||||
|
||||
bench((Glob ? "Bun.Glob - " : "micromatch - ") + "**/*.js", () => {
|
||||
doMatch("foo/bar.js", "**/*.js");
|
||||
});
|
||||
|
||||
bench((Glob ? "Bun.Glob - " : "micromatch - ") + "*.js", () => {
|
||||
doMatch("bar.js", "*.js");
|
||||
});
|
||||
|
||||
await run({
|
||||
avg: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
17
bench/snippets/buffer-includes.js
Normal file
17
bench/snippets/buffer-includes.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const buf = Buffer.from(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
);
|
||||
const INTERVAL = 9_999_999;
|
||||
|
||||
const time = (name, fn) => {
|
||||
for (let i = 0; i < INTERVAL; i++) fn();
|
||||
|
||||
console.time(name.padEnd(30));
|
||||
for (let i = 0; i < INTERVAL; i++) fn();
|
||||
console.timeEnd(name.padEnd(30));
|
||||
};
|
||||
|
||||
console.log(`Run ${new Intl.NumberFormat().format(INTERVAL)} times with a warmup:`, "\n");
|
||||
|
||||
time("includes true", () => buf.includes("nisi"));
|
||||
time("includes false", () => buf.includes("oopwo"));
|
||||
71
bench/snippets/decode.js
Normal file
71
bench/snippets/decode.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
let decodeURIComponentSIMD;
|
||||
if (typeof Bun !== "undefined") {
|
||||
({ decodeURIComponentSIMD } = await import("bun:internal-for-testing"));
|
||||
}
|
||||
|
||||
const hugeText = Buffer.alloc(1000000, "Hello, world!").toString();
|
||||
const hugeTextWithPercentAtEnd = Buffer.alloc(1000000, "Hello, world!%40").toString();
|
||||
|
||||
const tinyText = Buffer.alloc(100, "Hello, world!").toString();
|
||||
const tinyTextWithPercentAtEnd = Buffer.alloc(100, "Hello, world!%40").toString();
|
||||
|
||||
const veryTinyText = Buffer.alloc(8, "a").toString();
|
||||
const veryTinyTextWithPercentAtEnd = Buffer.alloc(8, "a%40").toString();
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - no % x 8 bytes", () => {
|
||||
decodeURIComponentSIMD(veryTinyText);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - no % x 8 bytes", () => {
|
||||
decodeURIComponent(veryTinyText);
|
||||
});
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - yes % x 8 bytes", () => {
|
||||
decodeURIComponentSIMD(veryTinyTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - yes % x 8 bytes", () => {
|
||||
decodeURIComponent(veryTinyTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - no % x 100 bytes", () => {
|
||||
decodeURIComponentSIMD(tinyText);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - no % x 100 bytes", () => {
|
||||
decodeURIComponent(tinyText);
|
||||
});
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - yes % x 100 bytes", () => {
|
||||
decodeURIComponentSIMD(tinyTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - yes % x 100 bytes", () => {
|
||||
decodeURIComponent(tinyTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - no % x 1 MB", () => {
|
||||
decodeURIComponentSIMD(hugeText);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - no % x 1 MB", () => {
|
||||
decodeURIComponent(hugeText);
|
||||
});
|
||||
|
||||
decodeURIComponentSIMD &&
|
||||
bench("decodeURIComponentSIMD - yes % x 1 MB", () => {
|
||||
decodeURIComponentSIMD(hugeTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
bench(" decodeURIComponent - yes % x 1 MB", () => {
|
||||
decodeURIComponent(hugeTextWithPercentAtEnd);
|
||||
});
|
||||
|
||||
await run();
|
||||
62
bun.lock
62
bun.lock
@@ -45,21 +45,21 @@
|
||||
"packages": {
|
||||
"@biomejs/biome": ["@biomejs/biome@1.8.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.8.3", "@biomejs/cli-darwin-x64": "1.8.3", "@biomejs/cli-linux-arm64": "1.8.3", "@biomejs/cli-linux-arm64-musl": "1.8.3", "@biomejs/cli-linux-x64": "1.8.3", "@biomejs/cli-linux-x64-musl": "1.8.3", "@biomejs/cli-win32-arm64": "1.8.3", "@biomejs/cli-win32-x64": "1.8.3" }, "bin": { "biome": "bin/biome" } }, "sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.8.3", "", { "os":"darwin", "cpu":"arm64" }, "sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A=="],
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.8.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.8.3", "", { "os":"darwin", "cpu":"x64" }, "sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw=="],
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.8.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.8.3", "", { "os":"linux", "cpu":"arm64" }, "sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw=="],
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.8.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.8.3", "", { "os":"linux", "cpu":"arm64" }, "sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ=="],
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.8.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-9yjUfOFN7wrYsXt/T/gEWfvVxKlnh3yBpnScw98IF+oOeCYb5/b/+K7YNqKROV2i1DlMjg9g/EcN9wvj+NkMuQ=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.8.3", "", { "os":"linux", "cpu":"x64" }, "sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw=="],
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.8.3", "", { "os": "linux", "cpu": "x64" }, "sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.8.3", "", { "os":"linux", "cpu":"x64" }, "sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA=="],
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.8.3", "", { "os": "linux", "cpu": "x64" }, "sha512-UHrGJX7PrKMKzPGoEsooKC9jXJMa28TUSMjcIlbDnIO4EAavCoVmNQaIuUSH0Ls2mpGMwUIf+aZJv657zfWWjA=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.8.3", "", { "os":"win32", "cpu":"arm64" }, "sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ=="],
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-J+Hu9WvrBevfy06eU1Na0lpc7uR9tibm9maHynLIoAjLZpQU3IW+OKHUtyL8p6/3pT2Ju5t5emReeIS2SAxhkQ=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.8.3", "", { "os":"win32", "cpu":"x64" }, "sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg=="],
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.8.3", "", { "os": "win32", "cpu": "x64" }, "sha512-/PJ59vA1pnQeKahemaQf4Nyj7IKUvGQSc3Ze1uIGi+Wvr1xF7rGobSrAAG01T/gUDG21vkDsZYM03NAmPiVkqg=="],
|
||||
|
||||
"@definitelytyped/dts-critic": ["@definitelytyped/dts-critic@0.0.191", "", { "dependencies": { "@definitelytyped/header-parser": "0.0.190", "command-exists": "^1.2.9", "semver": "^7.5.4", "tmp": "^0.2.1", "typescript": "^5.2.2", "yargs": "^17.7.2" } }, "sha512-j5HK3pQYiQwSXRLJzyhXJ6KxdzLl4gXXhz3ysCtLnRQkj+zsEfloDkEZ3x2bZMWS0OsKLXmR91JeQ2/c9DFEjg=="],
|
||||
|
||||
@@ -75,51 +75,51 @@
|
||||
|
||||
"@es-joy/jsdoccomment": ["@es-joy/jsdoccomment@0.39.4", "", { "dependencies": { "comment-parser": "1.3.1", "esquery": "^1.5.0", "jsdoc-type-pratt-parser": "~4.0.0" } }, "sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os":"aix", "cpu":"ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os":"android", "cpu":"arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os":"android", "cpu":"arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os":"android", "cpu":"x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os":"darwin", "cpu":"arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os":"darwin", "cpu":"x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os":"freebsd", "cpu":"arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os":"freebsd", "cpu":"x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os":"linux", "cpu":"arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os":"linux", "cpu":"arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os":"linux", "cpu":"ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os":"linux", "cpu":"none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os":"linux", "cpu":"none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os":"linux", "cpu":"ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os":"linux", "cpu":"none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os":"linux", "cpu":"s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os":"linux", "cpu":"x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os":"none", "cpu":"x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os":"openbsd", "cpu":"x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os":"sunos", "cpu":"x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os":"win32", "cpu":"arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os":"win32", "cpu":"ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os":"win32", "cpu":"x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.0", "", { "dependencies": { "eslint-visitor-keys": "^3.3.0" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA=="],
|
||||
|
||||
|
||||
84
ci/README.md
84
ci/README.md
@@ -1,84 +0,0 @@
|
||||
# CI
|
||||
|
||||
This directory contains scripts for building CI images for Bun.
|
||||
|
||||
## Building
|
||||
|
||||
### `macOS`
|
||||
|
||||
On macOS, images are built using [`tart`](https://tart.run/), a tool that abstracts over the [`Virtualization.Framework`](https://developer.apple.com/documentation/virtualization) APIs, to run macOS VMs.
|
||||
|
||||
To install the dependencies required, run:
|
||||
|
||||
```sh
|
||||
$ cd ci
|
||||
$ bun run bootstrap
|
||||
```
|
||||
|
||||
To build a vanilla macOS VM, run:
|
||||
|
||||
```sh
|
||||
$ bun run build:darwin-aarch64-vanilla
|
||||
```
|
||||
|
||||
This builds a vanilla macOS VM with the current macOS release on your machine. It runs scripts to disable things like spotlight and siri, but it does not install any software.
|
||||
|
||||
> Note: The image size is 50GB, so make sure you have enough disk space.
|
||||
|
||||
If you want to build a specific macOS release, you can run:
|
||||
|
||||
```sh
|
||||
$ bun run build:darwin-aarch64-vanilla-15
|
||||
```
|
||||
|
||||
> Note: You cannot build a newer release of macOS on an older macOS machine.
|
||||
|
||||
To build a macOS VM with software installed to build and test Bun, run:
|
||||
|
||||
```sh
|
||||
$ bun run build:darwin-aarch64
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### `macOS`
|
||||
|
||||
## How To
|
||||
|
||||
### Support a new macOS release
|
||||
|
||||
1. Visit [`ipsw.me`](https://ipsw.me/VirtualMac2,1) and find the IPSW of the macOS release you want to build.
|
||||
|
||||
2. Add an entry to [`ci/darwin/variables.pkr.hcl`](/ci/darwin/variables.pkr.hcl) with the following format:
|
||||
|
||||
```hcl
|
||||
sonoma = {
|
||||
distro = "sonoma"
|
||||
release = "15"
|
||||
ipsw = "https://updates.cdn-apple.com/..."
|
||||
}
|
||||
```
|
||||
|
||||
3. Add matching scripts to [`ci/package.json`](/ci/package.json) to build the image, then test it:
|
||||
|
||||
```sh
|
||||
$ bun run build:darwin-aarch64-vanilla-15
|
||||
```
|
||||
|
||||
> Note: If you need to troubleshoot the build, you can remove the `headless = true` property from [`ci/darwin/image-vanilla.pkr.hcl`](/ci/darwin/image-vanilla.pkr.hcl) and the VM's screen will be displayed.
|
||||
|
||||
4. Test and build the non-vanilla image:
|
||||
|
||||
```sh
|
||||
$ bun run build:darwin-aarch64-15
|
||||
```
|
||||
|
||||
This will use the vanilla image and run the [`scripts/bootstrap.sh`](/scripts/bootstrap.sh) script to install the required software to build and test Bun.
|
||||
|
||||
5. Publish the images:
|
||||
|
||||
```sh
|
||||
$ bun run login
|
||||
$ bun run publish:darwin-aarch64-vanilla-15
|
||||
$ bun run publish:darwin-aarch64-15
|
||||
```
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM alpine:edge AS build
|
||||
ARG GIT_SHA
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
WORKDIR /app/bun
|
||||
ENV HOME=/root
|
||||
|
||||
COPY . .
|
||||
RUN touch $HOME/.bashrc
|
||||
RUN ./scripts/bootstrap.sh
|
||||
RUN . $HOME/.bashrc && bun run build:release
|
||||
|
||||
RUN apk add file
|
||||
RUN file ./build/release/bun
|
||||
RUN ldd ./build/release/bun
|
||||
RUN ./build/release/bun
|
||||
|
||||
RUN cp -R /app/bun/build/* /output
|
||||
|
||||
FROM scratch AS artifact
|
||||
COPY --from=build /output /
|
||||
|
||||
# docker build -f ./ci/alpine/build.Dockerfile --progress=plain --build-arg GIT_SHA="$(git rev-parse HEAD)" --target=artifact --output type=local,dest=./build-alpine .
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM alpine:edge
|
||||
ENV HOME=/root
|
||||
WORKDIR /root
|
||||
COPY ./build-alpine/release/bun .
|
||||
COPY ./test ./test
|
||||
COPY ./scripts ./scripts
|
||||
COPY ./package.json ./package.json
|
||||
COPY ./packages ./packages
|
||||
|
||||
RUN apk update
|
||||
RUN apk add nodejs lsb-release-minimal git python3 npm make g++
|
||||
RUN apk add file
|
||||
|
||||
RUN file /root/bun
|
||||
RUN ldd /root/bun
|
||||
RUN /root/bun
|
||||
|
||||
RUN ./scripts/runner.node.mjs --exec-path /root/bun
|
||||
|
||||
# docker build -f ./ci/alpine/test.Dockerfile --progress=plain .
|
||||
@@ -1,46 +0,0 @@
|
||||
# Generates a vanilla macOS VM with optimized settings for virtualized environments.
|
||||
# See login.sh and optimize.sh for details.
|
||||
|
||||
data "external-raw" "boot-script" {
|
||||
program = ["sh", "-c", templatefile("scripts/boot-image.sh", var)]
|
||||
}
|
||||
|
||||
source "tart-cli" "bun-darwin-aarch64-vanilla" {
|
||||
vm_name = "bun-darwin-aarch64-vanilla-${local.release.distro}-${local.release.release}"
|
||||
from_ipsw = local.release.ipsw
|
||||
cpu_count = local.cpu_count
|
||||
memory_gb = local.memory_gb
|
||||
disk_size_gb = local.disk_size_gb
|
||||
ssh_username = local.username
|
||||
ssh_password = local.password
|
||||
ssh_timeout = "120s"
|
||||
create_grace_time = "30s"
|
||||
boot_command = split("\n", data.external-raw.boot-script.result)
|
||||
headless = true # Disable if you need to debug why the boot_command is not working
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.tart-cli.bun-darwin-aarch64-vanilla"]
|
||||
|
||||
provisioner "file" {
|
||||
content = file("scripts/setup-login.sh")
|
||||
destination = "/tmp/setup-login.sh"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["echo \"${local.password}\" | sudo -S sh -c 'sh /tmp/setup-login.sh \"${local.username}\" \"${local.password}\"'"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
content = file("scripts/optimize-machine.sh")
|
||||
destination = "/tmp/optimize-machine.sh"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["sudo sh /tmp/optimize-machine.sh"]
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["sudo rm -rf /tmp/*"]
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# Generates a macOS VM with software installed to build and test Bun.
|
||||
|
||||
source "tart-cli" "bun-darwin-aarch64" {
|
||||
vm_name = "bun-darwin-aarch64-${local.release.distro}-${local.release.release}"
|
||||
vm_base_name = "bun-darwin-aarch64-vanilla-${local.release.distro}-${local.release.release}"
|
||||
cpu_count = local.cpu_count
|
||||
memory_gb = local.memory_gb
|
||||
disk_size_gb = local.disk_size_gb
|
||||
ssh_username = local.username
|
||||
ssh_password = local.password
|
||||
ssh_timeout = "120s"
|
||||
headless = true
|
||||
}
|
||||
|
||||
build {
|
||||
sources = ["source.tart-cli.bun-darwin-aarch64"]
|
||||
|
||||
provisioner "file" {
|
||||
content = file("../../scripts/bootstrap.sh")
|
||||
destination = "/tmp/bootstrap.sh"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["CI=true sh /tmp/bootstrap.sh"]
|
||||
}
|
||||
|
||||
provisioner "file" {
|
||||
source = "darwin/plists/"
|
||||
destination = "/tmp/"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = [
|
||||
"sudo ls /tmp/",
|
||||
"sudo mv /tmp/*.plist /Library/LaunchDaemons/",
|
||||
"sudo chown root:wheel /Library/LaunchDaemons/*.plist",
|
||||
"sudo chmod 644 /Library/LaunchDaemons/*.plist",
|
||||
]
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
inline = ["sudo rm -rf /tmp/*"]
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.buildkite.buildkite-agent</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/buildkite-agent</string>
|
||||
<string>start</string>
|
||||
</array>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>SuccessfulExit</key>
|
||||
<false />
|
||||
</dict>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true />
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/buildkite-agent/logs/buildkite-agent.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/buildkite-agent/logs/buildkite-agent.log</string>
|
||||
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>BUILDKITE_AGENT_CONFIG</key>
|
||||
<string>/etc/buildkite-agent/buildkite-agent.cfg</string>
|
||||
</dict>
|
||||
|
||||
<key>LimitLoadToSessionType</key>
|
||||
<array>
|
||||
<string>Aqua</string>
|
||||
<string>LoginWindow</string>
|
||||
<string>Background</string>
|
||||
<string>StandardIO</string>
|
||||
<string>System</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.tailscale.tailscaled</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/tailscale</string>
|
||||
<string>up</string>
|
||||
<string>--ssh</string>
|
||||
<string>--authkey</string>
|
||||
<string>${TAILSCALE_AUTHKEY}</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.tailscale.tailscaled</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/tailscaled</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script generates the boot commands for the macOS installer GUI.
|
||||
# It is run on your local machine, not inside the VM.
|
||||
|
||||
# Sources:
|
||||
# - https://github.com/cirruslabs/macos-image-templates/blob/master/templates/vanilla-sequoia.pkr.hcl
|
||||
|
||||
if ! [ "${release}" ] || ! [ "${username}" ] || ! [ "${password}" ]; then
|
||||
echo "Script must be run with variables: release, username, and password" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Hello, hola, bonjour, etc.
|
||||
echo "<wait120s><spacebar>"
|
||||
|
||||
# Select Your Country and Region
|
||||
echo "<wait30s>italiano<esc>english<enter>"
|
||||
echo "<wait30s>united states<leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Written and Spoken Languages
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Accessibility
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Data & Privacy
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Migration Assistant
|
||||
echo "<wait30s><tab><tab><tab><spacebar>"
|
||||
|
||||
# Sign In with Your Apple ID
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Are you sure you want to skip signing in with an Apple ID?
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Terms and Conditions
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# I have read and agree to the macOS Software License Agreement
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Create a Computer Account
|
||||
echo "<wait30s>${username}<tab><tab>${password}<tab>${password}<tab><tab><tab><spacebar>"
|
||||
|
||||
# Enable Location Services
|
||||
echo "<wait60s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Are you sure you don't want to use Location Services?
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Select Your Time Zone
|
||||
echo "<wait30s><tab>UTC<enter><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Analytics
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Screen Time
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Siri
|
||||
echo "<wait30s><tab><spacebar><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Choose Your Look
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
if [ "${release}" = "13" ] || [ "${release}" = "14" ]; then
|
||||
# Enable Voice Over
|
||||
echo "<wait30s><leftAltOn><f5><leftAltOff><wait5s>v"
|
||||
else
|
||||
# Welcome to Mac
|
||||
echo "<wait30s><spacebar>"
|
||||
|
||||
# Enable Keyboard navigation
|
||||
echo "<wait30s><leftAltOn><spacebar><leftAltOff>Terminal<enter>"
|
||||
echo "<wait30s>defaults write NSGlobalDomain AppleKeyboardUIMode -int 3<enter>"
|
||||
echo "<wait30s><leftAltOn>q<leftAltOff>"
|
||||
fi
|
||||
|
||||
# Now that the installation is done, open "System Settings"
|
||||
echo "<wait30s><leftAltOn><spacebar><leftAltOff>System Settings<enter>"
|
||||
|
||||
# Navigate to "Sharing"
|
||||
echo "<wait30s><leftAltOn>f<leftAltOff>sharing<enter>"
|
||||
|
||||
if [ "${release}" = "13" ]; then
|
||||
# Navigate to "Screen Sharing" and enable it
|
||||
echo "<wait30s><tab><down><spacebar>"
|
||||
|
||||
# Navigate to "Remote Login" and enable it
|
||||
echo "<wait30s><tab><tab><tab><tab><tab><tab><spacebar>"
|
||||
|
||||
# Open "Remote Login" details
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Enable "Full Disk Access"
|
||||
echo "<wait30s><tab><spacebar>"
|
||||
|
||||
# Click "Done"
|
||||
echo "<wait30s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><spacebar>"
|
||||
|
||||
# Disable Voice Over
|
||||
echo "<leftAltOn><f5><leftAltOff>"
|
||||
elif [ "${release}" = "14" ]; then
|
||||
# Navigate to "Screen Sharing" and enable it
|
||||
echo "<wait30s><tab><tab><tab><tab><tab><spacebar>"
|
||||
|
||||
# Navigate to "Remote Login" and enable it
|
||||
echo "<wait30s><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><spacebar>"
|
||||
|
||||
# Disable Voice Over
|
||||
echo "<wait30s><leftAltOn><f5><leftAltOff>"
|
||||
elif [ "${release}" = "15" ]; then
|
||||
# Navigate to "Screen Sharing" and enable it
|
||||
echo "<wait30s><tab><tab><tab><tab><tab><spacebar>"
|
||||
|
||||
# Navigate to "Remote Login" and enable it
|
||||
echo "<wait30s><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><spacebar>"
|
||||
fi
|
||||
|
||||
# Quit System Settings
|
||||
echo "<wait30s><leftAltOn>q<leftAltOff>"
|
||||
@@ -1,122 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script optimizes macOS for virtualized environments.
|
||||
# It disables things like spotlight, screen saver, and sleep.
|
||||
|
||||
# Sources:
|
||||
# - https://github.com/sickcodes/osx-optimizer
|
||||
# - https://github.com/koding88/MacBook-Optimization-Script
|
||||
# - https://www.macstadium.com/blog/simple-optimizations-for-macos-and-ios-build-agents
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run using sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
execute() {
|
||||
echo "$ $@" >&2
|
||||
if ! "$@"; then
|
||||
echo "Command failed: $@" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
disable_software_update() {
|
||||
execute softwareupdate --schedule off
|
||||
execute defaults write com.apple.SoftwareUpdate AutomaticDownload -bool false
|
||||
execute defaults write com.apple.SoftwareUpdate AutomaticCheckEnabled -bool false
|
||||
execute defaults write com.apple.SoftwareUpdate ConfigDataInstall -int 0
|
||||
execute defaults write com.apple.SoftwareUpdate CriticalUpdateInstall -int 0
|
||||
execute defaults write com.apple.SoftwareUpdate ScheduleFrequency -int 0
|
||||
execute defaults write com.apple.SoftwareUpdate AutomaticDownload -int 0
|
||||
execute defaults write com.apple.commerce AutoUpdate -bool false
|
||||
execute defaults write com.apple.commerce AutoUpdateRestartRequired -bool false
|
||||
}
|
||||
|
||||
disable_spotlight() {
|
||||
execute mdutil -i off -a
|
||||
execute mdutil -E /
|
||||
}
|
||||
|
||||
disable_siri() {
|
||||
execute launchctl unload -w /System/Library/LaunchAgents/com.apple.Siri.agent.plist
|
||||
execute defaults write com.apple.Siri StatusMenuVisible -bool false
|
||||
execute defaults write com.apple.Siri UserHasDeclinedEnable -bool true
|
||||
execute defaults write com.apple.assistant.support "Assistant Enabled" 0
|
||||
}
|
||||
|
||||
disable_sleep() {
|
||||
execute systemsetup -setsleep Never
|
||||
execute systemsetup -setcomputersleep Never
|
||||
execute systemsetup -setdisplaysleep Never
|
||||
execute systemsetup -setharddisksleep Never
|
||||
}
|
||||
|
||||
disable_screen_saver() {
|
||||
execute defaults write com.apple.screensaver loginWindowIdleTime 0
|
||||
execute defaults write com.apple.screensaver idleTime 0
|
||||
}
|
||||
|
||||
disable_screen_lock() {
|
||||
execute defaults write com.apple.loginwindow DisableScreenLock -bool true
|
||||
}
|
||||
|
||||
disable_wallpaper() {
|
||||
execute defaults write com.apple.loginwindow DesktopPicture ""
|
||||
}
|
||||
|
||||
disable_application_state() {
|
||||
execute defaults write com.apple.loginwindow TALLogoutSavesState -bool false
|
||||
}
|
||||
|
||||
disable_accessibility() {
|
||||
execute defaults write com.apple.Accessibility DifferentiateWithoutColor -int 1
|
||||
execute defaults write com.apple.Accessibility ReduceMotionEnabled -int 1
|
||||
execute defaults write com.apple.universalaccess reduceMotion -int 1
|
||||
execute defaults write com.apple.universalaccess reduceTransparency -int 1
|
||||
}
|
||||
|
||||
disable_dashboard() {
|
||||
execute defaults write com.apple.dashboard mcx-disabled -boolean YES
|
||||
execute killall Dock
|
||||
}
|
||||
|
||||
disable_animations() {
|
||||
execute defaults write NSGlobalDomain NSAutomaticWindowAnimationsEnabled -bool false
|
||||
execute defaults write -g QLPanelAnimationDuration -float 0
|
||||
execute defaults write com.apple.finder DisableAllAnimations -bool true
|
||||
}
|
||||
|
||||
disable_time_machine() {
|
||||
execute tmutil disable
|
||||
}
|
||||
|
||||
enable_performance_mode() {
|
||||
# https://support.apple.com/en-us/101992
|
||||
if ! [ $(nvram boot-args 2>/dev/null | grep -q serverperfmode) ]; then
|
||||
execute nvram boot-args="serverperfmode=1 $(nvram boot-args 2>/dev/null | cut -f 2-)"
|
||||
fi
|
||||
}
|
||||
|
||||
add_terminal_to_desktop() {
|
||||
execute ln -sf /System/Applications/Utilities/Terminal.app ~/Desktop/Terminal
|
||||
}
|
||||
|
||||
main() {
|
||||
disable_software_update
|
||||
disable_spotlight
|
||||
disable_siri
|
||||
disable_sleep
|
||||
disable_screen_saver
|
||||
disable_screen_lock
|
||||
disable_wallpaper
|
||||
disable_application_state
|
||||
disable_accessibility
|
||||
disable_dashboard
|
||||
disable_animations
|
||||
disable_time_machine
|
||||
enable_performance_mode
|
||||
add_terminal_to_desktop
|
||||
}
|
||||
|
||||
main
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script generates a /etc/kcpassword file to enable auto-login on macOS.
|
||||
# Yes, this stores your password in plain text. Do NOT do this on your local machine.
|
||||
|
||||
# Sources:
|
||||
# - https://github.com/xfreebird/kcpassword/blob/master/kcpassword
|
||||
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
echo "This script must be run using sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
execute() {
|
||||
echo "$ $@" >&2
|
||||
if ! "$@"; then
|
||||
echo "Command failed: $@" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
kcpassword() {
|
||||
passwd="$1"
|
||||
key="7d 89 52 23 d2 bc dd ea a3 b9 1f"
|
||||
passwd_hex=$(printf "%s" "$passwd" | xxd -p | tr -d '\n')
|
||||
|
||||
key_len=33
|
||||
passwd_len=${#passwd_hex}
|
||||
remainder=$((passwd_len % key_len))
|
||||
if [ $remainder -ne 0 ]; then
|
||||
padding=$((key_len - remainder))
|
||||
passwd_hex="${passwd_hex}$(printf '%0*x' $((padding / 2)) 0)"
|
||||
fi
|
||||
|
||||
result=""
|
||||
i=0
|
||||
while [ $i -lt ${#passwd_hex} ]; do
|
||||
for byte in $key; do
|
||||
[ $i -ge ${#passwd_hex} ] && break
|
||||
p="${passwd_hex:$i:2}"
|
||||
r=$(printf '%02x' $((0x$p ^ 0x$byte)))
|
||||
result="${result}${r}"
|
||||
i=$((i + 2))
|
||||
done
|
||||
done
|
||||
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
login() {
|
||||
username="$1"
|
||||
password="$2"
|
||||
|
||||
enable_passwordless_sudo() {
|
||||
execute mkdir -p /etc/sudoers.d/
|
||||
echo "${username} ALL=(ALL) NOPASSWD: ALL" | EDITOR=tee execute visudo "/etc/sudoers.d/${username}-nopasswd"
|
||||
}
|
||||
|
||||
enable_auto_login() {
|
||||
echo "00000000: 1ced 3f4a bcbc ba2c caca 4e82" | execute xxd -r - /etc/kcpassword
|
||||
execute defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "${username}"
|
||||
}
|
||||
|
||||
disable_screen_lock() {
|
||||
execute sysadminctl -screenLock off -password "${password}"
|
||||
}
|
||||
|
||||
enable_passwordless_sudo
|
||||
enable_auto_login
|
||||
disable_screen_lock
|
||||
}
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $0 <username> <password>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
login "$@"
|
||||
@@ -1,78 +0,0 @@
|
||||
packer {
|
||||
required_plugins {
|
||||
tart = {
|
||||
version = ">= 1.12.0"
|
||||
source = "github.com/cirruslabs/tart"
|
||||
}
|
||||
external = {
|
||||
version = ">= 0.0.2"
|
||||
source = "github.com/joomcode/external"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "release" {
|
||||
type = number
|
||||
default = 13
|
||||
}
|
||||
|
||||
variable "username" {
|
||||
type = string
|
||||
default = "admin"
|
||||
}
|
||||
|
||||
variable "password" {
|
||||
type = string
|
||||
default = "admin"
|
||||
}
|
||||
|
||||
variable "cpu_count" {
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "memory_gb" {
|
||||
type = number
|
||||
default = 4
|
||||
}
|
||||
|
||||
variable "disk_size_gb" {
|
||||
type = number
|
||||
default = 50
|
||||
}
|
||||
|
||||
locals {
|
||||
sequoia = {
|
||||
tier = 1
|
||||
distro = "sequoia"
|
||||
release = "15"
|
||||
ipsw = "https://updates.cdn-apple.com/2024FallFCS/fullrestores/062-78489/BDA44327-C79E-4608-A7E0-455A7E91911F/UniversalMac_15.0_24A335_Restore.ipsw"
|
||||
}
|
||||
|
||||
sonoma = {
|
||||
tier = 2
|
||||
distro = "sonoma"
|
||||
release = "14"
|
||||
ipsw = "https://updates.cdn-apple.com/2023FallFCS/fullrestores/042-54934/0E101AD6-3117-4B63-9BF1-143B6DB9270A/UniversalMac_14.0_23A344_Restore.ipsw"
|
||||
}
|
||||
|
||||
ventura = {
|
||||
tier = 2
|
||||
distro = "ventura"
|
||||
release = "13"
|
||||
ipsw = "https://updates.cdn-apple.com/2022FallFCS/fullrestores/012-92188/2C38BCD1-2BFF-4A10-B358-94E8E28BE805/UniversalMac_13.0_22A380_Restore.ipsw"
|
||||
}
|
||||
|
||||
releases = {
|
||||
15 = local.sequoia
|
||||
14 = local.sonoma
|
||||
13 = local.ventura
|
||||
}
|
||||
|
||||
release = local.releases[var.release]
|
||||
username = var.username
|
||||
password = var.password
|
||||
cpu_count = var.cpu_count
|
||||
memory_gb = var.memory_gb
|
||||
disk_size_gb = var.disk_size_gb
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
ARG IMAGE=debian:11
|
||||
FROM $IMAGE
|
||||
COPY ./scripts/bootstrap.sh /tmp/bootstrap.sh
|
||||
ENV CI=true
|
||||
RUN sh /tmp/bootstrap.sh && rm -rf /tmp/*
|
||||
WORKDIR /workspace/bun
|
||||
COPY bunfig.toml bunfig.toml
|
||||
COPY package.json package.json
|
||||
COPY CMakeLists.txt CMakeLists.txt
|
||||
COPY cmake/ cmake/
|
||||
COPY scripts/ scripts/
|
||||
COPY patches/ patches/
|
||||
COPY *.zig ./
|
||||
COPY src/ src/
|
||||
COPY packages/ packages/
|
||||
COPY test/ test/
|
||||
RUN bun i
|
||||
RUN bun run build:ci
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script sets the hostname of the current machine.
|
||||
|
||||
execute() {
|
||||
echo "$ $@" >&2
|
||||
if ! "$@"; then
|
||||
echo "Command failed: $@" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <hostname>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$(which hostnamectl)" ]; then
|
||||
execute hostnamectl set-hostname "$1"
|
||||
else
|
||||
echo "Error: hostnamectl is not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script starts tailscale on the current machine.
|
||||
|
||||
execute() {
|
||||
echo "$ $@" >&2
|
||||
if ! "$@"; then
|
||||
echo "Command failed: $@" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <auth-key>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
execute tailscale up --reset --ssh --accept-risk=lose-ssh --auth-key="$1"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "brew install gh jq cirruslabs/cli/tart cirruslabs/cli/sshpass hashicorp/tap/packer && packer init darwin",
|
||||
"login": "token=$(gh auth token); username=$(gh api user --jq .login); echo \"Login as $username...\"; echo \"$token\" | tart login ghcr.io --username \"$username\" --password-stdin; echo \"$token\" | docker login ghcr.io --username \"$username\" --password-stdin",
|
||||
"fetch:image-name": "echo ghcr.io/oven-sh/bun-vm",
|
||||
"fetch:darwin-version": "echo 1",
|
||||
"fetch:macos-version": "sw_vers -productVersion | cut -d. -f1",
|
||||
"fetch:script-version": "cat ../scripts/bootstrap.sh | grep 'v=' | sed 's/v=\"//;s/\"//' | head -n 1",
|
||||
"build:darwin-aarch64-vanilla": "packer build '-only=*.bun-darwin-aarch64-vanilla' -var release=$(bun fetch:macos-version) darwin/",
|
||||
"build:darwin-aarch64-vanilla-15": "packer build '-only=*.bun-darwin-aarch64-vanilla' -var release=15 darwin/",
|
||||
"build:darwin-aarch64-vanilla-14": "packer build '-only=*.bun-darwin-aarch64-vanilla' -var release=14 darwin/",
|
||||
"build:darwin-aarch64-vanilla-13": "packer build '-only=*.bun-darwin-aarch64-vanilla' -var release=13 darwin/",
|
||||
"build:darwin-aarch64": "packer build '-only=*.bun-darwin-aarch64' -var release=$(bun fetch:macos-version) darwin/",
|
||||
"build:darwin-aarch64-15": "packer build '-only=*.bun-darwin-aarch64' -var release=15 darwin/",
|
||||
"build:darwin-aarch64-14": "packer build '-only=*.bun-darwin-aarch64' -var release=14 darwin/",
|
||||
"build:darwin-aarch64-13": "packer build '-only=*.bun-darwin-aarch64' -var release=13 darwin/",
|
||||
"publish:darwin-aarch64-vanilla": "image=$(tart list --format json | jq -r \".[] | select(.Name | test(\\\"^bun-darwin-aarch64-vanilla-.*-$(bun fetch:macos-version)$\\\")) | .Name\" | head -n 1 | sed 's/bun-//'); tart push \"bun-$image\" \"ghcr.io/oven-sh/bun-vm:$image-v$(bun fetch:darwin-version)\"",
|
||||
"publish:darwin-aarch64-vanilla-15": "tart push bun-darwin-aarch64-vanilla-sequoia-15 \"$(bun fetch:image-name):darwin-aarch64-vanilla-sequoia-15-v$(bun fetch:darwin-version)\"",
|
||||
"publish:darwin-aarch64-vanilla-14": "tart push bun-darwin-aarch64-vanilla-sonoma-14 \"$(bun fetch:image-name):darwin-aarch64-vanilla-sonoma-14-v$(bun fetch:darwin-version)\"",
|
||||
"publish:darwin-aarch64-vanilla-13": "tart push bun-darwin-aarch64-vanilla-ventura-13 \"$(bun fetch:image-name):darwin-aarch64-vanilla-ventura-13-v$(bun fetch:darwin-version)\"",
|
||||
"publish:darwin-aarch64": "image=$(tart list --format json | jq -r \".[] | select(.Name | test(\\\"^bun-darwin-aarch64-.*-$(bun fetch:macos-version)$\\\")) | .Name\" | head -n 1 | sed 's/bun-//'); tart push \"bun-$image\" \"ghcr.io/oven-sh/bun-vm:$image-v$(bun fetch:script-version)\"",
|
||||
"publish:darwin-aarch64-15": "tart push bun-darwin-aarch64-sequoia-15 \"$(bun fetch:image-name):darwin-aarch64-sequoia-15-v$(bun fetch:script-version)\"",
|
||||
"publish:darwin-aarch64-14": "tart push bun-darwin-aarch64-sonoma-14 \"$(bun fetch:image-name):darwin-aarch64-sonoma-14-v$(bun fetch:script-version)\"",
|
||||
"publish:darwin-aarch64-13": "tart push bun-darwin-aarch64-ventura-13 \"$(bun fetch:image-name):darwin-aarch64-ventura-13-v$(bun fetch:script-version)\""
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
get_filename_component(SCRIPT_NAME ${CMAKE_CURRENT_LIST_FILE} NAME)
|
||||
message(STATUS "Running script: ${SCRIPT_NAME}")
|
||||
|
||||
if(NOT ZIG_PATH OR NOT ZIG_COMMIT OR NOT ZIG_VERSION)
|
||||
message(FATAL_ERROR "ZIG_PATH, ZIG_COMMIT, and ZIG_VERSION are required")
|
||||
if(NOT ZIG_PATH OR NOT ZIG_COMMIT)
|
||||
message(FATAL_ERROR "ZIG_PATH and ZIG_COMMIT required")
|
||||
endif()
|
||||
|
||||
if(CMAKE_HOST_APPLE)
|
||||
set(ZIG_OS "macos")
|
||||
set(ZIG_OS_ABI "macos-none")
|
||||
elseif(CMAKE_HOST_WIN32)
|
||||
set(ZIG_OS "windows")
|
||||
set(ZIG_OS_ABI "windows-gnu")
|
||||
elseif(CMAKE_HOST_UNIX)
|
||||
set(ZIG_OS "linux")
|
||||
set(ZIG_OS_ABI "linux-musl")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported operating system: ${CMAKE_HOST_SYSTEM_NAME}")
|
||||
endif()
|
||||
@@ -28,22 +28,16 @@ else()
|
||||
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
set(ZIG_ASAN "")
|
||||
if(ENABLE_ASAN)
|
||||
set(ZIG_ASAN "+asan")
|
||||
endif()
|
||||
|
||||
set(ZIG_NAME zig-${ZIG_OS}-${ZIG_ARCH}-${ZIG_VERSION}${ZIG_ASAN})
|
||||
set(ZIG_NAME bootstrap-${ZIG_ARCH}-${ZIG_OS_ABI})
|
||||
set(ZIG_FILENAME ${ZIG_NAME}.zip)
|
||||
|
||||
if(CMAKE_HOST_WIN32)
|
||||
set(ZIG_EXE "zig.exe")
|
||||
set(ZIG_FILENAME ${ZIG_NAME}.zip)
|
||||
else()
|
||||
set(ZIG_EXE "zig")
|
||||
set(ZIG_FILENAME ${ZIG_NAME}.tar.xz)
|
||||
endif()
|
||||
|
||||
set(ZIG_DOWNLOAD_URL https://bun-ci-assets.bun.sh/${ZIG_FILENAME})
|
||||
set(ZIG_DOWNLOAD_URL https://github.com/oven-sh/zig/releases/download/autobuild-${ZIG_COMMIT}/${ZIG_FILENAME})
|
||||
|
||||
execute_process(
|
||||
COMMAND
|
||||
@@ -67,35 +61,8 @@ if(NOT EXISTS ${ZIG_PATH}/${ZIG_EXE})
|
||||
endif()
|
||||
|
||||
# Tools like VSCode need a stable path to the zig executable, on both Unix and Windows
|
||||
# To workaround this, we create a `bun.exe` symlink on Unix.
|
||||
# To workaround this, we create a `zig.exe` & `zls.exe` symlink on Unix.
|
||||
if(NOT WIN32)
|
||||
file(CREATE_LINK ${ZIG_PATH}/${ZIG_EXE} ${ZIG_PATH}/zig.exe SYMBOLIC)
|
||||
file(CREATE_LINK ${ZIG_PATH}/zls ${ZIG_PATH}/zls.exe SYMBOLIC)
|
||||
endif()
|
||||
|
||||
set(ZIG_REPOSITORY_PATH ${ZIG_PATH}/repository)
|
||||
|
||||
execute_process(
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-DGIT_PATH=${ZIG_REPOSITORY_PATH}
|
||||
-DGIT_REPOSITORY=oven-sh/zig
|
||||
-DGIT_COMMIT=${ZIG_COMMIT}
|
||||
-P ${CMAKE_CURRENT_LIST_DIR}/GitClone.cmake
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_VARIABLE
|
||||
ZIG_REPOSITORY_ERROR
|
||||
RESULT_VARIABLE
|
||||
ZIG_REPOSITORY_RESULT
|
||||
)
|
||||
|
||||
if(NOT ZIG_REPOSITORY_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Download failed: ${ZIG_REPOSITORY_ERROR}")
|
||||
endif()
|
||||
|
||||
file(REMOVE_RECURSE ${ZIG_PATH}/lib)
|
||||
|
||||
# Use copy_directory instead of file(RENAME) because there were
|
||||
# race conditions in CI where some files were not copied.
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory ${ZIG_REPOSITORY_PATH}/lib ${ZIG_PATH}/lib)
|
||||
|
||||
file(REMOVE_RECURSE ${ZIG_REPOSITORY_PATH})
|
||||
|
||||
@@ -432,6 +432,7 @@ set(BUN_OBJECT_LUT_SOURCES
|
||||
${CWD}/src/bun.js/bindings/BunProcess.cpp
|
||||
${CWD}/src/bun.js/bindings/ProcessBindingBuffer.cpp
|
||||
${CWD}/src/bun.js/bindings/ProcessBindingConstants.cpp
|
||||
${CWD}/src/bun.js/bindings/ProcessBindingFs.cpp
|
||||
${CWD}/src/bun.js/bindings/ProcessBindingNatives.cpp
|
||||
${CWD}/src/bun.js/modules/NodeModuleModule.cpp
|
||||
${CODEGEN_PATH}/ZigGeneratedClasses.lut.txt
|
||||
@@ -444,6 +445,7 @@ set(BUN_OBJECT_LUT_OUTPUTS
|
||||
${CODEGEN_PATH}/BunProcess.lut.h
|
||||
${CODEGEN_PATH}/ProcessBindingBuffer.lut.h
|
||||
${CODEGEN_PATH}/ProcessBindingConstants.lut.h
|
||||
${CODEGEN_PATH}/ProcessBindingFs.lut.h
|
||||
${CODEGEN_PATH}/ProcessBindingNatives.lut.h
|
||||
${CODEGEN_PATH}/NodeModuleModule.lut.h
|
||||
${CODEGEN_PATH}/ZigGeneratedClasses.lut.h
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
litespeedtech/ls-hpack
|
||||
COMMIT
|
||||
32e96f10593c7cb8553cd8c9c12721100ae9e924
|
||||
8905c024b6d052f083a3d11d0a169b3c2735c8a1
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
|
||||
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION 57004f91903936881b3301594d9d67708f1ff64c)
|
||||
set(WEBKIT_VERSION c333de54223425d7148603d63dd3f6152d0bc348)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
@@ -20,8 +20,7 @@ else()
|
||||
unsupported(CMAKE_SYSTEM_NAME)
|
||||
endif()
|
||||
|
||||
optionx(ZIG_VERSION STRING "The zig version of the compiler to download" DEFAULT "0.14.0-dev.2987+183bb8b08")
|
||||
optionx(ZIG_COMMIT STRING "The zig commit to use in oven-sh/zig" DEFAULT "02c57c7ee3b8fde7528c74dd06490834d2d6fae9")
|
||||
set(ZIG_COMMIT "bb9d6ab2c0bbbf20cc24dad03e88f3b3ffdb7de7")
|
||||
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
@@ -77,7 +76,6 @@ register_command(
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-DZIG_PATH=${ZIG_PATH}
|
||||
-DZIG_VERSION=${ZIG_VERSION}
|
||||
-DZIG_COMMIT=${ZIG_COMMIT}
|
||||
-DENABLE_ASAN=${ENABLE_ASAN}
|
||||
-P ${CWD}/cmake/scripts/DownloadZig.cmake
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bullseye-slim AS build
|
||||
FROM debian:bookworm-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bullseye-slim AS build
|
||||
FROM debian:bookworm-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bullseye-slim AS build
|
||||
FROM debian:bookworm-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{% callout %}
|
||||
**⚠️ Warning** — `bun:ffi` is **experimental**, with known bugs and limitations, and should not be relied on in production. The most stable way to interact with native code from Bun is to write a [Node-API module](/docs/api/node-api).
|
||||
{% /callout %}
|
||||
|
||||
Use the built-in `bun:ffi` module to efficiently call native libraries from JavaScript. It works with languages that support the C ABI (Zig, Rust, C/C++, C#, Nim, Kotlin, etc).
|
||||
|
||||
## dlopen usage (`bun:ffi`)
|
||||
@@ -298,7 +302,11 @@ setTimeout(() => {
|
||||
When you're done with a JSCallback, you should call `close()` to free the memory.
|
||||
|
||||
### Experimental thread-safe callbacks
|
||||
`JSCallback` has experimental support for thread-safe callbacks. This will be needed if you pass a callback function into a different thread from it's instantiation context. You can enable it with the optional `threadsafe` option flag.
|
||||
|
||||
`JSCallback` has experimental support for thread-safe callbacks. This will be needed if you pass a callback function into a different thread from its instantiation context. You can enable it with the optional `threadsafe` parameter.
|
||||
|
||||
Currently, thread-safe callbacks work best when run from another thread that is running JavaScript code, i.e. a [`Worker`](/docs/api/workers). A future version of Bun will enable them to be called from any thread (such as new threads spawned by your native library that Bun is not aware of).
|
||||
|
||||
```ts
|
||||
const searchIterator = new JSCallback(
|
||||
(ptr, length) => /hello/.test(new CString(ptr, length)),
|
||||
@@ -309,7 +317,6 @@ const searchIterator = new JSCallback(
|
||||
},
|
||||
);
|
||||
```
|
||||
Be aware that there are still cases where this does not 100% work.
|
||||
|
||||
{% callout %}
|
||||
|
||||
|
||||
524
docs/api/http.md
524
docs/api/http.md
@@ -8,19 +8,421 @@ To start a high-performance HTTP server with a clean API, the recommended approa
|
||||
|
||||
## `Bun.serve()`
|
||||
|
||||
Start an HTTP server in Bun with `Bun.serve`.
|
||||
Use `Bun.serve` to start an HTTP server in Bun.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
// `routes` requires Bun v1.2.3+
|
||||
routes: {
|
||||
// Static routes
|
||||
"/api/status": new Response("OK"),
|
||||
|
||||
// Dynamic routes
|
||||
"/users/:id": req => {
|
||||
return new Response(`Hello User ${req.params.id}!`);
|
||||
},
|
||||
|
||||
// Per-HTTP method handlers
|
||||
"/api/posts": {
|
||||
GET: () => new Response("List posts"),
|
||||
POST: async req => {
|
||||
const body = await req.json();
|
||||
return Response.json({ created: true, ...body });
|
||||
},
|
||||
},
|
||||
|
||||
// Wildcard route for all routes that start with "/api/" and aren't otherwise matched
|
||||
"/api/*": Response.json({ message: "Not found" }, { status: 404 }),
|
||||
|
||||
// Redirect from /blog/hello to /blog/hello/world
|
||||
"/blog/hello": Response.redirect("/blog/hello/world"),
|
||||
|
||||
// Serve a file by buffering it in memory
|
||||
"/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
|
||||
headers: {
|
||||
"Content-Type": "image/x-icon",
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
// (optional) fallback for unmatched routes:
|
||||
// Required if Bun's version < 1.2.3
|
||||
fetch(req) {
|
||||
return new Response("Bun!");
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Routing
|
||||
|
||||
Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`. This makes it easier to use the same code for both sending & receiving HTTP requests.
|
||||
|
||||
```ts
|
||||
// Simplified for brevity
|
||||
interface BunRequest<T extends string> extends Request {
|
||||
params: Record<T, string>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Async/await in routes
|
||||
|
||||
You can use async/await in route handlers to return a `Promise<Response>`.
|
||||
|
||||
```ts
|
||||
import { sql, serve } from "bun";
|
||||
|
||||
serve({
|
||||
port: 3001,
|
||||
routes: {
|
||||
"/api/version": async () => {
|
||||
const [version] = await sql`SELECT version()`;
|
||||
return Response.json(version);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Promise in routes
|
||||
|
||||
You can also return a `Promise<Response>` from a route handler.
|
||||
|
||||
```ts
|
||||
import { sql, serve } from "bun";
|
||||
|
||||
serve({
|
||||
routes: {
|
||||
"/api/version": () => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(async () => {
|
||||
const [version] = await sql`SELECT version()`;
|
||||
resolve(Response.json(version));
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Type-safe route parameters
|
||||
|
||||
TypeScript parses route parameters when passed as a string literal, so that your editor will show autocomplete when accessing `request.params`.
|
||||
|
||||
```ts
|
||||
import type { BunRequest } from "bun";
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// TypeScript knows the shape of params when passed as a string literal
|
||||
"/orgs/:orgId/repos/:repoId": req => {
|
||||
const { orgId, repoId } = req.params;
|
||||
return Response.json({ orgId, repoId });
|
||||
},
|
||||
|
||||
"/orgs/:orgId/repos/:repoId/settings": (
|
||||
// optional: you can explicitly pass a type to BunRequest:
|
||||
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
|
||||
) => {
|
||||
const { orgId, repoId } = req.params;
|
||||
return Response.json({ orgId, repoId });
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Percent-encoded route parameter values are automatically decoded. Unicode characters are supported. Invalid unicode is replaced with the unicode replacement character `&0xFFFD;`.
|
||||
|
||||
### Static responses
|
||||
|
||||
Routes can also be `Response` objects (without the handler function). Bun.serve() optimizes it for zero-allocation dispatch - perfect for health checks, redirects, and fixed content:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Health checks
|
||||
"/health": new Response("OK"),
|
||||
"/ready": new Response("Ready", {
|
||||
headers: {
|
||||
// Pass custom headers
|
||||
"X-Ready": "1",
|
||||
},
|
||||
}),
|
||||
|
||||
// Redirects
|
||||
"/blog": Response.redirect("https://bun.sh/blog"),
|
||||
|
||||
// API responses
|
||||
"/api/config": Response.json({
|
||||
version: "1.0.0",
|
||||
env: "production",
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object.
|
||||
|
||||
Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`.
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
static: {
|
||||
"/api/time": new Response(new Date().toISOString()),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
|
||||
// Update the time every second.
|
||||
setInterval(() => {
|
||||
server.reload({
|
||||
static: {
|
||||
"/api/time": new Response(new Date().toISOString()),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
Reloading routes only impact the next request. In-flight requests continue to use the old routes. After in-flight requests to old routes are finished, the old routes are freed from memory.
|
||||
|
||||
To simplify error handling, static routes do not support streaming response bodies from `ReadableStream` or an `AsyncIterator`. Fortunately, you can still buffer the response in memory first:
|
||||
|
||||
```ts
|
||||
const time = await fetch("https://api.example.com/v1/data");
|
||||
// Buffer the response in memory first.
|
||||
const blob = await time.blob();
|
||||
|
||||
const server = Bun.serve({
|
||||
static: {
|
||||
"/api/data": new Response(blob),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Route precedence
|
||||
|
||||
Routes are matched in order of specificity:
|
||||
|
||||
1. Exact routes (`/users/all`)
|
||||
2. Parameter routes (`/users/:id`)
|
||||
3. Wildcard routes (`/users/*`)
|
||||
4. Global catch-all (`/*`)
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Most specific first
|
||||
"/api/users/me": () => new Response("Current user"),
|
||||
"/api/users/:id": req => new Response(`User ${req.params.id}`),
|
||||
"/api/*": () => new Response("API catch-all"),
|
||||
"/*": () => new Response("Global catch-all"),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Per-HTTP Method Routes
|
||||
|
||||
Route handlers can be specialized by HTTP method:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/api/posts": {
|
||||
// Different handlers per method
|
||||
GET: () => new Response("List posts"),
|
||||
POST: async req => {
|
||||
const post = await req.json();
|
||||
return Response.json({ id: crypto.randomUUID(), ...post });
|
||||
},
|
||||
PUT: async req => {
|
||||
const updates = await req.json();
|
||||
return Response.json({ updated: true, ...updates });
|
||||
},
|
||||
DELETE: () => new Response(null, { status: 204 }),
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can pass any of the following methods:
|
||||
|
||||
| Method | Usecase example |
|
||||
| --------- | ------------------------------- |
|
||||
| `GET` | Fetch a resource |
|
||||
| `HEAD` | Check if a resource exists |
|
||||
| `OPTIONS` | Get allowed HTTP methods (CORS) |
|
||||
| `DELETE` | Delete a resource |
|
||||
| `PATCH` | Update a resource |
|
||||
| `POST` | Create a resource |
|
||||
| `PUT` | Update a resource |
|
||||
|
||||
When passing a function instead of an object, all methods will be handled by that function:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
"/api/version": () => Response.json({ version: "1.0.0" }),
|
||||
},
|
||||
});
|
||||
|
||||
await fetch(new URL("/api/version", server.url));
|
||||
await fetch(new URL("/api/version", server.url), { method: "PUT" });
|
||||
// ... etc
|
||||
```
|
||||
|
||||
### Hot Route Reloading
|
||||
|
||||
Update routes without server restarts using `server.reload()`:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
"/api/version": () => Response.json({ version: "1.0.0" }),
|
||||
},
|
||||
});
|
||||
|
||||
// Deploy new routes without downtime
|
||||
server.reload({
|
||||
routes: {
|
||||
"/api/version": () => Response.json({ version: "2.0.0" }),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Bun provides structured error handling for routes:
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Errors are caught automatically
|
||||
"/api/risky": () => {
|
||||
throw new Error("Something went wrong");
|
||||
},
|
||||
},
|
||||
// Global error handler
|
||||
error(error) {
|
||||
console.error(error);
|
||||
return new Response(`Internal Error: ${error.message}`, {
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### HTML imports
|
||||
|
||||
To add a client-side single-page app, you can use an HTML import:
|
||||
|
||||
```ts
|
||||
import myReactSinglePageApp from "./index.html";
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": myReactSinglePageApp,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
HTML imports don't just serve HTML. It's a full-featured frontend bundler, transpiler, and toolkit built using Bun's [bundler](https://bun.sh/docs/bundler), JavaScript transpiler and CSS parser.
|
||||
|
||||
You can use this to build a full-featured frontend with React, TypeScript, Tailwind CSS, and more. Check out [/docs/bundler/fullstack](https://bun.sh/docs/bundler/fullstack) to learn more.
|
||||
|
||||
### Practical example: REST API
|
||||
|
||||
Here's a basic database-backed REST API using Bun's router with zero dependencies:
|
||||
|
||||
{% codetabs %}
|
||||
|
||||
```ts#server.ts
|
||||
import type { Post } from "./types.ts";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("posts.db");
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// List posts
|
||||
"/api/posts": {
|
||||
GET: () => {
|
||||
const posts = db.query("SELECT * FROM posts").all();
|
||||
return Response.json(posts);
|
||||
},
|
||||
|
||||
// Create post
|
||||
POST: async req => {
|
||||
const post: Omit<Post, "id" | "created_at"> = await req.json();
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
db.query(
|
||||
`INSERT INTO posts (id, title, content, created_at)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
).run(id, post.title, post.content, new Date().toISOString());
|
||||
|
||||
return Response.json({ id, ...post }, { status: 201 });
|
||||
},
|
||||
},
|
||||
|
||||
// Get post by ID
|
||||
"/api/posts/:id": req => {
|
||||
const post = db
|
||||
.query("SELECT * FROM posts WHERE id = ?")
|
||||
.get(req.params.id);
|
||||
|
||||
if (!post) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json(post);
|
||||
},
|
||||
},
|
||||
|
||||
error(error) {
|
||||
console.error(error);
|
||||
return new Response("Internal Server Error", { status: 500 });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts#types.ts
|
||||
export interface Post {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
}
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
### Routing performance
|
||||
|
||||
`Bun.serve()`'s router builds on top uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64) to add [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows.
|
||||
|
||||
### `fetch` request handler
|
||||
|
||||
The `fetch` handler handles incoming requests. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`.
|
||||
The `fetch` handler handles incoming requests that weren't matched by any route. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
@@ -70,116 +472,6 @@ const server = Bun.serve({
|
||||
});
|
||||
```
|
||||
|
||||
### Static routes
|
||||
|
||||
Use the `static` option to serve static `Response` objects by route.
|
||||
|
||||
```ts
|
||||
// Bun v1.1.27+ required
|
||||
Bun.serve({
|
||||
static: {
|
||||
// health-check endpoint
|
||||
"/api/health-check": new Response("All good!"),
|
||||
|
||||
// redirect from /old-link to /new-link
|
||||
"/old-link": Response.redirect("/new-link", 301),
|
||||
|
||||
// serve static text
|
||||
"/": new Response("Hello World"),
|
||||
|
||||
// serve a file by buffering it in memory
|
||||
"/index.html": new Response(await Bun.file("./index.html").bytes(), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}),
|
||||
"/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
|
||||
headers: {
|
||||
"Content-Type": "image/x-icon",
|
||||
},
|
||||
}),
|
||||
|
||||
// serve JSON
|
||||
"/api/version.json": Response.json({ version: "1.0.0" }),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Static routes support headers, status code, and other `Response` options.
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
static: {
|
||||
"/api/time": new Response(new Date().toISOString(), {
|
||||
headers: {
|
||||
"X-Custom-Header": "Bun!",
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Static routes can serve Response bodies faster than `fetch` handlers because they don't create `Request` objects, they don't create `AbortSignal`, they don't create additional `Response` objects. The only per-request memory allocation is the TCP/TLS socket data needed for each request.
|
||||
|
||||
{% note %}
|
||||
`static` is experimental
|
||||
{% /note %}
|
||||
|
||||
Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`.
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
static: {
|
||||
"/api/time": new Response(new Date().toISOString()),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
|
||||
// Update the time every second.
|
||||
setInterval(() => {
|
||||
server.reload({
|
||||
static: {
|
||||
"/api/time": new Response(new Date().toISOString()),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
Reloading static routes only impact the next request. In-flight requests continue to use the old static routes. After in-flight requests to old static routes are finished, the old static routes are freed from memory.
|
||||
|
||||
To simplify error handling, static routes do not support streaming response bodies from `ReadableStream` or an `AsyncIterator`. Fortunately, you can still buffer the response in memory first:
|
||||
|
||||
```ts
|
||||
const time = await fetch("https://api.example.com/v1/data");
|
||||
// Buffer the response in memory first.
|
||||
const blob = await time.blob();
|
||||
|
||||
const server = Bun.serve({
|
||||
static: {
|
||||
"/api/data": new Response(blob),
|
||||
},
|
||||
|
||||
fetch(req) {
|
||||
return new Response("404!");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Changing the `port` and `hostname`
|
||||
|
||||
To configure which port and hostname the server will listen on, set `port` and `hostname` in the options object.
|
||||
@@ -553,7 +845,7 @@ Update the server's handlers without restarting:
|
||||
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
static: {
|
||||
routes: {
|
||||
"/api/version": Response.json({ version: "v1" }),
|
||||
},
|
||||
fetch(req) {
|
||||
@@ -563,7 +855,7 @@ const server = Bun.serve({
|
||||
|
||||
// Update to new handler
|
||||
server.reload({
|
||||
static: {
|
||||
routes: {
|
||||
"/api/version": Response.json({ version: "v2" }),
|
||||
},
|
||||
fetch(req) {
|
||||
@@ -572,7 +864,7 @@ server.reload({
|
||||
});
|
||||
```
|
||||
|
||||
This is useful for development and hot reloading. Only `fetch`, `error`, and `static` handlers can be updated.
|
||||
This is useful for development and hot reloading. Only `fetch`, `error`, and `routes` can be updated.
|
||||
|
||||
## Per-Request Controls
|
||||
|
||||
|
||||
@@ -181,8 +181,8 @@ const download = s3.presign("my-file.txt"); // GET, text/plain, expires in 24 ho
|
||||
|
||||
const upload = s3.presign("my-file", {
|
||||
expiresIn: 3600, // 1 hour
|
||||
method: 'PUT',
|
||||
type: 'application/json', // No extension for inferring, so we can specify the content type to be JSON
|
||||
method: "PUT",
|
||||
type: "application/json", // No extension for inferring, so we can specify the content type to be JSON
|
||||
});
|
||||
|
||||
// You can call .presign() if on a file reference, but avoid doing so
|
||||
@@ -361,6 +361,56 @@ const minio = new S3Client({
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with supabase
|
||||
|
||||
To use Bun's S3 client with [supabase](https://supabase.com/), set `endpoint` to the supabase endpoint in the `S3Client` constructor. The supabase endpoint includes your account ID and /storage/v1/s3 path. Make sure to set Enable connection via S3 protocol on in the supabase dashboard in https://supabase.com/dashboard/project/<account-id>/settings/storage and to set the region informed in the same section.
|
||||
|
||||
```ts
|
||||
import { S3Client } from "bun";
|
||||
|
||||
const supabase = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
region: "us-west-1",
|
||||
endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
|
||||
});
|
||||
```
|
||||
|
||||
### Using Bun's S3Client with S3 Virtual Hosted-Style endpoints
|
||||
|
||||
When using a S3 Virtual Hosted-Style endpoint, you need to set the `virtualHostedStyle` option to `true` and if no endpoint is provided, Bun will use region and bucket to infer the endpoint to AWS S3, if no region is provided it will use `us-east-1`. If you provide a the endpoint, there are no need to provide the bucket name.
|
||||
|
||||
```ts
|
||||
import { S3Client } from "bun";
|
||||
|
||||
// AWS S3 endpoint inferred from region and bucket
|
||||
const s3 = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
bucket: "my-bucket",
|
||||
virtualHostedStyle: true,
|
||||
// endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
|
||||
// region: "us-east-1",
|
||||
});
|
||||
|
||||
// AWS S3
|
||||
const s3WithEndpoint = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
|
||||
virtualHostedStyle: true,
|
||||
});
|
||||
|
||||
// Cloudflare R2
|
||||
const r2WithEndpoint = new S3Client({
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
|
||||
virtualHostedStyle: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
Credentials are one of the hardest parts of using S3, and we've tried to make it as easy as possible. By default, Bun reads the following environment variables for credentials.
|
||||
|
||||
115
docs/api/sql.md
115
docs/api/sql.md
@@ -87,7 +87,7 @@ await sql`INSERT INTO users ${sql(users)}`;
|
||||
|
||||
### Picking columns to insert
|
||||
|
||||
You can use `sql(object, Array<string>)` to pick which columns to insert. Each of the columns must be defined on the object.
|
||||
You can use `sql(object, ...string)` to pick which columns to insert. Each of the columns must be defined on the object.
|
||||
|
||||
```ts
|
||||
const user = {
|
||||
@@ -96,7 +96,7 @@ const user = {
|
||||
age: 25,
|
||||
};
|
||||
|
||||
await sql`INSERT INTO users ${sql(user, ["name", "email"])}`;
|
||||
await sql`INSERT INTO users ${sql(user, "name", "email")}`;
|
||||
// Only inserts name and email columns, ignoring other fields
|
||||
```
|
||||
|
||||
@@ -165,13 +165,72 @@ await sql`
|
||||
`;
|
||||
```
|
||||
|
||||
### Unsafe Queries
|
||||
### Dynamic columns in updates
|
||||
|
||||
You can use the `sql.unsafe` function to execute raw SQL strings. Use this with caution, as it will not escape user input.
|
||||
You can use `sql(object, ...string)` to pick which columns to update. Each of the columns must be defined on the object. If the columns are not informed all keys will be used to update the row.
|
||||
|
||||
```ts
|
||||
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
|
||||
// uses all keys from the object to update the row
|
||||
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;
|
||||
```
|
||||
|
||||
### Dynamic values and `where in`
|
||||
|
||||
Value lists can also be created dynamically, making where in queries simple too. Optionally you can pass a array of objects and inform what key to use to create the list.
|
||||
|
||||
```ts
|
||||
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;
|
||||
|
||||
const users = [
|
||||
{ id: 1, name: "Alice" },
|
||||
{ id: 2, name: "Bob" },
|
||||
{ id: 3, name: "Charlie" },
|
||||
];
|
||||
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;
|
||||
```
|
||||
|
||||
## `sql``.simple()`
|
||||
|
||||
The PostgreSQL wire protocol supports two types of queries: "simple" and "extended". Simple queries can contain multiple statements but don't support parameters, while extended queries (the default) support parameters but only allow one statement.
|
||||
|
||||
To run multiple statements in a single query, use `sql``.simple()`:
|
||||
|
||||
```ts
|
||||
// Multiple statements in one query
|
||||
await sql`
|
||||
SELECT 1;
|
||||
SELECT 2;
|
||||
`.simple();
|
||||
```
|
||||
|
||||
Simple queries are often useful for database migrations and setup scripts.
|
||||
|
||||
Note that simple queries cannot use parameters (`${value}`). If you need parameters, you must split your query into separate statements.
|
||||
|
||||
### Queries in files
|
||||
|
||||
You can use the `sql.file` method to read a query from a file and execute it, if the file includes $1, $2, etc you can pass parameters to the query. If no parameters are used it can execute multiple commands per file.
|
||||
|
||||
```ts
|
||||
const result = await sql.file("query.sql", [1, 2, 3]);
|
||||
```
|
||||
|
||||
### Unsafe Queries
|
||||
|
||||
You can use the `sql.unsafe` function to execute raw SQL strings. Use this with caution, as it will not escape user input. Executing more than one command per query is allowed if no parameters are used.
|
||||
|
||||
```ts
|
||||
// Multiple commands without parameters
|
||||
const result = await sql.unsafe(`
|
||||
SELECT ${userColumns} FROM users;
|
||||
SELECT ${accountColumns} FROM accounts;
|
||||
`);
|
||||
|
||||
// Using parameters (only one command is allowed)
|
||||
const result = await sql.unsafe(
|
||||
"SELECT " + columns + " FROM users WHERE id = " + id,
|
||||
"SELECT " + dangerous + " FROM users WHERE id = $1",
|
||||
[id],
|
||||
);
|
||||
```
|
||||
|
||||
@@ -262,6 +321,21 @@ const db = new SQL({
|
||||
});
|
||||
```
|
||||
|
||||
## Dynamic passwords
|
||||
|
||||
When clients need to use alternative authentication schemes such as access tokens or connections to databases with rotating passwords, provide either a synchronous or asynchronous function that will resolve the dynamic password value at connection time.
|
||||
|
||||
```ts
|
||||
import { SQL } from "bun";
|
||||
|
||||
const sql = new SQL(url, {
|
||||
// Other connection config
|
||||
...
|
||||
// Password function for the database user
|
||||
password: async () => await signer.getAuthToken(),
|
||||
});
|
||||
```
|
||||
|
||||
## Transactions
|
||||
|
||||
To start a new transaction, use `sql.begin`. This method reserves a dedicated connection for the duration of the transaction and provides a scoped `sql` instance to use within the callback function. Once the callback completes, `sql.begin` resolves with the return value of the callback.
|
||||
@@ -431,6 +505,34 @@ try {
|
||||
} // Automatically released
|
||||
```
|
||||
|
||||
## Prepared Statements
|
||||
|
||||
By default, Bun's SQL client automatically creates named prepared statements for queries where it can be inferred that the query is static. This provides better performance. However, you can change this behavior by setting `prepare: false` in the connection options:
|
||||
|
||||
```ts
|
||||
const sql = new SQL({
|
||||
// ... other options ...
|
||||
prepare: false, // Disable persisting named prepared statements on the server
|
||||
});
|
||||
```
|
||||
|
||||
When `prepare: false` is set:
|
||||
|
||||
Queries are still executed using the "extended" protocol, but they are executed using [unnamed prepared statements](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY), an unnamed prepared statement lasts only until the next Parse statement specifying the unnamed statement as destination is issued.
|
||||
|
||||
- Parameter binding is still safe against SQL injection
|
||||
- Each query is parsed and planned from scratch by the server
|
||||
- Queries will not be [pipelined](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-PIPELINING)
|
||||
|
||||
You might want to use `prepare: false` when:
|
||||
|
||||
- Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)
|
||||
- Debugging query execution plans
|
||||
- Working with dynamic SQL where query plans need to be regenerated frequently
|
||||
- More than one command per query will not be supported (unless you use `sql``.simple()`)
|
||||
|
||||
Note that disabling prepared statements may impact performance for queries that are executed frequently with different parameters, as the server needs to parse and plan each query from scratch.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The client provides typed errors for different failure scenarios:
|
||||
@@ -466,6 +568,7 @@ The client provides typed errors for different failure scenarios:
|
||||
| `ERR_POSTGRES_SERVER_ERROR` | General error from PostgreSQL server |
|
||||
| `ERR_POSTGRES_INVALID_QUERY_BINDING` | Invalid parameter binding |
|
||||
| `ERR_POSTGRES_QUERY_CANCELLED` | Query was cancelled |
|
||||
| `ERR_POSTGRES_NOT_TAGGED_CALL` | Query was called without a tagged call |
|
||||
|
||||
### Data Type Errors
|
||||
|
||||
@@ -501,7 +604,7 @@ The client provides typed errors for different failure scenarios:
|
||||
|
||||
## Numbers and BigInt
|
||||
|
||||
Bun's SQL client includes special handling for large numbers that exceed the range of a 53-bit integer. Here’s how it works:
|
||||
Bun's SQL client includes special handling for large numbers that exceed the range of a 53-bit integer. Here's how it works:
|
||||
|
||||
```ts
|
||||
import { sql } from "bun";
|
||||
|
||||
@@ -228,3 +228,17 @@ const worker = new Worker("./i-am-smol.ts", {
|
||||
{% details summary="What does `smol` mode actually do?" %}
|
||||
Setting `smol: true` sets `JSC::HeapSize` to be `Small` instead of the default `Large`.
|
||||
{% /details %}
|
||||
|
||||
## `Bun.isMainThread`
|
||||
|
||||
You can check if you're in the main thread by checking `Bun.isMainThread`.
|
||||
|
||||
```ts
|
||||
if (Bun.isMainThread) {
|
||||
console.log("I'm the main thread");
|
||||
} else {
|
||||
console.log("I'm in a worker");
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for conditionally running code based on whether you're in the main thread or not.
|
||||
|
||||
@@ -75,14 +75,16 @@ bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp
|
||||
|
||||
The order of the `--target` flag does not matter, as long as they're delimited by a `-`.
|
||||
|
||||
| --target | Operating System | Architecture | Modern | Baseline |
|
||||
| --------------------- | ---------------- | ------------ | ------ | -------- |
|
||||
| bun-linux-x64 | Linux | x64 | ✅ | ✅ |
|
||||
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A |
|
||||
| bun-windows-x64 | Windows | x64 | ✅ | ✅ |
|
||||
| ~~bun-windows-arm64~~ | Windows | arm64 | ❌ | ❌ |
|
||||
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ |
|
||||
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A |
|
||||
| --target | Operating System | Architecture | Modern | Baseline | Libc |
|
||||
| --------------------- | ---------------- | ------------ | ------ | -------- | ----- |
|
||||
| bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc |
|
||||
| bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc |
|
||||
| bun-windows-x64 | Windows | x64 | ✅ | ✅ | - |
|
||||
| ~~bun-windows-arm64~~ | Windows | arm64 | ❌ | ❌ | - |
|
||||
| bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - |
|
||||
| bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - |
|
||||
| bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl |
|
||||
| bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl |
|
||||
|
||||
On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version.
|
||||
|
||||
@@ -294,6 +296,55 @@ These flags currently cannot be used when cross-compiling because they depend on
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Code signing on macOS
|
||||
|
||||
To codesign a standalone executable on macOS (which fixes Gatekeeper warnings), use the `codesign` command.
|
||||
|
||||
```sh
|
||||
$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp
|
||||
```
|
||||
|
||||
We recommend including an `entitlements.plist` file with JIT permissions.
|
||||
|
||||
```xml#entitlements.plist
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
To codesign with JIT support, pass the `--entitlements` flag to `codesign`.
|
||||
|
||||
```sh
|
||||
$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp
|
||||
```
|
||||
|
||||
After codesigning, verify the executable:
|
||||
|
||||
```sh
|
||||
$ codesign -vvv --verify ./myapp
|
||||
./myapp: valid on disk
|
||||
./myapp: satisfies its Designated Requirement
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
|
||||
Codesign support requires Bun v1.2.4 or newer.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Unsupported CLI arguments
|
||||
|
||||
Currently, the `--compile` flag can only accept a single entrypoint at a time and does not support the following flags:
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
Using `Bun.serve()`'s `static` option, you can run your frontend and backend in the same app with no extra steps.
|
||||
Using `Bun.serve()`'s `routes` option, you can run your frontend and backend in the same app with no extra steps.
|
||||
|
||||
To get started, import HTML files and pass them to the `static` option in `Bun.serve()`.
|
||||
To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`.
|
||||
|
||||
```ts
|
||||
import { sql, serve } from "bun";
|
||||
import dashboard from "./dashboard.html";
|
||||
import homepage from "./index.html";
|
||||
|
||||
const server = Bun.serve({
|
||||
// Add HTML imports to `static`
|
||||
static: {
|
||||
// Bundle & route index.html to "/"
|
||||
const server = serve({
|
||||
routes: {
|
||||
// ** HTML imports **
|
||||
// Bundle & route index.html to "/". This uses HTMLRewriter to scan the HTML for `<script>` and `<link>` tags, run's Bun's JavaScript & CSS bundler on them, transpiles any TypeScript, JSX, and TSX, downlevels CSS with Bun's CSS parser and serves the result.
|
||||
"/": homepage,
|
||||
// Bundle & route dashboard.html to "/dashboard"
|
||||
"/dashboard": dashboard,
|
||||
|
||||
// ** API endpoints ** (Bun v1.2.3+ required)
|
||||
"/api/users": {
|
||||
async GET(req) {
|
||||
const users = await sql`SELECT * FROM users`;
|
||||
return Response.json(users);
|
||||
},
|
||||
async POST(req) {
|
||||
const { name, email } = await req.json();
|
||||
const [user] =
|
||||
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
|
||||
return Response.json(user);
|
||||
},
|
||||
},
|
||||
"/api/users/:id": async req => {
|
||||
const { id } = req.params;
|
||||
const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
|
||||
return Response.json(user);
|
||||
},
|
||||
},
|
||||
|
||||
// Enable development mode for:
|
||||
// - Detailed error messages
|
||||
// - Rebuild on request
|
||||
// - Hot reloading (Bun v1.2.3+ required)
|
||||
development: true,
|
||||
|
||||
// Handle API requests
|
||||
async fetch(req) {
|
||||
// ...your API code
|
||||
if (req.url.endsWith("/api/users")) {
|
||||
const users = await Bun.sql`SELECT * FROM users`;
|
||||
return Response.json(users);
|
||||
}
|
||||
|
||||
// Return 404 for unmatched routes
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
// Prior to v1.2.3, the `fetch` option was used to handle all API requests. It is now optional.
|
||||
// async fetch(req) {
|
||||
// // Return 404 for unmatched routes
|
||||
// return new Response("Not Found", { status: 404 });
|
||||
// },
|
||||
});
|
||||
|
||||
console.log(`Listening on ${server.url}`);
|
||||
@@ -55,7 +69,7 @@ These HTML files are used as routes in Bun's dev server you can pass to `Bun.ser
|
||||
|
||||
```ts
|
||||
Bun.serve({
|
||||
static: {
|
||||
routes: {
|
||||
"/": homepage,
|
||||
"/dashboard": dashboard,
|
||||
}
|
||||
@@ -113,7 +127,7 @@ import dashboard from "../public/dashboard.html";
|
||||
import { serve } from "bun";
|
||||
|
||||
serve({
|
||||
static: {
|
||||
routes: {
|
||||
"/": dashboard,
|
||||
},
|
||||
|
||||
@@ -171,7 +185,7 @@ import homepage from "./index.html";
|
||||
import dashboard from "./dashboard.html";
|
||||
|
||||
Bun.serve({
|
||||
static: {
|
||||
routes: {
|
||||
"/": homepage,
|
||||
"/dashboard": dashboard,
|
||||
}
|
||||
@@ -249,6 +263,8 @@ plugins = ["./my-plugin-implementation.ts"]
|
||||
|
||||
Bun will lazily resolve and load each plugin and use them to bundle your routes.
|
||||
|
||||
Note: this is currently in `bunfig.toml` to make it possible to know statically which plugins are in use when we eventually integrate this with the `bun build` CLI. These plugins work in `Bun.build()`'s JS API, but are not yet supported in the CLI.
|
||||
|
||||
## How this works
|
||||
|
||||
Bun uses [`HTMLRewriter`](/docs/api/html-rewriter) to scan for `<script>` and `<link>` tags in HTML files, uses them as entrypoints for [Bun's bundler](/docs/bundler), generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files, and serves the result.
|
||||
@@ -293,5 +309,5 @@ This works similarly to how [`Bun.build` processes HTML files](/docs/bundler/htm
|
||||
|
||||
## This is a work in progress
|
||||
|
||||
- Client-side hot reloading isn't wired up yet. It will be in the future.
|
||||
- ~Client-side hot reloading isn't wired up yet. It will be in the future.~ New in Bun v1.2.3
|
||||
- This doesn't support `bun build` yet. It also will in the future.
|
||||
|
||||
@@ -301,6 +301,6 @@ This is a small wrapper around Bun's support for HTML imports in JavaScript.
|
||||
|
||||
### Adding a backend to your frontend
|
||||
|
||||
To add a backend to your frontend, you can use the `"static"` option in `Bun.serve`.
|
||||
To add a backend to your frontend, you can use the `"routes"` option in `Bun.serve`.
|
||||
|
||||
Learn more in [the full-stack docs](/docs/bundler/fullstack).
|
||||
|
||||
@@ -12,9 +12,12 @@ Options for the `pack` command:
|
||||
|
||||
- `--dry-run`: Perform all tasks except writing the tarball to disk.
|
||||
- `--destination`: Specify the directory where the tarball will be saved.
|
||||
- `--filename`: Specify an exact file name for the tarball to be saved at.
|
||||
- `--ignore-scripts`: Skip running pre/postpack and prepare scripts.
|
||||
- `--gzip-level`: Set a custom compression level for gzip, ranging from 0 to 9 (default is 9).
|
||||
|
||||
> Note `--filename` and `--destination` cannot be used at the same time
|
||||
|
||||
## bin
|
||||
|
||||
To print the path to the `bin` directory for the local project:
|
||||
|
||||
@@ -70,7 +70,7 @@ In the Render UI, provide the following values during web service creation:
|
||||
| ----------------- | ------------- |
|
||||
| **Runtime** | `Node` |
|
||||
| **Build Command** | `bun install` |
|
||||
| **Start Command** | `bun app.js` |
|
||||
| **Start Command** | `bun app.ts` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
68
docs/guides/html-rewriter/extract-links.md
Normal file
68
docs/guides/html-rewriter/extract-links.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: Extract links from a webpage using HTMLRewriter
|
||||
---
|
||||
|
||||
## Extract links from a webpage
|
||||
|
||||
Bun's [HTMLRewriter](https://bun.sh/docs/api/html-rewriter) API can be used to efficiently extract links from HTML content. It works by chaining together CSS selectors to match the elements, text, and attributes you want to process. This is a simple example of how to extract links from a webpage. You can pass `.transform` a `Response`, `Blob`, or `string`.
|
||||
|
||||
```ts
|
||||
async function extractLinks(url: string) {
|
||||
const links = new Set<string>();
|
||||
const response = await fetch(url);
|
||||
|
||||
const rewriter = new HTMLRewriter().on("a[href]", {
|
||||
element(el) {
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
links.add(href);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the response to be processed
|
||||
await rewriter.transform(response).blob();
|
||||
console.log([...links]); // ["https://bun.sh", "/docs", ...]
|
||||
}
|
||||
|
||||
// Extract all links from the Bun website
|
||||
await extractLinks("https://bun.sh");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Convert relative URLs to absolute
|
||||
|
||||
When scraping websites, you often want to convert relative URLs (like `/docs`) to absolute URLs. Here's how to handle URL resolution:
|
||||
|
||||
```ts
|
||||
async function extractLinksFromURL(url: string) {
|
||||
const response = await fetch(url);
|
||||
const links = new Set<string>();
|
||||
|
||||
const rewriter = new HTMLRewriter().on("a[href]", {
|
||||
element(el) {
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
// Convert relative URLs to absolute
|
||||
try {
|
||||
const absoluteURL = new URL(href, url).href;
|
||||
links.add(absoluteURL);
|
||||
} catch {
|
||||
links.add(href);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for the response to be processed
|
||||
await rewriter.transform(response).blob();
|
||||
return [...links];
|
||||
}
|
||||
|
||||
const websiteLinks = await extractLinksFromURL("https://example.com");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See [Docs > API > HTMLRewriter](https://bun.sh/docs/api/html-rewriter) for complete documentation on HTML transformation with Bun.
|
||||
93
docs/guides/html-rewriter/extract-social-meta.md
Normal file
93
docs/guides/html-rewriter/extract-social-meta.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: Extract social share images and Open Graph tags
|
||||
---
|
||||
|
||||
## Extract social share images and Open Graph tags
|
||||
|
||||
Bun's [HTMLRewriter](https://bun.sh/docs/api/html-rewriter) API can be used to efficiently extract social share images and Open Graph metadata from HTML content. This is particularly useful for building link preview features, social media cards, or web scrapers. We can use HTMLRewriter to match CSS selectors to HTML elements, text, and attributes we want to process.
|
||||
|
||||
```ts
|
||||
interface SocialMetadata {
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
siteName?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
async function extractSocialMetadata(url: string): Promise<SocialMetadata> {
|
||||
const metadata: SocialMetadata = {};
|
||||
const response = await fetch(url);
|
||||
|
||||
const rewriter = new HTMLRewriter()
|
||||
// Extract Open Graph meta tags
|
||||
.on('meta[property^="og:"]', {
|
||||
element(el) {
|
||||
const property = el.getAttribute("property");
|
||||
const content = el.getAttribute("content");
|
||||
if (property && content) {
|
||||
// Convert "og:image" to "image" etc.
|
||||
const key = property.replace("og:", "") as keyof SocialMetadata;
|
||||
metadata[key] = content;
|
||||
}
|
||||
},
|
||||
})
|
||||
// Extract Twitter Card meta tags as fallback
|
||||
.on('meta[name^="twitter:"]', {
|
||||
element(el) {
|
||||
const name = el.getAttribute("name");
|
||||
const content = el.getAttribute("content");
|
||||
if (name && content) {
|
||||
const key = name.replace("twitter:", "") as keyof SocialMetadata;
|
||||
// Only use Twitter Card data if we don't have OG data
|
||||
if (!metadata[key]) {
|
||||
metadata[key] = content;
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
// Fallback to regular meta tags
|
||||
.on('meta[name="description"]', {
|
||||
element(el) {
|
||||
const content = el.getAttribute("content");
|
||||
if (content && !metadata.description) {
|
||||
metadata.description = content;
|
||||
}
|
||||
},
|
||||
})
|
||||
// Fallback to title tag
|
||||
.on("title", {
|
||||
text(text) {
|
||||
if (!metadata.title) {
|
||||
metadata.title = text.text;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Process the response
|
||||
await rewriter.transform(response).blob();
|
||||
|
||||
// Convert relative image URLs to absolute
|
||||
if (metadata.image && !metadata.image.startsWith("http")) {
|
||||
try {
|
||||
metadata.image = new URL(metadata.image, url).href;
|
||||
} catch {
|
||||
// Keep the original URL if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const metadata = await extractSocialMetadata("https://bun.sh");
|
||||
console.log(metadata);
|
||||
// {
|
||||
// title: "Bun — A fast all-in-one JavaScript runtime",
|
||||
// description: "Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a fast all-in-one JavaScript runtime & toolkit designed for speed, complete with a bundler, test runner, and Node.js-compatible package manager.",
|
||||
// image: "https://bun.sh/share.jpg",
|
||||
// type: "website",
|
||||
// ...
|
||||
// }
|
||||
```
|
||||
4
docs/guides/html-rewriter/index.json
Normal file
4
docs/guides/html-rewriter/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "HTMLRewriter",
|
||||
"description": "A collection of guides for using the HTMLRewriter streaming HTML parser with Bun"
|
||||
}
|
||||
56
docs/guides/runtime/codesign-macos-executable.md
Normal file
56
docs/guides/runtime/codesign-macos-executable.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: Codesign a single-file JavaScript executable on macOS
|
||||
description: Fix the "can't be opened because it is from an unidentified developer" Gatekeeper warning when running your JavaScript executable.
|
||||
---
|
||||
|
||||
Compile your executable using the `--compile` flag.
|
||||
|
||||
```sh
|
||||
$ bun build --compile ./path/to/entry.ts --outfile myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
List your available signing identities. One of these will be your signing identity that you pass to the `codesign` command. This command requires macOS.
|
||||
|
||||
```sh
|
||||
$ security find-identity -v -p codesigning
|
||||
1. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "Developer ID Application: Your Name (ZZZZZZZZZZ)"
|
||||
1 valid identities found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Optional, but recommended: create an `entitlements.plist` file with the necessary permissions for the JavaScript engine to work correctly.
|
||||
|
||||
```xml#entitlements.plist
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Sign your executable using the `codesign` command and verify it works.
|
||||
|
||||
```bash
|
||||
$ codesign --entitlements entitlements.plist -vvvv --deep --sign "XXXXXXXXXX" ./myapp --force
|
||||
$ codesign -vvv --verify ./myapp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
For more information on macOS codesigning, refer to [Apple's Code Signing documentation](https://developer.apple.com/documentation/security/code_signing_services). For details about creating single-file executables with Bun, see [Standalone Executables](/docs/bundler/executables). This guide requires Bun v1.2.4 or newer.
|
||||
@@ -28,10 +28,21 @@ BAR=world
|
||||
|
||||
Variables can also be set via the command line.
|
||||
|
||||
```sh
|
||||
{% codetabs %}
|
||||
|
||||
```sh#Linux/macOS
|
||||
$ FOO=helloworld bun run dev
|
||||
```
|
||||
|
||||
```sh#Windows
|
||||
# Using CMD
|
||||
$ set FOO=helloworld && bun run dev
|
||||
|
||||
# Using PowerShell
|
||||
$ $env:FOO="helloworld"; bun run dev
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
---
|
||||
|
||||
See [Docs > Runtime > Environment variables](https://bun.sh/docs/runtime/env) for more information on using environment variables with Bun.
|
||||
|
||||
@@ -8,7 +8,7 @@ VSCode extension support is currently buggy. We recommend the [Web Debugger](htt
|
||||
|
||||
{% /note %}
|
||||
|
||||
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-vscode/types/jsc.d.ts) so you can debug your code with an interactive debugger.
|
||||
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts) so you can debug your code with an interactive debugger.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -63,6 +63,20 @@ $ bun install --filter "pkg-*" --filter "!pkg-c"
|
||||
$ bun install --filter "./packages/pkg-*" --filter "!pkg-c" # or --filter "!./packages/pkg-c"
|
||||
```
|
||||
|
||||
When publishing, `workspace:` versions are replaced by the package's `package.json` version,
|
||||
|
||||
```
|
||||
"workspace:*" -> "1.0.1"
|
||||
"workspace:^" -> "^1.0.1"
|
||||
"workspace:~" -> "~1.0.1"
|
||||
```
|
||||
|
||||
Setting a specific version takes precedence over the package's `package.json` version,
|
||||
|
||||
```
|
||||
"workspace:1.0.2" -> "1.0.2" // Even if current version is 1.0.1
|
||||
```
|
||||
|
||||
Workspaces have a couple major benefits.
|
||||
|
||||
- **Code can be split into logical parts.** If one package relies on another, you can simply add it as a dependency in `package.json`. If package `b` depends on `a`, `bun install` will install your local `packages/a` directory into `node_modules` instead of downloading it from the npm registry.
|
||||
|
||||
@@ -147,6 +147,7 @@ If the command runs successfully but `bun --version` is not recognized, it means
|
||||
[System.EnvironmentVariableTarget]::User
|
||||
)
|
||||
```
|
||||
|
||||
After running the command, restart your terminal and test with `bun --version`
|
||||
|
||||
{% /details %}
|
||||
@@ -219,11 +220,12 @@ For convenience, here are download links for the latest version:
|
||||
- [`bun-linux-aarch64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64.zip)
|
||||
- [`bun-linux-aarch64-musl.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64-musl.zip)
|
||||
- [`bun-darwin-x64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-x64.zip)
|
||||
- [`bun-darwin-x64-baseline.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-x64-baseline.zip)
|
||||
|
||||
The `musl` binaries are built for distributions that do not ship with the glibc libraries by default, instead relying on musl. The two most popular distros are Void Linux and Alpine Linux, with the latter is used heavily in Docker containers. If you encounter an error like the following: `bun: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29' not found (required by bun)`, try using the musl binary. Bun's install script automatically chooses the correct binary for your system.
|
||||
|
||||
The `baseline` binaries are built for older CPUs which may not support AVX2 instructions. If you run into an "Illegal Instruction" error when running Bun, try using the `baseline` binaries instead. Bun's install scripts automatically chooses the correct binary for your system which helps avoid this issue. Baseline builds are slower than regular builds, so use them only if necessary.
|
||||
Bun's `x64` binaries target the Haswell CPU architecture, which means they require AVX and AVX2 instructions. For Linux and Windows, the `x64-baseline` binaries are also available which target the Nehalem architecture. If you run into an "Illegal Instruction" error when running Bun, try using the `baseline` binaries instead. Bun's install scripts automatically chooses the correct binary for your system which helps avoid this issue. Baseline builds are slower than regular builds, so use them only if necessary.
|
||||
|
||||
Bun also publishes `darwin-x64-baseline` binaries, but these are just a copy of the `darwin-x64` ones so they still have the same CPU requirement. We only maintain these since some tools expect them to exist. Bun requires macOS 13.0 or later, which does not support any CPUs that don't meet our requirement.
|
||||
|
||||
<!--
|
||||
## Native
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Debugging
|
||||
---
|
||||
|
||||
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-types/jsc.d.ts), so you can debug your code with an interactive debugger. For demonstration purposes, consider the following simple web server.
|
||||
Bun speaks the [WebKit Inspector Protocol](https://github.com/oven-sh/bun/blob/main/packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts), so you can debug your code with an interactive debugger. For demonstration purposes, consider the following simple web server.
|
||||
|
||||
## Debugging JavaScript and TypeScript
|
||||
|
||||
|
||||
@@ -15,10 +15,40 @@ BAR=world
|
||||
|
||||
Variables can also be set via the command line.
|
||||
|
||||
```sh
|
||||
{% codetabs %}
|
||||
|
||||
```sh#Linux/macOS
|
||||
$ FOO=helloworld bun run dev
|
||||
```
|
||||
|
||||
```sh#Windows
|
||||
# Using CMD
|
||||
$ set FOO=helloworld && bun run dev
|
||||
|
||||
# Using PowerShell
|
||||
$ $env:FOO="helloworld"; bun run dev
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
{% details summary="Cross-platform solution with Windows" %}
|
||||
|
||||
For a cross-platform solution, you can use [bun shell](https://bun.sh/docs/runtime/shell). For example, the `bun exec` command.
|
||||
|
||||
```sh
|
||||
$ bun exec 'FOO=helloworld bun run dev'
|
||||
```
|
||||
|
||||
On Windows, `package.json` scripts called with `bun run` will automatically use the **bun shell**, making the following also cross-platform.
|
||||
|
||||
```json#package.json
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development bun --watch app.ts",
|
||||
},
|
||||
```
|
||||
|
||||
{% /details %}
|
||||
|
||||
Or programmatically by assigning a property to `process.env`.
|
||||
|
||||
```ts
|
||||
|
||||
@@ -20,7 +20,7 @@ await $`cat < ${response} | wc -c`; // 1256
|
||||
- **Safety**: Bun Shell escapes all strings by default, preventing shell injection attacks.
|
||||
- **JavaScript interop**: Use `Response`, `ArrayBuffer`, `Blob`, `Bun.file(path)` and other JavaScript objects as stdin, stdout, and stderr.
|
||||
- **Shell scripting**: Bun Shell can be used to run shell scripts (`.bun.sh` files).
|
||||
- **Custom interpreter**: Bun Shell is written in Zig, along with it's lexer, parser, and interpreter. Bun Shell is a small programming language.
|
||||
- **Custom interpreter**: Bun Shell is written in Zig, along with its lexer, parser, and interpreter. Bun Shell is a small programming language.
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -62,6 +62,44 @@ console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
|
||||
console.log(stderr); // Buffer(0) []
|
||||
```
|
||||
|
||||
Because the shell uses tagged template literals, any value enclosed in `${}` will be passed to the shell command as a single argument, with no need for escaping:
|
||||
|
||||
```js#argv.js
|
||||
console.log(process.argv.slice(2));
|
||||
```
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
process.env.FOO = "BAR";
|
||||
// [ "hello world 'foo bar' $FOO $(pwd)" ]
|
||||
await $`bun argv.js ${"hello world 'foo bar' $FOO $(pwd)"}`;
|
||||
// [ "hello", "world", "foo bar", "BAR", "/path/to/cwd" ]
|
||||
await $`bun argv.js hello world 'foo bar' $FOO $(pwd)`;
|
||||
```
|
||||
|
||||
This also means that, if you have a string containing a complete command to run like `"echo bun"`, it will not work when interpolated. This is because Bun will treat this as a single item in the arguments array, and look for a program named `echo bun`:
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const cmd = "echo bun";
|
||||
await $`${cmd}`; // bun: command not found: echo bun
|
||||
```
|
||||
|
||||
If you want a string to be inlined into the command, and then split into arguments and parsed like normal, pass an object with `raw` set to your string. **Be careful if the string is user-controlled**, since malicious strings could execute commands with `$()`, look up sensitive environment variables, or cause various other unintended effects:
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
process.env.FOO = "BAR";
|
||||
const cmd = "echo bun";
|
||||
// [ "hello", "world", "foo bar", "BAR", "/path/to/cwd" ]
|
||||
await $`bun argv.js ${{ raw: "hello world 'foo bar' $FOO $(pwd)" }}`;
|
||||
// bun
|
||||
await $`${{ raw: cmd }}`;
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
By default, non-zero exit codes will throw an error. This `ShellError` contains information about the command run.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.5",
|
||||
"workspaces": [
|
||||
"./packages/bun-types"
|
||||
],
|
||||
|
||||
349
packages/bun-types/bun.d.ts
vendored
349
packages/bun-types/bun.d.ts
vendored
@@ -455,55 +455,6 @@ declare module "bun" {
|
||||
}
|
||||
const TOML: TOML;
|
||||
|
||||
type Serve<WebSocketDataType = undefined> =
|
||||
| ServeOptions
|
||||
| TLSServeOptions
|
||||
| UnixServeOptions
|
||||
| UnixTLSServeOptions
|
||||
| WebSocketServeOptions<WebSocketDataType>
|
||||
| TLSWebSocketServeOptions<WebSocketDataType>
|
||||
| UnixWebSocketServeOptions<WebSocketDataType>
|
||||
| UnixTLSWebSocketServeOptions<WebSocketDataType>;
|
||||
|
||||
/**
|
||||
* Start a fast HTTP server.
|
||||
*
|
||||
* @param options Server options (port defaults to $PORT || 3000)
|
||||
*
|
||||
* -----
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* Bun.serve({
|
||||
* fetch(req: Request): Response | Promise<Response> {
|
||||
* return new Response("Hello World!");
|
||||
* },
|
||||
*
|
||||
* // Optional port number - the default value is 3000
|
||||
* port: process.env.PORT || 3000,
|
||||
* });
|
||||
* ```
|
||||
* -----
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* Send a file
|
||||
*
|
||||
* ```ts
|
||||
* Bun.serve({
|
||||
* fetch(req: Request): Response | Promise<Response> {
|
||||
* return new Response(Bun.file("./package.json"));
|
||||
* },
|
||||
*
|
||||
* // Optional port number - the default value is 3000
|
||||
* port: process.env.PORT || 3000,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line @definitelytyped/no-unnecessary-generics
|
||||
function serve<T>(options: Serve<T>): Server;
|
||||
|
||||
/**
|
||||
* Synchronously resolve a `moduleId` as though it were imported from `parent`
|
||||
*
|
||||
@@ -1382,6 +1333,18 @@ declare module "bun" {
|
||||
*/
|
||||
endpoint?: string;
|
||||
|
||||
/**
|
||||
* Use virtual hosted style endpoint. default to false, when true if `endpoint` is informed it will ignore the `bucket`
|
||||
*
|
||||
* @example
|
||||
* // Using virtual hosted style
|
||||
* const file = s3("my-file.txt", {
|
||||
* virtualHostedStyle: true,
|
||||
* endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com"
|
||||
* });
|
||||
*/
|
||||
virtualHostedStyle?: boolean;
|
||||
|
||||
/**
|
||||
* The size of each part in multipart uploads (in bytes).
|
||||
* - Minimum: 5 MiB
|
||||
@@ -2021,9 +1984,9 @@ declare module "bun" {
|
||||
/** Database user for authentication (alias for username) */
|
||||
user?: string;
|
||||
/** Database password for authentication */
|
||||
password?: string;
|
||||
password?: string | (() => Promise<string>);
|
||||
/** Database password for authentication (alias for password) */
|
||||
pass?: string;
|
||||
pass?: string | (() => Promise<string>);
|
||||
/** Name of the database to connect to */
|
||||
database?: string;
|
||||
/** Name of the database to connect to (alias for database) */
|
||||
@@ -2054,6 +2017,8 @@ declare module "bun" {
|
||||
max?: number;
|
||||
/** By default values outside i32 range are returned as strings. If this is true, values outside i32 range are returned as BigInts. */
|
||||
bigint?: boolean;
|
||||
/** Automatic creation of prepared statements, defaults to true */
|
||||
prepare?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2067,6 +2032,8 @@ declare module "bun" {
|
||||
cancelled: boolean;
|
||||
/** Cancels the executing query */
|
||||
cancel(): SQLQuery;
|
||||
/** Execute as a simple query, no parameters are allowed but can execute multiple commands separated by semicolons */
|
||||
simple(): SQLQuery;
|
||||
/** Executes the query */
|
||||
execute(): SQLQuery;
|
||||
/** Returns the raw query result */
|
||||
@@ -2296,6 +2263,13 @@ declare module "bun" {
|
||||
* const result = await sql.unsafe(`select ${danger} from users where id = ${dragons}`)
|
||||
*/
|
||||
unsafe(string: string, values?: any[]): SQLQuery;
|
||||
/**
|
||||
* Reads a file and uses the contents as a query.
|
||||
* Optional parameters can be used if the file includes $1, $2, etc
|
||||
* @example
|
||||
* const result = await sql.file("query.sql", [1, 2, 3]);
|
||||
*/
|
||||
file(filename: string, values?: any[]): SQLQuery;
|
||||
|
||||
/** Current client options */
|
||||
options: SQLOptions;
|
||||
@@ -3669,6 +3643,30 @@ declare module "bun" {
|
||||
};
|
||||
}
|
||||
|
||||
namespace RouterTypes {
|
||||
type ExtractRouteParams<T> = T extends `${string}:${infer Param}/${infer Rest}`
|
||||
? { [K in Param]: string } & ExtractRouteParams<Rest>
|
||||
: T extends `${string}:${infer Param}`
|
||||
? { [K in Param]: string }
|
||||
: T extends `${string}*`
|
||||
? {}
|
||||
: {};
|
||||
|
||||
type RouteHandler<T extends string> = (req: BunRequest<T>, server: Server) => Response | Promise<Response>;
|
||||
|
||||
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
||||
|
||||
type RouteHandlerObject<T extends string> = {
|
||||
[K in HTTPMethod]?: RouteHandler<T>;
|
||||
};
|
||||
|
||||
type RouteValue<T extends string> = Response | false | RouteHandler<T> | RouteHandlerObject<T>;
|
||||
}
|
||||
|
||||
interface BunRequest<T extends string = string> extends Request {
|
||||
params: RouterTypes.ExtractRouteParams<T>;
|
||||
}
|
||||
|
||||
interface GenericServeOptions {
|
||||
/**
|
||||
* What URI should be used to make {@link Request.url} absolute?
|
||||
@@ -3730,26 +3728,6 @@ declare module "bun" {
|
||||
* This string will currently do nothing. But in the future it could be useful for logs or metrics.
|
||||
*/
|
||||
id?: string | null;
|
||||
|
||||
/**
|
||||
* Server static Response objects by route.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* Bun.serve({
|
||||
* static: {
|
||||
* "/": new Response("Hello World"),
|
||||
* "/about": new Response("About"),
|
||||
* },
|
||||
* fetch(req) {
|
||||
* return new Response("Fallback response");
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
static?: Record<`/${string}`, Response>;
|
||||
}
|
||||
|
||||
interface ServeOptions extends GenericServeOptions {
|
||||
@@ -4126,7 +4104,24 @@ declare module "bun" {
|
||||
*
|
||||
* Passing other options such as `port` or `hostname` won't do anything.
|
||||
*/
|
||||
reload(options: Serve): void;
|
||||
reload<T, R extends { [K in keyof R]: RouterTypes.RouteValue<K & string> }>(
|
||||
options: (
|
||||
| (Omit<ServeOptions, "fetch"> & {
|
||||
routes: R;
|
||||
fetch?: (this: Server, request: Request, server: Server) => Response | Promise<Response>;
|
||||
})
|
||||
| (Omit<ServeOptions, "routes"> & {
|
||||
routes?: never;
|
||||
fetch: (this: Server, request: Request, server: Server) => Response | Promise<Response>;
|
||||
})
|
||||
| WebSocketServeOptions<T>
|
||||
) & {
|
||||
/**
|
||||
* @deprecated Use `routes` instead in new code. This will continue to work for awhile though.
|
||||
*/
|
||||
static?: R;
|
||||
},
|
||||
): Server;
|
||||
|
||||
/**
|
||||
* Mock the fetch handler for a running server.
|
||||
@@ -4324,6 +4319,198 @@ declare module "bun" {
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
type Serve<WebSocketDataType = undefined> =
|
||||
| ServeOptions
|
||||
| TLSServeOptions
|
||||
| UnixServeOptions
|
||||
| UnixTLSServeOptions
|
||||
| WebSocketServeOptions<WebSocketDataType>
|
||||
| TLSWebSocketServeOptions<WebSocketDataType>
|
||||
| UnixWebSocketServeOptions<WebSocketDataType>
|
||||
| UnixTLSWebSocketServeOptions<WebSocketDataType>;
|
||||
|
||||
/**
|
||||
Bun.serve provides a high-performance HTTP server with built-in routing support.
|
||||
It enables both function-based and object-based route handlers with type-safe
|
||||
parameters and method-specific handling.
|
||||
|
||||
@example Basic Usage
|
||||
```ts
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
fetch(req) {
|
||||
return new Response("Hello World");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Route-based Handlers
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Static responses
|
||||
"/": new Response("Home page"),
|
||||
|
||||
// Function handlers with type-safe parameters
|
||||
"/users/:id": (req) => {
|
||||
// req.params.id is typed as string
|
||||
return new Response(`User ${req.params.id}`);
|
||||
},
|
||||
|
||||
// Method-specific handlers
|
||||
"/api/posts": {
|
||||
GET: () => new Response("Get posts"),
|
||||
POST: async (req) => {
|
||||
const body = await req.json();
|
||||
return new Response("Created post");
|
||||
},
|
||||
DELETE: (req) => new Response("Deleted post")
|
||||
},
|
||||
|
||||
// Wildcard routes
|
||||
"/static/*": (req) => {
|
||||
// Handle any path under /static/
|
||||
return new Response("Static file");
|
||||
},
|
||||
|
||||
// Disable route (fall through to fetch handler)
|
||||
"/api/legacy": false
|
||||
},
|
||||
|
||||
// Fallback handler for unmatched routes
|
||||
fetch(req) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Path Parameters
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
// Single parameter
|
||||
"/users/:id": (req: BunRequest<"/users/:id">) => {
|
||||
return new Response(`User ID: ${req.params.id}`);
|
||||
},
|
||||
|
||||
// Multiple parameters
|
||||
"/posts/:postId/comments/:commentId": (
|
||||
req: BunRequest<"/posts/:postId/comments/:commentId">
|
||||
) => {
|
||||
return new Response(JSON.stringify(req.params));
|
||||
// Output: {"postId": "123", "commentId": "456"}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Route Precedence
|
||||
```ts
|
||||
// Routes are matched in the following order:
|
||||
// 1. Exact static routes ("/about")
|
||||
// 2. Parameter routes ("/users/:id")
|
||||
// 3. Wildcard routes ("/api/*")
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/api/users": () => new Response("Users list"),
|
||||
"/api/users/:id": (req) => new Response(`User ${req.params.id}`),
|
||||
"/api/*": () => new Response("API catchall"),
|
||||
"/*": () => new Response("Root catchall")
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Error Handling
|
||||
```ts
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/error": () => {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
// Custom error handler
|
||||
console.error(error);
|
||||
return new Response(`Error: ${error.message}`, {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Server Lifecycle
|
||||
```ts
|
||||
const server = Bun.serve({
|
||||
// Server config...
|
||||
});
|
||||
|
||||
// Update routes at runtime
|
||||
server.reload({
|
||||
routes: {
|
||||
"/": () => new Response("Updated route")
|
||||
}
|
||||
});
|
||||
|
||||
// Stop the server
|
||||
server.stop();
|
||||
```
|
||||
|
||||
@example Development Mode
|
||||
```ts
|
||||
Bun.serve({
|
||||
development: true, // Enable hot reloading
|
||||
routes: {
|
||||
// Routes will auto-reload on changes
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@example Type-Safe Request Handling
|
||||
```ts
|
||||
type Post = {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/api/posts/:id": async (
|
||||
req: BunRequest<"/api/posts/:id">
|
||||
) => {
|
||||
if (req.method === "POST") {
|
||||
const body: Post = await req.json();
|
||||
return Response.json(body);
|
||||
}
|
||||
return new Response("Method not allowed", {
|
||||
status: 405
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
@param options - Server configuration options
|
||||
@param options.routes - Route definitions mapping paths to handlers
|
||||
*/
|
||||
function serve<T, R extends { [K in keyof R]: RouterTypes.RouteValue<K & string> }>(
|
||||
options: (
|
||||
| (Omit<ServeOptions, "fetch"> & {
|
||||
routes: R;
|
||||
fetch?: (this: Server, request: Request, server: Server) => Response | Promise<Response>;
|
||||
})
|
||||
| (Omit<ServeOptions, "routes"> & {
|
||||
routes?: never;
|
||||
fetch: (this: Server, request: Request, server: Server) => Response | Promise<Response>;
|
||||
})
|
||||
| WebSocketServeOptions<T>
|
||||
) & {
|
||||
/**
|
||||
* @deprecated Use `routes` instead in new code. This will continue to work for awhile though.
|
||||
*/
|
||||
static?: R;
|
||||
},
|
||||
): Server;
|
||||
|
||||
/**
|
||||
* [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) powered by the fastest system calls available for operating on files.
|
||||
*
|
||||
@@ -5349,12 +5536,14 @@ declare module "bun" {
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns `this` for method chaining
|
||||
*/
|
||||
onStart(callback: OnStartCallback): void;
|
||||
onStart(callback: OnStartCallback): this;
|
||||
onBeforeParse(
|
||||
constraints: PluginConstraints,
|
||||
callback: { napiModule: unknown; symbol: string; external?: unknown | undefined },
|
||||
): void;
|
||||
): this;
|
||||
/**
|
||||
* Register a callback to load imports with a specific import specifier
|
||||
* @param constraints The constraints to apply the plugin to
|
||||
@@ -5369,8 +5558,10 @@ declare module "bun" {
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns `this` for method chaining
|
||||
*/
|
||||
onLoad(constraints: PluginConstraints, callback: OnLoadCallback): void;
|
||||
onLoad(constraints: PluginConstraints, callback: OnLoadCallback): this;
|
||||
/**
|
||||
* Register a callback to resolve imports matching a filter and/or namespace
|
||||
* @param constraints The constraints to apply the plugin to
|
||||
@@ -5385,8 +5576,10 @@ declare module "bun" {
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns `this` for method chaining
|
||||
*/
|
||||
onResolve(constraints: PluginConstraints, callback: OnResolveCallback): void;
|
||||
onResolve(constraints: PluginConstraints, callback: OnResolveCallback): this;
|
||||
/**
|
||||
* The config object passed to `Bun.build` as is. Can be mutated.
|
||||
*/
|
||||
@@ -5417,8 +5610,10 @@ declare module "bun" {
|
||||
* const { foo } = require("hello:world");
|
||||
* console.log(foo); // "bar"
|
||||
* ```
|
||||
*
|
||||
* @returns `this` for method chaining
|
||||
*/
|
||||
module(specifier: string, callback: () => OnLoadResult | Promise<OnLoadResult>): void;
|
||||
module(specifier: string, callback: () => OnLoadResult | Promise<OnLoadResult>): this;
|
||||
}
|
||||
|
||||
interface BunPlugin {
|
||||
|
||||
@@ -408,7 +408,7 @@ static int bsd_socket_set_membership6(LIBUS_SOCKET_DESCRIPTOR fd, const struct s
|
||||
mreq.ipv6mr_interface = iface->sin6_scope_id;
|
||||
}
|
||||
int option = drop ? IPV6_LEAVE_GROUP : IPV6_JOIN_GROUP;
|
||||
return setsockopt(fd, IPPROTO_IP, option, &mreq, sizeof(mreq));
|
||||
return setsockopt(fd, IPPROTO_IPV6, option, &mreq, sizeof(mreq));
|
||||
}
|
||||
|
||||
int bsd_socket_set_membership(LIBUS_SOCKET_DESCRIPTOR fd, const struct sockaddr_storage *addr, const struct sockaddr_storage *iface, int drop) {
|
||||
|
||||
@@ -326,33 +326,18 @@ int us_internal_ssl_socket_is_closed(struct us_internal_ssl_socket_t *s) {
|
||||
return us_socket_is_closed(0, &s->s);
|
||||
}
|
||||
|
||||
struct us_internal_ssl_socket_t *
|
||||
us_internal_ssl_socket_close(struct us_internal_ssl_socket_t *s, int code,
|
||||
void *reason) {
|
||||
|
||||
// check if we are already closed
|
||||
if (us_internal_ssl_socket_is_closed(s)) return s;
|
||||
|
||||
if (s->handshake_state != HANDSHAKE_COMPLETED) {
|
||||
// if we have some pending handshake we cancel it and try to check the
|
||||
// latest handshake error this way we will always call on_handshake with the
|
||||
// latest error before closing this should always call
|
||||
// secureConnection/secure before close if we remove this here, we will need
|
||||
// to do this check on every on_close event on sockets, fetch etc and will
|
||||
// increase complexity on a lot of places
|
||||
us_internal_trigger_handshake_callback(s, 0);
|
||||
void us_internal_trigger_handshake_callback_econnreset(struct us_internal_ssl_socket_t *s) {
|
||||
struct us_internal_ssl_socket_context_t *context =
|
||||
(struct us_internal_ssl_socket_context_t *)us_socket_context(0, &s->s);
|
||||
|
||||
// always set the handshake state to completed
|
||||
s->handshake_state = HANDSHAKE_COMPLETED;
|
||||
if (context->on_handshake != NULL) {
|
||||
struct us_bun_verify_error_t verify_error = (struct us_bun_verify_error_t){ .error = -46, .code = "ECONNRESET", .reason = "Client network socket disconnected before secure TLS connection was established"};
|
||||
context->on_handshake(s, 0, verify_error, context->handshake_data);
|
||||
}
|
||||
|
||||
// if we are in the middle of a close_notify we need to finish it (code != 0 forces a fast shutdown)
|
||||
int can_close = us_internal_handle_shutdown(s, code != 0);
|
||||
|
||||
// only close the socket if we are not in the middle of a handshake
|
||||
if(can_close) {
|
||||
return (struct us_internal_ssl_socket_t *)us_socket_close(0, (struct us_socket_t *)s, code, reason);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void us_internal_trigger_handshake_callback(struct us_internal_ssl_socket_t *s,
|
||||
int success) {
|
||||
struct us_internal_ssl_socket_context_t *context =
|
||||
@@ -366,6 +351,32 @@ void us_internal_trigger_handshake_callback(struct us_internal_ssl_socket_t *s,
|
||||
context->on_handshake(s, success, verify_error, context->handshake_data);
|
||||
}
|
||||
}
|
||||
struct us_internal_ssl_socket_t *
|
||||
us_internal_ssl_socket_close(struct us_internal_ssl_socket_t *s, int code,
|
||||
void *reason) {
|
||||
|
||||
// check if we are already closed
|
||||
if (us_internal_ssl_socket_is_closed(s)) return s;
|
||||
us_internal_update_handshake(s);
|
||||
|
||||
if (s->handshake_state != HANDSHAKE_COMPLETED) {
|
||||
// if we have some pending handshake we cancel it and try to check the
|
||||
// latest handshake error this way we will always call on_handshake with the
|
||||
// ECONNRESET error if we remove this here, we will need
|
||||
// to do this check on every on_close event on sockets, fetch etc and will
|
||||
// increase complexity on a lot of places
|
||||
us_internal_trigger_handshake_callback_econnreset(s);
|
||||
}
|
||||
|
||||
// if we are in the middle of a close_notify we need to finish it (code != 0 forces a fast shutdown)
|
||||
int can_close = us_internal_handle_shutdown(s, code != 0);
|
||||
|
||||
// only close the socket if we are not in the middle of a handshake
|
||||
if(can_close) {
|
||||
return (struct us_internal_ssl_socket_t *)us_socket_close(0, (struct us_socket_t *)s, code, reason);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
int us_internal_ssl_renegotiate(struct us_internal_ssl_socket_t *s) {
|
||||
// handle renegotation here since we are using ssl_renegotiate_explicit
|
||||
|
||||
@@ -1767,9 +1778,9 @@ int us_internal_ssl_socket_write(struct us_internal_ssl_socket_t *s,
|
||||
loop_ssl_data->ssl_socket = &s->s;
|
||||
loop_ssl_data->msg_more = msg_more;
|
||||
loop_ssl_data->last_write_was_msg_more = 0;
|
||||
|
||||
int written = SSL_write(s->ssl, data, length);
|
||||
loop_ssl_data->msg_more = 0;
|
||||
|
||||
if (loop_ssl_data->last_write_was_msg_more && !msg_more) {
|
||||
us_socket_flush(0, &s->s);
|
||||
}
|
||||
|
||||
@@ -457,6 +457,7 @@ struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_clien
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
int us_socket_raw_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
|
||||
#ifndef LIBUS_NO_SSL
|
||||
if (ssl) {
|
||||
|
||||
@@ -167,7 +167,7 @@ export function registerTestRunner(context: vscode.ExtensionContext) {
|
||||
let command = customScript;
|
||||
|
||||
if (filePath.length !== 0) {
|
||||
command += ` ${filePath}`;
|
||||
command += ` "${filePath}"`;
|
||||
}
|
||||
|
||||
if (testName && testName.length) {
|
||||
|
||||
@@ -48,6 +48,7 @@ const testsPath = join(cwd, "test");
|
||||
const spawnTimeout = 5_000;
|
||||
const testTimeout = 3 * 60_000;
|
||||
const integrationTimeout = 5 * 60_000;
|
||||
const napiTimeout = 10 * 60_000;
|
||||
|
||||
function getNodeParallelTestTimeout(testPath) {
|
||||
if (testPath.includes("test-dns")) {
|
||||
@@ -680,6 +681,9 @@ function getTestTimeout(testPath) {
|
||||
if (/integration|3rd_party|docker|bun-install-registry|v8/i.test(testPath)) {
|
||||
return integrationTimeout;
|
||||
}
|
||||
if (/napi/i.test(testPath)) {
|
||||
return napiTimeout;
|
||||
}
|
||||
return testTimeout;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,10 @@ export fn Bun__atexit(function: ExitFn) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addExitCallback(function: ExitFn) void {
|
||||
Bun__atexit(function);
|
||||
}
|
||||
|
||||
pub fn runExitCallbacks() void {
|
||||
for (on_exit_callbacks.items) |callback| {
|
||||
callback();
|
||||
|
||||
@@ -79,7 +79,11 @@ pub fn scan(this: *HTMLScanner, input: []const u8) !void {
|
||||
try processor.run(this, input);
|
||||
}
|
||||
|
||||
pub fn HTMLProcessor(comptime T: type, comptime visit_head_and_body: bool) type {
|
||||
pub fn HTMLProcessor(
|
||||
comptime T: type,
|
||||
/// If the visitor should visit html, head, body
|
||||
comptime visit_document_tags: bool,
|
||||
) type {
|
||||
return struct {
|
||||
const TagHandler = struct {
|
||||
/// CSS selector to match elements
|
||||
@@ -151,12 +155,6 @@ pub fn HTMLProcessor(comptime T: type, comptime visit_head_and_body: bool) type
|
||||
.url_attribute = "href",
|
||||
.kind = .url,
|
||||
},
|
||||
// Catch-all for other links with href
|
||||
.{
|
||||
.selector = "link:not([rel~='stylesheet']):not([rel~='modulepreload']):not([rel~='manifest']):not([rel~='icon']):not([rel~='apple-touch-icon'])[href]",
|
||||
.url_attribute = "href",
|
||||
.kind = .url,
|
||||
},
|
||||
// Images with src
|
||||
.{
|
||||
.selector = "img[src]",
|
||||
@@ -231,7 +229,7 @@ pub fn HTMLProcessor(comptime T: type, comptime visit_head_and_body: bool) type
|
||||
var builder = lol.HTMLRewriter.Builder.init();
|
||||
defer builder.deinit();
|
||||
|
||||
var selectors: std.BoundedArray(*lol.HTMLSelector, tag_handlers.len + if (visit_head_and_body) 2 else 0) = .{};
|
||||
var selectors: std.BoundedArray(*lol.HTMLSelector, tag_handlers.len + if (visit_document_tags) 3 else 0) = .{};
|
||||
defer for (selectors.slice()) |selector| {
|
||||
selector.deinit();
|
||||
};
|
||||
@@ -254,36 +252,23 @@ pub fn HTMLProcessor(comptime T: type, comptime visit_head_and_body: bool) type
|
||||
);
|
||||
}
|
||||
|
||||
if (visit_head_and_body) {
|
||||
const head_selector = try lol.HTMLSelector.parse("head");
|
||||
selectors.appendAssumeCapacity(head_selector);
|
||||
try builder.addElementContentHandlers(
|
||||
head_selector,
|
||||
T,
|
||||
T.onHeadTag,
|
||||
this,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
const body_selector = try lol.HTMLSelector.parse("body");
|
||||
selectors.appendAssumeCapacity(body_selector);
|
||||
try builder.addElementContentHandlers(
|
||||
body_selector,
|
||||
T,
|
||||
T.onBodyTag,
|
||||
this,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
if (visit_document_tags) {
|
||||
inline for (.{ "body", "head", "html" }, &.{ T.onBodyTag, T.onHeadTag, T.onHtmlTag }) |tag, cb| {
|
||||
const head_selector = try lol.HTMLSelector.parse(tag);
|
||||
selectors.appendAssumeCapacity(head_selector);
|
||||
try builder.addElementContentHandlers(
|
||||
head_selector,
|
||||
T,
|
||||
cb,
|
||||
this,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
void,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const memory_settings = lol.MemorySettings{
|
||||
|
||||
@@ -367,7 +367,7 @@ pub fn toJS(
|
||||
JSC.Node.PathOrFileDescriptor{
|
||||
.path = JSC.Node.PathLike{ .string = bun.PathString.init(globalObject.allocator().dupe(u8, copy.pathname) catch unreachable) },
|
||||
},
|
||||
this.loader.toMimeType(),
|
||||
this.loader.toMimeType(&.{owned_pathname orelse ""}),
|
||||
globalObject.allocator(),
|
||||
) catch |err| {
|
||||
Output.panic("error: Unable to create file blob: \"{s}\"", .{@errorName(err)});
|
||||
@@ -398,7 +398,7 @@ pub fn toJS(
|
||||
JSC.Node.PathOrFileDescriptor{
|
||||
.path = JSC.Node.PathLike{ .string = bun.PathString.init(owned_pathname orelse (bun.default_allocator.dupe(u8, this.src_path.text) catch unreachable)) },
|
||||
},
|
||||
this.loader.toMimeType(),
|
||||
this.loader.toMimeType(&.{owned_pathname orelse ""}),
|
||||
globalObject.allocator(),
|
||||
) catch |err| {
|
||||
Output.panic("error: Unable to create file blob: \"{s}\"", .{@errorName(err)});
|
||||
@@ -424,10 +424,10 @@ pub fn toJS(
|
||||
.buffer => |buffer| brk: {
|
||||
var blob = JSC.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalObject);
|
||||
if (blob.store) |store| {
|
||||
store.mime_type = this.loader.toMimeType();
|
||||
store.mime_type = this.loader.toMimeType(&.{owned_pathname orelse ""});
|
||||
blob.content_type = store.mime_type.value;
|
||||
} else {
|
||||
blob.content_type = this.loader.toMimeType().value;
|
||||
blob.content_type = this.loader.toMimeType(&.{owned_pathname orelse ""}).value;
|
||||
}
|
||||
|
||||
blob.size = @as(JSC.WebCore.Blob.SizeType, @truncate(buffer.bytes.len));
|
||||
@@ -471,7 +471,7 @@ pub fn toBlob(
|
||||
JSC.Node.PathOrFileDescriptor{
|
||||
.path = JSC.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, copy.pathname) catch unreachable) },
|
||||
},
|
||||
this.loader.toMimeType(),
|
||||
this.loader.toMimeType(&.{ this.dest_path, this.src_path.text }),
|
||||
allocator,
|
||||
);
|
||||
|
||||
@@ -489,7 +489,7 @@ pub fn toBlob(
|
||||
JSC.Node.PathOrFileDescriptor{
|
||||
.path = JSC.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, this.src_path.text) catch unreachable) },
|
||||
},
|
||||
this.loader.toMimeType(),
|
||||
this.loader.toMimeType(&.{ this.dest_path, this.src_path.text }),
|
||||
allocator,
|
||||
);
|
||||
|
||||
@@ -505,10 +505,10 @@ pub fn toBlob(
|
||||
.buffer => |buffer| brk: {
|
||||
var blob = JSC.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalThis);
|
||||
if (blob.store) |store| {
|
||||
store.mime_type = this.loader.toMimeType();
|
||||
store.mime_type = this.loader.toMimeType(&.{ this.dest_path, this.src_path.text });
|
||||
blob.content_type = store.mime_type.value;
|
||||
} else {
|
||||
blob.content_type = this.loader.toMimeType().value;
|
||||
blob.content_type = this.loader.toMimeType(&.{ this.dest_path, this.src_path.text }).value;
|
||||
}
|
||||
|
||||
this.value = .{
|
||||
|
||||
@@ -12,6 +12,7 @@ const Syscall = bun.sys;
|
||||
const SourceMap = bun.sourcemap;
|
||||
const StringPointer = bun.StringPointer;
|
||||
|
||||
const macho = bun.macho;
|
||||
const w = std.os.windows;
|
||||
|
||||
pub const StandaloneModuleGraph = struct {
|
||||
@@ -113,6 +114,23 @@ pub const StandaloneModuleGraph = struct {
|
||||
cjs = 2,
|
||||
};
|
||||
|
||||
const Macho = struct {
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32;
|
||||
|
||||
pub fn getData() ?[]const u8 {
|
||||
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
|
||||
if (length.* < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slice_ptr: [*]const u8 = @ptrCast(length);
|
||||
return slice_ptr[4..][0..length.*];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const File = struct {
|
||||
name: []const u8 = "",
|
||||
loader: bun.options.Loader,
|
||||
@@ -233,7 +251,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
});
|
||||
|
||||
stored.external_source_names = file_names;
|
||||
stored.underlying_provider = .{ .data = @truncate(@intFromPtr(data)) };
|
||||
stored.underlying_provider = .{ .data = @truncate(@intFromPtr(data)), .load_hint = .none };
|
||||
stored.is_standalone_module_graph = true;
|
||||
|
||||
const parsed = stored.new(); // allocate this on the heap
|
||||
@@ -458,7 +476,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
windows_hide_console: bool = false,
|
||||
};
|
||||
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions) bun.FileDescriptor {
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions, target: *const CompileTarget) bun.FileDescriptor {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var zname: [:0]const u8 = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get temporary file name: {s}", .{@errorName(err)});
|
||||
@@ -597,71 +615,127 @@ pub const StandaloneModuleGraph = struct {
|
||||
break :brk fd;
|
||||
};
|
||||
|
||||
var total_byte_count: usize = undefined;
|
||||
switch (target.os) {
|
||||
.mac => {
|
||||
const input_result = bun.sys.File.readToEnd(.{ .handle = cloned_executable_fd }, bun.default_allocator);
|
||||
if (input_result.err) |err| {
|
||||
Output.prettyErrorln("Error reading standalone module graph: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
}
|
||||
var macho_file = bun.macho.MachoFile.init(bun.default_allocator, input_result.bytes.items, bytes.len) catch |err| {
|
||||
Output.prettyErrorln("Error initializing standalone module graph: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
};
|
||||
defer macho_file.deinit();
|
||||
macho_file.writeSection(bytes) catch |err| {
|
||||
Output.prettyErrorln("Error writing standalone module graph: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
};
|
||||
input_result.bytes.deinit();
|
||||
|
||||
if (Environment.isWindows) {
|
||||
total_byte_count = bytes.len + 8 + (Syscall.setFileOffsetToEndWindows(cloned_executable_fd).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file\n{}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
});
|
||||
} else {
|
||||
const seek_position = @as(u64, @intCast(brk: {
|
||||
const fstat = switch (Syscall.fstat(cloned_executable_fd)) {
|
||||
.result => |res| res,
|
||||
switch (Syscall.setFileOffset(cloned_executable_fd, 0)) {
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("{}", .{err});
|
||||
Output.prettyErrorln("Error seeking to start of temporary file: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
var file = bun.sys.File{ .handle = cloned_executable_fd };
|
||||
const writer = file.writer();
|
||||
const BufferedWriter = std.io.BufferedWriter(512 * 1024, @TypeOf(writer));
|
||||
var buffered_writer = bun.default_allocator.create(BufferedWriter) catch bun.outOfMemory();
|
||||
buffered_writer.* = .{
|
||||
.unbuffered_writer = writer,
|
||||
};
|
||||
macho_file.buildAndSign(buffered_writer.writer()) catch |err| {
|
||||
Output.prettyErrorln("Error writing standalone module graph: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
};
|
||||
buffered_writer.flush() catch |err| {
|
||||
Output.prettyErrorln("Error flushing standalone module graph: {}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
};
|
||||
if (comptime !Environment.isWindows) {
|
||||
_ = bun.C.fchmod(cloned_executable_fd.int(), 0o777);
|
||||
}
|
||||
return cloned_executable_fd;
|
||||
},
|
||||
else => {
|
||||
var total_byte_count: usize = undefined;
|
||||
if (Environment.isWindows) {
|
||||
total_byte_count = bytes.len + 8 + (Syscall.setFileOffsetToEndWindows(cloned_executable_fd).unwrap() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file\n{}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
});
|
||||
} else {
|
||||
const seek_position = @as(u64, @intCast(brk: {
|
||||
const fstat = switch (Syscall.fstat(cloned_executable_fd)) {
|
||||
.result => |res| res,
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("{}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
break :brk @max(fstat.size, 0);
|
||||
}));
|
||||
break :brk @max(fstat.size, 0);
|
||||
}));
|
||||
|
||||
total_byte_count = seek_position + bytes.len + 8;
|
||||
total_byte_count = seek_position + bytes.len + 8;
|
||||
|
||||
// From https://man7.org/linux/man-pages/man2/lseek.2.html
|
||||
//
|
||||
// lseek() allows the file offset to be set beyond the end of the
|
||||
// file (but this does not change the size of the file). If data is
|
||||
// later written at this point, subsequent reads of the data in the
|
||||
// gap (a "hole") return null bytes ('\0') until data is actually
|
||||
// written into the gap.
|
||||
//
|
||||
switch (Syscall.setFileOffset(cloned_executable_fd, seek_position)) {
|
||||
.err => |err| {
|
||||
Output.prettyErrorln(
|
||||
"{}\nwhile seeking to end of temporary file (pos: {d})",
|
||||
.{
|
||||
err,
|
||||
seek_position,
|
||||
// From https://man7.org/linux/man-pages/man2/lseek.2.html
|
||||
//
|
||||
// lseek() allows the file offset to be set beyond the end of the
|
||||
// file (but this does not change the size of the file). If data is
|
||||
// later written at this point, subsequent reads of the data in the
|
||||
// gap (a "hole") return null bytes ('\0') until data is actually
|
||||
// written into the gap.
|
||||
//
|
||||
switch (Syscall.setFileOffset(cloned_executable_fd, seek_position)) {
|
||||
.err => |err| {
|
||||
Output.prettyErrorln(
|
||||
"{}\nwhile seeking to end of temporary file (pos: {d})",
|
||||
.{
|
||||
err,
|
||||
seek_position,
|
||||
},
|
||||
);
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
},
|
||||
);
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
Global.exit(1);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
var remain = bytes;
|
||||
while (remain.len > 0) {
|
||||
switch (Syscall.write(cloned_executable_fd, bytes)) {
|
||||
.result => |written| remain = remain[written..],
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
var remain = bytes;
|
||||
while (remain.len > 0) {
|
||||
switch (Syscall.write(cloned_executable_fd, bytes)) {
|
||||
.result => |written| remain = remain[written..],
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{}", .{err});
|
||||
cleanup(zname, cloned_executable_fd);
|
||||
|
||||
Global.exit(1);
|
||||
},
|
||||
}
|
||||
}
|
||||
Global.exit(1);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// the final 8 bytes in the file are the length of the module graph with padding, excluding the trailer and offsets
|
||||
_ = Syscall.write(cloned_executable_fd, std.mem.asBytes(&total_byte_count));
|
||||
if (comptime !Environment.isWindows) {
|
||||
_ = bun.C.fchmod(cloned_executable_fd.int(), 0o777);
|
||||
// the final 8 bytes in the file are the length of the module graph with padding, excluding the trailer and offsets
|
||||
_ = Syscall.write(cloned_executable_fd, std.mem.asBytes(&total_byte_count));
|
||||
if (comptime !Environment.isWindows) {
|
||||
_ = bun.C.fchmod(cloned_executable_fd.int(), 0o777);
|
||||
}
|
||||
|
||||
return cloned_executable_fd;
|
||||
},
|
||||
}
|
||||
|
||||
if (Environment.isWindows and inject_options.windows_hide_console) {
|
||||
@@ -719,6 +793,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
Global.exit(1);
|
||||
},
|
||||
.{ .windows_hide_console = windows_hide_console },
|
||||
target,
|
||||
);
|
||||
fd.assertKind(.system);
|
||||
|
||||
@@ -761,29 +836,6 @@ pub const StandaloneModuleGraph = struct {
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
if (comptime Environment.isMac) {
|
||||
if (target.os == .mac) {
|
||||
var signer = std.process.Child.init(
|
||||
&.{
|
||||
"codesign",
|
||||
"--remove-signature",
|
||||
temp_location,
|
||||
},
|
||||
bun.default_allocator,
|
||||
);
|
||||
if (bun.logger.Log.default_log_level.atLeast(.verbose)) {
|
||||
signer.stdout_behavior = .Inherit;
|
||||
signer.stderr_behavior = .Inherit;
|
||||
signer.stdin_behavior = .Inherit;
|
||||
} else {
|
||||
signer.stdout_behavior = .Ignore;
|
||||
signer.stderr_behavior = .Ignore;
|
||||
signer.stdin_behavior = .Ignore;
|
||||
}
|
||||
_ = signer.spawnAndWait() catch {};
|
||||
}
|
||||
}
|
||||
|
||||
bun.C.moveFileZWithHandle(
|
||||
fd,
|
||||
bun.FD.cwd(),
|
||||
@@ -805,6 +857,22 @@ pub const StandaloneModuleGraph = struct {
|
||||
}
|
||||
|
||||
pub fn fromExecutable(allocator: std.mem.Allocator) !?StandaloneModuleGraph {
|
||||
if (comptime Environment.isMac) {
|
||||
const macho_bytes = Macho.getData() orelse return null;
|
||||
if (macho_bytes.len < @sizeOf(Offsets) + trailer.len) {
|
||||
Output.debugWarn("bun standalone module graph is too small to be valid", .{});
|
||||
return null;
|
||||
}
|
||||
const macho_bytes_slice = macho_bytes[macho_bytes.len - @sizeOf(Offsets) - trailer.len ..];
|
||||
const trailer_bytes = macho_bytes[macho_bytes.len - trailer.len ..][0..trailer.len];
|
||||
if (!bun.strings.eqlComptime(trailer_bytes, trailer)) {
|
||||
Output.debugWarn("bun standalone module graph has invalid trailer", .{});
|
||||
return null;
|
||||
}
|
||||
const offsets = std.mem.bytesAsValue(Offsets, macho_bytes_slice).*;
|
||||
return try StandaloneModuleGraph.fromBytes(allocator, @constCast(macho_bytes), offsets);
|
||||
}
|
||||
|
||||
// Do not invoke libuv here.
|
||||
const self_exe = openSelf() catch return null;
|
||||
defer _ = Syscall.close(self_exe);
|
||||
|
||||
142
src/allocators/AllocationScope.zig
Normal file
142
src/allocators/AllocationScope.zig
Normal file
@@ -0,0 +1,142 @@
|
||||
//! AllocationScope wraps another allocator, providing leak and invalid free assertions.
|
||||
//! It also allows measuring how much memory a scope has allocated.
|
||||
const AllocationScope = @This();
|
||||
|
||||
pub const enabled = bun.Environment.isDebug;
|
||||
|
||||
parent: Allocator,
|
||||
state: if (enabled) struct {
|
||||
mutex: bun.Mutex,
|
||||
total_memory_allocated: usize,
|
||||
allocations: std.AutoHashMapUnmanaged([*]const u8, Entry),
|
||||
} else void,
|
||||
|
||||
pub const Entry = struct {
|
||||
allocated_at: StoredTrace,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
pub fn init(parent: Allocator) AllocationScope {
|
||||
return if (enabled)
|
||||
.{
|
||||
.parent = parent,
|
||||
.state = .{
|
||||
.total_memory_allocated = 0,
|
||||
.allocations = .empty,
|
||||
.mutex = .{},
|
||||
},
|
||||
}
|
||||
else
|
||||
.{ .parent = parent, .state = {} };
|
||||
}
|
||||
|
||||
pub fn deinit(scope: *AllocationScope) void {
|
||||
if (enabled) {
|
||||
scope.state.mutex.lock();
|
||||
defer scope.state.allocations.deinit(scope.parent);
|
||||
const count = scope.state.allocations.count();
|
||||
if (count == 0) return;
|
||||
Output.debugWarn("Allocation scope leaked {d} allocations ({})", .{
|
||||
count,
|
||||
bun.fmt.size(scope.state.total_memory_allocated, .{}),
|
||||
});
|
||||
var it = scope.state.allocations.iterator();
|
||||
var n: usize = 0;
|
||||
while (it.next()) |entry| {
|
||||
Output.debugWarn("- {any}, len {d}, at:", .{ entry.key_ptr.*, entry.value_ptr.len });
|
||||
bun.crash_handler.dumpStackTrace(entry.value_ptr.allocated_at.trace());
|
||||
n += 1;
|
||||
if (n >= 8) {
|
||||
Output.debugWarn("(only showing first 10 leaks)", .{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocator(scope: *AllocationScope) Allocator {
|
||||
return if (enabled) .{ .ptr = scope, .vtable = &vtable } else scope.parent;
|
||||
}
|
||||
|
||||
const vtable: Allocator.VTable = .{
|
||||
.alloc = alloc,
|
||||
.resize = resize,
|
||||
.free = free,
|
||||
};
|
||||
|
||||
fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
|
||||
const scope: *AllocationScope = @ptrCast(@alignCast(ctx));
|
||||
scope.state.mutex.lock();
|
||||
defer scope.state.mutex.unlock();
|
||||
scope.state.allocations.ensureUnusedCapacity(scope.parent, 1) catch
|
||||
return null;
|
||||
const result = scope.parent.vtable.alloc(scope.parent.ptr, len, ptr_align, ret_addr) orelse
|
||||
return null;
|
||||
const trace = StoredTrace.capture(ret_addr);
|
||||
scope.state.allocations.putAssumeCapacityNoClobber(result, .{
|
||||
.allocated_at = trace,
|
||||
.len = len,
|
||||
});
|
||||
scope.state.total_memory_allocated += len;
|
||||
return result;
|
||||
}
|
||||
|
||||
fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
|
||||
const scope: *AllocationScope = @ptrCast(@alignCast(ctx));
|
||||
return scope.parent.vtable.resize(scope.parent.ptr, buf, buf_align, new_len, ret_addr);
|
||||
}
|
||||
|
||||
fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
|
||||
const scope: *AllocationScope = @ptrCast(@alignCast(ctx));
|
||||
scope.state.mutex.lock();
|
||||
defer scope.state.mutex.unlock();
|
||||
if (scope.state.allocations.fetchRemove(buf.ptr)) |entry| {
|
||||
scope.state.total_memory_allocated -= entry.value.len;
|
||||
} else {
|
||||
bun.Output.debugWarn("Invalid free, pointer {any}, len {d}", .{ buf.ptr, buf.len });
|
||||
// do not panic because address sanitizer will catch this case better.
|
||||
// the log message is in case there is a situation where address
|
||||
// sanitizer does not catch the invalid free.
|
||||
}
|
||||
return scope.parent.vtable.free(scope.parent.ptr, buf, buf_align, ret_addr);
|
||||
}
|
||||
|
||||
pub fn assertOwned(scope: *AllocationScope, ptr: anytype) void {
|
||||
if (!enabled) return;
|
||||
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
||||
.c, .one, .many => ptr,
|
||||
.slice => if (ptr.len > 0) ptr.ptr else return,
|
||||
});
|
||||
scope.state.mutex.lock();
|
||||
defer scope.state.mutex.unlock();
|
||||
_ = scope.state.allocations.getPtr(cast_ptr) orelse
|
||||
@panic("this pointer was not owned by the allocation scope");
|
||||
}
|
||||
|
||||
pub fn assertUnowned(scope: *AllocationScope, ptr: anytype) void {
|
||||
if (!enabled) return;
|
||||
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
||||
.c, .one, .many => ptr,
|
||||
.slice => if (ptr.len > 0) ptr.ptr else return,
|
||||
});
|
||||
scope.state.mutex.lock();
|
||||
defer scope.state.mutex.unlock();
|
||||
if (scope.state.allocations.getPtr(cast_ptr)) |owned| {
|
||||
Output.debugWarn("Pointer allocated here:");
|
||||
bun.crash_handler.dumpStackTrace(owned.allocated_at.trace());
|
||||
}
|
||||
@panic("this pointer was owned by the allocation scope when it was not supposed to be");
|
||||
}
|
||||
|
||||
pub inline fn downcast(a: Allocator) ?*AllocationScope {
|
||||
return if (enabled and a.vtable == &vtable)
|
||||
@ptrCast(@alignCast(a.ptr))
|
||||
else
|
||||
null;
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const bun = @import("root").bun;
|
||||
const Output = bun.Output;
|
||||
const StoredTrace = bun.crash_handler.StoredTrace;
|
||||
@@ -95,6 +95,10 @@ pub const Features = struct {
|
||||
pub var fetch: usize = 0;
|
||||
pub var git_dependencies: usize = 0;
|
||||
pub var html_rewriter: usize = 0;
|
||||
/// TCP server from `Bun.listen`
|
||||
pub var tcp_server: usize = 0;
|
||||
/// TLS server from `Bun.listen`
|
||||
pub var tls_server: usize = 0;
|
||||
pub var http_server: usize = 0;
|
||||
pub var https_server: usize = 0;
|
||||
/// Set right before JSC::initialize is called
|
||||
|
||||
@@ -1709,6 +1709,8 @@ pub const Api = struct {
|
||||
serve_env_prefix: ?[]const u8 = null,
|
||||
serve_splitting: bool = false,
|
||||
serve_public_path: ?[]const u8 = null,
|
||||
serve_hmr: ?bool = null,
|
||||
serve_define: ?StringMap = null,
|
||||
|
||||
bunfig_path: []const u8,
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ pub fn BabyList(comptime Type: type) type {
|
||||
bun.assert(this.cap >= this.len);
|
||||
}
|
||||
|
||||
pub fn initCapacity(allocator: std.mem.Allocator, len: usize) !ListType {
|
||||
pub fn initCapacity(allocator: std.mem.Allocator, len: usize) std.mem.Allocator.Error!ListType {
|
||||
return initWithBuffer(try allocator.alloc(Type, len));
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -423,7 +423,7 @@ pub const Style = union(enum) {
|
||||
|
||||
pub fn fromJS(value: JSValue, global: *JSC.JSGlobalObject) !Style {
|
||||
if (value.isString()) {
|
||||
const bun_string = try value.toBunString2(global);
|
||||
const bun_string = try value.toBunString(global);
|
||||
var sfa = std.heap.stackFallback(4096, bun.default_allocator);
|
||||
const utf8 = bun_string.toUTF8(sfa.get());
|
||||
defer utf8.deinit();
|
||||
@@ -1166,7 +1166,7 @@ pub const JSFrameworkRouter = struct {
|
||||
|
||||
pub fn match(jsfr: *JSFrameworkRouter, global: *JSGlobalObject, callframe: *JSC.CallFrame) !JSValue {
|
||||
const path_js = callframe.argumentsAsArray(1)[0];
|
||||
const path_str = try path_js.toBunString2(global);
|
||||
const path_str = try path_js.toBunString(global);
|
||||
defer path_str.deref();
|
||||
const path_slice = path_str.toSlice(bun.default_allocator);
|
||||
defer path_slice.deinit();
|
||||
|
||||
2
src/bake/bake.private.d.ts
vendored
2
src/bake/bake.private.d.ts
vendored
@@ -11,6 +11,8 @@ interface Config {
|
||||
separateSSRGraph?: true;
|
||||
|
||||
// Client
|
||||
/** Bun version */
|
||||
bun: string;
|
||||
/** Dev Server's `configuration_hash_key` */
|
||||
version: string;
|
||||
/** If available, this is the Id of `react-refresh/runtime` */
|
||||
|
||||
@@ -93,14 +93,12 @@ pub const StringRefList = struct {
|
||||
|
||||
pub const SplitBundlerOptions = struct {
|
||||
plugin: ?*Plugin = null,
|
||||
all: BuildConfigSubset = .{},
|
||||
client: BuildConfigSubset = .{},
|
||||
server: BuildConfigSubset = .{},
|
||||
ssr: BuildConfigSubset = .{},
|
||||
|
||||
pub const empty: SplitBundlerOptions = .{
|
||||
.plugin = null,
|
||||
.all = .{},
|
||||
.client = .{},
|
||||
.server = .{},
|
||||
.ssr = .{},
|
||||
@@ -156,12 +154,7 @@ const BuildConfigSubset = struct {
|
||||
drop: bun.StringArrayHashMapUnmanaged(void) = .{},
|
||||
env: bun.Schema.Api.DotEnvBehavior = ._none,
|
||||
env_prefix: ?[]const u8 = null,
|
||||
|
||||
pub fn loadFromJs(config: *BuildConfigSubset, value: JSValue, arena: Allocator) !void {
|
||||
_ = config; // autofix
|
||||
_ = value; // autofix
|
||||
_ = arena; // autofix
|
||||
}
|
||||
define: bun.Schema.Api.StringMap = .{ .keys = &.{}, .values = &.{} },
|
||||
};
|
||||
|
||||
/// A "Framework" in our eyes is simply set of bundler options that a framework
|
||||
@@ -368,7 +361,7 @@ pub const Framework = struct {
|
||||
arena: Allocator,
|
||||
) !Framework {
|
||||
if (opts.isString()) {
|
||||
const str = try opts.toBunString2(global);
|
||||
const str = try opts.toBunString(global);
|
||||
defer str.deref();
|
||||
|
||||
// Deprecated
|
||||
@@ -408,7 +401,7 @@ pub const Framework = struct {
|
||||
return global.throwInvalidArguments("'framework.reactFastRefresh' is missing 'importSource'", .{});
|
||||
};
|
||||
|
||||
const str = try prop.toBunString2(global);
|
||||
const str = try prop.toBunString(global);
|
||||
defer str.deref();
|
||||
|
||||
break :brk .{
|
||||
@@ -594,7 +587,7 @@ pub const Framework = struct {
|
||||
|
||||
pub fn initTranspiler(
|
||||
framework: *Framework,
|
||||
allocator: std.mem.Allocator,
|
||||
arena: std.mem.Allocator,
|
||||
log: *bun.logger.Log,
|
||||
mode: Mode,
|
||||
renderer: Graph,
|
||||
@@ -602,7 +595,7 @@ pub const Framework = struct {
|
||||
bundler_options: *const BuildConfigSubset,
|
||||
) !void {
|
||||
out.* = try bun.Transpiler.init(
|
||||
allocator, // TODO: this is likely a memory leak
|
||||
arena,
|
||||
log,
|
||||
std.mem.zeroes(bun.Schema.Api.TransformOptions),
|
||||
null,
|
||||
@@ -634,7 +627,7 @@ pub const Framework = struct {
|
||||
out.options.react_fast_refresh = mode == .development and renderer == .client and framework.react_fast_refresh != null;
|
||||
out.options.server_components = framework.server_components != null;
|
||||
|
||||
out.options.conditions = try bun.options.ESMConditions.init(allocator, out.options.target.defaultConditions());
|
||||
out.options.conditions = try bun.options.ESMConditions.init(arena, out.options.target.defaultConditions());
|
||||
if (renderer == .server and framework.server_components != null) {
|
||||
try out.options.conditions.appendSlice(&.{"react-server"});
|
||||
}
|
||||
@@ -642,6 +635,9 @@ pub const Framework = struct {
|
||||
// Support `esm-env` package using this condition.
|
||||
try out.options.conditions.appendSlice(&.{"development"});
|
||||
}
|
||||
if (bundler_options.conditions.count() > 0) {
|
||||
try out.options.conditions.appendSlice(bundler_options.conditions.keys());
|
||||
}
|
||||
|
||||
out.options.production = mode != .development;
|
||||
out.options.tree_shaking = mode != .development;
|
||||
@@ -650,10 +646,13 @@ pub const Framework = struct {
|
||||
out.options.minify_whitespace = mode != .development;
|
||||
out.options.css_chunking = true;
|
||||
out.options.framework = framework;
|
||||
if (bundler_options.ignoreDCEAnnotations) |ignore|
|
||||
out.options.ignore_dce_annotations = ignore;
|
||||
|
||||
out.options.source_map = switch (mode) {
|
||||
// Source maps must always be linked, as DevServer special cases the
|
||||
// linking and part of the generation of these.
|
||||
// Source maps must always be external, as DevServer special cases
|
||||
// the linking and part of the generation of these. It also relies
|
||||
// on source maps always being enabled.
|
||||
.development => .external,
|
||||
// TODO: follow user configuration
|
||||
else => .none,
|
||||
@@ -669,11 +668,25 @@ pub const Framework = struct {
|
||||
|
||||
out.options.jsx.development = mode == .development;
|
||||
|
||||
try addImportMetaDefines(allocator, out.options.define, mode, switch (renderer) {
|
||||
try addImportMetaDefines(arena, out.options.define, mode, switch (renderer) {
|
||||
.client => .client,
|
||||
.server, .ssr => .server,
|
||||
});
|
||||
|
||||
if ((bundler_options.define.keys.len + bundler_options.drop.count()) > 0) {
|
||||
for (bundler_options.define.keys, bundler_options.define.values) |k, v| {
|
||||
const parsed = try bun.options.Define.Data.parse(k, v, false, false, log, arena);
|
||||
try out.options.define.insert(arena, k, parsed);
|
||||
}
|
||||
|
||||
for (bundler_options.drop.keys()) |drop_item| {
|
||||
if (drop_item.len > 0) {
|
||||
const parsed = try bun.options.Define.Data.parse(drop_item, "", true, true, log, arena);
|
||||
try out.options.define.insert(arena, drop_item, parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != .development) {
|
||||
// Hide information about the source repository, at the cost of debugging quality.
|
||||
out.options.entry_naming = "_bun/[hash].[ext]";
|
||||
@@ -696,7 +709,7 @@ fn getOptionalString(
|
||||
return null;
|
||||
if (value == .undefined or value == .null)
|
||||
return null;
|
||||
const str = try value.toBunString2(global);
|
||||
const str = try value.toBunString(global);
|
||||
return allocations.track(str.toUTF8(arena));
|
||||
}
|
||||
|
||||
|
||||
@@ -562,6 +562,24 @@ export class DraculaSyntaxHighlighter {
|
||||
return this.buildHtmlElement("pre", { "class": classAttr }, result);
|
||||
}
|
||||
|
||||
public highlightLine() {
|
||||
let lineContent = "";
|
||||
|
||||
for (const token of this.tokenize()) {
|
||||
if (token.type === TokenType.Newline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.tokenClass) {
|
||||
lineContent += this.wrap(token.value, token.tokenClass);
|
||||
} else {
|
||||
lineContent += this.escapeHtml(token.value);
|
||||
}
|
||||
}
|
||||
|
||||
return lineContent;
|
||||
}
|
||||
|
||||
private escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, "&")
|
||||
@@ -794,3 +812,7 @@ export class DraculaSyntaxHighlighter {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function syntaxHighlight(code: string) {
|
||||
return new DraculaSyntaxHighlighter(code).highlightLine();
|
||||
}
|
||||
|
||||
103
src/bake/client/data-view.ts
Normal file
103
src/bake/client/data-view.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { td, te } from "../shared";
|
||||
|
||||
export class DataViewReader {
|
||||
view: DataView<ArrayBuffer>;
|
||||
cursor: number;
|
||||
|
||||
constructor(view: DataView<ArrayBuffer>, cursor: number = 0) {
|
||||
this.view = view;
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
u32() {
|
||||
const value = this.view.getUint32(this.cursor, true);
|
||||
this.cursor += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
i32() {
|
||||
const value = this.view.getInt32(this.cursor, true);
|
||||
this.cursor += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
u16() {
|
||||
const value = this.view.getUint16(this.cursor, true);
|
||||
this.cursor += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
u8() {
|
||||
const value = this.view.getUint8(this.cursor);
|
||||
this.cursor += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
stringWithLength(byteLength: number) {
|
||||
const str = td.decode(this.view.buffer.slice(this.cursor, this.cursor + byteLength));
|
||||
this.cursor += byteLength;
|
||||
return str;
|
||||
}
|
||||
|
||||
string32() {
|
||||
return this.stringWithLength(this.u32());
|
||||
}
|
||||
|
||||
hasMoreData() {
|
||||
return this.cursor < this.view.byteLength;
|
||||
}
|
||||
|
||||
rest() {
|
||||
return this.view.buffer.slice(this.cursor);
|
||||
}
|
||||
}
|
||||
|
||||
export class DataViewWriter {
|
||||
view: DataView<ArrayBuffer>;
|
||||
uint8ArrayView: Uint8Array;
|
||||
cursor: number;
|
||||
capacity: number;
|
||||
|
||||
static initCapacity(capacity: number) {
|
||||
const view = new DataView(new ArrayBuffer(capacity));
|
||||
return new DataViewWriter(view, 0, capacity);
|
||||
}
|
||||
|
||||
constructor(view: DataView<ArrayBuffer>, cursor: number, capacity: number) {
|
||||
this.view = view;
|
||||
this.cursor = cursor;
|
||||
this.capacity = capacity;
|
||||
this.uint8ArrayView = new Uint8Array(view.buffer);
|
||||
}
|
||||
|
||||
u8(value: number) {
|
||||
this.view.setUint8(this.cursor, value);
|
||||
this.cursor += 1;
|
||||
}
|
||||
|
||||
u32(value: number) {
|
||||
this.view.setUint32(this.cursor, value, true);
|
||||
this.cursor += 4;
|
||||
}
|
||||
|
||||
i32(value: number) {
|
||||
this.view.setInt32(this.cursor, value, true);
|
||||
this.cursor += 4;
|
||||
}
|
||||
|
||||
string(value: string) {
|
||||
if (value.length === 0) return;
|
||||
const encodeResult = te.encodeInto(value, this.uint8ArrayView.subarray(this.cursor));
|
||||
if (encodeResult.read !== value.length) {
|
||||
throw new Error("Failed to encode string");
|
||||
}
|
||||
this.cursor += encodeResult.written;
|
||||
}
|
||||
|
||||
stringWithLength(value: string) {
|
||||
const cursor = this.cursor;
|
||||
this.u32(0);
|
||||
this.string(value);
|
||||
this.view.setUint32(cursor, this.cursor - cursor - 4, true);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// This implements error deserialization from the WebSocket protocol
|
||||
import { BundlerMessageLevel } from "../enums";
|
||||
import { DataViewReader } from "./reader";
|
||||
import { DataViewReader } from "./data-view";
|
||||
|
||||
export interface DeserializedFailure {
|
||||
// If not specified, it is a client-side error.
|
||||
|
||||
1
src/bake/client/icons/dismiss.svg
Normal file
1
src/bake/client/icons/dismiss.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFF"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>
|
||||
|
After Width: | Height: | Size: 219 B |
1
src/bake/client/icons/next.svg
Normal file
1
src/bake/client/icons/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ff5858"><path d="M647-440H160v-80h487L423-744l57-56 320 320-320 320-57-56 224-224Z"/></svg>
|
||||
|
After Width: | Height: | Size: 190 B |
1
src/bake/client/icons/prev.svg
Normal file
1
src/bake/client/icons/prev.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ff5858"><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
@@ -3,6 +3,8 @@
|
||||
* the user's application causes no issue. This sheet is used to
|
||||
* style error popups and other elements provided by DevServer.
|
||||
*/
|
||||
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
@@ -10,18 +12,43 @@
|
||||
}
|
||||
|
||||
.root {
|
||||
color-scheme: light dark;
|
||||
|
||||
all: initial;
|
||||
|
||||
/* TODO: revive light theme error modal */
|
||||
/* color-scheme: light dark;
|
||||
--modal-bg: light-dark(#efefef, #202020);
|
||||
--modal-text: light-dark(#0a0a0a, #fafafa);
|
||||
--modal-text-faded: light-dark(#0a0a0a88, #fafafa88);
|
||||
--item-bg: light-dark(#d4d4d4, #0f0f0f);
|
||||
--item-bg-hover: light-dark(#cccccc, #171717);
|
||||
--red: #ff5858;
|
||||
--item-bg-main: light-dark(#d4d4d4, #262626);
|
||||
--log-error: light-dark(#dc0000, #ff5858);
|
||||
--log-warn: light-dark(#eab308, #fbbf24);
|
||||
--log-note: light-dark(#008ae6, #22d3ee);
|
||||
--log-colon: light-dark(#888, #888);
|
||||
--log-colon: light-dark(#888, #888); */
|
||||
|
||||
--modal-bg: #202020;
|
||||
--modal-text: #fafafa;
|
||||
--modal-text-faded: #fafafa88;
|
||||
--item-bg: #0f0f0f;
|
||||
--item-bg-main: #262626;
|
||||
--log-error: #ff5858;
|
||||
--log-warn: #fbbf24;
|
||||
--log-note: #22d3ee;
|
||||
--log-colon: #888;
|
||||
|
||||
--red: #ff5858;
|
||||
--red-faded: #ff58582F;
|
||||
--error-bg: #ff58582F;
|
||||
--warn-bg: #eab3082F;
|
||||
|
||||
--syntax-comment: #6272a4;
|
||||
--syntax-cyan: #8be9fd;
|
||||
--syntax-green: #50fa7b;
|
||||
--syntax-orange: #ffb86c;
|
||||
--syntax-pink: #ff79c6;
|
||||
--syntax-purple: #bd93f9;
|
||||
--syntax-red: #ff5555;
|
||||
--syntax-yellow: #f1fa8c;
|
||||
|
||||
font-family:
|
||||
system-ui,
|
||||
@@ -46,11 +73,30 @@
|
||||
}
|
||||
|
||||
code,
|
||||
.message,
|
||||
.function-name,
|
||||
.file-name,
|
||||
.message {
|
||||
.code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font: unset;
|
||||
}
|
||||
|
||||
button {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: unset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Helper classes */
|
||||
.flex { flex: 1 }
|
||||
.muted { color: var(--modal-text-faded) }
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
color: var(--modal-text);
|
||||
background-color: var(--modal-bg);
|
||||
@@ -64,109 +110,169 @@ code,
|
||||
0 2px 32px #0003;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
color: var(--modal-text-faded);
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
/* Navigation bar */
|
||||
nav {
|
||||
display: flex;
|
||||
margin: 1rem;
|
||||
align-items: center;
|
||||
color: var(--modal-text-faded);
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--red-faded);
|
||||
}
|
||||
.tab-button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.tab-button.left {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.tab-button.right {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.dismiss-all {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* Header / Title */
|
||||
header {
|
||||
margin: 1rem 1rem;
|
||||
margin: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--red);
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: var(--modal-text-faded);
|
||||
margin: 1rem;
|
||||
/* Runtime Error */
|
||||
.r-error {
|
||||
background-color: var(--item-bg);
|
||||
}
|
||||
.message-desc {
|
||||
padding: 0rem 1.5rem;
|
||||
}
|
||||
.message-desc.error {
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: var(--error-bg);
|
||||
}
|
||||
.message-desc.warn{
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: var(--warn-bg);
|
||||
}
|
||||
.r-error .name {
|
||||
color: var(--red);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
pre {
|
||||
font: unset;
|
||||
.file-name {
|
||||
color: var(--modal-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
.r-code-wrap {
|
||||
background-color: var(--item-bg-main);
|
||||
--color: var(--log-error);
|
||||
}
|
||||
|
||||
.message-group {
|
||||
|
||||
/* Bundler messages */
|
||||
.b-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--item-bg);
|
||||
}
|
||||
|
||||
/* this is a <button> */
|
||||
.file-name {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-size: unset;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: left;
|
||||
/* cursor: pointer; */
|
||||
.b-group + .b-group {
|
||||
border-top: 1px solid var(--modal-text-faded);
|
||||
}
|
||||
|
||||
/* .file-name:hover,
|
||||
.file-name:focus-visible {
|
||||
background-color: var(--item-bg-hover);
|
||||
.b-group .code {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.file-name::after {
|
||||
color: var(--modal-text-faded);
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
.file-name:hover::after,
|
||||
.file-name:focus-visible {
|
||||
content: " (click to open in editor)";
|
||||
} */
|
||||
|
||||
.message {
|
||||
margin: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button + .message {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.message-text > span {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.message-text:last-child {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.log-error {
|
||||
--color: var(--log-error);
|
||||
font-weight: bold;
|
||||
}
|
||||
.log-warn { --color: var(--log-warn); }
|
||||
.log-note { --color: var(--log-note); }
|
||||
.log-colon { --color: var(--log-colon); }
|
||||
.log-label { color: var(--color); }
|
||||
|
||||
.log-warn {
|
||||
--color: var(--log-warn);
|
||||
.message-desc.note + .code {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.log-note {
|
||||
--color: var(--log-note);
|
||||
/* Trace Frames */
|
||||
.trace-frame {
|
||||
padding: 0.7rem 1.5rem;
|
||||
}
|
||||
.trace-frame + .trace-frame {
|
||||
margin-top: -0.5rem;
|
||||
}
|
||||
.function-name {
|
||||
color: var(--syntax-cyan);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.log-colon {
|
||||
--color: var(--log-colon);
|
||||
}
|
||||
|
||||
.code-line {
|
||||
/* Code View + Message Highlighting (Underline) */
|
||||
.code {
|
||||
display: flex;
|
||||
margin: 0.5rem 0;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.line-num {
|
||||
.code .gutter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.3rem 0;
|
||||
padding-left: 0.5rem;
|
||||
text-align: right;
|
||||
margin-right: 1rem;
|
||||
padding-right: 0.5rem;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
color: var(--modal-text-faded);
|
||||
margin-right: 10px;
|
||||
border-right: 1px solid var(--modal-text-faded);
|
||||
border-width: 1.5px;
|
||||
}
|
||||
.code .gutter div {
|
||||
white-space: pre;
|
||||
}
|
||||
.code .gutter .highlight-gap {
|
||||
font-size: 8px;
|
||||
}
|
||||
.code .view {
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
|
||||
.highlight-wrap {
|
||||
color: transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
pointer-events: none;
|
||||
transform: translateY(-20px);
|
||||
margin-bottom: -5px;
|
||||
transform: translateY(-16px);
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.highlight-wrap:last-child {
|
||||
margin-bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.highlight-gap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.highlight-wrap .line {
|
||||
margin-left: 10px;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: var(--color);
|
||||
@@ -178,3 +284,24 @@ button + .message {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/* Syntax Highlighting */
|
||||
.syntax-pink { color: var(--syntax-pink); }
|
||||
.syntax-cyan { color: var(--syntax-cyan); }
|
||||
.syntax-orange { color: var(--syntax-orange); }
|
||||
.syntax-red { color: var(--syntax-red); }
|
||||
.syntax-green { color: var(--syntax-green); }
|
||||
.syntax-yellow { color: var(--syntax-yellow); }
|
||||
.syntax-gray { color: var(--syntax-comment); }
|
||||
.syntax-purple { color: var(--syntax-purple); }
|
||||
|
||||
/* Icons */
|
||||
.tab-button.left { background-image: url(./icons/prev.svg); }
|
||||
.tab-button.right { background-image: url(./icons/next.svg); }
|
||||
.dismiss-all {
|
||||
background-image: url(./icons/dismiss.svg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
.dismiss-all { filter: invert(1); }
|
||||
}
|
||||
|
||||
@@ -2,30 +2,70 @@
|
||||
// React could collide with the user's code (consider React DevTools), this
|
||||
// entire modal is written from scratch using the standard DOM APIs. All CSS is
|
||||
// scoped in `overlay.css`, and all elements exist under a shadow root. These
|
||||
// constraints make the overlay simple to understand and work on.
|
||||
// constraints make the overlay very simple to understand and work on.
|
||||
//
|
||||
// This file has two consumers:
|
||||
// - The bundler error page which embeds a list of bundler errors to render.
|
||||
// - The client runtime, for when reloading errors happen.
|
||||
// Both use a WebSocket to coordinate followup updates, when new errors are
|
||||
// added or previous ones are solved.
|
||||
import { BundlerMessageLevel } from "../enums";
|
||||
import { css } from "../macros" with { type: "macro" };
|
||||
import {
|
||||
BundlerMessage,
|
||||
BundlerMessageLocation,
|
||||
BundlerNote,
|
||||
decodeSerializedError,
|
||||
type DeserializedFailure,
|
||||
} from "./error-serialization";
|
||||
import { DataViewReader } from "./reader";
|
||||
|
||||
if (side !== "client") throw new Error("Not client side!");
|
||||
// NOTE: imports are at the bottom for readability
|
||||
|
||||
/** When set, the next successful build will reload the page. */
|
||||
export let hasFatalError = false;
|
||||
|
||||
// I would have used JSX, but TypeScript types interfere in odd ways.
|
||||
function elem(tagName: string, props?: null | Record<string, string>, children?: (HTMLElement | Text)[]) {
|
||||
/**
|
||||
* 32-bit integer corresponding to `SerializedFailure.Owner.Packed`
|
||||
* It is never decoded client-side.
|
||||
*/
|
||||
type FailureOwner = number;
|
||||
|
||||
/**
|
||||
* Build errors come from SerializedFailure objects on the server, with the key
|
||||
* being the the SerializedFailure.Owner bitcast to an i32.
|
||||
*/
|
||||
const buildErrors = new Map<FailureOwner, DeserializedFailure>();
|
||||
/** Runtime errors are stored in a list and are cleared before any hot update. */
|
||||
const runtimeErrors: RuntimeError[] = [];
|
||||
const errorDoms = new Map<FailureOwner, ErrorDomNodes>();
|
||||
const updatedErrorOwners = new Set<FailureOwner>();
|
||||
|
||||
/**
|
||||
* -1 => All build errors
|
||||
* 0.. => Runtime error by index
|
||||
*/
|
||||
let activeErrorIndex = -1;
|
||||
let lastActiveErrorIndex = -1;
|
||||
let needUpdateNavbar = false;
|
||||
|
||||
let domShadowRoot: HTMLElement;
|
||||
let domModalTitle: HTMLElement;
|
||||
let domErrorContent: HTMLElement;
|
||||
/** For build errors */
|
||||
let domFooterText: HTMLElement;
|
||||
/** For runtime errors */
|
||||
let domNavBar: {
|
||||
root: HTMLElement;
|
||||
active: HTMLElement;
|
||||
total: HTMLElement;
|
||||
label: Text;
|
||||
prevBtn: HTMLButtonElement;
|
||||
nextBtn: HTMLButtonElement;
|
||||
dismissAllBtn: HTMLButtonElement;
|
||||
} = {} as any;
|
||||
|
||||
// I would have used JSX, but TypeScript types interfere in odd ways. However,
|
||||
// this pattern allows concise construction of DOM nodes, but also extremely
|
||||
// simple capturing of referenced nodes. Consider:
|
||||
// let title;
|
||||
// const btn = elem("button", { class: "file-name" }, [(title = textNode())]);
|
||||
// Now you can edit `title.textContent` freely.
|
||||
function elem<T extends keyof HTMLElementTagNameMap>(
|
||||
tagName: T,
|
||||
props?: null | Record<string, string>,
|
||||
children?: Node[],
|
||||
) {
|
||||
const node = document.createElement(tagName);
|
||||
if (props)
|
||||
for (let key in props) {
|
||||
@@ -38,7 +78,11 @@ function elem(tagName: string, props?: null | Record<string, string>, children?:
|
||||
return node;
|
||||
}
|
||||
|
||||
function elemText(tagName: string, props: null | Record<string, string>, innerHTML: string) {
|
||||
function elemText<T extends keyof HTMLElementTagNameMap>(
|
||||
tagName: T,
|
||||
props: null | Record<string, string>,
|
||||
innerHTML: string,
|
||||
) {
|
||||
const node = document.createElement(tagName);
|
||||
if (props)
|
||||
for (let key in props) {
|
||||
@@ -50,26 +94,38 @@ function elemText(tagName: string, props: null | Record<string, string>, innerHT
|
||||
|
||||
const textNode = (str = "") => document.createTextNode(str);
|
||||
|
||||
/**
|
||||
* 32-bit integer corresponding to `SerializedFailure.Owner.Packed`
|
||||
* It is never decoded client-side; treat this as an opaque identifier.
|
||||
*/
|
||||
type ErrorId = number;
|
||||
|
||||
const errors = new Map<ErrorId, DeserializedFailure>();
|
||||
const errorDoms = new Map<ErrorId, ErrorDomNodes>();
|
||||
const updatedErrorOwners = new Set<ErrorId>();
|
||||
|
||||
let domShadowRoot: HTMLElement;
|
||||
let domModalTitle: Text;
|
||||
let domErrorList: HTMLElement;
|
||||
|
||||
interface ErrorDomNodes {
|
||||
root: HTMLElement;
|
||||
title: Text;
|
||||
fileName: Text;
|
||||
messages: HTMLElement[];
|
||||
}
|
||||
|
||||
interface RuntimeError {
|
||||
/** error.name */
|
||||
name: string;
|
||||
/** error.message */
|
||||
message: string;
|
||||
/** error.stack after remapping */
|
||||
trace: RemappedFrame[];
|
||||
/** When the `fetch` request fails or takes too long */
|
||||
remapped: boolean;
|
||||
/** Promise rejection */
|
||||
async: boolean;
|
||||
|
||||
code?: CodePreview;
|
||||
}
|
||||
|
||||
interface CodePreview {
|
||||
lines: string[];
|
||||
col: number;
|
||||
loi: number;
|
||||
len: number;
|
||||
firstLine: number;
|
||||
}
|
||||
|
||||
interface RemappedFrame extends Frame {}
|
||||
|
||||
declare const OVERLAY_CSS: string;
|
||||
/**
|
||||
* Initial mount is done lazily. The modal starts invisible, controlled
|
||||
* by `setModalVisible`.
|
||||
@@ -78,29 +134,56 @@ function mountModal() {
|
||||
if (domModalTitle) return;
|
||||
domShadowRoot = elem("bun-hmr", {
|
||||
style:
|
||||
"position:absolute!important;" +
|
||||
"position:fixed!important;" +
|
||||
"display:none!important;" +
|
||||
"top:0!important;" +
|
||||
"left:0!important;" +
|
||||
"width:100%!important;" +
|
||||
"height:100%!important;" +
|
||||
"background:#8883!important",
|
||||
"background:#8883!important" +
|
||||
"z-index:2147483647!important",
|
||||
});
|
||||
const shadow = domShadowRoot.attachShadow({ mode: "open" });
|
||||
const sheet = new CSSStyleSheet();
|
||||
sheet.replace(css("client/overlay.css", IS_BUN_DEVELOPMENT));
|
||||
sheet.replace(OVERLAY_CSS);
|
||||
shadow.adoptedStyleSheets = [sheet];
|
||||
|
||||
const root = elem("div", { class: "root" }, [
|
||||
elem("div", { class: "modal" }, [
|
||||
elem("header", null, [(domModalTitle = textNode())]),
|
||||
(domErrorList = elem("div", { class: "error-list" })),
|
||||
// Runtime errors get a switcher to toggle between each runtime error and
|
||||
// the build errors. This is done because runtime errors are very big.
|
||||
// Only visible when a runtime error is present.
|
||||
(domNavBar.root = elem("nav", null, [
|
||||
// TODO: use SVG for this
|
||||
(domNavBar.prevBtn = elemText(
|
||||
"button",
|
||||
{ class: "tab-button left", disabled: "true", "aria-label": "Previous error" },
|
||||
"",
|
||||
)),
|
||||
(domNavBar.nextBtn = elemText("button", { class: "tab-button right", "aria-label": "Next error" }, "")),
|
||||
elem("span", null, [
|
||||
(domNavBar.active = elem("code")),
|
||||
textNode(" of "),
|
||||
(domNavBar.total = elem("code")),
|
||||
(domNavBar.label = textNode(" Errors")),
|
||||
]),
|
||||
elem("div", { class: "flex" }),
|
||||
(domNavBar.dismissAllBtn = elem("button", { class: "dismiss-all", "aria-label": "Dismiss all errors" })),
|
||||
])),
|
||||
// The active page's header
|
||||
elem("header", null, [(domModalTitle = elem("div", { class: "title" }))]),
|
||||
// The active page's content
|
||||
(domErrorContent = elem("div", { class: "error-content" })),
|
||||
elem("footer", null, [
|
||||
// TODO: for HMR turn this into a clickable thing + say it can be dismissed
|
||||
textNode("Errors during a build can only be dismissed fixing them."),
|
||||
(domFooterText = elemText("div", null, "")),
|
||||
elem("div", { class: "flex" }),
|
||||
elemText("div", null, "Bun v" + config.bun),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
domNavBar.dismissAllBtn.addEventListener("click", onDismissAllErrors);
|
||||
domNavBar.prevBtn.addEventListener("click", onPrevError);
|
||||
domNavBar.nextBtn.addEventListener("click", onNextError);
|
||||
shadow.appendChild(root);
|
||||
document.body.appendChild(domShadowRoot);
|
||||
}
|
||||
@@ -113,42 +196,176 @@ function setModalVisible(visible: boolean) {
|
||||
}
|
||||
|
||||
/** Handler for `MessageId.errors` websocket packet */
|
||||
export function onErrorMessage(view: DataView<ArrayBuffer>) {
|
||||
export function onServerErrorPayload(view: DataView<ArrayBuffer>) {
|
||||
const reader = new DataViewReader(view, 1);
|
||||
const removedCount = reader.u32();
|
||||
|
||||
for (let i = 0; i < removedCount; i++) {
|
||||
const removed = reader.u32();
|
||||
updatedErrorOwners.add(removed);
|
||||
errors.delete(removed);
|
||||
buildErrors.delete(removed);
|
||||
}
|
||||
|
||||
while (reader.hasMoreData()) {
|
||||
decodeAndAppendError(reader);
|
||||
decodeAndAppendServerError(reader);
|
||||
}
|
||||
|
||||
updateErrorOverlay();
|
||||
}
|
||||
|
||||
export const enum RuntimeErrorType {
|
||||
recoverable,
|
||||
/** Requires that clearances perform a full page reload */
|
||||
fatal,
|
||||
export async function onRuntimeError(err: any, fatal = false, async = false) {
|
||||
try {
|
||||
if (fatal) {
|
||||
hasFatalError = true;
|
||||
}
|
||||
|
||||
// Parse the stack trace and normalize the error message.
|
||||
let name = err?.name ?? "error";
|
||||
if (name === "Error") name = "error";
|
||||
let message = err?.message;
|
||||
if (!message)
|
||||
try {
|
||||
message = JSON.stringify(err);
|
||||
} catch (e) {
|
||||
message = "[error while serializing error: " + e + "]";
|
||||
}
|
||||
else if (typeof message !== "string") {
|
||||
try {
|
||||
message = JSON.stringify(message);
|
||||
} catch (e) {
|
||||
message = "[error while serializing error message: " + e + "]";
|
||||
}
|
||||
}
|
||||
const parsed = parseStackTrace(err) ?? [];
|
||||
|
||||
const browserUrl = location.href;
|
||||
|
||||
// Serialize the request into a binary buffer. Pre-allocate a little above what it needs.
|
||||
let bufferLength = 3 * 4 + (name.length + message.length + browserUrl.length) * 3;
|
||||
for (const frame of parsed) {
|
||||
bufferLength += 4 * 4 + ((frame.fn?.length ?? 0) + (frame.file?.length ?? 0)) * 3;
|
||||
}
|
||||
const writer = DataViewWriter.initCapacity(bufferLength);
|
||||
writer.stringWithLength(name);
|
||||
writer.stringWithLength(message);
|
||||
writer.stringWithLength(browserUrl);
|
||||
writer.u32(parsed.length);
|
||||
for (const frame of parsed) {
|
||||
writer.u32(frame.line ?? 0);
|
||||
writer.u32(frame.col ?? 0);
|
||||
writer.stringWithLength(frame.fn ?? "");
|
||||
const fileName = frame.file;
|
||||
if (fileName) {
|
||||
writer.stringWithLength(fileName);
|
||||
} else {
|
||||
writer.u32(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Request the error to be reported and remapped.
|
||||
const response = await fetch("/_bun/report_error", {
|
||||
method: "POST",
|
||||
body: writer.view.buffer,
|
||||
});
|
||||
try {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to report error");
|
||||
}
|
||||
const reader = new DataViewReader(new DataView(await response.arrayBuffer()), 0);
|
||||
const trace: Frame[] = [];
|
||||
const traceLen = reader.u32();
|
||||
for (let i = 0; i < traceLen; i++) {
|
||||
const line = reader.i32();
|
||||
const col = reader.i32();
|
||||
const fn = reader.string32();
|
||||
const file = reader.string32();
|
||||
trace.push({
|
||||
fn,
|
||||
file,
|
||||
line,
|
||||
col,
|
||||
});
|
||||
}
|
||||
let code: CodePreview | undefined;
|
||||
const codePreviewLineCount = reader.u8();
|
||||
if (codePreviewLineCount > 0) {
|
||||
const lineOfInterestOffset = reader.u32();
|
||||
const firstLineNumber = reader.u32();
|
||||
const highlightedColumn = reader.u32();
|
||||
let lines = new Array(codePreviewLineCount);
|
||||
for (let i = 0; i < codePreviewLineCount; i++) {
|
||||
const line = reader.string32();
|
||||
lines[i] = line;
|
||||
}
|
||||
const { col, len } = expandHighlight(lines[lineOfInterestOffset], highlightedColumn);
|
||||
lines = lines.map(line => syntaxHighlight(line));
|
||||
code = {
|
||||
lines,
|
||||
col,
|
||||
loi: lineOfInterestOffset,
|
||||
len,
|
||||
firstLine: firstLineNumber,
|
||||
};
|
||||
}
|
||||
runtimeErrors.push({
|
||||
name,
|
||||
message,
|
||||
trace,
|
||||
remapped: true,
|
||||
async,
|
||||
code,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to remap error", e);
|
||||
runtimeErrors.push({
|
||||
name,
|
||||
message,
|
||||
trace: parsed,
|
||||
remapped: false,
|
||||
async,
|
||||
});
|
||||
}
|
||||
|
||||
needUpdateNavbar = true;
|
||||
updateErrorOverlay();
|
||||
} catch (e) {
|
||||
console.error("Failed to report error", e);
|
||||
}
|
||||
}
|
||||
|
||||
export function onRuntimeError(err: any, type: RuntimeErrorType) {
|
||||
if (type === RuntimeErrorType.fatal) {
|
||||
hasFatalError = true;
|
||||
function expandHighlight(line: string, col: number) {
|
||||
let rest = line.slice(Math.max(0, col - 1));
|
||||
let len = 1;
|
||||
len = 0;
|
||||
let prev = line.slice(0, col - 1);
|
||||
// expand forward from new
|
||||
if (rest.match(/^new\s/)) {
|
||||
len += 4;
|
||||
rest = rest.slice(4);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
// expand backward from new
|
||||
const newText = prev.match(/new\s+$/)?.[0];
|
||||
if (newText) {
|
||||
len += newText.length;
|
||||
col -= newText.length;
|
||||
prev = prev.slice(0, prev.length - newText.length);
|
||||
}
|
||||
// expand backward from throw
|
||||
const throwText = prev.match(/throw\s+$/)?.[0];
|
||||
if (throwText) {
|
||||
len += throwText.length;
|
||||
col -= throwText.length;
|
||||
}
|
||||
len += (rest.match(/.\b/)?.index ?? -1) + 1;
|
||||
if (len <= 0) len = 1;
|
||||
return { col, len };
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this for each error, then call `updateErrorOverlay` to commit the
|
||||
* changes to the UI in one smooth motion.
|
||||
*/
|
||||
export function decodeAndAppendError(r: DataViewReader) {
|
||||
export function decodeAndAppendServerError(r: DataViewReader) {
|
||||
const owner = r.u32();
|
||||
const file = r.string32() || null;
|
||||
const messageCount = r.u32();
|
||||
@@ -156,12 +373,20 @@ export function decodeAndAppendError(r: DataViewReader) {
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
messages[i] = decodeSerializedError(r);
|
||||
}
|
||||
errors.set(owner, { file, messages });
|
||||
buildErrors.set(owner, { file, messages });
|
||||
updatedErrorOwners.add(owner);
|
||||
|
||||
activeErrorIndex = -1;
|
||||
needUpdateNavbar = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the list of errors changes, bundling errors change, or the active error page changes.
|
||||
*/
|
||||
export function updateErrorOverlay() {
|
||||
if (errors.size === 0) {
|
||||
// if there are no errors, hide the modal
|
||||
const totalErrors = runtimeErrors.length + buildErrors.size;
|
||||
if (totalErrors === 0) {
|
||||
if (IS_ERROR_RUNTIME) {
|
||||
location.reload();
|
||||
} else {
|
||||
@@ -169,13 +394,138 @@ export function updateErrorOverlay() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure the target page is valid
|
||||
if (activeErrorIndex === -1 && buildErrors.size === 0) {
|
||||
activeErrorIndex = 0; // there is a runtime error, else this modal will be hidden
|
||||
needUpdateNavbar = true;
|
||||
} else if (activeErrorIndex >= runtimeErrors.length) {
|
||||
needUpdateNavbar = true;
|
||||
if (activeErrorIndex === 0) {
|
||||
activeErrorIndex = -1; // there must be a build error, else this modal will be hidden
|
||||
} else {
|
||||
activeErrorIndex = runtimeErrors.length - 1;
|
||||
}
|
||||
}
|
||||
mountModal();
|
||||
|
||||
if (needUpdateNavbar) {
|
||||
needUpdateNavbar = false;
|
||||
if (activeErrorIndex >= 0) {
|
||||
// Runtime errors
|
||||
const err = runtimeErrors[activeErrorIndex];
|
||||
domModalTitle.innerHTML = err.async ? "Unhandled Promise Rejection" : "Runtime Error";
|
||||
updateRuntimeErrorOverlay(err);
|
||||
} else {
|
||||
// Build errors
|
||||
domModalTitle.innerHTML = `<span class="count">${buildErrors.size}</span> Build Error${buildErrors.size === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
domNavBar.active.textContent = (activeErrorIndex + 1 + (buildErrors.size > 0 ? 1 : 0)).toString();
|
||||
domNavBar.total.textContent = totalErrors.toString();
|
||||
domNavBar.label.textContent = totalErrors === 1 ? " Error" : " Errors";
|
||||
|
||||
domNavBar.nextBtn.disabled = activeErrorIndex >= runtimeErrors.length - 1;
|
||||
domNavBar.prevBtn.disabled = buildErrors.size > 0 ? activeErrorIndex < 0 : activeErrorIndex == 0;
|
||||
}
|
||||
|
||||
if (activeErrorIndex === -1) {
|
||||
if (lastActiveErrorIndex !== -1) {
|
||||
// clear the error content from the runtime error
|
||||
domErrorContent.innerHTML = "";
|
||||
updateBuildErrorOverlay({ remountAll: true });
|
||||
} else {
|
||||
updateBuildErrorOverlay({});
|
||||
}
|
||||
}
|
||||
|
||||
lastActiveErrorIndex = activeErrorIndex;
|
||||
|
||||
// The footer is only visible if there are build errors.
|
||||
if (buildErrors.size > 0) {
|
||||
domFooterText.style.display = "block";
|
||||
domFooterText.innerText =
|
||||
activeErrorIndex === -1
|
||||
? "Errors during a build can only be dismissed by fixing them."
|
||||
: "This dialog cannot be dismissed as there are additional build errors.";
|
||||
} else {
|
||||
domFooterText.style.display = "none";
|
||||
}
|
||||
domNavBar.dismissAllBtn.style.display = buildErrors.size > 0 ? "none" : "block";
|
||||
// The navbar is only visible if there are runtime errors. It contains the dismiss button.
|
||||
domNavBar.root.style.display = runtimeErrors.length > 0 ? "flex" : "none";
|
||||
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when switching between runtime errors.
|
||||
*/
|
||||
function updateRuntimeErrorOverlay(err: RuntimeError) {
|
||||
domErrorContent.innerHTML = ""; // clear contents
|
||||
const dom = elem("div", { class: "r-error" });
|
||||
let name = err.name;
|
||||
if (!name || name === "Error") name = "error";
|
||||
dom.appendChild(
|
||||
elem("div", { class: "message-desc error" }, [
|
||||
elemText("code", { class: "name" }, name),
|
||||
elemText("code", { class: "muted" }, ": "),
|
||||
elemText("code", {}, err.message),
|
||||
]),
|
||||
);
|
||||
const { code } = err;
|
||||
let trace = err.trace;
|
||||
if (code) {
|
||||
const {
|
||||
lines,
|
||||
col: columnToHighlight,
|
||||
loi: lineOfInterestOffset,
|
||||
len: highlightLength,
|
||||
firstLine: firstLineNumber,
|
||||
} = code;
|
||||
const codeFrame = trace[0];
|
||||
trace = trace.slice(1);
|
||||
|
||||
const domCode = elem("div", { class: "r-code-wrap" });
|
||||
|
||||
const aboveRoi = lines.slice(0, lineOfInterestOffset + 1);
|
||||
const belowRoi = lines.slice(lineOfInterestOffset + 1);
|
||||
|
||||
const gutter = elem("div", { class: "gutter" }, [
|
||||
elemText("div", null, aboveRoi.map((_, i) => `${i + firstLineNumber}`).join("\n")),
|
||||
elem("div", { class: "highlight-gap" }),
|
||||
elemText("div", null, belowRoi.map((_, i) => `${i + firstLineNumber + aboveRoi.length}`).join("\n")),
|
||||
]);
|
||||
domCode.appendChild(
|
||||
elem("div", { class: "code" }, [
|
||||
gutter,
|
||||
elem("div", { class: "view" }, [
|
||||
...aboveRoi.map(line => mapCodePreviewLine(line)),
|
||||
elem("div", { class: "highlight-wrap log-error" }, [
|
||||
elemText("span", { class: "space" }, "_".repeat(columnToHighlight - 1)),
|
||||
elemText("span", { class: "line" }, "_".repeat(highlightLength)),
|
||||
]),
|
||||
...belowRoi.map(line => mapCodePreviewLine(line)),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
domCode.appendChild(renderTraceFrame(codeFrame, "trace-frame"));
|
||||
|
||||
dom.appendChild(domCode);
|
||||
}
|
||||
|
||||
dom.appendChild(
|
||||
elem("div", { class: "r-error-trace" }, [...trace.map(frame => renderTraceFrame(frame, "trace-frame"))]),
|
||||
);
|
||||
domErrorContent.appendChild(dom);
|
||||
}
|
||||
|
||||
function updateBuildErrorOverlay({ remountAll = false }) {
|
||||
let totalCount = 0;
|
||||
|
||||
for (const owner of updatedErrorOwners) {
|
||||
const data = errors.get(owner);
|
||||
const owners = remountAll ? buildErrors.keys() : updatedErrorOwners;
|
||||
|
||||
for (const owner of owners) {
|
||||
const data = buildErrors.get(owner);
|
||||
let dom = errorDoms.get(owner);
|
||||
|
||||
// If this failure was removed, delete it.
|
||||
@@ -188,28 +538,13 @@ export function updateErrorOverlay() {
|
||||
totalCount += data.messages.length;
|
||||
|
||||
// Create the element for the root if it does not yet exist.
|
||||
if (!dom) {
|
||||
let title;
|
||||
let btn;
|
||||
const root = elem("div", { class: "message-group" }, [
|
||||
// (btn = elem("button", { class: "file-name" }, [(title = textNode())])),
|
||||
elem("div", { class: "file-name" }, [(title = textNode())]),
|
||||
if (!dom || remountAll) {
|
||||
let fileName;
|
||||
const root = elem("div", { class: "b-group" }, [
|
||||
elem("div", { class: "trace-frame" }, [elem("div", { class: "file-name" }, [(fileName = textNode())])]),
|
||||
]);
|
||||
// btn.addEventListener("click", () => {
|
||||
// const firstLocation = errors.get(owner)?.messages[0]?.location;
|
||||
// if (!firstLocation) return;
|
||||
// let fileName = title.textContent.replace(/^\//, "");
|
||||
// fetch("/_bun/src/" + fileName, {
|
||||
// headers: {
|
||||
// "Open-In-Editor": "1",
|
||||
// "Editor-Line": firstLocation.line.toString(),
|
||||
// "Editor-Column": firstLocation.column.toString(),
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
dom = { root, title, messages: [] };
|
||||
// TODO: sorted insert?
|
||||
domErrorList.appendChild(root);
|
||||
dom = { root, fileName, messages: [] };
|
||||
domErrorContent.appendChild(root);
|
||||
errorDoms.set(owner, dom);
|
||||
} else {
|
||||
// For simplicity, messages are not reused, even if left unchanged.
|
||||
@@ -217,7 +552,7 @@ export function updateErrorOverlay() {
|
||||
}
|
||||
|
||||
// Update the DOM with the new data.
|
||||
dom.title.textContent = data.file;
|
||||
dom.fileName.textContent = data.file;
|
||||
|
||||
for (const msg of data.messages) {
|
||||
const domMessage = renderBundlerMessage(msg);
|
||||
@@ -225,12 +560,13 @@ export function updateErrorOverlay() {
|
||||
dom.messages.push(domMessage);
|
||||
}
|
||||
}
|
||||
|
||||
domModalTitle.textContent = `${errors.size} Build Error${errors.size !== 1 ? "s" : ""}`;
|
||||
|
||||
updatedErrorOwners.clear();
|
||||
}
|
||||
|
||||
setModalVisible(true);
|
||||
function mapCodePreviewLine(line: string) {
|
||||
const pre = elem("pre");
|
||||
pre.innerHTML = line;
|
||||
return pre;
|
||||
}
|
||||
|
||||
const bundleLogLevelToName = ["error", "warn", "note", "debug", "verbose"];
|
||||
@@ -238,7 +574,7 @@ const bundleLogLevelToName = ["error", "warn", "note", "debug", "verbose"];
|
||||
function renderBundlerMessage(msg: BundlerMessage) {
|
||||
return elem(
|
||||
"div",
|
||||
{ class: "message" },
|
||||
{ class: "b-msg" },
|
||||
[
|
||||
renderErrorMessageLine(msg.level, msg.message),
|
||||
...(msg.location ? renderCodeLine(msg.location, msg.level) : []),
|
||||
@@ -247,13 +583,31 @@ function renderBundlerMessage(msg: BundlerMessage) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderTraceFrame(frame: Frame, className: string) {
|
||||
const hasFn = !!frame.fn;
|
||||
return elem("div", { class: className }, [
|
||||
elemText("span", { class: "muted" }, "at "),
|
||||
...(hasFn
|
||||
? [
|
||||
//
|
||||
elemText("span", { class: "function-name" }, frame.fn),
|
||||
elemText("span", { class: "muted" }, " in "),
|
||||
]
|
||||
: []),
|
||||
elemText("span", { class: "file-name" }, frame.file!),
|
||||
...(frame.line
|
||||
? [elemText("code", { class: "muted" }, `:${frame.line}` + (frame.col ? `:${frame.col}` : ""))]
|
||||
: []),
|
||||
]);
|
||||
}
|
||||
|
||||
function renderErrorMessageLine(level: BundlerMessageLevel, text: string) {
|
||||
const levelName = bundleLogLevelToName[level];
|
||||
if (IS_BUN_DEVELOPMENT && !levelName) {
|
||||
throw new Error("Unknown log level: " + level);
|
||||
}
|
||||
return elem("div", { class: "message-text" }, [
|
||||
elemText("span", { class: "log-" + levelName }, levelName),
|
||||
return elem("div", { class: "message-desc " + levelName }, [
|
||||
elemText("span", { class: "log-label log-" + levelName }, levelName),
|
||||
elemText("span", { class: "log-colon" }, ": "),
|
||||
elemText("span", { class: "log-text" }, text),
|
||||
]);
|
||||
@@ -261,13 +615,15 @@ function renderErrorMessageLine(level: BundlerMessageLevel, text: string) {
|
||||
|
||||
function renderCodeLine(location: BundlerMessageLocation, level: BundlerMessageLevel) {
|
||||
return [
|
||||
elem("div", { class: "code-line" }, [
|
||||
elemText("code", { class: "line-num" }, `${location.line}`),
|
||||
elemText("pre", { class: "code-view" }, location.lineText),
|
||||
]),
|
||||
elem("div", { class: "highlight-wrap log-" + bundleLogLevelToName[level] }, [
|
||||
elemText("span", { class: "space" }, "_".repeat(`${location.line}`.length + location.column - 1)),
|
||||
elemText("span", { class: "line" }, "_".repeat(location.length)),
|
||||
elem("div", { class: "code" }, [
|
||||
elem("div", { class: "gutter" }, [elemText("div", null, `${location.line}`)]),
|
||||
elem("div", { class: "view" }, [
|
||||
mapCodePreviewLine(syntaxHighlight(location.lineText)),
|
||||
elem("div", { class: "highlight-wrap log-" + bundleLogLevelToName[level] }, [
|
||||
elemText("span", { class: "space" }, "_".repeat(location.column - 1)),
|
||||
elemText("span", { class: "line" }, "_".repeat(location.length)),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
}
|
||||
@@ -278,3 +634,47 @@ function renderNote(note: BundlerNote) {
|
||||
...(note.location ? renderCodeLine(note.location, BundlerMessageLevel.note) : []),
|
||||
];
|
||||
}
|
||||
|
||||
function onDismissAllErrors() {
|
||||
if (buildErrors.size === 0) {
|
||||
setModalVisible(false);
|
||||
} else {
|
||||
// Cannot dismiss build errors?
|
||||
activeErrorIndex = -1;
|
||||
updateErrorOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function onPrevError() {
|
||||
if (activeErrorIndex === -1) return;
|
||||
if (activeErrorIndex === 0 && buildErrors.size === 0) return;
|
||||
activeErrorIndex--;
|
||||
needUpdateNavbar = true;
|
||||
updateErrorOverlay();
|
||||
}
|
||||
|
||||
function onNextError() {
|
||||
if (activeErrorIndex >= runtimeErrors.length - 1) return;
|
||||
activeErrorIndex++;
|
||||
needUpdateNavbar = true;
|
||||
updateErrorOverlay();
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"bun-hmr": HTMLElement;
|
||||
}
|
||||
}
|
||||
|
||||
import { BundlerMessageLevel } from "../enums";
|
||||
import { css } from "../macros" with { type: "macro" };
|
||||
import {
|
||||
BundlerMessage,
|
||||
BundlerMessageLocation,
|
||||
BundlerNote,
|
||||
decodeSerializedError,
|
||||
type DeserializedFailure,
|
||||
} from "./error-serialization";
|
||||
import { DataViewReader, DataViewWriter } from "./data-view";
|
||||
import { parseStackTrace, type Frame } from "./stack-trace";
|
||||
import { syntaxHighlight } from "./JavaScriptSyntaxHighlighter";
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { td } from "../shared";
|
||||
|
||||
export class DataViewReader {
|
||||
view: DataView<ArrayBuffer>;
|
||||
cursor: number;
|
||||
|
||||
constructor(view: DataView<ArrayBuffer>, cursor: number = 0) {
|
||||
this.view = view;
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
u32() {
|
||||
const value = this.view.getUint32(this.cursor, true);
|
||||
this.cursor += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
i32() {
|
||||
const value = this.view.getInt32(this.cursor, true);
|
||||
this.cursor += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
u16() {
|
||||
const value = this.view.getUint16(this.cursor, true);
|
||||
this.cursor += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
u8() {
|
||||
const value = this.view.getUint8(this.cursor);
|
||||
this.cursor += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
stringWithLength(byteLength: number) {
|
||||
const str = td.decode(this.view.buffer.slice(this.cursor, this.cursor + byteLength));
|
||||
this.cursor += byteLength;
|
||||
return str;
|
||||
}
|
||||
|
||||
string32() {
|
||||
return this.stringWithLength(this.u32());
|
||||
}
|
||||
|
||||
hasMoreData() {
|
||||
return this.cursor < this.view.byteLength;
|
||||
}
|
||||
|
||||
rest() {
|
||||
return this.view.buffer.slice(this.cursor);
|
||||
}
|
||||
}
|
||||
100
src/bake/client/stack-trace.ts
Normal file
100
src/bake/client/stack-trace.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
export interface Frame {
|
||||
fn: string;
|
||||
file: string | null;
|
||||
line: number | null;
|
||||
col: number | null;
|
||||
}
|
||||
|
||||
const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
|
||||
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;
|
||||
const LOCATION_REGEXP = /(.+?)(?::(\d+))?(?::(\d+))?$/;
|
||||
|
||||
/**
|
||||
* Modern port of the error-stack-parser library
|
||||
* https://github.com/stacktracejs/error-stack-parser/blob/9f33c224b5d7b607755eb277f9d51fcdb7287e24/error-stack-parser.js
|
||||
*/
|
||||
export function parseStackTrace(error: Error | any): null | Frame[] {
|
||||
const stack = error?.stack;
|
||||
if (typeof stack === "string") {
|
||||
if (stack.match(CHROME_IE_STACK_REGEXP)) {
|
||||
return parseV8OrIE(stack);
|
||||
}
|
||||
return parseFFOrSafari(stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseV8OrIE(stack: string): Frame[] {
|
||||
return stack
|
||||
.split("\n")
|
||||
.filter(line => !!line.match(CHROME_IE_STACK_REGEXP) && !line.includes("Bun HMR Runtime"))
|
||||
.map(function (line) {
|
||||
let sanitizedLine = line
|
||||
.replace(/^\s+/, "")
|
||||
.replace(/\(eval code/g, "(")
|
||||
.replace(/^.*?\s+/, "");
|
||||
|
||||
// capture and preserve the parenthesized location "(/foo/my bar.js:12:87)" in
|
||||
// case it has spaces in it, as the string is split on \s+ later on
|
||||
let loc = sanitizedLine.match(/ (\(.+\)$)/);
|
||||
|
||||
// remove the parenthesized location from the line, if it was matched
|
||||
sanitizedLine = loc ? sanitizedLine.replace(loc[0], "") : sanitizedLine;
|
||||
|
||||
// if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine
|
||||
// because this line doesn't have function name
|
||||
let locationParts = extractLocation(loc ? loc[1] : sanitizedLine);
|
||||
let functionName = (loc && sanitizedLine) || undefined;
|
||||
let fileName = ["eval", "<anonymous>"].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];
|
||||
|
||||
return {
|
||||
fn: functionName,
|
||||
file: fileName,
|
||||
line: 0 | locationParts[1],
|
||||
col: 0 | locationParts[2],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function parseFFOrSafari(stack: string): Frame[] {
|
||||
// Using string literal "\n" does not work in Safari.
|
||||
return stack.split(/\n/g).map((source, i) => {
|
||||
let fn = "";
|
||||
let file: string | null = null;
|
||||
let line: number | null = null;
|
||||
let col: number | null = null;
|
||||
if (source.endsWith("@")) {
|
||||
// Safari eval frames only have function names and nothing else
|
||||
fn = source.slice(0, -1);
|
||||
} else if (source.indexOf("@") === -1 && source.indexOf(":") === -1) {
|
||||
// Safari eval frames only have function names and nothing else
|
||||
fn = source.endsWith("@") ? source.slice(0, -1) : source;
|
||||
} else {
|
||||
var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
|
||||
var matches = source.match(functionNameRegex);
|
||||
var functionName = matches && matches[1] ? matches[1] : undefined;
|
||||
var locationParts = extractLocation(source.replace(functionNameRegex, ""));
|
||||
fn = functionName!;
|
||||
file = locationParts[0];
|
||||
line = 0 | locationParts[1];
|
||||
col = 0 | locationParts[2];
|
||||
}
|
||||
if (fn === "module code") fn = "";
|
||||
return {
|
||||
fn,
|
||||
file,
|
||||
line,
|
||||
col,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function extractLocation(urlLike: string) {
|
||||
// Fail-fast but return locations like "(native)"
|
||||
if (urlLike.indexOf(":") === -1) {
|
||||
return [urlLike];
|
||||
}
|
||||
|
||||
const parts: any = LOCATION_REGEXP.exec(urlLike.replace(/[()]/g, ""));
|
||||
return [parts[1], parts[2] || undefined, parts[3] || undefined];
|
||||
}
|
||||
@@ -36,6 +36,8 @@ let wait =
|
||||
})
|
||||
: () => new Promise<void>(done => setTimeout(done, 2_500));
|
||||
|
||||
let mainWebSocket: WebSocketWrapper | null = null;
|
||||
|
||||
interface WebSocketWrapper {
|
||||
/** When re-connected, this is re-assigned */
|
||||
wrapped: WebSocket | null;
|
||||
@@ -44,6 +46,10 @@ interface WebSocketWrapper {
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
|
||||
export function getMainWebSocket(): WebSocketWrapper | null {
|
||||
return mainWebSocket;
|
||||
}
|
||||
|
||||
export function initWebSocket(
|
||||
handlers: Record<number, (dv: DataView<ArrayBuffer>, ws: WebSocket) => void>,
|
||||
{ url = "/_bun/hmr", displayMessage = "Live-reloading socket" }: { url?: string; displayMessage?: string } = {},
|
||||
@@ -62,12 +68,19 @@ export function initWebSocket(
|
||||
close() {
|
||||
closed = true;
|
||||
this.wrapped?.close();
|
||||
if (mainWebSocket === this) {
|
||||
mainWebSocket = null;
|
||||
}
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
this.close();
|
||||
},
|
||||
};
|
||||
|
||||
if (mainWebSocket === null) {
|
||||
mainWebSocket = wsProxy;
|
||||
}
|
||||
|
||||
function onOpen() {
|
||||
if (firstConnection) {
|
||||
firstConnection = false;
|
||||
@@ -87,15 +100,19 @@ export function initWebSocket(
|
||||
}
|
||||
|
||||
function onError(ev: Event) {
|
||||
console.error(ev);
|
||||
if (ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
|
||||
// Auto-reconnection already logged a warning.
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
async function onClose() {
|
||||
console.warn("[Bun] Hot-module-reloading socket disconnected, reconnecting...");
|
||||
|
||||
await new Promise(done => setTimeout(done, 1000));
|
||||
|
||||
while (true) {
|
||||
if (closed) return;
|
||||
await wait();
|
||||
|
||||
// Note: Cannot use Promise.withResolvers due to lacking support on iOS
|
||||
let done;
|
||||
@@ -111,13 +128,14 @@ export function initWebSocket(
|
||||
};
|
||||
ws.onmessage = onMessage;
|
||||
ws.onerror = ev => {
|
||||
onError(ev);
|
||||
ev.preventDefault();
|
||||
done(false);
|
||||
};
|
||||
|
||||
if (await promise) {
|
||||
break;
|
||||
}
|
||||
await wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,31 @@ interface DepEntry {
|
||||
_expectedImports: string[] | undefined;
|
||||
}
|
||||
|
||||
/** See `runtime.js`'s `__toCommonJS`. This omits the cache. */
|
||||
export var toCommonJSUncached = /* @__PURE__ */ from => {
|
||||
var desc,
|
||||
entry = Object.defineProperty({}, "__esModule", { value: true });
|
||||
if ((from && typeof from === "object") || typeof from === "function")
|
||||
Object.getOwnPropertyNames(from).map(
|
||||
key =>
|
||||
!Object.prototype.hasOwnProperty.call(entry, key) &&
|
||||
Object.defineProperty(entry, key, {
|
||||
get: () => from[key],
|
||||
enumerable: !(desc = Object.getOwnPropertyDescriptor(from, key)) || desc.enumerable,
|
||||
}),
|
||||
);
|
||||
return entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* The expression `import(a,b)` is not supported in all browsers, most notably
|
||||
* in Mozilla Firefox. It is lazily evaluated, and will throw a SyntaxError
|
||||
* upon first usage.
|
||||
*/
|
||||
let lazyDynamicImportWithOptions;
|
||||
|
||||
const kDebugModule = /* @__PURE__ */ Symbol("HotModule");
|
||||
|
||||
/**
|
||||
* This object is passed as the CommonJS "module", but has a bunch of
|
||||
* non-standard properties that are used for implementing hot-module reloading.
|
||||
@@ -45,31 +70,39 @@ export class HotModule<E = any> {
|
||||
exports: E = {} as E;
|
||||
|
||||
_state = State.Loading;
|
||||
/** for MJS <-> CJS interop. this stores the other module exports */
|
||||
_ext_exports = undefined;
|
||||
__esModule = false;
|
||||
_esm = false;
|
||||
_import_meta: ImportMeta | undefined = undefined;
|
||||
_cached_failure: any = undefined;
|
||||
// modules that import THIS module
|
||||
/** modules that import THIS module */
|
||||
_deps: Map<HotModule, DepEntry | undefined> = new Map();
|
||||
/** from `import.meta.hot.dispose` */
|
||||
_onDispose: HotDisposeFunction[] | undefined = undefined;
|
||||
|
||||
constructor(id: Id) {
|
||||
this.id = id;
|
||||
|
||||
if (IS_BUN_DEVELOPMENT) {
|
||||
Object.defineProperty(this.exports, kDebugModule, {
|
||||
value: this,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
require(id: Id, onReload?: ExportsCallbackFunction) {
|
||||
const mod = loadModule(id, LoadModuleType.SyncUserDynamic) as HotModule;
|
||||
mod._deps.set(this, onReload ? { _callback: onReload, _expectedImports: undefined } : undefined);
|
||||
return mod.exports;
|
||||
const { exports, _esm } = mod;
|
||||
return _esm ? (mod._ext_exports ??= runtimeHelpers.__toCommonJS(exports)) : exports;
|
||||
}
|
||||
|
||||
async importStmt(id: Id, onReload?: ExportsCallbackFunction, expectedImports?: string[]) {
|
||||
const mod = await (loadModule(id, LoadModuleType.AsyncAssertPresent) as Promise<HotModule>);
|
||||
mod._deps.set(this, onReload ? { _callback: onReload, _expectedImports: expectedImports } : undefined);
|
||||
const { exports, __esModule } = mod;
|
||||
const object = __esModule
|
||||
? exports
|
||||
: (mod._ext_exports ??= { ...(typeof exports === "object" && exports), default: exports });
|
||||
const { exports, _esm } = mod;
|
||||
const object = _esm ? exports : (mod._ext_exports ??= runtimeHelpers.__toESM(exports));
|
||||
|
||||
// if (expectedImports && mod._state === State.Ready) {
|
||||
// for (const key of expectedImports) {
|
||||
@@ -85,7 +118,12 @@ export class HotModule<E = any> {
|
||||
async dynamicImport(specifier: string, opts?: ImportCallOptions) {
|
||||
if (!registry.has(specifier) && !input_graph[specifier]) {
|
||||
try {
|
||||
return await import(specifier, opts);
|
||||
if (opts != null)
|
||||
return await (lazyDynamicImportWithOptions ??= new Function("specifier, opts", "import(specifier, opts)"))(
|
||||
specifier,
|
||||
opts,
|
||||
);
|
||||
return await import(specifier);
|
||||
} catch (err) {
|
||||
// fall through to loadModule, which will throw a more specific error.
|
||||
// but still show this one.
|
||||
@@ -95,8 +133,8 @@ export class HotModule<E = any> {
|
||||
const mod = await (loadModule(specifier, LoadModuleType.AsyncUserDynamic) as Promise<HotModule>);
|
||||
// insert into the map if not present
|
||||
mod._deps.set(this, mod._deps.get(this));
|
||||
const { exports, __esModule } = mod;
|
||||
return __esModule ? exports : (mod._ext_exports ??= { ...exports, default: exports });
|
||||
const { exports, _esm } = mod;
|
||||
return _esm ? exports : (mod._ext_exports ??= { ...exports, default: exports });
|
||||
}
|
||||
|
||||
importMeta() {
|
||||
@@ -255,7 +293,6 @@ export function loadModule<T = any>(
|
||||
return mod;
|
||||
},
|
||||
err => {
|
||||
console.error(err);
|
||||
mod._cached_failure = err;
|
||||
mod._state = State.Error;
|
||||
throw err;
|
||||
@@ -267,7 +304,6 @@ export function loadModule<T = any>(
|
||||
entry?._callback(mod.exports);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
mod._cached_failure = err;
|
||||
mod._state = State.Error;
|
||||
throw err;
|
||||
@@ -345,7 +381,7 @@ export async function replaceModules(modules: any) {
|
||||
{
|
||||
const runtime = new HotModule("bun:wrap");
|
||||
runtime.exports = runtimeHelpers;
|
||||
runtime.__esModule = true;
|
||||
runtime._esm = true;
|
||||
registry.set("bun:wrap", runtime);
|
||||
}
|
||||
|
||||
@@ -356,7 +392,7 @@ export let onServerSideReload: (() => Promise<void>) | null = null;
|
||||
|
||||
if (side === "server") {
|
||||
const server_module = new HotModule("bun:bake/server");
|
||||
server_module.__esModule = true;
|
||||
server_module._esm = true;
|
||||
server_module.exports = { serverManifest, ssrManifest, actionManifest: null };
|
||||
registry.set(server_module.id, server_module);
|
||||
}
|
||||
@@ -369,7 +405,7 @@ if (side === "client") {
|
||||
}
|
||||
|
||||
const server_module = new HotModule("bun:bake/client");
|
||||
server_module.__esModule = true;
|
||||
server_module._esm = true;
|
||||
server_module.exports = {
|
||||
onServerSideReload: async cb => {
|
||||
onServerSideReload = cb;
|
||||
@@ -378,6 +414,7 @@ if (side === "client") {
|
||||
registry.set(server_module.id, server_module);
|
||||
}
|
||||
|
||||
runtimeHelpers.__name(HotModule.prototype.importStmt, "<HMR runtime> importStmt");
|
||||
runtimeHelpers.__name(HotModule.prototype.require, "<HMR runtime> require");
|
||||
runtimeHelpers.__name(loadModule, "<HMR runtime> loadModule");
|
||||
runtimeHelpers.__name(HotModule.prototype.importStmt, "<Bun HMR Runtime> importStmt");
|
||||
runtimeHelpers.__name(HotModule.prototype.dynamicImport, "<Bun HMR Runtime> import");
|
||||
runtimeHelpers.__name(HotModule.prototype.require, "<Bun HMR Runtime> require");
|
||||
runtimeHelpers.__name(loadModule, "<Bun HMR Runtime> loadModule");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This file is the entrypoint to the hot-module-reloading runtime
|
||||
// In the browser, this uses a WebSocket to communicate with the bundler.
|
||||
import { loadModule, LoadModuleType, onServerSideReload, replaceModules } from "./hmr-module";
|
||||
import { hasFatalError, onErrorMessage, onRuntimeError, RuntimeErrorType } from "./client/overlay";
|
||||
import { hasFatalError, onServerErrorPayload, onRuntimeError } from "./client/overlay";
|
||||
import { Bake } from "bun";
|
||||
import { DataViewReader } from "./client/reader";
|
||||
import { DataViewReader } from "./client/data-view";
|
||||
import { initWebSocket } from "./client/websocket";
|
||||
import { MessageId } from "./generated";
|
||||
import { editCssContent, editCssArray } from "./client/css-reloader";
|
||||
@@ -104,14 +104,15 @@ const ws = initWebSocket(
|
||||
// Skip to the last route
|
||||
let nextRouteId = reader.i32();
|
||||
while (nextRouteId != null && nextRouteId !== -1) {
|
||||
reader.string32();
|
||||
reader.cursor += 16 * reader.u32();
|
||||
const i = reader.i32();
|
||||
reader.cursor += 16 * Math.max(0, i);
|
||||
nextRouteId = reader.i32();
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// Skip to the next route
|
||||
reader.cursor += 16 * reader.u32();
|
||||
const i = reader.i32();
|
||||
reader.cursor += 16 * Math.max(0, i);
|
||||
}
|
||||
} while (true);
|
||||
// List 3
|
||||
@@ -131,6 +132,10 @@ const ws = initWebSocket(
|
||||
if (reader.hasMoreData()) {
|
||||
const code = td.decode(reader.rest());
|
||||
try {
|
||||
// TODO: This functions in all browsers, but WebKit browsers do not
|
||||
// provide stack traces to errors thrown in eval, meaning client-side
|
||||
// errors from hot-reloaded modules cannot be mapped back to their
|
||||
// source.
|
||||
const modules = (0, eval)(code);
|
||||
if (IS_BUN_DEVELOPMENT) {
|
||||
console.info("Updated modules:\n" + Object.keys(modules).join("\n"));
|
||||
@@ -139,7 +144,7 @@ const ws = initWebSocket(
|
||||
} catch (e) {
|
||||
if (IS_BUN_DEVELOPMENT) {
|
||||
console.error(e, "Failed to parse HMR payload", { code });
|
||||
onRuntimeError(e, RuntimeErrorType.fatal);
|
||||
onRuntimeError(e, true, false);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
@@ -153,7 +158,7 @@ const ws = initWebSocket(
|
||||
const reader = new DataViewReader(view, 1);
|
||||
currentRouteIndex = reader.u32();
|
||||
},
|
||||
[MessageId.errors]: onErrorMessage,
|
||||
[MessageId.errors]: onServerErrorPayload,
|
||||
},
|
||||
{
|
||||
displayMessage: config.refresh ? "Hot-module-reloading socket" : "Live-reloading socket",
|
||||
@@ -179,8 +184,16 @@ const ws = initWebSocket(
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("error", event => {
|
||||
onRuntimeError(event.error, true, false);
|
||||
});
|
||||
window.addEventListener("unhandledrejection", event => {
|
||||
onRuntimeError(event.reason, true, true);
|
||||
});
|
||||
|
||||
try {
|
||||
await loadModule<Bake.ClientEntryPoint>(config.main, LoadModuleType.AsyncAssertPresent);
|
||||
} catch (e) {
|
||||
onRuntimeError(e, RuntimeErrorType.fatal);
|
||||
console.error(e);
|
||||
onRuntimeError(e, true, false);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// This is embedded in `DevServer.sendSerializedFailures`. SSR is
|
||||
// left unused for simplicity; a flash of unstyled content is
|
||||
// stopped by the fact this script runs synchronously.
|
||||
import { decodeAndAppendError, onErrorMessage, updateErrorOverlay } from "./client/overlay";
|
||||
import { DataViewReader } from "./client/reader";
|
||||
import { decodeAndAppendServerError, onServerErrorPayload, updateErrorOverlay } from "./client/overlay";
|
||||
import { DataViewReader } from "./client/data-view";
|
||||
import { initWebSocket } from "./client/websocket";
|
||||
import { MessageId } from "./generated";
|
||||
|
||||
@@ -17,7 +17,12 @@ declare const error: Uint8Array<ArrayBuffer>;
|
||||
{
|
||||
const reader = new DataViewReader(new DataView(error.buffer), 0);
|
||||
while (reader.hasMoreData()) {
|
||||
decodeAndAppendError(reader);
|
||||
try {
|
||||
decodeAndAppendServerError(reader);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateErrorOverlay();
|
||||
}
|
||||
@@ -35,10 +40,10 @@ const ws = initWebSocket(
|
||||
// ensure this bundle is enqueued.
|
||||
location.reload();
|
||||
}
|
||||
ws.send("se"); // IncomingMessageId.subscribe with route_update
|
||||
ws.send("se"); // IncomingMessageId.subscribe with errors
|
||||
},
|
||||
|
||||
[MessageId.errors]: onErrorMessage,
|
||||
[MessageId.errors]: onServerErrorPayload,
|
||||
},
|
||||
{ displayMessage: "Live-reloading socket" },
|
||||
);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// @ts-ignore
|
||||
export async function css(file: string, is_development: boolean): string {
|
||||
// TODO: CSS does not process the error modal correctly.
|
||||
{
|
||||
return readFileSync(resolve(import.meta.dir, file), "utf-8");
|
||||
}
|
||||
|
||||
// const { success, stdout, stderr } = await Bun.spawnSync({
|
||||
// // TODO: remove the --experimental-css flag here once CI is upgraded to a post-#16561 bun
|
||||
// cmd: [process.execPath, "build", file, "--experimental-css", ...(is_development ? [] : ["--minify"])],
|
||||
// cwd: import.meta.dir,
|
||||
// stdio: ["ignore", "pipe", "pipe"],
|
||||
// });
|
||||
// if (!success) throw new Error(stderr.toString("utf-8"));
|
||||
// return stdout.toString("utf-8");
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export const td = /* #__PURE__ */ new TextDecoder();
|
||||
export const te = /* #__PURE__ */ new TextEncoder();
|
||||
|
||||
@@ -91,7 +91,7 @@ pub const BuildMessage = struct {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const str = args[0].getZigString(globalThis);
|
||||
const str = try args[0].getZigString(globalThis);
|
||||
if (str.eqlComptime("default") or str.eqlComptime("string")) {
|
||||
return this.toStringFn(globalThis);
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ pub const TablePrinter = struct {
|
||||
var properties_iter = JSC.JSArrayIterator.init(this.properties, globalObject);
|
||||
while (properties_iter.next()) |value| {
|
||||
try columns.append(.{
|
||||
.name = value.toBunString(globalObject),
|
||||
.name = try value.toBunString(globalObject),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -784,7 +784,6 @@ pub const FormatOptions = struct {
|
||||
if (try arg1.getBooleanLoose(globalThis, "sorted")) |opt| {
|
||||
formatOptions.ordered_properties = opt;
|
||||
}
|
||||
|
||||
if (try arg1.getBooleanLoose(globalThis, "compact")) |opt| {
|
||||
formatOptions.single_line = opt;
|
||||
}
|
||||
@@ -986,11 +985,12 @@ const CustomFormattedObject = struct {
|
||||
};
|
||||
|
||||
pub const Formatter = struct {
|
||||
globalThis: *JSGlobalObject,
|
||||
|
||||
remaining_values: []const JSValue = &[_]JSValue{},
|
||||
map: Visited.Map = undefined,
|
||||
map_node: ?*Visited.Pool.Node = null,
|
||||
hide_native: bool = false,
|
||||
globalThis: *JSGlobalObject,
|
||||
indent: u32 = 0,
|
||||
depth: u16 = 0,
|
||||
max_depth: u16 = 8,
|
||||
@@ -2176,7 +2176,7 @@ pub const Formatter = struct {
|
||||
writer.print(comptime Output.prettyFmt("<r><yellow>{d}<r>", enable_ansi_colors), .{int});
|
||||
},
|
||||
.BigInt => {
|
||||
const out_str = value.getZigString(this.globalThis).slice();
|
||||
const out_str = (try value.getZigString(this.globalThis)).slice();
|
||||
this.addForNewLine(out_str.len);
|
||||
|
||||
writer.print(comptime Output.prettyFmt("<r><yellow>{s}n<r>", enable_ansi_colors), .{out_str});
|
||||
@@ -2187,7 +2187,7 @@ pub const Formatter = struct {
|
||||
value.getClassName(this.globalThis, &number_name);
|
||||
|
||||
var number_value = ZigString.Empty;
|
||||
value.toZigString(&number_value, this.globalThis);
|
||||
try value.toZigString(&number_value, this.globalThis);
|
||||
|
||||
if (!strings.eqlComptime(number_name.slice(), "Number")) {
|
||||
this.addForNewLine(number_name.len + number_value.len + "[Number ():]".len);
|
||||
@@ -2541,7 +2541,7 @@ pub const Formatter = struct {
|
||||
response.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
||||
return;
|
||||
} else if (value.as(JSC.WebCore.Request)) |request| {
|
||||
request.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
||||
request.writeFormat(value, ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
||||
return;
|
||||
} else if (value.as(JSC.API.BuildArtifact)) |build| {
|
||||
build.writeFormat(ConsoleObject.Formatter, this, writer_, enable_ansi_colors) catch {};
|
||||
@@ -2589,19 +2589,26 @@ pub const Formatter = struct {
|
||||
|
||||
// this case should never happen
|
||||
return try this.printAs(.Undefined, Writer, writer_, .undefined, .Cell, enable_ansi_colors);
|
||||
} else if (value.as(JSC.API.Bun.Timer.TimerObject)) |timer| {
|
||||
this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.id, 0)))));
|
||||
if (timer.kind == .setInterval) {
|
||||
this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.id, 0)))));
|
||||
} else if (value.as(JSC.API.Bun.Timer.TimeoutObject)) |timer| {
|
||||
this.addForNewLine("Timeout(# ) ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.internals.id, 0)))));
|
||||
if (timer.internals.kind == .setInterval) {
|
||||
this.addForNewLine("repeats ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(timer.internals.id, 0)))));
|
||||
writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>, repeats)<r>", enable_ansi_colors), .{
|
||||
timer.id,
|
||||
timer.internals.id,
|
||||
});
|
||||
} else {
|
||||
writer.print(comptime Output.prettyFmt("<r><blue>Timeout<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{
|
||||
timer.id,
|
||||
timer.internals.id,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (value.as(JSC.API.Bun.Timer.ImmediateObject)) |immediate| {
|
||||
this.addForNewLine("Immediate(# ) ".len + bun.fmt.fastDigitCount(@as(u64, @intCast(@max(immediate.internals.id, 0)))));
|
||||
writer.print(comptime Output.prettyFmt("<r><blue>Immediate<r> <d>(#<yellow>{d}<r><d>)<r>", enable_ansi_colors), .{
|
||||
immediate.internals.id,
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (value.as(JSC.BuildMessage)) |build_log| {
|
||||
build_log.msg.writeFormat(writer_, enable_ansi_colors) catch {};
|
||||
@@ -2652,7 +2659,7 @@ pub const Formatter = struct {
|
||||
var bool_name = ZigString.Empty;
|
||||
value.getClassName(this.globalThis, &bool_name);
|
||||
var bool_value = ZigString.Empty;
|
||||
value.toZigString(&bool_value, this.globalThis);
|
||||
try value.toZigString(&bool_value, this.globalThis);
|
||||
|
||||
if (!strings.eqlComptime(bool_name.slice(), "Boolean")) {
|
||||
this.addForNewLine(bool_value.len + bool_name.len + "[Boolean (): ]".len);
|
||||
@@ -3010,7 +3017,7 @@ pub const Formatter = struct {
|
||||
});
|
||||
|
||||
if (_tag.cell == .Symbol) {} else if (_tag.cell.isStringLike()) {
|
||||
type_value.toZigString(&tag_name_str, this.globalThis);
|
||||
try type_value.toZigString(&tag_name_str, this.globalThis);
|
||||
is_tag_kind_primitive = true;
|
||||
} else if (_tag.cell.isObject() or type_value.isCallable(this.globalThis.vm())) {
|
||||
type_value.getNameProperty(this.globalThis, &tag_name_str);
|
||||
@@ -3018,7 +3025,7 @@ pub const Formatter = struct {
|
||||
tag_name_str = ZigString.init("NoName");
|
||||
}
|
||||
} else {
|
||||
type_value.toZigString(&tag_name_str, this.globalThis);
|
||||
try type_value.toZigString(&tag_name_str, this.globalThis);
|
||||
}
|
||||
|
||||
tag_name_slice = tag_name_str.toSlice(default_allocator);
|
||||
@@ -3140,7 +3147,7 @@ pub const Formatter = struct {
|
||||
print_children: {
|
||||
switch (tag.tag) {
|
||||
.String => {
|
||||
const children_string = children.getZigString(this.globalThis);
|
||||
const children_string = try children.getZigString(this.globalThis);
|
||||
if (children_string.len == 0) break :print_children;
|
||||
if (comptime enable_ansi_colors) writer.writeAll(comptime Output.prettyFmt("<r>", true));
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ pub const ResolveMessage = struct {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const str = args[0].getZigString(globalThis);
|
||||
const str = try args[0].getZigString(globalThis);
|
||||
if (str.eqlComptime("default") or str.eqlComptime("string")) {
|
||||
return this.toStringFn(globalThis);
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ pub fn shellEscape(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) b
|
||||
}
|
||||
|
||||
const jsval = arguments.ptr[0];
|
||||
const bunstr = jsval.toBunString(globalThis);
|
||||
const bunstr = try jsval.toBunString(globalThis);
|
||||
if (globalThis.hasException()) return .zero;
|
||||
defer bunstr.deref();
|
||||
|
||||
@@ -500,7 +500,6 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J
|
||||
if (arguments.len > 1) {
|
||||
try formatOptions.fromJS(globalThis, arguments[1..]);
|
||||
}
|
||||
const value = arguments[0];
|
||||
|
||||
// very stable memory address
|
||||
var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable;
|
||||
@@ -509,13 +508,13 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J
|
||||
var buffered_writer = &buffered_writer_;
|
||||
|
||||
const writer = buffered_writer.writer();
|
||||
const Writer = @TypeOf(writer);
|
||||
const Writer = MutableString.BufferedWriter.Writer;
|
||||
// we buffer this because it'll almost always be < 4096
|
||||
// when it's under 4096, we want to avoid the dynamic allocation
|
||||
try ConsoleObject.format2(
|
||||
.Debug,
|
||||
globalThis,
|
||||
@as([*]const JSValue, @ptrCast(&value)),
|
||||
arguments.ptr,
|
||||
1,
|
||||
Writer,
|
||||
Writer,
|
||||
@@ -547,6 +546,26 @@ export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) bun.String {
|
||||
return bun.String.createUTF8(array.slice());
|
||||
}
|
||||
|
||||
export fn Bun__inspect_singleline(globalThis: *JSGlobalObject, value: JSValue) bun.String {
|
||||
var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable;
|
||||
defer array.deinit();
|
||||
var buffered_writer = MutableString.BufferedWriter{ .context = &array };
|
||||
const writer = buffered_writer.writer();
|
||||
const Writer = MutableString.BufferedWriter.Writer;
|
||||
ConsoleObject.format2(.Debug, globalThis, (&value)[0..1].ptr, 1, Writer, Writer, writer, .{
|
||||
.enable_colors = false,
|
||||
.add_newline = false,
|
||||
.flush = false,
|
||||
.max_depth = std.math.maxInt(u16),
|
||||
.quote_strings = true,
|
||||
.ordered_properties = false,
|
||||
.single_line = true,
|
||||
}) catch return .empty;
|
||||
if (globalThis.hasException()) return .empty;
|
||||
buffered_writer.flush() catch return .empty;
|
||||
return bun.String.createUTF8(array.slice());
|
||||
}
|
||||
|
||||
pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
|
||||
const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, inspect);
|
||||
var str = ZigString.init("nodejs.util.inspect.custom");
|
||||
@@ -863,9 +882,9 @@ fn doResolve(globalThis: *JSC.JSGlobalObject, arguments: []const JSValue) bun.JS
|
||||
}
|
||||
}
|
||||
|
||||
const specifier_str = specifier.toBunString(globalThis);
|
||||
const specifier_str = try specifier.toBunString(globalThis);
|
||||
defer specifier_str.deref();
|
||||
const from_str = from.toBunString(globalThis);
|
||||
const from_str = try from.toBunString(globalThis);
|
||||
defer from_str.deref();
|
||||
return doResolveWithArgs(
|
||||
globalThis,
|
||||
@@ -941,10 +960,10 @@ pub fn resolve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun
|
||||
}
|
||||
|
||||
export fn Bun__resolve(global: *JSGlobalObject, specifier: JSValue, source: JSValue, is_esm: bool) JSC.JSValue {
|
||||
const specifier_str = specifier.toBunString(global);
|
||||
const specifier_str = specifier.toBunString(global) catch return .zero;
|
||||
defer specifier_str.deref();
|
||||
|
||||
const source_str = source.toBunString(global);
|
||||
const source_str = source.toBunString(global) catch return .zero;
|
||||
defer source_str.deref();
|
||||
|
||||
const value = doResolveWithArgs(global, specifier_str, source_str, is_esm, true) catch {
|
||||
@@ -956,10 +975,10 @@ export fn Bun__resolve(global: *JSGlobalObject, specifier: JSValue, source: JSVa
|
||||
}
|
||||
|
||||
export fn Bun__resolveSync(global: *JSGlobalObject, specifier: JSValue, source: JSValue, is_esm: bool) JSC.JSValue {
|
||||
const specifier_str = specifier.toBunString(global);
|
||||
const specifier_str = specifier.toBunString(global) catch return .zero;
|
||||
defer specifier_str.deref();
|
||||
|
||||
const source_str = source.toBunString(global);
|
||||
const source_str = source.toBunString(global) catch return .zero;
|
||||
defer source_str.deref();
|
||||
|
||||
return JSC.toJSHostValue(global, doResolveWithArgs(global, specifier_str, source_str, is_esm, true));
|
||||
@@ -971,7 +990,7 @@ export fn Bun__resolveSyncWithStrings(global: *JSGlobalObject, specifier: *bun.S
|
||||
}
|
||||
|
||||
export fn Bun__resolveSyncWithSource(global: *JSGlobalObject, specifier: JSValue, source: *bun.String, is_esm: bool) JSC.JSValue {
|
||||
const specifier_str = specifier.toBunString(global);
|
||||
const specifier_str = specifier.toBunString(global) catch return .zero;
|
||||
defer specifier_str.deref();
|
||||
return JSC.toJSHostValue(global, doResolveWithArgs(global, specifier_str, source.*, is_esm, true));
|
||||
}
|
||||
@@ -1580,7 +1599,7 @@ pub const Crypto = struct {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = algorithm_value.getZigString(globalObject);
|
||||
const algorithm_string = try algorithm_value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
@@ -1646,7 +1665,7 @@ pub const Crypto = struct {
|
||||
return globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
const algorithm_string = value.getZigString(globalObject);
|
||||
const algorithm_string = try value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
@@ -1952,7 +1971,7 @@ pub const Crypto = struct {
|
||||
|
||||
pub fn deinit(this: *HashJob) void {
|
||||
this.promise.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
@@ -2165,8 +2184,10 @@ pub const Crypto = struct {
|
||||
|
||||
pub fn deinit(this: *VerifyJob) void {
|
||||
this.promise.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.default_allocator.free(this.prev_hash);
|
||||
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
bun.freeSensitive(bun.default_allocator, this.prev_hash);
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
@@ -2211,7 +2232,7 @@ pub const Crypto = struct {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
@@ -2261,7 +2282,7 @@ pub const Crypto = struct {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
@@ -2439,7 +2460,7 @@ pub const Crypto = struct {
|
||||
return globalThis.throwInvalidArguments("algorithm must be a string", .{});
|
||||
}
|
||||
|
||||
const algorithm = algorithm_name.getZigString(globalThis);
|
||||
const algorithm = try algorithm_name.getZigString(globalThis);
|
||||
|
||||
if (algorithm.len == 0) {
|
||||
return globalThis.throwInvalidArguments("Invalid algorithm name", .{});
|
||||
@@ -2805,9 +2826,12 @@ pub const Crypto = struct {
|
||||
}
|
||||
|
||||
fn final(self: *CryptoHasherZig, output_digest_slice: []u8) []u8 {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
item[1].final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice));
|
||||
inline for (algo_map) |pair| {
|
||||
const name, const T = pair;
|
||||
if (self.algorithm == @field(EVP.Algorithm, name)) {
|
||||
T.final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice));
|
||||
const reset: *T = @ptrCast(@alignCast(self.state));
|
||||
reset.* = T.init(.{});
|
||||
return output_digest_slice[0..self.digest_length];
|
||||
}
|
||||
}
|
||||
@@ -3057,8 +3081,11 @@ pub fn serve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J
|
||||
globalObject,
|
||||
&config,
|
||||
&args,
|
||||
callframe.isFromBunMain(globalObject.vm()),
|
||||
true,
|
||||
.{
|
||||
.allow_bake_config = bun.FeatureFlags.bake() and callframe.isFromBunMain(globalObject.vm()),
|
||||
.is_fetch_required = true,
|
||||
.has_user_routes = false,
|
||||
},
|
||||
);
|
||||
|
||||
if (globalObject.hasException()) {
|
||||
@@ -3122,11 +3149,14 @@ pub fn serve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J
|
||||
if (globalObject.hasException()) {
|
||||
return .zero;
|
||||
}
|
||||
server.listen();
|
||||
const route_list_object = server.listen();
|
||||
if (globalObject.hasException()) {
|
||||
return .zero;
|
||||
}
|
||||
const obj = server.toJS(globalObject);
|
||||
if (route_list_object != .zero) {
|
||||
ServerType.routeListSetCached(obj, globalObject, route_list_object);
|
||||
}
|
||||
obj.protect();
|
||||
|
||||
server.thisObject = obj;
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
|
||||
#define ZIG_REPR_TYPE int64_t
|
||||
|
||||
#ifdef _WIN32
|
||||
#define BUN_FFI_IMPORT __declspec(dllimport)
|
||||
#else
|
||||
#define BUN_FFI_IMPORT
|
||||
#endif
|
||||
|
||||
// /* 7.18.1.1 Exact-width integer types */
|
||||
typedef unsigned char uint8_t;
|
||||
@@ -60,9 +65,9 @@ typedef enum {
|
||||
napi_detachable_arraybuffer_expected,
|
||||
napi_would_deadlock // unused
|
||||
} napi_status;
|
||||
void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
BUN_FFI_IMPORT void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
BUN_FFI_IMPORT void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
BUN_FFI_IMPORT extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -136,7 +141,7 @@ typedef void* JSContext;
|
||||
|
||||
#ifdef IS_CALLBACK
|
||||
void* callback_ctx;
|
||||
ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
// We wrap
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
|
||||
@@ -348,7 +353,7 @@ static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
|
||||
}
|
||||
|
||||
#ifndef IS_CALLBACK
|
||||
ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user