mirror of
https://github.com/oven-sh/bun
synced 2026-02-06 17:08:51 +00:00
Compare commits
130 Commits
claude/css
...
alii/testi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24395f24ed | ||
|
|
822d75a380 | ||
|
|
bffccf3d5f | ||
|
|
0300150324 | ||
|
|
34a1e2adad | ||
|
|
8484e1b827 | ||
|
|
3898ed5e3f | ||
|
|
c08ffadf56 | ||
|
|
fa983247b2 | ||
|
|
99b0a16c33 | ||
|
|
085e25d5d1 | ||
|
|
ce5c336ea5 | ||
|
|
05b12e0ed0 | ||
|
|
d9459f8540 | ||
|
|
e79b512a9d | ||
|
|
dee17270ae | ||
|
|
34fcffd1eb | ||
|
|
87d2e28f6c | ||
|
|
0d2f84c625 | ||
|
|
267ebf501c | ||
|
|
d75322ed05 | ||
|
|
b084d8e9bf | ||
|
|
9902039b1f | ||
|
|
f3fd7506ef | ||
|
|
c21c51a0ff | ||
|
|
0bbf6c74b5 | ||
|
|
57cbbc09e4 | ||
|
|
7f589ffb4b | ||
|
|
cea59d7fc0 | ||
|
|
4ea1454e4a | ||
|
|
8941a363c3 | ||
|
|
722ac3aa5a | ||
|
|
a333d02f84 | ||
|
|
c1acb0b9a4 | ||
|
|
ffd2240c31 | ||
|
|
fa5a5bbe55 | ||
|
|
1e86cebd74 | ||
|
|
bc47f87450 | ||
|
|
698b004ea4 | ||
|
|
b135c207ed | ||
|
|
a1dd26d7db | ||
|
|
7c06320d0f | ||
|
|
dd04c57258 | ||
|
|
344b2c1dfe | ||
|
|
aef0b5b4a6 | ||
|
|
740fb23315 | ||
|
|
2dd997c4b5 | ||
|
|
4061e1cb4f | ||
|
|
6386eef8aa | ||
|
|
3394fd3bdd | ||
|
|
5a8cdc08f0 | ||
|
|
dcc3386611 | ||
|
|
8dc79641c8 | ||
|
|
d865ef41e2 | ||
|
|
e66b4639bd | ||
|
|
8698d25c52 | ||
|
|
81a5c79928 | ||
|
|
fa996ad1a8 | ||
|
|
ed1d6e595c | ||
|
|
7c98b0f440 | ||
|
|
f0d18d73c9 | ||
|
|
a5712b92b8 | ||
|
|
7dcd49f832 | ||
|
|
c59a6997cd | ||
|
|
1d50af7fe8 | ||
|
|
98cee5a57e | ||
|
|
ac0099ebc6 | ||
|
|
64146d47f9 | ||
|
|
a2d8b75962 | ||
|
|
a15fe76bf2 | ||
|
|
8dc084af5f | ||
|
|
2028e21d60 | ||
|
|
f25ea59683 | ||
|
|
55c6afb498 | ||
|
|
0aca002161 | ||
|
|
4980736786 | ||
|
|
3af0d23d53 | ||
|
|
9c96937329 | ||
|
|
d4eaaf8363 | ||
|
|
e1aa437694 | ||
|
|
73c3f0004f | ||
|
|
b80cb629c6 | ||
|
|
8773f7ab65 | ||
|
|
5eb2145b31 | ||
|
|
cde167cacd | ||
|
|
6ce419d3f8 | ||
|
|
05508a627d | ||
|
|
23383b32b0 | ||
|
|
0d5a7c36ed | ||
|
|
b4c8379447 | ||
|
|
438aaf9e95 | ||
|
|
4d60b6f69d | ||
|
|
e9e93244cb | ||
|
|
800a937cc2 | ||
|
|
830fd9b0ae | ||
|
|
fe0aba79f4 | ||
|
|
a4aaec5b2f | ||
|
|
24bc8aa416 | ||
|
|
2ab6efeea3 | ||
|
|
6745bdaa85 | ||
|
|
dce7a02f4d | ||
|
|
9c420c9eff | ||
|
|
9c2ca4b8fd | ||
|
|
e624f1e571 | ||
|
|
27381063b6 | ||
|
|
9ca8de6eb9 | ||
|
|
fdcfac6a75 | ||
|
|
ce1981c525 | ||
|
|
cc3fc5a1d3 | ||
|
|
d83e0eb1f1 | ||
|
|
72b9525507 | ||
|
|
0f7494569e | ||
|
|
9fd6b54c10 | ||
|
|
19acc4dcac | ||
|
|
56da7c4fd9 | ||
|
|
5bdb8ec0cb | ||
|
|
4cf9b794c9 | ||
|
|
998ec54da9 | ||
|
|
0305f3d4d2 | ||
|
|
1006a4fac2 | ||
|
|
c7f7d9bb82 | ||
|
|
37bce389a0 | ||
|
|
bab583497c | ||
|
|
a83fceafc7 | ||
|
|
ef8eef3df8 | ||
|
|
69b571da41 | ||
|
|
908ab9ce30 | ||
|
|
43c46b1f77 | ||
|
|
a0c5f3dc69 | ||
|
|
5965ff18ea |
@@ -125,16 +125,13 @@ const testPlatforms = [
|
||||
{ os: "darwin", arch: "aarch64", release: "13", tier: "previous" },
|
||||
{ os: "darwin", arch: "x64", release: "14", tier: "latest" },
|
||||
{ os: "darwin", arch: "x64", release: "13", tier: "previous" },
|
||||
{ os: "linux", arch: "aarch64", distro: "debian", release: "12", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "debian", release: "12", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "12", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", distro: "debian", release: "13", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "debian", release: "13", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "13", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "13", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "ubuntu", release: "24.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" },
|
||||
@@ -575,6 +572,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
|
||||
if (buildId) {
|
||||
args.push(`--build-id=${buildId}`);
|
||||
}
|
||||
|
||||
if (testFiles) {
|
||||
args.push(...testFiles.map(testFile => `--include=${testFile}`));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
language: en-US
|
||||
|
||||
issue_enrichment:
|
||||
auto_enrich:
|
||||
enabled: false
|
||||
|
||||
reviews:
|
||||
profile: assertive
|
||||
request_changes_workflow: false
|
||||
|
||||
66
.github/workflows/claude.yml
vendored
66
.github/workflows/claude.yml
vendored
@@ -1,66 +0,0 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
github.repository == 'oven-sh/bun' &&
|
||||
(
|
||||
(github.event_name == 'issue_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'pull_request_review' && (github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR')) ||
|
||||
(github.event_name == 'issues' && (github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR'))
|
||||
) &&
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: claude
|
||||
env:
|
||||
IS_SANDBOX: 1
|
||||
container:
|
||||
image: localhost:5000/claude-bun:latest
|
||||
options: --privileged --user 1000:1000
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
working-directory: /workspace/bun
|
||||
run: |
|
||||
git config --global user.email "claude-bot@bun.sh" && \
|
||||
git config --global user.name "Claude Bot" && \
|
||||
git config --global url."git@github.com:".insteadOf "https://github.com/" && \
|
||||
git config --global url."git@github.com:".insteadOf "http://github.com/" && \
|
||||
git config --global --add safe.directory /workspace/bun && \
|
||||
git config --global push.default current && \
|
||||
git config --global pull.rebase true && \
|
||||
git config --global init.defaultBranch main && \
|
||||
git config --global core.editor "vim" && \
|
||||
git config --global color.ui auto && \
|
||||
git config --global fetch.prune true && \
|
||||
git config --global diff.colorMoved zebra && \
|
||||
git config --global merge.conflictStyle diff3 && \
|
||||
git config --global rerere.enabled true && \
|
||||
git config --global core.autocrlf input
|
||||
git fetch origin ${{ github.event.pull_request.head.sha }}
|
||||
git checkout ${{ github.event.pull_request.head.ref }}
|
||||
git reset --hard origin/${{ github.event.pull_request.head.ref }}
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
timeout_minutes: "180"
|
||||
claude_args: |
|
||||
--dangerously-skip-permissions
|
||||
--system-prompt "You are working on the Bun codebase"
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
58
.github/workflows/codex-test-sync.yml
vendored
58
.github/workflows/codex-test-sync.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: Codex Test Sync
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, opened]
|
||||
|
||||
env:
|
||||
BUN_VERSION: "1.2.15"
|
||||
|
||||
jobs:
|
||||
sync-node-tests:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'codex') ||
|
||||
(github.event.action == 'opened' && contains(github.event.pull_request.labels.*.name, 'codex')) ||
|
||||
contains(github.head_ref, 'codex')
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: ${{ env.BUN_VERSION }}
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files: |
|
||||
test/js/node/test/parallel/**/*.{js,mjs,ts}
|
||||
test/js/node/test/sequential/**/*.{js,mjs,ts}
|
||||
|
||||
- name: Sync tests
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Changed test files:"
|
||||
echo "${{ steps.changed-files.outputs.all_changed_files }}"
|
||||
|
||||
# Process each changed test file
|
||||
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
|
||||
# Extract test name from file path
|
||||
test_name=$(basename "$file" | sed 's/\.[^.]*$//')
|
||||
echo "Syncing test: $test_name"
|
||||
bun node:test:cp "$test_name"
|
||||
done
|
||||
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "Sync Node.js tests with upstream"
|
||||
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@@ -27,18 +27,22 @@
|
||||
"git.ignoreLimitWarning": true,
|
||||
|
||||
// Zig
|
||||
"zig.initialSetupDone": true,
|
||||
"zig.buildOption": "build",
|
||||
// "zig.initialSetupDone": true,
|
||||
// "zig.buildOption": "build",
|
||||
"zig.zls.zigLibPath": "${workspaceFolder}/vendor/zig/lib",
|
||||
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen", "--watch", "-fincremental"],
|
||||
"zig.zls.buildOnSaveStep": "check",
|
||||
"zig.buildOnSaveArgs": [
|
||||
"-Dgenerated-code=./build/debug/codegen",
|
||||
"--watch",
|
||||
"-fincremental"
|
||||
],
|
||||
// "zig.zls.buildOnSaveStep": "check",
|
||||
// "zig.zls.enableBuildOnSave": true,
|
||||
// "zig.buildOnSave": true,
|
||||
"zig.buildFilePath": "${workspaceFolder}/build.zig",
|
||||
// "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.zls.enableInlayHints": false,
|
||||
"[zig]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.useTabStops": false,
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed
|
||||
|
||||
- **Build Bun**: `bun bd`
|
||||
- Creates a debug build at `./build/debug/bun-debug`
|
||||
- **CRITICAL**: no need for a timeout, the build is really fast!
|
||||
- **CRITICAL**: do not set a timeout when running `bun bd`
|
||||
- **Run tests with your debug build**: `bun bd test <test-file>`
|
||||
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
|
||||
- **Run any command with debug build**: `bun bd <command>`
|
||||
@@ -94,7 +94,7 @@ test("(multi-file test) my feature", async () => {
|
||||
|
||||
- Always use `port: 0`. Do not hardcode ports. Do not use your own random port number function.
|
||||
- Use `normalizeBunSnapshot` to normalize snapshot output of the test.
|
||||
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. That is NOT a valid test.
|
||||
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. These tests will never fail in CI.
|
||||
- Use `tempDir` from `"harness"` to create a temporary directory. **Do not** use `tmpdirSync` or `fs.mkdtempSync` to create temporary directories.
|
||||
- When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure.
|
||||
- **CRITICAL**: Do not write flaky tests. Do not use `setTimeout` in tests. Instead, `await` the condition to be met. You are not testing the TIME PASSING, you are testing the CONDITION.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bench",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "installbench",
|
||||
@@ -12,11 +13,11 @@
|
||||
"@trpc/server": "^11.0.0",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"esbuild": "^0.25.11",
|
||||
"next": "^15.2.3",
|
||||
"next-auth": "5.0.0-beta.25",
|
||||
"next": "^16.1.0",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"postgres": "^3.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"server-only": "^0.0.1",
|
||||
"superjson": "^2.2.1",
|
||||
"zod": "^3.24.2",
|
||||
@@ -25,8 +26,8 @@
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@tailwindcss/postcss": "^4.0.15",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react": "^19.2.3",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.15",
|
||||
@@ -175,23 +176,23 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="],
|
||||
"@next/env": ["@next/env@16.1.0", "", {}, "sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="],
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ=="],
|
||||
|
||||
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
|
||||
|
||||
@@ -243,13 +244,13 @@
|
||||
|
||||
"@trpc/server": ["@trpc/server@11.7.1", "", { "peerDependencies": { "typescript": "5.9.3" } }, "sha512-N3U8LNLIP4g9C7LJ/sLkjuPHwqlvE3bnspzC4DEFVdvx2+usbn70P80E3wj5cjOTLhmhRiwJCSXhlB+MHfGeCw=="],
|
||||
|
||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||
|
||||
"@types/node": ["@types/node@20.19.24", "", { "dependencies": { "undici-types": "6.21.0" } }, "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "19.2.2" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
@@ -257,11 +258,9 @@
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
|
||||
|
||||
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "5.5.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
@@ -323,9 +322,9 @@
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="],
|
||||
"next": ["next@16.1.0", "", { "dependencies": { "@next/env": "16.1.0", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0", "@next/swc-darwin-x64": "16.1.0", "@next/swc-linux-arm64-gnu": "16.1.0", "@next/swc-linux-arm64-musl": "16.1.0", "@next/swc-linux-x64-gnu": "16.1.0", "@next/swc-linux-x64-musl": "16.1.0", "@next/swc-win32-arm64-msvc": "16.1.0", "@next/swc-win32-x64-msvc": "16.1.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw=="],
|
||||
|
||||
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "next": "15.5.6", "react": "19.2.0" } }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],
|
||||
"next-auth": ["next-auth@5.0.0-beta.30", "", { "dependencies": { "@auth/core": "0.41.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", "nodemailer": "^7.0.7", "react": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg=="],
|
||||
|
||||
"oauth4webapi": ["oauth4webapi@3.8.2", "", {}, "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw=="],
|
||||
|
||||
@@ -339,11 +338,9 @@
|
||||
|
||||
"preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": "10.24.3" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="],
|
||||
|
||||
"pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="],
|
||||
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
|
||||
|
||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
@@ -387,7 +384,7 @@
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"next-auth/@auth/core": ["@auth/core@0.37.2", "", { "dependencies": { "@panva/hkdf": "1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "5.10.0", "oauth4webapi": "3.8.2", "preact": "10.11.3", "preact-render-to-string": "5.2.3" } }, "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw=="],
|
||||
"next-auth/@auth/core": ["@auth/core@0.41.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
@@ -478,11 +475,5 @@
|
||||
"drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
|
||||
|
||||
"drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||
|
||||
"next-auth/@auth/core/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
|
||||
|
||||
"next-auth/@auth/core/preact": ["preact@10.11.3", "", {}, "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="],
|
||||
|
||||
"next-auth/@auth/core/preact-render-to-string": ["preact-render-to-string@5.2.3", "", { "dependencies": { "pretty-format": "3.8.0" }, "peerDependencies": { "preact": "10.11.3" } }, "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
"@trpc/server": "^11.0.0",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"esbuild": "^0.25.11",
|
||||
"next": "^15.2.3",
|
||||
"next-auth": "5.0.0-beta.25",
|
||||
"next": "^16.1.0",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"postgres": "^3.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"server-only": "^0.0.1",
|
||||
"superjson": "^2.2.1",
|
||||
"zod": "^3.24.2"
|
||||
@@ -39,8 +39,8 @@
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@tailwindcss/postcss": "^4.0.15",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react": "^19.2.3",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.0.15",
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "react-hello-world",
|
||||
"dependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"author": "Colin McDonnell",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
48
bench/snippets/urlpattern.js
Normal file
48
bench/snippets/urlpattern.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
const patterns = [
|
||||
{ name: "string pattern", input: "https://(sub.)?example(.com/)foo" },
|
||||
{ name: "hostname IDN", input: { hostname: "xn--caf-dma.com" } },
|
||||
{
|
||||
name: "pathname + search + hash + baseURL",
|
||||
input: {
|
||||
pathname: "/foo",
|
||||
search: "bar",
|
||||
hash: "baz",
|
||||
baseURL: "https://example.com:8080",
|
||||
},
|
||||
},
|
||||
{ name: "pathname with regex", input: { pathname: "/([[a-z]--a])" } },
|
||||
{ name: "named groups", input: { pathname: "/users/:id/posts/:postId" } },
|
||||
{ name: "wildcard", input: { pathname: "/files/*" } },
|
||||
];
|
||||
|
||||
const testURL = "https://sub.example.com/foo";
|
||||
|
||||
group("URLPattern parse (constructor)", () => {
|
||||
for (const { name, input } of patterns) {
|
||||
bench(name, () => {
|
||||
return new URLPattern(input);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
group("URLPattern.test()", () => {
|
||||
for (const { name, input } of patterns) {
|
||||
const pattern = new URLPattern(input);
|
||||
bench(name, () => {
|
||||
return pattern.test(testURL);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
group("URLPattern.exec()", () => {
|
||||
for (const { name, input } of patterns) {
|
||||
const pattern = new URLPattern(input);
|
||||
bench(name, () => {
|
||||
return pattern.exec(testURL);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -607,7 +607,7 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
|
||||
obj.llvm_codegen_threads = opts.llvm_codegen_threads orelse 0;
|
||||
}
|
||||
|
||||
obj.no_link_obj = opts.os != .windows;
|
||||
obj.no_link_obj = opts.os != .windows and !opts.no_llvm;
|
||||
|
||||
|
||||
if (opts.enable_asan and !enableFastBuild(b)) {
|
||||
|
||||
3
bun.lock
3
bun.lock
@@ -36,6 +36,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@types/bun": "workspace:packages/@types/bun",
|
||||
"@types/node": "25.0.0",
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
},
|
||||
"packages": {
|
||||
@@ -155,7 +156,7 @@
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||
"@types/node": ["@types/node@25.0.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew=="],
|
||||
|
||||
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
|
||||
|
||||
|
||||
@@ -872,6 +872,7 @@ target_include_directories(${bun} PRIVATE
|
||||
${CODEGEN_PATH}
|
||||
${VENDOR_PATH}
|
||||
${VENDOR_PATH}/picohttpparser
|
||||
${VENDOR_PATH}/zlib
|
||||
${NODEJS_HEADERS_PATH}/include
|
||||
${NODEJS_HEADERS_PATH}/include/node
|
||||
)
|
||||
@@ -1199,6 +1200,29 @@ set_target_properties(${bun} PROPERTIES LINK_DEPENDS ${BUN_SYMBOLS_PATH})
|
||||
|
||||
include(SetupWebKit)
|
||||
|
||||
if(BUN_LINK_ONLY)
|
||||
register_command(
|
||||
TARGET
|
||||
${bun}
|
||||
TARGET_PHASE
|
||||
POST_BUILD
|
||||
COMMENT
|
||||
"Uploading link metadata"
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env
|
||||
BUN_VERSION=${VERSION}
|
||||
WEBKIT_DOWNLOAD_URL=${WEBKIT_DOWNLOAD_URL}
|
||||
WEBKIT_VERSION=${WEBKIT_VERSION}
|
||||
ZIG_COMMIT=${ZIG_COMMIT}
|
||||
${BUN_EXECUTABLE} ${CWD}/scripts/create-link-metadata.mjs ${BUILD_PATH} ${bun}
|
||||
SOURCES
|
||||
${BUN_ZIG_OUTPUT}
|
||||
${BUN_CPP_OUTPUT}
|
||||
ARTIFACTS
|
||||
${BUILD_PATH}/link-metadata.json
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
target_link_libraries(${bun} PRIVATE
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
c-ares/c-ares
|
||||
COMMIT
|
||||
d3a507e920e7af18a5efb7f9f1d8044ed4750013
|
||||
3ac47ee46edd8ea40370222f91613fc16c434853
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
|
||||
@@ -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 6d0f3aac0b817cc01a846b3754b21271adedac12)
|
||||
set(WEBKIT_VERSION 8400ec68b2649d7b95f10576ad648c3aa8a92b77)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bookworm-slim AS build
|
||||
FROM debian:trixie-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
|
||||
&& which bun \
|
||||
&& bun --version
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
# Disable the runtime transpiler cache by default inside Docker containers.
|
||||
# On ephemeral containers, the cache is not useful
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bookworm-slim AS build
|
||||
FROM debian:trixie-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
@@ -56,7 +56,7 @@ RUN apt-get update -qq \
|
||||
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
|
||||
&& chmod +x /usr/local/bin/bun
|
||||
|
||||
FROM debian:bookworm
|
||||
FROM debian:trixie
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin
|
||||
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:bookworm-slim AS build
|
||||
FROM debian:trixie-slim AS build
|
||||
|
||||
# https://github.com/oven-sh/bun/releases
|
||||
ARG BUN_VERSION=latest
|
||||
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
|
||||
&& which bun \
|
||||
&& bun --version
|
||||
|
||||
FROM gcr.io/distroless/base-nossl-debian11
|
||||
FROM gcr.io/distroless/base-nossl-debian13
|
||||
|
||||
# Disable the runtime transpiler cache by default inside Docker containers.
|
||||
# On ephemeral containers, the cache is not useful
|
||||
@@ -71,6 +71,7 @@ ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin"
|
||||
|
||||
# Temporarily use the `build`-stage image binaries to create a symlink:
|
||||
RUN --mount=type=bind,from=build,source=/usr/bin,target=/usr/bin \
|
||||
--mount=type=bind,from=build,source=/etc/alternatives/which,target=/etc/alternatives/which \
|
||||
--mount=type=bind,from=build,source=/bin,target=/bin \
|
||||
--mount=type=bind,from=build,source=/usr/lib,target=/usr/lib \
|
||||
--mount=type=bind,from=build,source=/lib,target=/lib \
|
||||
|
||||
@@ -65,6 +65,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
|
||||
| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--color` | n/a | Always enabled |
|
||||
| `--drop` | `--drop` | |
|
||||
| n/a | `--feature` | Bun-specific. Enable feature flags for compile-time dead-code elimination via `import { feature } from "bun:bundle"` |
|
||||
| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time |
|
||||
| `--ignore-annotations` | `--ignore-dce-annotations` | |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -427,8 +427,8 @@ This will allow you to use TailwindCSS utility classes in your HTML and CSS file
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
<!-- [!code ++] -->
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
</head>
|
||||
<!-- the rest of your HTML... -->
|
||||
</html>
|
||||
@@ -448,8 +448,8 @@ Alternatively, you can import TailwindCSS in your CSS file:
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<!-- [!code ++] -->
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<!-- the rest of your HTML... -->
|
||||
</html>
|
||||
@@ -492,6 +492,28 @@ Bun will lazily resolve and load each plugin and use them to bundle your routes.
|
||||
the CLI.
|
||||
</Note>
|
||||
|
||||
## Inline Environment Variables
|
||||
|
||||
Bun can replace `process.env.*` references in your frontend JavaScript and TypeScript with their actual values at build time. Configure the `env` option in your `bunfig.toml`:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[serve.static]
|
||||
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
|
||||
# env = "inline" # inline all environment variables
|
||||
# env = "disable" # disable env var replacement (default)
|
||||
```
|
||||
|
||||
<Note>
|
||||
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
|
||||
process.env; env.FOO`.
|
||||
|
||||
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
|
||||
is not defined` in the browser.
|
||||
|
||||
</Note>
|
||||
|
||||
See the [HTML & static sites documentation](/bundler/html-static#inline-environment-variables) for more details on build-time configuration and examples.
|
||||
|
||||
## How It Works
|
||||
|
||||
Bun uses `HTMLRewriter` to scan for `<script>` and `<link>` tags in HTML files, uses them as entrypoints for Bun's bundler, generates an optimized bundle for the JavaScript/TypeScript/TSX/JSX and CSS files, and serves the result.
|
||||
|
||||
@@ -262,6 +262,93 @@ Then, reference TailwindCSS in your HTML via `<link>` tag, `@import` in CSS, or
|
||||
|
||||
<Info>Only one of those are necessary, not all three.</Info>
|
||||
|
||||
## Inline environment variables
|
||||
|
||||
Bun can replace `process.env.*` references in your JavaScript and TypeScript with their actual values at build time. This is useful for injecting configuration like API URLs or feature flags into your frontend code.
|
||||
|
||||
### Dev server (runtime)
|
||||
|
||||
To inline environment variables when using `bun ./index.html`, configure the `env` option in your `bunfig.toml`:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[serve.static]
|
||||
env = "PUBLIC_*" # only inline env vars starting with PUBLIC_ (recommended)
|
||||
# env = "inline" # inline all environment variables
|
||||
# env = "disable" # disable env var replacement (default)
|
||||
```
|
||||
|
||||
<Note>
|
||||
This only works with literal `process.env.FOO` references, not `import.meta.env` or indirect access like `const env =
|
||||
process.env; env.FOO`.
|
||||
|
||||
If an environment variable is not set, you may see runtime errors like `ReferenceError: process
|
||||
is not defined` in the browser.
|
||||
|
||||
</Note>
|
||||
|
||||
Then run the dev server:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
PUBLIC_API_URL=https://api.example.com bun ./index.html
|
||||
```
|
||||
|
||||
### Build for production
|
||||
|
||||
When building static HTML for production, use the `env` option to inline environment variables:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="CLI">
|
||||
```bash terminal icon="terminal"
|
||||
# Inline all environment variables
|
||||
bun build ./index.html --outdir=dist --env=inline
|
||||
|
||||
# Only inline env vars with a specific prefix (recommended)
|
||||
bun build ./index.html --outdir=dist --env=PUBLIC_*
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
// Inline all environment variables
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
env: "inline", // [!code highlight]
|
||||
});
|
||||
|
||||
// Only inline env vars with a specific prefix (recommended)
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
outdir: "./dist",
|
||||
env: "PUBLIC_*", // [!code highlight]
|
||||
});
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Example
|
||||
|
||||
Given this source file:
|
||||
|
||||
```ts title="app.ts" icon="/icons/typescript.svg"
|
||||
const apiUrl = process.env.PUBLIC_API_URL;
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
```
|
||||
|
||||
And running with `PUBLIC_API_URL=https://api.example.com`:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_*
|
||||
```
|
||||
|
||||
The bundled output will contain:
|
||||
|
||||
```js title="dist/app.js" icon="/icons/javascript.svg"
|
||||
const apiUrl = "https://api.example.com";
|
||||
console.log(`API URL: ${apiUrl}`);
|
||||
```
|
||||
|
||||
## Echo console logs from browser to terminal
|
||||
|
||||
Bun's dev server supports streaming console logs from the browser to the terminal.
|
||||
|
||||
@@ -1141,6 +1141,84 @@ Remove function calls from a bundle. For example, `--drop=console` will remove a
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### features
|
||||
|
||||
Enable compile-time feature flags for dead-code elimination. This provides a way to conditionally include or exclude code paths at bundle time using `import { feature } from "bun:bundle"`.
|
||||
|
||||
```ts title="app.ts" icon="/icons/typescript.svg"
|
||||
import { feature } from "bun:bundle";
|
||||
|
||||
if (feature("PREMIUM")) {
|
||||
// Only included when PREMIUM flag is enabled
|
||||
initPremiumFeatures();
|
||||
}
|
||||
|
||||
if (feature("DEBUG")) {
|
||||
// Only included when DEBUG flag is enabled
|
||||
console.log("Debug mode");
|
||||
}
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<Tab title="JavaScript">
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ['./app.ts'],
|
||||
outdir: './out',
|
||||
features: ["PREMIUM"], // PREMIUM=true, DEBUG=false
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="CLI">
|
||||
```bash terminal icon="terminal"
|
||||
bun build ./app.ts --outdir ./out --feature PREMIUM
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
The `feature()` function is replaced with `true` or `false` at bundle time. Combined with minification, unreachable code is eliminated:
|
||||
|
||||
```ts title="Input" icon="/icons/typescript.svg"
|
||||
import { feature } from "bun:bundle";
|
||||
const mode = feature("PREMIUM") ? "premium" : "free";
|
||||
```
|
||||
|
||||
```js title="Output (with --feature PREMIUM --minify)" icon="/icons/javascript.svg"
|
||||
var mode = "premium";
|
||||
```
|
||||
|
||||
```js title="Output (without --feature PREMIUM, with --minify)" icon="/icons/javascript.svg"
|
||||
var mode = "free";
|
||||
```
|
||||
|
||||
**Key behaviors:**
|
||||
|
||||
- `feature()` requires a string literal argument — dynamic values are not supported
|
||||
- The `bun:bundle` import is completely removed from the output
|
||||
- Works with `bun build`, `bun run`, and `bun test`
|
||||
- Multiple flags can be enabled: `--feature FLAG_A --feature FLAG_B`
|
||||
- For type safety, augment the `Registry` interface to restrict `feature()` to known flags (see below)
|
||||
|
||||
**Use cases:**
|
||||
|
||||
- Platform-specific code (`feature("SERVER")` vs `feature("CLIENT")`)
|
||||
- Environment-based features (`feature("DEVELOPMENT")`)
|
||||
- Gradual feature rollouts
|
||||
- A/B testing variants
|
||||
- Paid tier features
|
||||
|
||||
**Type safety:** By default, `feature()` accepts any string. To get autocomplete and catch typos at compile time, create an `env.d.ts` file (or add to an existing `.d.ts`) and augment the `Registry` interface:
|
||||
|
||||
```ts title="env.d.ts" icon="/icons/typescript.svg"
|
||||
declare module "bun:bundle" {
|
||||
interface Registry {
|
||||
features: "DEBUG" | "PREMIUM" | "BETA_FEATURES";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ensure the file is included in your `tsconfig.json` (e.g., `"include": ["src", "env.d.ts"]`). Now `feature()` only accepts those flags, and invalid strings like `feature("TYPO")` become type errors.
|
||||
|
||||
## Outputs
|
||||
|
||||
The `Bun.build` function returns a `Promise<BuildOutput>`, defined as:
|
||||
|
||||
@@ -326,6 +326,7 @@
|
||||
"group": "Utilities",
|
||||
"icon": "wrench",
|
||||
"pages": [
|
||||
"/guides/util/upgrade",
|
||||
"/guides/util/detect-bun",
|
||||
"/guides/util/version",
|
||||
"/guides/util/hash-a-password",
|
||||
|
||||
@@ -74,6 +74,12 @@ export default defineNuxtConfig({
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, you can set the preset via environment variable:
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
NITRO_PRESET=bun bun run build
|
||||
```
|
||||
|
||||
<Note>
|
||||
Some packages provide Bun-specific exports that Nitro will not bundle correctly using the default preset. In this
|
||||
case, you need to use Bun preset so that the packages will work correctly in production builds.
|
||||
|
||||
@@ -4,63 +4,59 @@ sidebarTitle: "SolidStart with Bun"
|
||||
mode: center
|
||||
---
|
||||
|
||||
<Warning>
|
||||
SolidStart currently relies on Node.js APIs that Bun does not yet implement. The guide below uses Bun to initialize a
|
||||
project and install dependencies, but it uses Node.js to run the dev server.
|
||||
</Warning>
|
||||
|
||||
---
|
||||
|
||||
Initialize a SolidStart app with `create-solid`.
|
||||
Initialize a SolidStart app with `create-solid`. You can specify the `--solidstart` flag to create a SolidStart project, and `--ts` for TypeScript support. When prompted for a template, select `basic` for a minimal starter app.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun create solid my-app
|
||||
bun create solid my-app --solidstart --ts
|
||||
```
|
||||
|
||||
```txt
|
||||
create-solid version 0.2.31
|
||||
|
||||
Welcome to the SolidStart setup wizard!
|
||||
|
||||
There are definitely bugs and some feature might not work yet.
|
||||
If you encounter an issue, have a look at
|
||||
https://github.com/solidjs/solid-start/issues and open a new one,
|
||||
if it is not already tracked.
|
||||
|
||||
✔ Which template do you want to use? › todomvc
|
||||
✔ Server Side Rendering? … yes
|
||||
✔ Use TypeScript? … yes
|
||||
cloned solidjs/solid-start#main to /path/to/my-app/.solid-start
|
||||
✔ Copied project files
|
||||
┌
|
||||
Create-Solid v0.6.11
|
||||
│
|
||||
◇ Project Name
|
||||
│ my-app
|
||||
│
|
||||
◇ Which template would you like to use?
|
||||
│ basic
|
||||
│
|
||||
◇ Project created 🎉
|
||||
│
|
||||
◇ To get started, run: ─╮
|
||||
│ │
|
||||
│ cd my-app │
|
||||
│ bun install │
|
||||
│ bun dev │
|
||||
│ │
|
||||
├────────────────────────╯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
As instructed by the `create-solid` CLI, let's install our dependencies.
|
||||
As instructed by the `create-solid` CLI, install the dependencies.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
cd my-app
|
||||
bun install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Then run the development server.
|
||||
Then run the development server with `bun dev`.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
bun run dev
|
||||
# or, equivalently
|
||||
bunx solid-start dev
|
||||
bun dev
|
||||
```
|
||||
|
||||
---
|
||||
```txt
|
||||
$ vinxi dev
|
||||
vinxi v0.5.8
|
||||
vinxi starting dev server
|
||||
|
||||
➜ Local: http://localhost:3000/
|
||||
➜ Network: use --host to expose
|
||||
```
|
||||
|
||||
Open [localhost:3000](http://localhost:3000). Any changes you make to `src/routes/index.tsx` will be hot-reloaded automatically.
|
||||
|
||||
<Frame>
|
||||

|
||||
</Frame>
|
||||
|
||||
---
|
||||
|
||||
Refer to the [SolidStart website](https://start.solidjs.com/getting-started/what-is-solidstart) for complete framework documentation.
|
||||
Refer to the [SolidStart website](https://docs.solidjs.com/solid-start) for complete framework documentation.
|
||||
|
||||
@@ -9,18 +9,42 @@ In Bun, `fetch` supports sending requests through an HTTP or HTTPS proxy. This i
|
||||
```ts proxy.ts icon="/icons/typescript.svg"
|
||||
await fetch("https://example.com", {
|
||||
// The URL of the proxy server
|
||||
proxy: "https://usertitle:password@proxy.example.com:8080",
|
||||
proxy: "https://username:password@proxy.example.com:8080",
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The `proxy` option is a URL string that specifies the proxy server. It can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
|
||||
The `proxy` option can be a URL string or an object with `url` and optional `headers`. The URL can include the username and password if the proxy requires authentication. It can be `http://` or `https://`.
|
||||
|
||||
---
|
||||
|
||||
## Custom proxy headers
|
||||
|
||||
To send custom headers to the proxy server (useful for proxy authentication tokens, custom routing, etc.), use the object format:
|
||||
|
||||
```ts proxy-headers.ts icon="/icons/typescript.svg"
|
||||
await fetch("https://example.com", {
|
||||
proxy: {
|
||||
url: "https://proxy.example.com:8080",
|
||||
headers: {
|
||||
"Proxy-Authorization": "Bearer my-token",
|
||||
"X-Proxy-Region": "us-east-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `headers` property accepts a plain object or a `Headers` instance. These headers are sent directly to the proxy server in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets).
|
||||
|
||||
If you provide a `Proxy-Authorization` header, it will override any credentials specified in the proxy URL.
|
||||
|
||||
---
|
||||
|
||||
## Environment variables
|
||||
|
||||
You can also set the `$HTTP_PROXY` or `$HTTPS_PROXY` environment variable to the proxy URL. This is useful when you want to use the same proxy for all requests.
|
||||
|
||||
```sh terminal icon="terminal"
|
||||
HTTPS_PROXY=https://usertitle:password@proxy.example.com:8080 bun run index.ts
|
||||
HTTPS_PROXY=https://username:password@proxy.example.com:8080 bun run index.ts
|
||||
```
|
||||
|
||||
@@ -8,7 +8,9 @@ Unlike other npm clients, Bun does not execute arbitrary lifecycle scripts for i
|
||||
|
||||
<Note>
|
||||
Bun includes a default allowlist of popular packages containing `postinstall` scripts that are known to be safe. You
|
||||
can see this list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).
|
||||
can see this list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt). This
|
||||
default list only applies to packages installed from npm. For packages from other sources (such as `file:`, `link:`,
|
||||
`git:`, or `github:` dependencies), you must explicitly add them to `trustedDependencies`.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
@@ -14,16 +14,6 @@ process.on("SIGINT", () => {
|
||||
|
||||
---
|
||||
|
||||
If you don't know which signal to listen for, you listen to the umbrella `"exit"` event.
|
||||
|
||||
```ts
|
||||
process.on("exit", code => {
|
||||
console.log(`Process exited with code ${code}`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
If you don't know which signal to listen for, you listen to the [`"beforeExit"`](https://nodejs.org/api/process.html#event-beforeexit) and [`"exit"`](https://nodejs.org/api/process.html#event-exit) events.
|
||||
|
||||
```ts
|
||||
|
||||
@@ -60,7 +60,7 @@ test("random", async () => {
|
||||
|
||||
expect(random).toHaveBeenCalled();
|
||||
expect(random).toHaveBeenCalledTimes(3);
|
||||
expect(random.mock.args).toEqual([[1], [2], [3]]);
|
||||
expect(random.mock.calls).toEqual([[1], [2], [3]]);
|
||||
expect(random.mock.results[0]).toEqual({ type: "return", value: a });
|
||||
});
|
||||
```
|
||||
|
||||
@@ -76,7 +76,7 @@ declare module "bun:test" {
|
||||
|
||||
You should now be able to use Testing Library in your tests
|
||||
|
||||
```ts matchers.d.ts icon="/icons/typescript.svg"
|
||||
```tsx myComponent.test.tsx icon="/icons/typescript.svg"
|
||||
import { test, expect } from "bun:test";
|
||||
import { screen, render } from "@testing-library/react";
|
||||
import { MyComponent } from "./myComponent";
|
||||
|
||||
@@ -4,22 +4,25 @@ sidebarTitle: Detect Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
The recommended way to conditionally detect when code is being executed with `bun` is to check for the existence of the `Bun` global.
|
||||
|
||||
This is similar to how you'd check for the existence of the `window` variable to detect when code is being executed in a browser.
|
||||
|
||||
```ts
|
||||
if (typeof Bun !== "undefined") {
|
||||
// this code will only run when the file is run with Bun
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
In TypeScript environments, the previous approach will result in a type error unless `@types/bun` is installed. To avoid this, you can check `process.versions` instead.
|
||||
The recommended way to detect when code is being executed with Bun is to check `process.versions.bun`. This works in both JavaScript and TypeScript without requiring any additional type definitions.
|
||||
|
||||
```ts
|
||||
if (process.versions.bun) {
|
||||
// this code will only run when the file is run with Bun
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Alternatively, you can check for the existence of the `Bun` global. This is similar to how you'd check for the existence of the `window` variable to detect when code is being executed in a browser.
|
||||
|
||||
<Note>
|
||||
This approach will result in a type error in TypeScript unless `@types/bun` is installed. You can install it with `bun
|
||||
add -d @types/bun`.
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
if (typeof Bun !== "undefined") {
|
||||
// this code will only run when the file is run with Bun
|
||||
}
|
||||
```
|
||||
|
||||
93
docs/guides/util/upgrade.mdx
Normal file
93
docs/guides/util/upgrade.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Upgrade Bun to the latest version
|
||||
sidebarTitle: Upgrade Bun
|
||||
mode: center
|
||||
---
|
||||
|
||||
Bun can upgrade itself using the built-in `bun upgrade` command. This is the fastest way to get the latest features and bug fixes.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun upgrade
|
||||
```
|
||||
|
||||
This downloads and installs the latest stable version of Bun, replacing the currently installed version.
|
||||
|
||||
<Note>To see the current version of Bun, run `bun --version`.</Note>
|
||||
|
||||
---
|
||||
|
||||
## Verify the upgrade
|
||||
|
||||
After upgrading, verify the new version:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun --version
|
||||
# Output: 1.x.y
|
||||
|
||||
# See the exact commit of the Bun binary
|
||||
bun --revision
|
||||
# Output: 1.x.y+abc123def
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upgrade to canary builds
|
||||
|
||||
Canary builds are automatically released on every commit to the `main` branch. These are untested but useful for trying new features or verifying bug fixes before they're released.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun upgrade --canary
|
||||
```
|
||||
|
||||
<Warning>Canary builds are not recommended for production use. They may contain bugs or breaking changes.</Warning>
|
||||
|
||||
---
|
||||
|
||||
## Switch back to stable
|
||||
|
||||
If you're on a canary build and want to return to the latest stable release:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun upgrade --stable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Install a specific version
|
||||
|
||||
To install a specific version of Bun, use the install script with a version tag:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS & Linux">
|
||||
```bash terminal icon="terminal"
|
||||
curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.3"
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```powershell PowerShell icon="windows"
|
||||
iex "& {$(irm https://bun.sh/install.ps1)} -Version 1.3.3"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Package manager users
|
||||
|
||||
If you installed Bun via a package manager, use that package manager to upgrade instead of `bun upgrade` to avoid conflicts.
|
||||
|
||||
<Tip>
|
||||
**Homebrew users** <br />
|
||||
To avoid conflicts with Homebrew, use `brew upgrade bun` instead.
|
||||
|
||||
**Scoop users** <br />
|
||||
To avoid conflicts with Scoop, use `scoop update bun` instead.
|
||||
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- [Installation](/installation) — Install Bun for the first time
|
||||
- [Update packages](/pm/cli/update) — Update dependencies to latest versions
|
||||
@@ -3,6 +3,8 @@ title: "bunx"
|
||||
description: "Run packages from npm"
|
||||
---
|
||||
|
||||
import Bunx from "/snippets/cli/bunx.mdx";
|
||||
|
||||
<Note>`bunx` is an alias for `bun x`. The `bunx` CLI will be auto-installed when you install `bun`.</Note>
|
||||
|
||||
Use `bunx` to auto-install and run packages from `npm`. It's Bun's equivalent of `npx` or `yarn dlx`.
|
||||
@@ -52,6 +54,8 @@ To pass additional command-line flags and arguments through to the executable, p
|
||||
bunx my-cli --foo bar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shebangs
|
||||
|
||||
By default, Bun respects shebangs. If an executable is marked with `#!/usr/bin/env node`, Bun will spin up a `node` process to execute the file. However, in some cases it may be desirable to run executables using Bun's runtime, even if the executable indicates otherwise. To do so, include the `--bun` flag.
|
||||
@@ -81,3 +85,7 @@ To force bun to always be used with a script, use a shebang.
|
||||
```js dist/index.js icon="/icons/javascript.svg"
|
||||
#!/usr/bin/env bun
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<Bunx />
|
||||
|
||||
@@ -46,6 +46,13 @@ Once added to `trustedDependencies`, install/re-install the package. Bun will re
|
||||
|
||||
The top 500 npm packages with lifecycle scripts are allowed by default. You can see the full list [here](https://github.com/oven-sh/bun/blob/main/src/install/default-trusted-dependencies.txt).
|
||||
|
||||
<Note>
|
||||
The default trusted dependencies list only applies to packages installed from npm. For packages from other sources
|
||||
(such as `file:`, `link:`, `git:`, or `github:` dependencies), you must explicitly add them to `trustedDependencies`
|
||||
to run their lifecycle scripts, even if the package name matches an entry in the default list. This prevents malicious
|
||||
packages from spoofing trusted package names through local file paths or git repositories.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## `--ignore-scripts`
|
||||
|
||||
@@ -107,12 +107,34 @@ Bun supports the following loaders:
|
||||
|
||||
### `telemetry`
|
||||
|
||||
The `telemetry` field permit to enable/disable the analytics records. Bun records bundle timings (so we can answer with data, "is Bun getting faster?") and feature usage (e.g., "are people actually using macros?"). The request body size is about 60 bytes, so it's not a lot of data. By default the telemetry is enabled. Equivalent of `DO_NOT_TRACK` env variable.
|
||||
The `telemetry` field is used to enable/disable analytics. By default, telemetry is enabled. This is equivalent to the `DO_NOT_TRACK` environment variable.
|
||||
|
||||
Currently we do not collect telemetry and this setting is only used for enabling/disabling anonymous crash reports, but in the future we plan to collect information like which Bun APIs are used most or how long `bun build` takes.
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
telemetry = false
|
||||
```
|
||||
|
||||
### `env`
|
||||
|
||||
Configure automatic `.env` file loading. By default, Bun automatically loads `.env` files. To disable this behavior:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
# Disable automatic .env file loading
|
||||
env = false
|
||||
```
|
||||
|
||||
You can also use object syntax with the `file` property:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[env]
|
||||
file = false
|
||||
```
|
||||
|
||||
This is useful in production environments or CI/CD pipelines where you want to rely solely on system environment variables.
|
||||
|
||||
Note: Explicitly provided environment files via `--env-file` will still be loaded even when default loading is disabled.
|
||||
|
||||
### `console`
|
||||
|
||||
Configure console output behavior.
|
||||
|
||||
@@ -315,6 +315,109 @@ if (typeof Bun !== "undefined") {
|
||||
|
||||
---
|
||||
|
||||
## Terminal (PTY) support
|
||||
|
||||
For interactive terminal applications, you can spawn a subprocess with a pseudo-terminal (PTY) attached using the `terminal` option. This makes the subprocess think it's running in a real terminal, enabling features like colored output, cursor movement, and interactive prompts.
|
||||
|
||||
```ts
|
||||
const proc = Bun.spawn(["bash"], {
|
||||
terminal: {
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
data(terminal, data) {
|
||||
// Called when data is received from the terminal
|
||||
process.stdout.write(data);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Write to the terminal
|
||||
proc.terminal.write("echo hello\n");
|
||||
|
||||
// Wait for the process to exit
|
||||
await proc.exited;
|
||||
|
||||
// Close the terminal
|
||||
proc.terminal.close();
|
||||
```
|
||||
|
||||
When the `terminal` option is provided:
|
||||
|
||||
- The subprocess sees `process.stdout.isTTY` as `true`
|
||||
- `stdin`, `stdout`, and `stderr` are all connected to the terminal
|
||||
- `proc.stdin`, `proc.stdout`, and `proc.stderr` return `null` — use the terminal instead
|
||||
- Access the terminal via `proc.terminal`
|
||||
|
||||
### Terminal options
|
||||
|
||||
| Option | Description | Default |
|
||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
|
||||
| `cols` | Number of columns | `80` |
|
||||
| `rows` | Number of rows | `24` |
|
||||
| `name` | Terminal type for PTY configuration (set `TERM` env var separately via `env` option) | `"xterm-256color"` |
|
||||
| `data` | Callback when data is received `(terminal, data) => void` | — |
|
||||
| `exit` | Callback when PTY stream closes (EOF or error). `exitCode` is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. Use `proc.exited` for process exit. | — |
|
||||
| `drain` | Callback when ready for more data `(terminal) => void` | — |
|
||||
|
||||
### Terminal methods
|
||||
|
||||
The `Terminal` object returned by `proc.terminal` has the following methods:
|
||||
|
||||
```ts
|
||||
// Write data to the terminal
|
||||
proc.terminal.write("echo hello\n");
|
||||
|
||||
// Resize the terminal
|
||||
proc.terminal.resize(120, 40);
|
||||
|
||||
// Set raw mode (disable line buffering and echo)
|
||||
proc.terminal.setRawMode(true);
|
||||
|
||||
// Keep event loop alive while terminal is open
|
||||
proc.terminal.ref();
|
||||
proc.terminal.unref();
|
||||
|
||||
// Close the terminal
|
||||
proc.terminal.close();
|
||||
```
|
||||
|
||||
### Reusable Terminal
|
||||
|
||||
You can create a terminal independently and reuse it across multiple subprocesses:
|
||||
|
||||
```ts
|
||||
await using terminal = new Bun.Terminal({
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
data(term, data) {
|
||||
process.stdout.write(data);
|
||||
},
|
||||
});
|
||||
|
||||
// Spawn first process
|
||||
const proc1 = Bun.spawn(["echo", "first"], { terminal });
|
||||
await proc1.exited;
|
||||
|
||||
// Reuse terminal for another process
|
||||
const proc2 = Bun.spawn(["echo", "second"], { terminal });
|
||||
await proc2.exited;
|
||||
|
||||
// Terminal is closed automatically by `await using`
|
||||
```
|
||||
|
||||
When passing an existing `Terminal` object:
|
||||
|
||||
- The terminal can be reused across multiple spawns
|
||||
- You control when to close the terminal
|
||||
- The `exit` callback fires when you call `terminal.close()`, not when each subprocess exits
|
||||
- Use `proc.exited` to detect individual subprocess exits
|
||||
|
||||
This is useful for running multiple commands in sequence through the same terminal session.
|
||||
|
||||
<Note>Terminal support is only available on POSIX systems (Linux, macOS). It is not available on Windows.</Note>
|
||||
|
||||
---
|
||||
|
||||
## Blocking API (`Bun.spawnSync()`)
|
||||
|
||||
Bun provides a synchronous equivalent of `Bun.spawn` called `Bun.spawnSync`. This is a blocking API that supports the same inputs and parameters as `Bun.spawn`. It returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.
|
||||
@@ -407,6 +510,7 @@ namespace SpawnOptions {
|
||||
timeout?: number;
|
||||
killSignal?: string | number;
|
||||
maxBuffer?: number;
|
||||
terminal?: TerminalOptions; // PTY support (POSIX only)
|
||||
}
|
||||
|
||||
type Readable =
|
||||
@@ -435,10 +539,11 @@ namespace SpawnOptions {
|
||||
}
|
||||
|
||||
interface Subprocess extends AsyncDisposable {
|
||||
readonly stdin: FileSink | number | undefined;
|
||||
readonly stdout: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly stderr: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly readable: ReadableStream<Uint8Array> | number | undefined;
|
||||
readonly stdin: FileSink | number | undefined | null;
|
||||
readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
|
||||
readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
|
||||
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
|
||||
readonly terminal: Terminal | undefined;
|
||||
readonly pid: number;
|
||||
readonly exited: Promise<number>;
|
||||
readonly exitCode: number | null;
|
||||
@@ -465,6 +570,28 @@ interface SyncSubprocess {
|
||||
pid: number;
|
||||
}
|
||||
|
||||
interface TerminalOptions {
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
name?: string;
|
||||
data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
|
||||
/** Called when PTY stream closes (EOF or error). exitCode is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. */
|
||||
exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
|
||||
drain?: (terminal: Terminal) => void;
|
||||
}
|
||||
|
||||
interface Terminal extends AsyncDisposable {
|
||||
readonly stdin: number;
|
||||
readonly stdout: number;
|
||||
readonly closed: boolean;
|
||||
write(data: string | BufferSource): number;
|
||||
resize(cols: number, rows: number): void;
|
||||
setRawMode(enabled: boolean): void;
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
interface ResourceUsage {
|
||||
contextSwitches: {
|
||||
voluntary: number;
|
||||
|
||||
@@ -193,15 +193,17 @@ This is the maximum amount of time a connection is allowed to be idle before the
|
||||
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
|
||||
|
||||
```ts server.ts
|
||||
import { type Serve } from "bun";
|
||||
import type { Serve } from "bun";
|
||||
|
||||
export default {
|
||||
fetch(req) {
|
||||
return new Response("Bun!");
|
||||
},
|
||||
} satisfies Serve;
|
||||
} satisfies Serve.Options<undefined>;
|
||||
```
|
||||
|
||||
The type parameter `<undefined>` represents WebSocket data — if you add a `websocket` handler with custom data attached via `server.upgrade(req, { data: ... })`, replace `undefined` with your data type.
|
||||
|
||||
Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
|
||||
|
||||
---
|
||||
|
||||
@@ -51,7 +51,7 @@ const response = await fetch("http://example.com", {
|
||||
|
||||
### Proxying requests
|
||||
|
||||
To proxy a request, pass an object with the `proxy` property set to a URL.
|
||||
To proxy a request, pass an object with the `proxy` property set to a URL string:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
@@ -59,6 +59,22 @@ const response = await fetch("http://example.com", {
|
||||
});
|
||||
```
|
||||
|
||||
You can also use an object format to send custom headers to the proxy server:
|
||||
|
||||
```ts
|
||||
const response = await fetch("http://example.com", {
|
||||
proxy: {
|
||||
url: "http://proxy.com",
|
||||
headers: {
|
||||
"Proxy-Authorization": "Bearer my-token",
|
||||
"X-Custom-Proxy-Header": "value",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `headers` are sent directly to the proxy in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.
|
||||
|
||||
### Custom headers
|
||||
|
||||
To set custom headers, pass an object with the `headers` property set to an object.
|
||||
|
||||
@@ -23,10 +23,11 @@ if (!githubToken) {
|
||||
name: "github-token",
|
||||
value: githubToken,
|
||||
});
|
||||
|
||||
console.log("GitHub token stored");
|
||||
}
|
||||
|
||||
const response = await fetch("https://api.github.com/name", {
|
||||
const response = await fetch("https://api.github.com/user", {
|
||||
headers: { Authorization: `token ${githubToken}` },
|
||||
});
|
||||
|
||||
|
||||
@@ -172,12 +172,25 @@ const query = db.query(`select "Hello world" as message`);
|
||||
```
|
||||
|
||||
<Note>
|
||||
Use the `.prepare()` method to prepare a query _without_ caching it on the `Database` instance.
|
||||
**What does "cached" mean?**
|
||||
|
||||
```ts
|
||||
// compile the prepared statement
|
||||
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
|
||||
```
|
||||
The caching refers to the **compiled prepared statement** (the SQL bytecode), not the query results. When you call `db.query()` with the same SQL string multiple times, Bun returns the same cached `Statement` object instead of recompiling the SQL.
|
||||
|
||||
It is completely safe to reuse a cached statement with different parameter values:
|
||||
|
||||
```ts
|
||||
const query = db.query("SELECT * FROM users WHERE id = ?");
|
||||
query.get(1); // ✓ Works
|
||||
query.get(2); // ✓ Also works - parameters are bound fresh each time
|
||||
query.get(3); // ✓ Still works
|
||||
```
|
||||
|
||||
Use `.prepare()` instead of `.query()` when you want a fresh `Statement` instance that isn't cached, for example if you're dynamically generating SQL and don't want to fill the cache with one-off queries.
|
||||
|
||||
```ts
|
||||
// compile the prepared statement without caching
|
||||
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -190,7 +203,7 @@ SQLite supports [write-ahead log mode](https://www.sqlite.org/wal.html) (WAL) wh
|
||||
To enable WAL mode, run this pragma query at the beginning of your application:
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg"
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
db.run("PRAGMA journal_mode = WAL;");
|
||||
```
|
||||
|
||||
<Accordion title="What is WAL mode?">
|
||||
@@ -290,7 +303,7 @@ Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sq
|
||||
|
||||
### `.run()`
|
||||
|
||||
Use `.run()` to run a query and get back `undefined`. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
|
||||
Use `.run()` to run a query and get back an object with execution metadata. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
|
||||
|
||||
```ts db.ts icon="/icons/typescript.svg" highlight={2}
|
||||
const query = db.query(`create table foo;`);
|
||||
@@ -650,8 +663,8 @@ class Database {
|
||||
},
|
||||
);
|
||||
|
||||
prepare<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
|
||||
query<ReturnType, Params>(sql: string): Statement<ReturnType, Params>;
|
||||
query<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
|
||||
prepare<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
|
||||
run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
|
||||
exec = this.run;
|
||||
|
||||
@@ -664,14 +677,14 @@ class Database {
|
||||
close(throwOnError?: boolean): void;
|
||||
}
|
||||
|
||||
class Statement<ReturnType, Params> {
|
||||
all(params: Params): ReturnType[];
|
||||
get(params: Params): ReturnType | undefined;
|
||||
run(params: Params): {
|
||||
class Statement<ReturnType, ParamsType> {
|
||||
all(...params: ParamsType[]): ReturnType[];
|
||||
get(...params: ParamsType[]): ReturnType | null;
|
||||
run(...params: ParamsType[]): {
|
||||
lastInsertRowid: number;
|
||||
changes: number;
|
||||
};
|
||||
values(params: Params): unknown[][];
|
||||
values(...params: ParamsType[]): unknown[][];
|
||||
|
||||
finalize(): void; // destroy statement and clean up resources
|
||||
toString(): string; // serialize to SQL
|
||||
@@ -682,7 +695,7 @@ class Statement<ReturnType, Params> {
|
||||
paramsCount: number; // the number of parameters expected by the statement
|
||||
native: any; // the native object representing the statement
|
||||
|
||||
as(Class: new () => ReturnType): this;
|
||||
as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
|
||||
}
|
||||
|
||||
type SQLQueryBindings =
|
||||
|
||||
49
docs/snippets/cli/bunx.mdx
Normal file
49
docs/snippets/cli/bunx.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
bunx [flags] <package>[@version] [flags and arguments for the package]
|
||||
```
|
||||
|
||||
Execute an npm package executable (CLI), automatically installing into a global shared cache if not installed in `node_modules`.
|
||||
|
||||
### Flags
|
||||
|
||||
<ParamField path="--bun" type="boolean">
|
||||
Force the command to run with Bun instead of Node.js, even if the executable contains a Node shebang (`#!/usr/bin/env
|
||||
node`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="-p, --package" type="string">
|
||||
Specify package to install when binary name differs from package name
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="--no-install" type="boolean">
|
||||
Skip installation if package is not already installed
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="--verbose" type="boolean">
|
||||
Enable verbose output during installation
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="--silent" type="boolean">
|
||||
Suppress output during installation
|
||||
</ParamField>
|
||||
|
||||
### Examples
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Run Prisma migrations
|
||||
bunx prisma migrate
|
||||
|
||||
# Format a file with Prettier
|
||||
bunx prettier foo.js
|
||||
|
||||
# Run a specific version of a package
|
||||
bunx uglify-js@3.14.0 app.js
|
||||
|
||||
# Use --package when binary name differs from package name
|
||||
bunx -p @angular/cli ng new my-app
|
||||
|
||||
# Force running with Bun instead of Node.js, even if the executable contains a Node shebang
|
||||
bunx --bun vite dev foo.js
|
||||
```
|
||||
@@ -376,16 +376,18 @@ timeout = 10000
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can also set environment variables in your configuration that affect test behavior:
|
||||
Environment variables for tests should be set using `.env` files. Bun automatically loads `.env` files from your project root. For test-specific variables, create a `.env.test` file:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[env]
|
||||
NODE_ENV = "test"
|
||||
DATABASE_URL = "postgresql://localhost:5432/test_db"
|
||||
LOG_LEVEL = "error"
|
||||
```ini title=".env.test" icon="settings"
|
||||
NODE_ENV=test
|
||||
DATABASE_URL=postgresql://localhost:5432/test_db
|
||||
LOG_LEVEL=error
|
||||
```
|
||||
|
||||
[test]
|
||||
coverage = true
|
||||
Then load it with `--env-file`:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun test --env-file=.env.test
|
||||
```
|
||||
|
||||
## Complete Configuration Example
|
||||
@@ -398,13 +400,6 @@ Here's a comprehensive example showing all available test configuration options:
|
||||
registry = "https://registry.npmjs.org/"
|
||||
exact = true
|
||||
|
||||
[env]
|
||||
# Environment variables for tests
|
||||
NODE_ENV = "test"
|
||||
DATABASE_URL = "postgresql://localhost:5432/test_db"
|
||||
API_URL = "http://localhost:3001"
|
||||
LOG_LEVEL = "error"
|
||||
|
||||
[test]
|
||||
# Test discovery
|
||||
root = "src"
|
||||
|
||||
@@ -428,26 +428,26 @@ test("foo, bar, baz", () => {
|
||||
const barSpy = spyOn(barModule, "bar");
|
||||
const bazSpy = spyOn(bazModule, "baz");
|
||||
|
||||
// Original values
|
||||
expect(fooSpy).toBe("foo");
|
||||
expect(barSpy).toBe("bar");
|
||||
expect(bazSpy).toBe("baz");
|
||||
// Original implementations still work
|
||||
expect(fooModule.foo()).toBe("foo");
|
||||
expect(barModule.bar()).toBe("bar");
|
||||
expect(bazModule.baz()).toBe("baz");
|
||||
|
||||
// Mock implementations
|
||||
fooSpy.mockImplementation(() => 42);
|
||||
barSpy.mockImplementation(() => 43);
|
||||
bazSpy.mockImplementation(() => 44);
|
||||
|
||||
expect(fooSpy()).toBe(42);
|
||||
expect(barSpy()).toBe(43);
|
||||
expect(bazSpy()).toBe(44);
|
||||
expect(fooModule.foo()).toBe(42);
|
||||
expect(barModule.bar()).toBe(43);
|
||||
expect(bazModule.baz()).toBe(44);
|
||||
|
||||
// Restore all
|
||||
mock.restore();
|
||||
|
||||
expect(fooSpy()).toBe("foo");
|
||||
expect(barSpy()).toBe("bar");
|
||||
expect(bazSpy()).toBe("baz");
|
||||
expect(fooModule.foo()).toBe("foo");
|
||||
expect(barModule.bar()).toBe("bar");
|
||||
expect(bazModule.baz()).toBe("baz");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -455,10 +455,10 @@ Using `mock.restore()` can reduce the amount of code in your tests by adding it
|
||||
|
||||
## Vitest Compatibility
|
||||
|
||||
For added compatibility with tests written for Vitest, Bun provides the `vi` global object as an alias for parts of the Jest mocking API:
|
||||
For added compatibility with tests written for Vitest, Bun provides the `vi` object as an alias for parts of the Jest mocking API:
|
||||
|
||||
```ts title="test.ts" icon="/icons/typescript.svg"
|
||||
import { test, expect } from "bun:test";
|
||||
import { test, expect, vi } from "bun:test";
|
||||
|
||||
// Using the 'vi' alias similar to Vitest
|
||||
test("vitest compatibility", () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.3.4",
|
||||
"version": "1.3.6",
|
||||
"workspaces": [
|
||||
"./packages/bun-types",
|
||||
"./packages/@types/bun"
|
||||
@@ -23,7 +23,8 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
"@types/bun": "workspace:packages/@types/bun"
|
||||
"@types/bun": "workspace:packages/@types/bun",
|
||||
"@types/node": "25.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bun --silent run build:debug",
|
||||
@@ -87,7 +88,7 @@
|
||||
"node:test:cp": "bun ./scripts/fetch-node-test.ts ",
|
||||
"clean:zig": "rm -rf build/debug/cache/zig build/debug/CMakeCache.txt 'build/debug/*.o' .zig-cache zig-out || true",
|
||||
"machine:linux:ubuntu": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=ubuntu --release=25.04",
|
||||
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=12",
|
||||
"machine:linux:debian": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=debian --release=13",
|
||||
"machine:linux:alpine": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=alpine --release=3.22",
|
||||
"machine:linux:amazonlinux": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=linux --distro=amazonlinux --release=2023",
|
||||
"machine:windows:2019": "./scripts/machine.mjs ssh --cloud=aws --arch=x64 --instance-type c7i.2xlarge --os=windows --release=2019",
|
||||
|
||||
@@ -615,7 +615,6 @@ const NativeStackFrame = ({
|
||||
<div
|
||||
title={StackFrameScope[scope]}
|
||||
className="BunError-StackFrame-identifier"
|
||||
// @ts-expect-error Custom CSS variables are not known by TypeScript
|
||||
style={{ "--max-length": `${maxLength}ch` }}
|
||||
>
|
||||
{getNativeStackFrameIdentifier(frame)}
|
||||
|
||||
246
packages/bun-types/bun.d.ts
vendored
246
packages/bun-types/bun.d.ts
vendored
@@ -1740,7 +1740,6 @@ declare module "bun" {
|
||||
* @default "esm"
|
||||
*/
|
||||
format?: /**
|
||||
|
||||
* ECMAScript Module format
|
||||
*/
|
||||
| "esm"
|
||||
@@ -1892,6 +1891,24 @@ declare module "bun" {
|
||||
*/
|
||||
drop?: string[];
|
||||
|
||||
/**
|
||||
* Enable feature flags for dead-code elimination via `import { feature } from "bun:bundle"`.
|
||||
*
|
||||
* When `feature("FLAG_NAME")` is called, it returns `true` if FLAG_NAME is in this array,
|
||||
* or `false` otherwise. This enables static dead-code elimination at bundle time.
|
||||
*
|
||||
* Equivalent to the CLI `--feature` flag.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./src/index.ts'],
|
||||
* features: ['FEATURE_A', 'FEATURE_B'],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
features?: string[];
|
||||
|
||||
/**
|
||||
* - When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
|
||||
* - When set to `false`, returns a {@link BuildOutput} with `{success: false}`
|
||||
@@ -1953,6 +1970,26 @@ declare module "bun" {
|
||||
* @default true
|
||||
*/
|
||||
autoloadBunfig?: boolean;
|
||||
/**
|
||||
* Whether to autoload tsconfig.json when the standalone executable runs
|
||||
*
|
||||
* Standalone-only: applies only when building/running the standalone executable.
|
||||
*
|
||||
* Equivalent CLI flags: `--compile-autoload-tsconfig`, `--no-compile-autoload-tsconfig`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
autoloadTsconfig?: boolean;
|
||||
/**
|
||||
* Whether to autoload package.json when the standalone executable runs
|
||||
*
|
||||
* Standalone-only: applies only when building/running the standalone executable.
|
||||
*
|
||||
* Equivalent CLI flags: `--compile-autoload-package-json`, `--no-compile-autoload-package-json`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
autoloadPackageJson?: boolean;
|
||||
windows?: {
|
||||
hideConsole?: boolean;
|
||||
icon?: string;
|
||||
@@ -3888,6 +3925,9 @@ declare module "bun" {
|
||||
static readonly byteLength: 32;
|
||||
}
|
||||
|
||||
/** Extends the standard web formats with `brotli` and `zstd` support. */
|
||||
type CompressionFormat = "gzip" | "deflate" | "deflate-raw" | "brotli" | "zstd";
|
||||
|
||||
/** Compression options for `Bun.deflateSync` and `Bun.gzipSync` */
|
||||
interface ZlibCompressionOptions {
|
||||
/**
|
||||
@@ -5677,6 +5717,44 @@ declare module "bun" {
|
||||
* ```
|
||||
*/
|
||||
lazy?: boolean;
|
||||
|
||||
/**
|
||||
* Spawn the subprocess with a pseudo-terminal (PTY) attached.
|
||||
*
|
||||
* When this option is provided:
|
||||
* - `stdin`, `stdout`, and `stderr` are all connected to the terminal
|
||||
* - The subprocess sees itself running in a real terminal (`isTTY = true`)
|
||||
* - Access the terminal via `subprocess.terminal`
|
||||
* - `subprocess.stdin`, `subprocess.stdout`, `subprocess.stderr` return `null`
|
||||
*
|
||||
* Only available on POSIX systems (Linux, macOS).
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const proc = Bun.spawn(["bash"], {
|
||||
* terminal: {
|
||||
* cols: 80,
|
||||
* rows: 24,
|
||||
* data: (term, data) => console.log(data.toString()),
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* proc.terminal.write("echo hello\n");
|
||||
* await proc.exited;
|
||||
* proc.terminal.close();
|
||||
* ```
|
||||
*
|
||||
* You can also pass an existing `Terminal` object for reuse across multiple spawns:
|
||||
* ```ts
|
||||
* const terminal = new Bun.Terminal({ ... });
|
||||
* const proc1 = Bun.spawn(["echo", "first"], { terminal });
|
||||
* await proc1.exited;
|
||||
* const proc2 = Bun.spawn(["echo", "second"], { terminal });
|
||||
* await proc2.exited;
|
||||
* terminal.close();
|
||||
* ```
|
||||
*/
|
||||
terminal?: TerminalOptions | Terminal;
|
||||
}
|
||||
|
||||
type ReadableToIO<X extends Readable> = X extends "pipe" | undefined
|
||||
@@ -5791,6 +5869,24 @@ declare module "bun" {
|
||||
readonly stdout: SpawnOptions.ReadableToIO<Out>;
|
||||
readonly stderr: SpawnOptions.ReadableToIO<Err>;
|
||||
|
||||
/**
|
||||
* The terminal attached to this subprocess, if spawned with the `terminal` option.
|
||||
* Returns `undefined` if no terminal was attached.
|
||||
*
|
||||
* When a terminal is attached, `stdin`, `stdout`, and `stderr` return `null`.
|
||||
* Use `terminal.write()` and the `data` callback instead.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const proc = Bun.spawn(["bash"], {
|
||||
* terminal: { data: (term, data) => console.log(data.toString()) },
|
||||
* });
|
||||
*
|
||||
* proc.terminal?.write("echo hello\n");
|
||||
* ```
|
||||
*/
|
||||
readonly terminal: Terminal | undefined;
|
||||
|
||||
/**
|
||||
* Access extra file descriptors passed to the `stdio` option in the options object.
|
||||
*/
|
||||
@@ -6082,6 +6178,154 @@ declare module "bun" {
|
||||
"ignore" | "inherit" | null | undefined
|
||||
>;
|
||||
|
||||
/**
|
||||
* Options for creating a pseudo-terminal (PTY).
|
||||
*/
|
||||
interface TerminalOptions {
|
||||
/**
|
||||
* Number of columns for the terminal.
|
||||
* @default 80
|
||||
*/
|
||||
cols?: number;
|
||||
/**
|
||||
* Number of rows for the terminal.
|
||||
* @default 24
|
||||
*/
|
||||
rows?: number;
|
||||
/**
|
||||
* Terminal name (e.g., "xterm-256color").
|
||||
* @default "xterm-256color"
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Callback invoked when data is received from the terminal.
|
||||
* @param terminal The terminal instance
|
||||
* @param data The data received as a Uint8Array
|
||||
*/
|
||||
data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
|
||||
/**
|
||||
* Callback invoked when the PTY stream closes (EOF or read error).
|
||||
* Note: exitCode is a PTY lifecycle status (0=clean EOF, 1=error), NOT the subprocess exit code.
|
||||
* Use Subprocess.exited or onExit callback for actual process exit information.
|
||||
* @param terminal The terminal instance
|
||||
* @param exitCode PTY lifecycle status (0 for EOF, 1 for error)
|
||||
* @param signal Reserved for future signal reporting, currently null
|
||||
*/
|
||||
exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
|
||||
/**
|
||||
* Callback invoked when the terminal is ready to receive more data.
|
||||
* @param terminal The terminal instance
|
||||
*/
|
||||
drain?: (terminal: Terminal) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pseudo-terminal (PTY) that can be used to spawn interactive terminal programs.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await using terminal = new Bun.Terminal({
|
||||
* cols: 80,
|
||||
* rows: 24,
|
||||
* data(term, data) {
|
||||
* console.log("Received:", new TextDecoder().decode(data));
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* // Spawn a shell connected to the PTY
|
||||
* const proc = Bun.spawn(["bash"], { terminal });
|
||||
*
|
||||
* // Write to the terminal
|
||||
* terminal.write("echo hello\n");
|
||||
*
|
||||
* // Wait for process to exit
|
||||
* await proc.exited;
|
||||
*
|
||||
* // Terminal is closed automatically by `await using`
|
||||
* ```
|
||||
*/
|
||||
class Terminal implements AsyncDisposable {
|
||||
constructor(options: TerminalOptions);
|
||||
|
||||
/**
|
||||
* Whether the terminal is closed.
|
||||
*/
|
||||
readonly closed: boolean;
|
||||
|
||||
/**
|
||||
* Write data to the terminal.
|
||||
* @param data The data to write (string or BufferSource)
|
||||
* @returns The number of bytes written
|
||||
*/
|
||||
write(data: string | BufferSource): number;
|
||||
|
||||
/**
|
||||
* Resize the terminal.
|
||||
* @param cols New number of columns
|
||||
* @param rows New number of rows
|
||||
*/
|
||||
resize(cols: number, rows: number): void;
|
||||
|
||||
/**
|
||||
* Set raw mode on the terminal.
|
||||
* In raw mode, input is passed directly without processing.
|
||||
* @param enabled Whether to enable raw mode
|
||||
*/
|
||||
setRawMode(enabled: boolean): void;
|
||||
|
||||
/**
|
||||
* Reference the terminal to keep the event loop alive.
|
||||
*/
|
||||
ref(): void;
|
||||
|
||||
/**
|
||||
* Unreference the terminal to allow the event loop to exit.
|
||||
*/
|
||||
unref(): void;
|
||||
|
||||
/**
|
||||
* Close the terminal.
|
||||
*/
|
||||
close(): void;
|
||||
|
||||
/**
|
||||
* Async dispose for use with `await using`.
|
||||
*/
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
|
||||
/**
|
||||
* Terminal input flags (c_iflag from termios).
|
||||
* Controls input processing behavior like ICRNL, IXON, etc.
|
||||
* Returns 0 if terminal is closed.
|
||||
* Setting returns true on success, false on failure.
|
||||
*/
|
||||
inputFlags: number;
|
||||
|
||||
/**
|
||||
* Terminal output flags (c_oflag from termios).
|
||||
* Controls output processing behavior like OPOST, ONLCR, etc.
|
||||
* Returns 0 if terminal is closed.
|
||||
* Setting returns true on success, false on failure.
|
||||
*/
|
||||
outputFlags: number;
|
||||
|
||||
/**
|
||||
* Terminal local flags (c_lflag from termios).
|
||||
* Controls local processing like ICANON, ECHO, ISIG, etc.
|
||||
* Returns 0 if terminal is closed.
|
||||
* Setting returns true on success, false on failure.
|
||||
*/
|
||||
localFlags: number;
|
||||
|
||||
/**
|
||||
* Terminal control flags (c_cflag from termios).
|
||||
* Controls hardware characteristics like CSIZE, PARENB, etc.
|
||||
* Returns 0 if terminal is closed.
|
||||
* Setting returns true on success, false on failure.
|
||||
*/
|
||||
controlFlags: number;
|
||||
}
|
||||
|
||||
// Blocked on https://github.com/oven-sh/bun/issues/8329
|
||||
// /**
|
||||
// *
|
||||
|
||||
74
packages/bun-types/bundle.d.ts
vendored
Normal file
74
packages/bun-types/bundle.d.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* The `bun:bundle` module provides compile-time utilities for dead-code elimination.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { feature } from "bun:bundle";
|
||||
*
|
||||
* if (feature("SUPER_SECRET")) {
|
||||
* console.log("Secret feature enabled!");
|
||||
* } else {
|
||||
* console.log("Normal mode");
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Enable feature flags via CLI:
|
||||
* ```bash
|
||||
* # During build
|
||||
* bun build --feature=SUPER_SECRET index.ts
|
||||
*
|
||||
* # At runtime
|
||||
* bun run --feature=SUPER_SECRET index.ts
|
||||
*
|
||||
* # In tests
|
||||
* bun test --feature=SUPER_SECRET
|
||||
* ```
|
||||
*
|
||||
* @module bun:bundle
|
||||
*/
|
||||
declare module "bun:bundle" {
|
||||
/**
|
||||
* Registry for type-safe feature flags.
|
||||
*
|
||||
* Augment this interface to get autocomplete and type checking for your feature flags:
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // env.d.ts
|
||||
* declare module "bun:bundle" {
|
||||
* interface Registry {
|
||||
* features: "DEBUG" | "PREMIUM" | "BETA";
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Now `feature()` only accepts `"DEBUG"`, `"PREMIUM"`, or `"BETA"`:
|
||||
* ```ts
|
||||
* feature("DEBUG"); // OK
|
||||
* feature("TYPO"); // Type error
|
||||
* ```
|
||||
*/
|
||||
interface Registry {}
|
||||
|
||||
/**
|
||||
* Check if a feature flag is enabled at compile time.
|
||||
*
|
||||
* This function is replaced with a boolean literal (`true` or `false`) at bundle time,
|
||||
* enabling dead-code elimination. The bundler will remove unreachable branches.
|
||||
*
|
||||
* @param flag - The name of the feature flag to check
|
||||
* @returns `true` if the flag was passed via `--feature=FLAG`, `false` otherwise
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { feature } from "bun:bundle";
|
||||
*
|
||||
* // With --feature=DEBUG, this becomes: if (true) { ... }
|
||||
* // Without --feature=DEBUG, this becomes: if (false) { ... }
|
||||
* if (feature("DEBUG")) {
|
||||
* console.log("Debug mode enabled");
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function feature(flag: Registry extends { features: infer Features extends string } ? Features : string): boolean;
|
||||
}
|
||||
91
packages/bun-types/globals.d.ts
vendored
91
packages/bun-types/globals.d.ts
vendored
@@ -1,9 +1,12 @@
|
||||
declare module "bun" {
|
||||
namespace __internal {
|
||||
type NodeCryptoWebcryptoSubtleCrypto = import("crypto").webcrypto.SubtleCrypto;
|
||||
type NodeCryptoWebcryptoCryptoKey = import("crypto").webcrypto.CryptoKey;
|
||||
type NodeCryptoWebcryptoCryptoKeyPair = import("crypto").webcrypto.CryptoKeyPair;
|
||||
|
||||
type LibEmptyOrNodeCryptoWebcryptoSubtleCrypto = LibDomIsLoaded extends true
|
||||
? {}
|
||||
: import("crypto").webcrypto.SubtleCrypto;
|
||||
|
||||
type LibWorkerOrBunWorker = LibDomIsLoaded extends true ? {} : Bun.Worker;
|
||||
type LibEmptyOrBunWebSocket = LibDomIsLoaded extends true ? {} : Bun.WebSocket;
|
||||
|
||||
@@ -14,7 +17,9 @@ declare module "bun" {
|
||||
? {}
|
||||
: import("node:stream/web").DecompressionStream;
|
||||
|
||||
type LibPerformanceOrNodePerfHooksPerformance = LibDomIsLoaded extends true ? {} : import("perf_hooks").Performance;
|
||||
type LibPerformanceOrNodePerfHooksPerformance = LibDomIsLoaded extends true
|
||||
? {}
|
||||
: import("node:perf_hooks").Performance;
|
||||
type LibEmptyOrPerformanceEntry = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceEntry;
|
||||
type LibEmptyOrPerformanceMark = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceMark;
|
||||
type LibEmptyOrPerformanceMeasure = LibDomIsLoaded extends true ? {} : import("node:perf_hooks").PerformanceMeasure;
|
||||
@@ -83,6 +88,24 @@ declare var WritableStream: Bun.__internal.UseLibDomIfAvailable<
|
||||
}
|
||||
>;
|
||||
|
||||
interface CompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
|
||||
declare var CompressionStream: Bun.__internal.UseLibDomIfAvailable<
|
||||
"CompressionStream",
|
||||
{
|
||||
prototype: CompressionStream;
|
||||
new (format: Bun.CompressionFormat): CompressionStream;
|
||||
}
|
||||
>;
|
||||
|
||||
interface DecompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebDecompressionStream {}
|
||||
declare var DecompressionStream: Bun.__internal.UseLibDomIfAvailable<
|
||||
"DecompressionStream",
|
||||
{
|
||||
prototype: DecompressionStream;
|
||||
new (format: Bun.CompressionFormat): DecompressionStream;
|
||||
}
|
||||
>;
|
||||
|
||||
interface Worker extends Bun.__internal.LibWorkerOrBunWorker {}
|
||||
declare var Worker: Bun.__internal.UseLibDomIfAvailable<
|
||||
"Worker",
|
||||
@@ -206,7 +229,7 @@ interface TextEncoder extends Bun.__internal.LibEmptyOrNodeUtilTextEncoder {
|
||||
* @param src The text to encode.
|
||||
* @param dest The array to hold the encode result.
|
||||
*/
|
||||
encodeInto(src?: string, dest?: Bun.BufferSource): import("util").EncodeIntoResult;
|
||||
encodeInto(src?: string, dest?: Bun.BufferSource): import("node:util").TextEncoderEncodeIntoResult;
|
||||
}
|
||||
declare var TextEncoder: Bun.__internal.UseLibDomIfAvailable<
|
||||
"TextEncoder",
|
||||
@@ -278,30 +301,6 @@ declare var Event: {
|
||||
new (type: string, eventInitDict?: Bun.EventInit): Event;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unimplemented in Bun
|
||||
*/
|
||||
interface CompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
|
||||
/**
|
||||
* Unimplemented in Bun
|
||||
*/
|
||||
declare var CompressionStream: Bun.__internal.UseLibDomIfAvailable<
|
||||
"CompressionStream",
|
||||
typeof import("node:stream/web").CompressionStream
|
||||
>;
|
||||
|
||||
/**
|
||||
* Unimplemented in Bun
|
||||
*/
|
||||
interface DecompressionStream extends Bun.__internal.LibEmptyOrNodeStreamWebCompressionStream {}
|
||||
/**
|
||||
* Unimplemented in Bun
|
||||
*/
|
||||
declare var DecompressionStream: Bun.__internal.UseLibDomIfAvailable<
|
||||
"DecompressionStream",
|
||||
typeof import("node:stream/web").DecompressionStream
|
||||
>;
|
||||
|
||||
interface EventTarget {
|
||||
/**
|
||||
* Adds a new handler for the `type` event. Any given `listener` is added only once per `type` and per `capture` option value.
|
||||
@@ -958,7 +957,7 @@ declare function alert(message?: string): void;
|
||||
declare function confirm(message?: string): boolean;
|
||||
declare function prompt(message?: string, _default?: string): string | null;
|
||||
|
||||
interface SubtleCrypto extends Bun.__internal.NodeCryptoWebcryptoSubtleCrypto {}
|
||||
interface SubtleCrypto extends Bun.__internal.LibEmptyOrNodeCryptoWebcryptoSubtleCrypto {}
|
||||
declare var SubtleCrypto: {
|
||||
prototype: SubtleCrypto;
|
||||
new (): SubtleCrypto;
|
||||
@@ -1694,6 +1693,10 @@ declare var EventSource: Bun.__internal.UseLibDomIfAvailable<
|
||||
|
||||
interface Performance extends Bun.__internal.LibPerformanceOrNodePerfHooksPerformance {}
|
||||
declare var performance: Bun.__internal.UseLibDomIfAvailable<"performance", Performance>;
|
||||
declare var Performance: Bun.__internal.UseLibDomIfAvailable<
|
||||
"Performance",
|
||||
{ new (): Performance; prototype: Performance }
|
||||
>;
|
||||
|
||||
interface PerformanceEntry extends Bun.__internal.LibEmptyOrPerformanceEntry {}
|
||||
declare var PerformanceEntry: Bun.__internal.UseLibDomIfAvailable<
|
||||
@@ -1920,14 +1923,44 @@ interface BunFetchRequestInit extends RequestInit {
|
||||
* Override http_proxy or HTTPS_PROXY
|
||||
* This is a custom property that is not part of the Fetch API specification.
|
||||
*
|
||||
* Can be a string URL or an object with `url` and optional `headers`.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* // String format
|
||||
* const response = await fetch("http://example.com", {
|
||||
* proxy: "https://username:password@127.0.0.1:8080"
|
||||
* });
|
||||
*
|
||||
* // Object format with custom headers sent to the proxy
|
||||
* const response = await fetch("http://example.com", {
|
||||
* proxy: {
|
||||
* url: "https://127.0.0.1:8080",
|
||||
* headers: {
|
||||
* "Proxy-Authorization": "Bearer token",
|
||||
* "X-Custom-Proxy-Header": "value"
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If a `Proxy-Authorization` header is provided in `proxy.headers`, it takes
|
||||
* precedence over credentials parsed from the proxy URL.
|
||||
*/
|
||||
proxy?: string;
|
||||
proxy?:
|
||||
| string
|
||||
| {
|
||||
/**
|
||||
* The proxy URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Custom headers to send to the proxy server.
|
||||
* These headers are sent in the CONNECT request (for HTTPS targets)
|
||||
* or in the proxy request (for HTTP targets).
|
||||
*/
|
||||
headers?: Bun.HeadersInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the default S3 options
|
||||
|
||||
1
packages/bun-types/index.d.ts
vendored
1
packages/bun-types/index.d.ts
vendored
@@ -23,6 +23,7 @@
|
||||
/// <reference path="./serve.d.ts" />
|
||||
/// <reference path="./sql.d.ts" />
|
||||
/// <reference path="./security.d.ts" />
|
||||
/// <reference path="./bundle.d.ts" />
|
||||
|
||||
/// <reference path="./bun.ns.d.ts" />
|
||||
|
||||
|
||||
21
packages/bun-types/overrides.d.ts
vendored
21
packages/bun-types/overrides.d.ts
vendored
@@ -86,7 +86,7 @@ declare global {
|
||||
reallyExit(code?: number): never;
|
||||
dlopen(module: { exports: any }, filename: string, flags?: number): void;
|
||||
_exiting: boolean;
|
||||
noDeprecation: boolean;
|
||||
noDeprecation?: boolean | undefined;
|
||||
|
||||
binding(m: "constants"): {
|
||||
os: typeof import("node:os").constants;
|
||||
@@ -308,11 +308,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
declare module "fs/promises" {
|
||||
declare module "node:fs/promises" {
|
||||
function exists(path: Bun.PathLike): Promise<boolean>;
|
||||
}
|
||||
|
||||
declare module "tls" {
|
||||
declare module "node:tls" {
|
||||
interface BunConnectionOptions extends Omit<ConnectionOptions, "key" | "ca" | "tls" | "cert"> {
|
||||
/**
|
||||
* Optionally override the trusted CA certificates. Default is to trust
|
||||
@@ -359,3 +359,18 @@ declare module "tls" {
|
||||
|
||||
function connect(options: BunConnectionOptions, secureConnectListener?: () => void): TLSSocket;
|
||||
}
|
||||
|
||||
declare module "console" {
|
||||
interface Console {
|
||||
/**
|
||||
* Asynchronously read lines from standard input (fd 0)
|
||||
*
|
||||
* ```ts
|
||||
* for await (const line of console) {
|
||||
* console.log(line);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<string>;
|
||||
}
|
||||
}
|
||||
|
||||
18
packages/bun-types/s3.d.ts
vendored
18
packages/bun-types/s3.d.ts
vendored
@@ -281,6 +281,24 @@ declare module "bun" {
|
||||
*/
|
||||
type?: string;
|
||||
|
||||
/**
|
||||
* The Content-Disposition header value.
|
||||
* Controls how the file is presented when downloaded.
|
||||
*
|
||||
* @example
|
||||
* // Setting attachment disposition with filename
|
||||
* const file = s3.file("report.pdf", {
|
||||
* contentDisposition: "attachment; filename=\"quarterly-report.pdf\""
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Setting inline disposition
|
||||
* await s3.write("image.png", imageData, {
|
||||
* contentDisposition: "inline"
|
||||
* });
|
||||
*/
|
||||
contentDisposition?: string | undefined;
|
||||
|
||||
/**
|
||||
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
|
||||
*
|
||||
|
||||
13
packages/bun-types/serve.d.ts
vendored
13
packages/bun-types/serve.d.ts
vendored
@@ -446,7 +446,7 @@ declare module "bun" {
|
||||
closeOnBackpressureLimit?: boolean;
|
||||
|
||||
/**
|
||||
* Sets the the number of seconds to wait before timing out a connection
|
||||
* Sets the number of seconds to wait before timing out a connection
|
||||
* due to no messages or pings.
|
||||
*
|
||||
* @default 120
|
||||
@@ -758,7 +758,7 @@ declare module "bun" {
|
||||
ipv6Only?: boolean;
|
||||
|
||||
/**
|
||||
* Sets the the number of seconds to wait before timing out a connection
|
||||
* Sets the number of seconds to wait before timing out a connection
|
||||
* due to inactivity.
|
||||
*
|
||||
* @default 10
|
||||
@@ -1082,6 +1082,15 @@ declare module "bun" {
|
||||
*/
|
||||
readonly hostname: string | undefined;
|
||||
|
||||
/**
|
||||
* The protocol the server is listening on.
|
||||
*
|
||||
* - "http" for normal servers
|
||||
* - "https" when TLS is enabled
|
||||
* - null for unix sockets or when unavailable
|
||||
*/
|
||||
readonly protocol: "http" | "https" | null;
|
||||
|
||||
/**
|
||||
* Is the server running in development mode?
|
||||
*
|
||||
|
||||
31
packages/bun-types/sqlite.d.ts
vendored
31
packages/bun-types/sqlite.d.ts
vendored
@@ -154,12 +154,6 @@ declare module "bun:sqlite" {
|
||||
* | `bigint` | `INTEGER` |
|
||||
* | `null` | `NULL` |
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* db.run("CREATE TABLE foo (bar TEXT)");
|
||||
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
|
||||
* ```
|
||||
*
|
||||
* Useful for queries like:
|
||||
* - `CREATE TABLE`
|
||||
* - `INSERT INTO`
|
||||
@@ -180,8 +174,14 @@ declare module "bun:sqlite" {
|
||||
*
|
||||
* @param sql The SQL query to run
|
||||
* @param bindings Optional bindings for the query
|
||||
* @returns A `Changes` object with `changes` and `lastInsertRowid` properties
|
||||
*
|
||||
* @returns `Database` instance
|
||||
* @example
|
||||
* ```ts
|
||||
* db.run("CREATE TABLE foo (bar TEXT)");
|
||||
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
|
||||
* // => { changes: 1, lastInsertRowid: 1 }
|
||||
* ```
|
||||
*/
|
||||
run<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
|
||||
|
||||
@@ -670,18 +670,19 @@ declare module "bun:sqlite" {
|
||||
* Execute the prepared statement.
|
||||
*
|
||||
* @param params optional values to bind to the statement. If omitted, the statement is run with the last bound values or no parameters if there are none.
|
||||
* @returns A `Changes` object with `changes` and `lastInsertRowid` properties
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const stmt = db.prepare("UPDATE foo SET bar = ?");
|
||||
* stmt.run("baz");
|
||||
* // => undefined
|
||||
* const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
|
||||
* insert.run("Alice");
|
||||
* // => { changes: 1, lastInsertRowid: 1 }
|
||||
* insert.run("Bob");
|
||||
* // => { changes: 1, lastInsertRowid: 2 }
|
||||
*
|
||||
* stmt.run();
|
||||
* // => undefined
|
||||
*
|
||||
* stmt.run("foo");
|
||||
* // => undefined
|
||||
* const update = db.prepare("UPDATE users SET name = ? WHERE id = ?");
|
||||
* update.run("Charlie", 1);
|
||||
* // => { changes: 1, lastInsertRowid: 2 }
|
||||
* ```
|
||||
*
|
||||
* The following types can be used when binding parameters:
|
||||
|
||||
36
packages/bun-types/test.d.ts
vendored
36
packages/bun-types/test.d.ts
vendored
@@ -95,8 +95,15 @@ declare module "bun:test" {
|
||||
function fn<T extends (...args: any[]) => any>(func?: T): Mock<T>;
|
||||
function setSystemTime(now?: number | Date): void;
|
||||
function setTimeout(milliseconds: number): void;
|
||||
function useFakeTimers(): void;
|
||||
function useRealTimers(): void;
|
||||
function useFakeTimers(options?: { now?: number | Date }): typeof vi;
|
||||
function useRealTimers(): typeof vi;
|
||||
function advanceTimersByTime(milliseconds: number): typeof vi;
|
||||
function advanceTimersToNextTimer(): typeof vi;
|
||||
function runAllTimers(): typeof vi;
|
||||
function runOnlyPendingTimers(): typeof vi;
|
||||
function getTimerCount(): number;
|
||||
function clearAllTimers(): void;
|
||||
function isFakeTimers(): boolean;
|
||||
function spyOn<T extends object, K extends keyof T>(
|
||||
obj: T,
|
||||
methodOrPropertyValue: K,
|
||||
@@ -184,6 +191,13 @@ declare module "bun:test" {
|
||||
resetAllMocks: typeof jest.resetAllMocks;
|
||||
useFakeTimers: typeof jest.useFakeTimers;
|
||||
useRealTimers: typeof jest.useRealTimers;
|
||||
advanceTimersByTime: typeof jest.advanceTimersByTime;
|
||||
advanceTimersToNextTimer: typeof jest.advanceTimersToNextTimer;
|
||||
runAllTimers: typeof jest.runAllTimers;
|
||||
runOnlyPendingTimers: typeof jest.runOnlyPendingTimers;
|
||||
getTimerCount: typeof jest.getTimerCount;
|
||||
clearAllTimers: typeof jest.clearAllTimers;
|
||||
isFakeTimers: typeof jest.isFakeTimers;
|
||||
};
|
||||
|
||||
interface FunctionLike {
|
||||
@@ -414,6 +428,8 @@ declare module "bun:test" {
|
||||
}
|
||||
|
||||
namespace __internal {
|
||||
type IfNeverThenElse<T, Else> = [T] extends [never] ? Else : T;
|
||||
|
||||
type IsTuple<T> = T extends readonly unknown[]
|
||||
? number extends T["length"]
|
||||
? false // It's an array with unknown length, not a tuple
|
||||
@@ -1083,8 +1099,8 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param expected the expected value
|
||||
*/
|
||||
toContainKey(expected: keyof T): void;
|
||||
toContainKey<X = T>(expected: NoInfer<keyof X>): void;
|
||||
toContainKey(expected: __internal.IfNeverThenElse<keyof T, PropertyKey>): void;
|
||||
toContainKey<X = T>(expected: __internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>): void;
|
||||
|
||||
/**
|
||||
* Asserts that an `object` contains all the provided keys.
|
||||
@@ -1100,8 +1116,8 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param expected the expected value
|
||||
*/
|
||||
toContainAllKeys(expected: Array<keyof T>): void;
|
||||
toContainAllKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
|
||||
toContainAllKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
|
||||
toContainAllKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
|
||||
|
||||
/**
|
||||
* Asserts that an `object` contains at least one of the provided keys.
|
||||
@@ -1117,8 +1133,8 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param expected the expected value
|
||||
*/
|
||||
toContainAnyKeys(expected: Array<keyof T>): void;
|
||||
toContainAnyKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
|
||||
toContainAnyKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
|
||||
toContainAnyKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
|
||||
|
||||
/**
|
||||
* Asserts that an `object` contain the provided value.
|
||||
@@ -1210,8 +1226,8 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param expected the expected value
|
||||
*/
|
||||
toContainKeys(expected: Array<keyof T>): void;
|
||||
toContainKeys<X = T>(expected: NoInfer<Array<keyof X>>): void;
|
||||
toContainKeys(expected: Array<__internal.IfNeverThenElse<keyof T, PropertyKey>>): void;
|
||||
toContainKeys<X = T>(expected: Array<__internal.IfNeverThenElse<NoInfer<keyof X>, PropertyKey>>): void;
|
||||
|
||||
/**
|
||||
* Asserts that a value contains and equals what is expected.
|
||||
|
||||
4
packages/bun-types/wasm.d.ts
vendored
4
packages/bun-types/wasm.d.ts
vendored
@@ -100,8 +100,8 @@ declare module "bun" {
|
||||
|
||||
declare namespace WebAssembly {
|
||||
interface ValueTypeMap extends Bun.WebAssembly.ValueTypeMap {}
|
||||
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap>
|
||||
extends Bun.WebAssembly.GlobalDescriptor<T> {}
|
||||
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends Bun.WebAssembly
|
||||
.GlobalDescriptor<T> {}
|
||||
interface MemoryDescriptor extends Bun.WebAssembly.MemoryDescriptor {}
|
||||
interface ModuleExportDescriptor extends Bun.WebAssembly.ModuleExportDescriptor {}
|
||||
interface ModuleImportDescriptor extends Bun.WebAssembly.ModuleImportDescriptor {}
|
||||
|
||||
@@ -54,8 +54,8 @@ void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
|
||||
s->next = loop->data.closed_head;
|
||||
loop->data.closed_head = s;
|
||||
|
||||
/* Any socket with prev = context is marked as closed */
|
||||
s->prev = (struct us_socket_t *) context;
|
||||
/* Mark the socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
}
|
||||
|
||||
/* We cannot immediately free a listen socket as we can be inside an accept loop */
|
||||
@@ -154,7 +154,9 @@ void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_sock
|
||||
|
||||
/* We always add in the top, so we don't modify any s.next */
|
||||
void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
|
||||
|
||||
struct us_socket_t* s = &ls->s;
|
||||
if(us_socket_is_closed(ssl, s)) return;
|
||||
s->context = context;
|
||||
s->next = (struct us_socket_t *) context->head_listen_sockets;
|
||||
s->prev = 0;
|
||||
@@ -166,6 +168,8 @@ void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_con
|
||||
}
|
||||
|
||||
void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket_context_t *context, struct us_connecting_socket_t *c) {
|
||||
if(c->closed) return;
|
||||
|
||||
c->context = context;
|
||||
c->next_pending = context->head_connecting_sockets;
|
||||
c->prev_pending = 0;
|
||||
@@ -180,6 +184,8 @@ void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket
|
||||
|
||||
/* We always add in the top, so we don't modify any s.next */
|
||||
void us_internal_socket_context_link_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s) {
|
||||
if(us_socket_is_closed(ssl,s)) return;
|
||||
|
||||
s->context = context;
|
||||
s->next = context->head_sockets;
|
||||
s->prev = 0;
|
||||
@@ -386,6 +392,9 @@ struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_co
|
||||
s->flags.low_prio_state = 0;
|
||||
s->flags.is_paused = 0;
|
||||
s->flags.is_ipc = 0;
|
||||
s->flags.is_closed = 0;
|
||||
s->flags.adopted = 0;
|
||||
s->flags.is_tls = ssl;
|
||||
s->next = 0;
|
||||
s->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
|
||||
us_internal_socket_context_link_listen_socket(ssl, context, ls);
|
||||
@@ -422,6 +431,9 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock
|
||||
s->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
|
||||
s->flags.is_paused = 0;
|
||||
s->flags.is_ipc = 0;
|
||||
s->flags.is_closed = 0;
|
||||
s->flags.adopted = 0;
|
||||
s->flags.is_tls = ssl;
|
||||
s->next = 0;
|
||||
us_internal_socket_context_link_listen_socket(ssl, context, ls);
|
||||
|
||||
@@ -430,7 +442,7 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock
|
||||
return ls;
|
||||
}
|
||||
|
||||
struct us_socket_t* us_socket_context_connect_resolved_dns(struct us_socket_context_t *context, struct sockaddr_storage* addr, int options, int socket_ext_size) {
|
||||
struct us_socket_t* us_socket_context_connect_resolved_dns(int ssl, struct us_socket_context_t *context, struct sockaddr_storage* addr, int options, int socket_ext_size) {
|
||||
LIBUS_SOCKET_DESCRIPTOR connect_socket_fd = bsd_create_connect_socket(addr, options);
|
||||
if (connect_socket_fd == LIBUS_SOCKET_ERROR) {
|
||||
return NULL;
|
||||
@@ -453,6 +465,9 @@ struct us_socket_t* us_socket_context_connect_resolved_dns(struct us_socket_cont
|
||||
socket->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
|
||||
socket->flags.is_paused = 0;
|
||||
socket->flags.is_ipc = 0;
|
||||
socket->flags.is_closed = 0;
|
||||
socket->flags.adopted = 0;
|
||||
socket->flags.is_tls = ssl;
|
||||
socket->connect_state = NULL;
|
||||
socket->connect_next = NULL;
|
||||
|
||||
@@ -514,7 +529,7 @@ void *us_socket_context_connect(int ssl, struct us_socket_context_t *context, co
|
||||
struct sockaddr_storage addr;
|
||||
if (try_parse_ip(host, port, &addr)) {
|
||||
*has_dns_resolved = 1;
|
||||
return us_socket_context_connect_resolved_dns(context, &addr, options, socket_ext_size);
|
||||
return us_socket_context_connect_resolved_dns(ssl, context, &addr, options, socket_ext_size);
|
||||
}
|
||||
|
||||
struct addrinfo_request* ai_req;
|
||||
@@ -534,7 +549,7 @@ void *us_socket_context_connect(int ssl, struct us_socket_context_t *context, co
|
||||
struct sockaddr_storage addr;
|
||||
init_addr_with_port(&entries->info, port, &addr);
|
||||
*has_dns_resolved = 1;
|
||||
struct us_socket_t *s = us_socket_context_connect_resolved_dns(context, &addr, options, socket_ext_size);
|
||||
struct us_socket_t *s = us_socket_context_connect_resolved_dns(ssl, context, &addr, options, socket_ext_size);
|
||||
Bun__addrinfo_freeRequest(ai_req, s == NULL);
|
||||
return s;
|
||||
}
|
||||
@@ -583,6 +598,9 @@ int start_connections(struct us_connecting_socket_t *c, int count) {
|
||||
flags->allow_half_open = (c->options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
|
||||
flags->is_paused = 0;
|
||||
flags->is_ipc = 0;
|
||||
flags->is_closed = 0;
|
||||
flags->adopted = 0;
|
||||
flags->is_tls = c->ssl;
|
||||
/* Link it into context so that timeout fires properly */
|
||||
us_internal_socket_context_link_socket(0, context, s);
|
||||
|
||||
@@ -760,6 +778,9 @@ struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_con
|
||||
connect_socket->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
|
||||
connect_socket->flags.is_paused = 0;
|
||||
connect_socket->flags.is_ipc = 0;
|
||||
connect_socket->flags.is_closed = 0;
|
||||
connect_socket->flags.adopted = 0;
|
||||
connect_socket->flags.is_tls = ssl;
|
||||
connect_socket->connect_state = NULL;
|
||||
connect_socket->connect_next = NULL;
|
||||
us_internal_socket_context_link_socket(ssl, context, connect_socket);
|
||||
@@ -780,10 +801,10 @@ struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_so
|
||||
}
|
||||
|
||||
/* Note: This will set timeout to 0 */
|
||||
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
|
||||
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int old_ext_size, int ext_size) {
|
||||
#ifndef LIBUS_NO_SSL
|
||||
if (ssl) {
|
||||
return (struct us_socket_t *) us_internal_ssl_socket_context_adopt_socket((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t *) s, ext_size);
|
||||
return (struct us_socket_t *) us_internal_ssl_socket_context_adopt_socket((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t *) s, old_ext_size, ext_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -807,7 +828,18 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
|
||||
struct us_socket_t *new_s = s;
|
||||
if (ext_size != -1) {
|
||||
struct us_poll_t *pool_ref = &s->p;
|
||||
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + ext_size);
|
||||
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + old_ext_size, sizeof(struct us_socket_t) + ext_size);
|
||||
if(new_s != s) {
|
||||
/* Mark the old socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
/* Link this socket to the close-list and let it be deleted after this iteration */
|
||||
s->next = s->context->loop->data.closed_head;
|
||||
s->context->loop->data.closed_head = s;
|
||||
/* Mark the old socket as adopted (reallocated) */
|
||||
s->flags.adopted = 1;
|
||||
/* Tell the event loop what is the new socket so we can process to send info to the right place and callbacks like more data and EOF*/
|
||||
s->prev = new_s;
|
||||
}
|
||||
if (c) {
|
||||
c->connecting_head = new_s;
|
||||
c->context = context;
|
||||
|
||||
@@ -396,7 +396,7 @@ void us_internal_update_handshake(struct us_internal_ssl_socket_t *s) {
|
||||
}
|
||||
|
||||
int result = SSL_do_handshake(s->ssl);
|
||||
|
||||
|
||||
if (SSL_get_shutdown(s->ssl) & SSL_RECEIVED_SHUTDOWN) {
|
||||
us_internal_ssl_socket_close(s, 0, NULL);
|
||||
return;
|
||||
@@ -417,7 +417,7 @@ void us_internal_update_handshake(struct us_internal_ssl_socket_t *s) {
|
||||
}
|
||||
s->handshake_state = HANDSHAKE_PENDING;
|
||||
s->ssl_write_wants_read = 1;
|
||||
|
||||
s->s.flags.last_write_failed = 1;
|
||||
return;
|
||||
}
|
||||
// success
|
||||
@@ -434,6 +434,7 @@ ssl_on_close(struct us_internal_ssl_socket_t *s, int code, void *reason) {
|
||||
struct us_internal_ssl_socket_t * ret = context->on_close(s, code, reason);
|
||||
SSL_free(s->ssl); // free SSL after on_close
|
||||
s->ssl = NULL; // set to NULL
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1855,15 +1856,16 @@ void us_internal_ssl_socket_shutdown(struct us_internal_ssl_socket_t *s) {
|
||||
|
||||
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_adopt_socket(
|
||||
struct us_internal_ssl_socket_context_t *context,
|
||||
struct us_internal_ssl_socket_t *s, int ext_size) {
|
||||
struct us_internal_ssl_socket_t *s, int old_ext_size, int ext_size) {
|
||||
// todo: this is completely untested
|
||||
int new_old_ext_size = sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + old_ext_size;
|
||||
int new_ext_size = ext_size;
|
||||
if (ext_size != -1) {
|
||||
new_ext_size = sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + ext_size;
|
||||
}
|
||||
return (struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
|
||||
0, &context->sc, &s->s,
|
||||
new_ext_size);
|
||||
new_old_ext_size, new_ext_size);
|
||||
}
|
||||
|
||||
struct us_internal_ssl_socket_t *
|
||||
@@ -1920,10 +1922,11 @@ ssl_wrapped_context_on_data(struct us_internal_ssl_socket_t *s, char *data,
|
||||
struct us_wrapped_socket_context_t *wrapped_context =
|
||||
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
|
||||
context);
|
||||
// raw data if needed
|
||||
// raw data if needed
|
||||
if (wrapped_context->old_events.on_data) {
|
||||
wrapped_context->old_events.on_data((struct us_socket_t *)s, data, length);
|
||||
}
|
||||
|
||||
// ssl wrapped data
|
||||
return ssl_on_data(s, data, length);
|
||||
}
|
||||
@@ -2028,7 +2031,7 @@ us_internal_ssl_socket_open(struct us_internal_ssl_socket_t *s, int is_client,
|
||||
// already opened
|
||||
if (s->ssl)
|
||||
return s;
|
||||
|
||||
|
||||
// start SSL open
|
||||
return ssl_on_open(s, is_client, ip, ip_length, NULL);
|
||||
}
|
||||
@@ -2040,6 +2043,7 @@ struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r
|
||||
struct us_internal_ssl_socket_t *socket =
|
||||
(struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
|
||||
0, new_context, s,
|
||||
sizeof(void*),
|
||||
(sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t)) + sizeof(void*));
|
||||
socket->ssl = NULL;
|
||||
socket->ssl_write_wants_read = 0;
|
||||
@@ -2058,7 +2062,7 @@ struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r
|
||||
|
||||
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(
|
||||
struct us_socket_t *s, struct us_bun_socket_context_options_t options,
|
||||
struct us_socket_events_t events, int socket_ext_size) {
|
||||
struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size) {
|
||||
/* Cannot wrap a closed socket */
|
||||
if (us_socket_is_closed(0, s)) {
|
||||
return NULL;
|
||||
@@ -2163,6 +2167,7 @@ us_socket_context_on_socket_connect_error(
|
||||
struct us_internal_ssl_socket_t *socket =
|
||||
(struct us_internal_ssl_socket_t *)us_socket_context_adopt_socket(
|
||||
0, context, s,
|
||||
old_socket_ext_size,
|
||||
sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) +
|
||||
socket_ext_size);
|
||||
socket->ssl = NULL;
|
||||
|
||||
@@ -228,8 +228,8 @@ void us_loop_run(struct us_loop_t *loop) {
|
||||
// > Instead, the filter will aggregate the events into a single kevent struct
|
||||
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
|
||||
int events = 0
|
||||
| ((filter & EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter & EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
| ((filter == EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter == EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
|
||||
const int eof = (flags & (EV_EOF));
|
||||
#endif
|
||||
@@ -325,7 +325,7 @@ void us_internal_loop_update_pending_ready_polls(struct us_loop_t *loop, struct
|
||||
int num_entries_possibly_remaining = 1;
|
||||
#else
|
||||
/* Ready polls may contain same poll twice under kqueue, as one poll may hold two filters */
|
||||
int num_entries_possibly_remaining = 2;//((old_events & LIBUS_SOCKET_READABLE) ? 1 : 0) + ((old_events & LIBUS_SOCKET_WRITABLE) ? 1 : 0);
|
||||
int num_entries_possibly_remaining = 2;
|
||||
#endif
|
||||
|
||||
/* Todo: for kqueue if we track things in us_change_poll it is possible to have a fast path with no seeking in cases of:
|
||||
@@ -360,11 +360,11 @@ int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_d
|
||||
if(!is_readable && !is_writable) {
|
||||
if(!(old_events & LIBUS_SOCKET_WRITABLE)) {
|
||||
// if we are not reading or writing, we need to add writable to receive FIN
|
||||
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, EV_ADD, 0, 0, (uint64_t)(void*)user_data, 0, 0);
|
||||
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, (uint64_t)(void*)user_data, 0, 0);
|
||||
}
|
||||
} else if ((new_events & LIBUS_SOCKET_WRITABLE) != (old_events & LIBUS_SOCKET_WRITABLE)) {
|
||||
/* Do they differ in writable? */
|
||||
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
|
||||
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD | EV_ONESHOT : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
|
||||
}
|
||||
int ret;
|
||||
do {
|
||||
@@ -377,22 +377,30 @@ int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_d
|
||||
}
|
||||
#endif
|
||||
|
||||
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int ext_size) {
|
||||
int events = us_poll_events(p);
|
||||
|
||||
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int old_ext_size, unsigned int ext_size) {
|
||||
|
||||
struct us_poll_t *new_p = us_realloc(p, sizeof(struct us_poll_t) + ext_size);
|
||||
if (p != new_p) {
|
||||
unsigned int old_size = sizeof(struct us_poll_t) + old_ext_size;
|
||||
unsigned int new_size = sizeof(struct us_poll_t) + ext_size;
|
||||
if(new_size <= old_size) return p;
|
||||
|
||||
struct us_poll_t *new_p = us_calloc(1, new_size);
|
||||
memcpy(new_p, p, old_size);
|
||||
|
||||
/* Increment poll count for the new poll - the old poll will be freed separately
|
||||
* which decrements the count, keeping the total correct */
|
||||
loop->num_polls++;
|
||||
|
||||
int events = us_poll_events(p);
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
/* Hack: forcefully update poll by stripping away already set events */
|
||||
new_p->state.poll_type = us_internal_poll_type(new_p);
|
||||
us_poll_change(new_p, loop, events);
|
||||
/* Hack: forcefully update poll by stripping away already set events */
|
||||
new_p->state.poll_type = us_internal_poll_type(new_p);
|
||||
us_poll_change(new_p, loop, events);
|
||||
#else
|
||||
/* Forcefully update poll by resetting them with new_p as user data */
|
||||
kqueue_change(loop->fd, new_p->state.fd, 0, LIBUS_SOCKET_WRITABLE | LIBUS_SOCKET_READABLE, new_p);
|
||||
#endif /* This is needed for epoll also (us_change_poll doesn't update the old poll) */
|
||||
us_internal_loop_update_pending_ready_polls(loop, p, new_p, events, events);
|
||||
}
|
||||
/* Forcefully update poll by resetting them with new_p as user data */
|
||||
kqueue_change(loop->fd, new_p->state.fd, 0, LIBUS_SOCKET_WRITABLE | LIBUS_SOCKET_READABLE, new_p);
|
||||
#endif
|
||||
/* This is needed for epoll also (us_change_poll doesn't update the old poll) */
|
||||
us_internal_loop_update_pending_ready_polls(loop, p, new_p, events, events);
|
||||
|
||||
return new_p;
|
||||
}
|
||||
@@ -444,7 +452,7 @@ void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
|
||||
kqueue_change(loop->fd, p->state.fd, old_events, events, p);
|
||||
#endif
|
||||
/* Set all removed events to null-polls in pending ready poll list */
|
||||
// us_internal_loop_update_pending_ready_polls(loop, p, p, old_events, events);
|
||||
us_internal_loop_update_pending_ready_polls(loop, p, p, old_events, events);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ void us_poll_init(struct us_poll_t *p, LIBUS_SOCKET_DESCRIPTOR fd,
|
||||
}
|
||||
|
||||
void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop) {
|
||||
// poll was resized and dont own uv_poll_t anymore
|
||||
if(!p->uv_p) {
|
||||
free(p);
|
||||
return;
|
||||
}
|
||||
/* The idea here is like so; in us_poll_stop we call uv_close after setting
|
||||
* data of uv-poll to 0. This means that in close_cb_free we call free on 0
|
||||
* with does nothing, since us_poll_stop should not really free the poll.
|
||||
@@ -86,6 +91,7 @@ void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop) {
|
||||
}
|
||||
|
||||
void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) {
|
||||
if(!p->uv_p) return;
|
||||
p->poll_type = us_internal_poll_type(p) |
|
||||
((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0) |
|
||||
((events & LIBUS_SOCKET_WRITABLE) ? POLL_TYPE_POLLING_OUT : 0);
|
||||
@@ -99,6 +105,7 @@ void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) {
|
||||
}
|
||||
|
||||
void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
|
||||
if(!p->uv_p) return;
|
||||
if (us_poll_events(p) != events) {
|
||||
p->poll_type =
|
||||
us_internal_poll_type(p) |
|
||||
@@ -109,6 +116,7 @@ void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
|
||||
}
|
||||
|
||||
void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop) {
|
||||
if(!p->uv_p) return;
|
||||
uv_poll_stop(p->uv_p);
|
||||
|
||||
/* We normally only want to close the poll here, not free it. But if we stop
|
||||
@@ -217,10 +225,20 @@ struct us_poll_t *us_create_poll(struct us_loop_t *loop, int fallthrough,
|
||||
/* If we update our block position we have to update the uv_poll data to point
|
||||
* to us */
|
||||
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop,
|
||||
unsigned int ext_size) {
|
||||
unsigned int old_ext_size, unsigned int ext_size) {
|
||||
|
||||
// cannot resize if we dont own uv_poll_t
|
||||
if(!p->uv_p) return p;
|
||||
|
||||
unsigned int old_size = sizeof(struct us_poll_t) + old_ext_size;
|
||||
unsigned int new_size = sizeof(struct us_poll_t) + ext_size;
|
||||
if(new_size <= old_size) return p;
|
||||
|
||||
struct us_poll_t *new_p = calloc(1, new_size);
|
||||
memcpy(new_p, p, old_size);
|
||||
|
||||
struct us_poll_t *new_p = realloc(p, sizeof(struct us_poll_t) + ext_size);
|
||||
new_p->uv_p->data = new_p;
|
||||
p->uv_p = NULL;
|
||||
|
||||
return new_p;
|
||||
}
|
||||
|
||||
@@ -170,6 +170,14 @@ struct us_socket_flags {
|
||||
unsigned char low_prio_state: 2;
|
||||
/* If true, the socket should be read using readmsg to support receiving file descriptors */
|
||||
bool is_ipc: 1;
|
||||
/* If true, the socket has been closed */
|
||||
bool is_closed: 1;
|
||||
/* If true, the socket was reallocated during adoption */
|
||||
bool adopted: 1;
|
||||
/* If true, the socket is a TLS socket */
|
||||
bool is_tls: 1;
|
||||
/* If true, the last write to this socket failed (would block) */
|
||||
bool last_write_failed: 1;
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -435,11 +443,11 @@ void us_internal_ssl_socket_shutdown(us_internal_ssl_socket_r s);
|
||||
|
||||
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_adopt_socket(
|
||||
us_internal_ssl_socket_context_r context,
|
||||
us_internal_ssl_socket_r s, int ext_size);
|
||||
us_internal_ssl_socket_r s, int old_ext_size, int ext_size);
|
||||
|
||||
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(
|
||||
us_socket_r s, struct us_bun_socket_context_options_t options,
|
||||
struct us_socket_events_t events, int socket_ext_size);
|
||||
struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size);
|
||||
struct us_internal_ssl_socket_context_t *
|
||||
us_internal_create_child_ssl_socket_context(
|
||||
us_internal_ssl_socket_context_r context, int context_ext_size);
|
||||
|
||||
@@ -37,7 +37,6 @@ struct us_internal_loop_data_t {
|
||||
struct us_timer_t *sweep_timer;
|
||||
int sweep_timer_count;
|
||||
struct us_internal_async *wakeup_async;
|
||||
int last_write_failed;
|
||||
struct us_socket_context_t *head;
|
||||
struct us_socket_context_t *iterator;
|
||||
struct us_socket_context_t *closed_context_head;
|
||||
|
||||
@@ -349,7 +349,7 @@ struct us_loop_t *us_socket_context_loop(int ssl, us_socket_context_r context) n
|
||||
|
||||
/* Invalidates passed socket, returning a new resized socket which belongs to a different socket context.
|
||||
* Used mainly for "socket upgrades" such as when transitioning from HTTP to WebSocket. */
|
||||
struct us_socket_t *us_socket_context_adopt_socket(int ssl, us_socket_context_r context, us_socket_r s, int ext_size);
|
||||
struct us_socket_t *us_socket_context_adopt_socket(int ssl, us_socket_context_r context, us_socket_r s, int old_ext_size, int ext_size);
|
||||
|
||||
struct us_socket_t *us_socket_upgrade_to_tls(us_socket_r s, us_socket_context_r new_context, const char *sni);
|
||||
|
||||
@@ -411,7 +411,7 @@ void *us_poll_ext(us_poll_r p) nonnull_fn_decl;
|
||||
LIBUS_SOCKET_DESCRIPTOR us_poll_fd(us_poll_r p) nonnull_fn_decl;
|
||||
|
||||
/* Resize an active poll */
|
||||
struct us_poll_t *us_poll_resize(us_poll_r p, us_loop_r loop, unsigned int ext_size) nonnull_fn_decl;
|
||||
struct us_poll_t *us_poll_resize(us_poll_r p, us_loop_r loop, unsigned int old_ext_size, unsigned int ext_size) nonnull_fn_decl;
|
||||
|
||||
/* Public interfaces for sockets */
|
||||
|
||||
@@ -470,7 +470,7 @@ void us_socket_local_address(int ssl, us_socket_r s, char *nonnull_arg buf, int
|
||||
/* Bun extras */
|
||||
struct us_socket_t *us_socket_pair(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR* fds);
|
||||
struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socket_ext_size, LIBUS_SOCKET_DESCRIPTOR fd, int ipc);
|
||||
struct us_socket_t *us_socket_wrap_with_tls(int ssl, us_socket_r s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size);
|
||||
struct us_socket_t *us_socket_wrap_with_tls(int ssl, us_socket_r s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size);
|
||||
int us_socket_raw_write(int ssl, us_socket_r s, const char *data, int length);
|
||||
struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length);
|
||||
int us_raw_root_certs(struct us_cert_string_t**out);
|
||||
|
||||
@@ -193,9 +193,17 @@ void us_internal_handle_low_priority_sockets(struct us_loop_t *loop) {
|
||||
loop_data->low_prio_head = s->next;
|
||||
if (s->next) s->next->prev = 0;
|
||||
s->next = 0;
|
||||
int ssl = s->flags.is_tls;
|
||||
|
||||
if(us_socket_is_closed(ssl, s)) {
|
||||
s->flags.low_prio_state = 2;
|
||||
us_socket_context_unref(ssl, s->context);
|
||||
continue;
|
||||
}
|
||||
|
||||
us_internal_socket_context_link_socket(0, s->context, s);
|
||||
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
|
||||
us_internal_socket_context_link_socket(ssl, s->context, s);
|
||||
us_socket_context_unref(ssl, s->context);
|
||||
us_poll_change(&s->p, us_socket_context(ssl, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
|
||||
|
||||
s->flags.low_prio_state = 2;
|
||||
}
|
||||
@@ -243,6 +251,7 @@ void us_internal_free_closed_sockets(struct us_loop_t *loop) {
|
||||
/* Free all closed sockets (maybe it is better to reverse order?) */
|
||||
for (struct us_socket_t *s = loop->data.closed_head; s; ) {
|
||||
struct us_socket_t *next = s->next;
|
||||
s->prev = s->next = 0;
|
||||
us_poll_free((struct us_poll_t *) s, loop);
|
||||
s = next;
|
||||
}
|
||||
@@ -347,6 +356,9 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
s->flags.allow_half_open = listen_socket->s.flags.allow_half_open;
|
||||
s->flags.is_paused = 0;
|
||||
s->flags.is_ipc = 0;
|
||||
s->flags.is_closed = 0;
|
||||
s->flags.adopted = 0;
|
||||
s->flags.is_tls = listen_socket->s.flags.is_tls;
|
||||
|
||||
/* We always use nodelay */
|
||||
bsd_socket_nodelay(client_fd, 1);
|
||||
@@ -354,7 +366,10 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
us_internal_socket_context_link_socket(0, listen_socket->s.context, s);
|
||||
|
||||
listen_socket->s.context->on_open(s, 0, bsd_addr_get_ip(&addr), bsd_addr_get_ip_length(&addr));
|
||||
|
||||
/* After socket adoption, track the new socket; the old one becomes invalid */
|
||||
if(s && s->flags.adopted && s->prev) {
|
||||
s = s->prev;
|
||||
}
|
||||
/* Exit accept loop if listen socket was closed in on_open handler */
|
||||
if (us_socket_is_closed(0, &listen_socket->s)) {
|
||||
break;
|
||||
@@ -369,22 +384,37 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
case POLL_TYPE_SOCKET: {
|
||||
/* We should only use s, no p after this point */
|
||||
struct us_socket_t *s = (struct us_socket_t *) p;
|
||||
/* After socket adoption, track the new socket; the old one becomes invalid */
|
||||
if(s && s->flags.adopted && s->prev) {
|
||||
s = s->prev;
|
||||
}
|
||||
/* The context can change after calling a callback but the loop is always the same */
|
||||
struct us_loop_t* loop = s->context->loop;
|
||||
if (events & LIBUS_SOCKET_WRITABLE && !error) {
|
||||
/* Note: if we failed a write as a socket of one loop then adopted
|
||||
* to another loop, this will be wrong. Absurd case though */
|
||||
loop->data.last_write_failed = 0;
|
||||
s->flags.last_write_failed = 0;
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue is one-shot so is not writable anymore */
|
||||
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0);
|
||||
#endif
|
||||
|
||||
s = s->context->on_writable(s);
|
||||
/* After socket adoption, track the new socket; the old one becomes invalid */
|
||||
if(s && s->flags.adopted && s->prev) {
|
||||
s = s->prev;
|
||||
}
|
||||
|
||||
if (!s || us_socket_is_closed(0, s)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we have no failed write or if we shut down, then stop polling for more writable */
|
||||
if (!loop->data.last_write_failed || us_socket_is_shut_down(0, s)) {
|
||||
if (!s->flags.last_write_failed || us_socket_is_shut_down(0, s)) {
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
|
||||
} else {
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue one-shot writable needs to be re-enabled */
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) | LIBUS_SOCKET_WRITABLE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,6 +498,10 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
|
||||
if (length > 0) {
|
||||
s = s->context->on_data(s, loop->data.recv_buf + LIBUS_RECV_BUFFER_PADDING, length);
|
||||
/* After socket adoption, track the new socket; the old one becomes invalid */
|
||||
if(s && s->flags.adopted && s->prev) {
|
||||
s = s->prev;
|
||||
}
|
||||
// loop->num_ready_polls isn't accessible on Windows.
|
||||
#ifndef WIN32
|
||||
// rare case: we're reading a lot of data, there's more to be read, and either:
|
||||
|
||||
@@ -125,7 +125,7 @@ int us_socket_is_closed(int ssl, struct us_socket_t *s) {
|
||||
if(ssl) {
|
||||
return us_internal_ssl_socket_is_closed((struct us_internal_ssl_socket_t *) s);
|
||||
}
|
||||
return s->prev == (struct us_socket_t *) s->context;
|
||||
return s->flags.is_closed;
|
||||
}
|
||||
|
||||
int us_connecting_socket_is_closed(int ssl, struct us_connecting_socket_t *c) {
|
||||
@@ -159,8 +159,8 @@ void us_connecting_socket_close(int ssl, struct us_connecting_socket_t *c) {
|
||||
s->next = s->context->loop->data.closed_head;
|
||||
s->context->loop->data.closed_head = s;
|
||||
|
||||
/* Any socket with prev = context is marked as closed */
|
||||
s->prev = (struct us_socket_t *) s->context;
|
||||
/* Mark the socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
}
|
||||
if(!c->error) {
|
||||
// if we have no error, we have to set that we were aborted aka we called close
|
||||
@@ -218,11 +218,10 @@ struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s, int code, vo
|
||||
|
||||
bsd_close_socket(us_poll_fd((struct us_poll_t *) s));
|
||||
|
||||
/* Mark the socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
|
||||
/* Any socket with prev = context is marked as closed */
|
||||
s->prev = (struct us_socket_t *) s->context;
|
||||
|
||||
/* mark it as closed and call the callback */
|
||||
/* call the callback */
|
||||
struct us_socket_t *res = s;
|
||||
if (!(us_internal_poll_type(&s->p) & POLL_TYPE_SEMI_SOCKET)) {
|
||||
res = s->context->on_close(s, code, reason);
|
||||
@@ -268,8 +267,8 @@ struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s) {
|
||||
s->next = s->context->loop->data.closed_head;
|
||||
s->context->loop->data.closed_head = s;
|
||||
|
||||
/* Any socket with prev = context is marked as closed */
|
||||
s->prev = (struct us_socket_t *) s->context;
|
||||
/* Mark the socket as closed */
|
||||
s->flags.is_closed = 1;
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -321,8 +320,10 @@ struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socke
|
||||
s->flags.low_prio_state = 0;
|
||||
s->flags.allow_half_open = 0;
|
||||
s->flags.is_paused = 0;
|
||||
s->flags.is_ipc = 0;
|
||||
s->flags.is_ipc = ipc;
|
||||
s->flags.is_closed = 0;
|
||||
s->flags.adopted = 0;
|
||||
s->flags.is_tls = 0;
|
||||
s->connect_state = NULL;
|
||||
|
||||
/* We always use nodelay */
|
||||
@@ -369,7 +370,7 @@ int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length
|
||||
|
||||
int written = bsd_send(us_poll_fd(&s->p), data, length);
|
||||
if (written != length) {
|
||||
s->context->loop->data.last_write_failed = 1;
|
||||
s->flags.last_write_failed = 1;
|
||||
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
|
||||
}
|
||||
|
||||
@@ -406,7 +407,7 @@ int us_socket_ipc_write_fd(struct us_socket_t *s, const char* data, int length,
|
||||
int sent = bsd_sendmsg(us_poll_fd(&s->p), &msg, 0);
|
||||
|
||||
if (sent != length) {
|
||||
s->context->loop->data.last_write_failed = 1;
|
||||
s->flags.last_write_failed = 1;
|
||||
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
|
||||
}
|
||||
|
||||
@@ -476,13 +477,13 @@ int us_connecting_socket_get_error(int ssl, struct us_connecting_socket_t *c) {
|
||||
Note: this assumes that the socket is non-TLS and will be adopted and wrapped with a new TLS context
|
||||
context ext will not be copied to the new context, new context will contain us_wrapped_socket_context_t on ext
|
||||
*/
|
||||
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size) {
|
||||
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int old_socket_ext_size, int socket_ext_size) {
|
||||
// only accepts non-TLS sockets
|
||||
if (ssl) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return(struct us_socket_t *) us_internal_ssl_socket_wrap_with_tls(s, options, events, socket_ext_size);
|
||||
return(struct us_socket_t *) us_internal_ssl_socket_wrap_with_tls(s, options, events, old_socket_ext_size, socket_ext_size);
|
||||
}
|
||||
|
||||
// if a TLS socket calls this, it will start SSL call open event and TLS handshake if required
|
||||
|
||||
@@ -84,6 +84,7 @@ struct AsyncSocketData {
|
||||
/* Or empty */
|
||||
AsyncSocketData() = default;
|
||||
bool isIdle = false;
|
||||
bool isAuthorized = false; // per-socket TLS authorization status
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -213,6 +213,16 @@ namespace uWS {
|
||||
emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
|
||||
shouldEmit = true;
|
||||
}
|
||||
// Validate that the chunk terminator is \r\n to prevent request smuggling
|
||||
// The last 2 bytes of the chunk must be exactly \r\n
|
||||
// Note: chunkSize always includes +2 for the terminator (added in consumeHexNumber),
|
||||
// and chunks with size 0 (chunkSize == 2) are handled earlier at line 190.
|
||||
// Therefore chunkSize >= 3 here, so no underflow is possible.
|
||||
size_t terminatorOffset = chunkSize(state) - 2;
|
||||
if (data[terminatorOffset] != '\r' || data[terminatorOffset + 1] != '\n') {
|
||||
state = STATE_IS_ERROR;
|
||||
return std::nullopt;
|
||||
}
|
||||
data.remove_prefix(chunkSize(state));
|
||||
state = STATE_IS_CHUNKED;
|
||||
if (shouldEmit) {
|
||||
|
||||
@@ -124,15 +124,16 @@ private:
|
||||
// if we are closing or already closed, we don't need to do anything
|
||||
if (!us_socket_is_closed(SSL, s)) {
|
||||
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
|
||||
httpContextData->flags.isAuthorized = success;
|
||||
// Set per-socket authorization status
|
||||
auto *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(us_socket_ext(SSL, s));
|
||||
if(httpContextData->flags.rejectUnauthorized) {
|
||||
if(!success || verify_error.error != 0) {
|
||||
// we failed to handshake, close the socket
|
||||
us_socket_close(SSL, s, 0, nullptr);
|
||||
return;
|
||||
}
|
||||
httpContextData->flags.isAuthorized = true;
|
||||
}
|
||||
httpResponseData->isAuthorized = success;
|
||||
|
||||
/* Any connected socket should timeout until it has a request */
|
||||
((HttpResponse<SSL> *) s)->resetTimeout();
|
||||
|
||||
@@ -331,7 +331,7 @@ public:
|
||||
|
||||
|
||||
/* Adopting a socket invalidates it, do not rely on it directly to carry any data */
|
||||
us_socket_t *usSocket = us_socket_context_adopt_socket(SSL, (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(WebSocketData) + sizeof(UserData));
|
||||
us_socket_t *usSocket = us_socket_context_adopt_socket(SSL, (us_socket_context_t *) webSocketContext, (us_socket_t *) this, sizeof(HttpResponseData<SSL>), sizeof(WebSocketData) + sizeof(UserData));
|
||||
WebSocket<SSL, true, UserData> *webSocket = (WebSocket<SSL, true, UserData> *) usSocket;
|
||||
|
||||
/* For whatever reason we were corked, update cork to the new socket */
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "bun-vscode",
|
||||
"dependencies": {
|
||||
"glob": "^13.0.0",
|
||||
"ws": "^8.18.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.1.10",
|
||||
"@types/vscode": "^1.60.0",
|
||||
@@ -107,10 +103,6 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
|
||||
@@ -353,7 +345,7 @@
|
||||
|
||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||
|
||||
"glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
|
||||
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
@@ -541,7 +533,7 @@
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
||||
|
||||
@@ -699,8 +691,6 @@
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"@vscode/test-cli/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"@vscode/vsce/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"@vscode/vsce/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
@@ -715,8 +705,6 @@
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -745,7 +733,7 @@
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
|
||||
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
|
||||
|
||||
@@ -773,8 +761,6 @@
|
||||
|
||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"@vscode/test-cli/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@vscode/vsce/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
||||
@@ -805,8 +791,6 @@
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@vscode/test-cli/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"log-symbols/chalk/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"mocha/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
@@ -378,9 +378,5 @@
|
||||
"workspaces": [
|
||||
"../bun-debug-adapter-protocol",
|
||||
"../bun-inspector-protocol"
|
||||
],
|
||||
"overrides": {
|
||||
"glob": "^13.0.0",
|
||||
"ws": "^8.18.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -533,15 +533,19 @@ describe("BunTestController (static file parser)", () => {
|
||||
test.todo("todo test", () => {});
|
||||
test.only("only test", () => {});
|
||||
test.failing("failing test", () => {});
|
||||
test.concurrent("concurrent test", () => {});
|
||||
test.serial("serial test", () => {});
|
||||
`;
|
||||
|
||||
const result = parseTestBlocks(content);
|
||||
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result).toHaveLength(6);
|
||||
expect(result[0].name).toBe("skipped test");
|
||||
expect(result[1].name).toBe("todo test");
|
||||
expect(result[2].name).toBe("only test");
|
||||
expect(result[3].name).toBe("failing test");
|
||||
expect(result[4].name).toBe("concurrent test");
|
||||
expect(result[5].name).toBe("serial test");
|
||||
});
|
||||
|
||||
test("should handle conditional tests", () => {
|
||||
@@ -549,14 +553,41 @@ describe("BunTestController (static file parser)", () => {
|
||||
test.if(true)("conditional test", () => {});
|
||||
test.skipIf(false)("skip if test", () => {});
|
||||
test.todoIf(true)("todo if test", () => {});
|
||||
test.failingIf(true)("failing if test", () => {});
|
||||
test.concurrentIf(true)("concurrent if test", () => {});
|
||||
test.serialIf(true)("serial if test", () => {});
|
||||
`;
|
||||
|
||||
const result = parseTestBlocks(content);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toHaveLength(6);
|
||||
expect(result[0].name).toBe("conditional test");
|
||||
expect(result[1].name).toBe("skip if test");
|
||||
expect(result[2].name).toBe("todo if test");
|
||||
expect(result[3].name).toBe("failing if test");
|
||||
expect(result[4].name).toBe("concurrent if test");
|
||||
expect(result[5].name).toBe("serial if test");
|
||||
});
|
||||
|
||||
test("should handle describe modifiers", () => {
|
||||
const content = `
|
||||
describe.concurrent("concurrent describe", () => {
|
||||
test("test in concurrent", () => {});
|
||||
});
|
||||
describe.serial("serial describe", () => {
|
||||
test("test in serial", () => {});
|
||||
});
|
||||
`;
|
||||
|
||||
const result = parseTestBlocks(content);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].name).toBe("concurrent describe");
|
||||
expect(result[0].type).toBe("describe");
|
||||
expect(result[0].children).toHaveLength(1);
|
||||
expect(result[1].name).toBe("serial describe");
|
||||
expect(result[1].type).toBe("describe");
|
||||
expect(result[1].children).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("should ignore comments", () => {
|
||||
|
||||
@@ -340,7 +340,7 @@ export class BunTestController implements vscode.Disposable {
|
||||
});
|
||||
|
||||
const testRegex =
|
||||
/\b(describe|test|it)(?:\.(?:skip|todo|failing|only))?(?:\.(?:if|todoIf|skipIf)\s*\([^)]*\))?(?:\.each\s*\([^)]*\))?\s*\(\s*(['"`])((?:\\\2|.)*?)\2\s*(?:,|\))/g;
|
||||
/\b(describe|test|it)(?:\.(?:skip|todo|failing|only|concurrent|serial))*(?:\.(?:if|todoIf|skipIf|failingIf|concurrentIf|serialIf)\s*\([^)]*\))?(?:\.each\s*\([^)]*\))?\s*\(\s*(['"`])((?:\\\2|.)*?)\2\s*(?:,|\))/g;
|
||||
|
||||
const stack: TestNode[] = [];
|
||||
const root: TestNode[] = [];
|
||||
@@ -1391,7 +1391,7 @@ export class BunTestController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
const { bunCommand, testArgs } = this.getBunExecutionConfig();
|
||||
const args = [...testArgs, ...testFiles];
|
||||
const args = [bunCommand, ...testArgs, ...testFiles];
|
||||
|
||||
if (!isIndividualTestRun) {
|
||||
args.push("--inspect-brk");
|
||||
@@ -1416,14 +1416,14 @@ export class BunTestController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
const debugConfiguration: vscode.DebugConfiguration = {
|
||||
args: args.slice(1),
|
||||
args: args.slice(2),
|
||||
console: "integratedTerminal",
|
||||
cwd: "${workspaceFolder}",
|
||||
internalConsoleOptions: "neverOpen",
|
||||
name: "Bun Test Debug",
|
||||
program: args.at(1),
|
||||
request: "launch",
|
||||
runtime: bunCommand,
|
||||
runtime: args.at(0),
|
||||
type: "bun",
|
||||
};
|
||||
|
||||
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2025-12-10"
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Version: 20
|
||||
# Version: 25
|
||||
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on macOS and Linux with a POSIX shell.
|
||||
@@ -1155,12 +1155,23 @@ llvm_version() {
|
||||
install_llvm() {
|
||||
case "$pm" in
|
||||
apt)
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
# Debian 13 (Trixie) has LLVM 19 natively, and apt.llvm.org doesn't have a trixie repo
|
||||
if [ "$distro" = "debian" ]; then
|
||||
install_packages \
|
||||
"llvm-$(llvm_version)" \
|
||||
"clang-$(llvm_version)" \
|
||||
"lld-$(llvm_version)" \
|
||||
"llvm-$(llvm_version)-dev" \
|
||||
"llvm-$(llvm_version)-tools" \
|
||||
"libclang-rt-$(llvm_version)-dev"
|
||||
else
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
fi
|
||||
;;
|
||||
brew)
|
||||
install_packages "llvm@$(llvm_version)"
|
||||
@@ -1297,11 +1308,6 @@ install_sccache() {
|
||||
|
||||
install_rust() {
|
||||
case "$distro" in
|
||||
alpine)
|
||||
install_packages \
|
||||
rust \
|
||||
cargo
|
||||
;;
|
||||
freebsd)
|
||||
install_packages lang/rust
|
||||
create_directory "$HOME/.cargo/bin"
|
||||
@@ -1317,6 +1323,9 @@ install_rust() {
|
||||
rustup_script=$(download_file "https://sh.rustup.rs")
|
||||
execute "$sh" -lc "$rustup_script -y --no-modify-path"
|
||||
append_to_path "$rust_home/bin"
|
||||
|
||||
# Ensure all rustup files are accessible (for CI builds where different users run builds)
|
||||
grant_to_user "$rust_home"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1565,7 +1574,7 @@ install_buildkite() {
|
||||
return
|
||||
fi
|
||||
|
||||
buildkite_version="3.87.0"
|
||||
buildkite_version="3.114.0"
|
||||
case "$arch" in
|
||||
aarch64)
|
||||
buildkite_arch="arm64"
|
||||
@@ -1766,7 +1775,7 @@ ensure_no_tmpfs() {
|
||||
if ! [ "$os" = "linux" ]; then
|
||||
return
|
||||
fi
|
||||
if ! [ "$distro" = "ubuntu" ]; then
|
||||
if ! ( [ "$distro" = "ubuntu" ] || [ "$distro" = "debian" ] ); then
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ async function build(args) {
|
||||
flag.startsWith("-D") ? [`${flag}=${value}`] : [flag, value],
|
||||
);
|
||||
|
||||
try {
|
||||
await Bun.file(buildPath + "/CMakeCache.txt").delete();
|
||||
} catch (e) {}
|
||||
await startGroup("CMake Configure", () => spawn("cmake", generateArgs, { env }));
|
||||
|
||||
const envPath = resolve(buildPath, ".env");
|
||||
|
||||
50
scripts/create-link-metadata.mjs
Normal file
50
scripts/create-link-metadata.mjs
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Creates link-metadata.json with link command and build metadata
|
||||
*
|
||||
* Usage: bun scripts/create-link-metadata.mjs <build-path> <bun-target>
|
||||
*/
|
||||
|
||||
import { $ } from "bun";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const [buildPath, bunTarget] = process.argv.slice(2);
|
||||
|
||||
if (!buildPath || !bunTarget) {
|
||||
console.error("Usage: bun scripts/create-link-metadata.mjs <build-path> <bun-target>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get the repo root (parent of scripts directory)
|
||||
const repoRoot = dirname(import.meta.dir);
|
||||
|
||||
// Extract link command using ninja
|
||||
console.log("Extracting link command...");
|
||||
const linkCommandResult = await $`ninja -C ${buildPath} -t commands ${bunTarget}`.quiet();
|
||||
const linkCommand = linkCommandResult.stdout.toString().trim();
|
||||
|
||||
// Read linker-related files from src/
|
||||
console.log("Reading linker files...");
|
||||
const linkerLds = await Bun.file(join(repoRoot, "src", "linker.lds")).text();
|
||||
const symbolsDyn = await Bun.file(join(repoRoot, "src", "symbols.dyn")).text();
|
||||
const symbolsTxt = await Bun.file(join(repoRoot, "src", "symbols.txt")).text();
|
||||
|
||||
// Create metadata JSON with link command included
|
||||
const metadata = {
|
||||
bun_version: process.env.BUN_VERSION || "",
|
||||
webkit_url: process.env.WEBKIT_DOWNLOAD_URL || "",
|
||||
webkit_version: process.env.WEBKIT_VERSION || "",
|
||||
zig_commit: process.env.ZIG_COMMIT || "",
|
||||
target: bunTarget,
|
||||
timestamp: new Date().toISOString(),
|
||||
link_command: linkCommand,
|
||||
linker_lds: linkerLds,
|
||||
symbols_dyn: symbolsDyn,
|
||||
symbols_txt: symbolsTxt,
|
||||
};
|
||||
|
||||
const metadataPath = join(buildPath, "link-metadata.json");
|
||||
await Bun.write(metadataPath, JSON.stringify(metadata, null, 2));
|
||||
console.log(`Written to ${metadataPath}`);
|
||||
|
||||
console.log("Done");
|
||||
@@ -77,9 +77,8 @@ const testTimeout = 3 * 60_000;
|
||||
const integrationTimeout = 5 * 60_000;
|
||||
|
||||
function getNodeParallelTestTimeout(testPath) {
|
||||
if (testPath.includes("test-dns")) {
|
||||
return 90_000;
|
||||
}
|
||||
if (testPath.includes("test-dns")) return 60_000;
|
||||
if (testPath.includes("-docker-")) return 60_000;
|
||||
if (!isCI) return 60_000; // everything slower in debug mode
|
||||
if (options["step"]?.includes("-asan-")) return 60_000;
|
||||
return 20_000;
|
||||
|
||||
@@ -2842,7 +2842,7 @@ export function printEnvironment() {
|
||||
|
||||
if (isCI) {
|
||||
startGroup("Environment", () => {
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
for (const [key, value] of Object.entries(process.env).toSorted()) {
|
||||
console.log(`${key}:`, value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -119,7 +119,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
};
|
||||
|
||||
const Macho = struct {
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32;
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u64;
|
||||
|
||||
pub fn getData() ?[]const u8 {
|
||||
if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
|
||||
@@ -127,8 +127,10 @@ pub const StandaloneModuleGraph = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
// BlobHeader has 8 bytes size (u64), so data starts at offset 8.
|
||||
const data_offset = @sizeOf(u64);
|
||||
const slice_ptr: [*]const u8 = @ptrCast(length);
|
||||
return slice_ptr[4..][0..length.*];
|
||||
return slice_ptr[data_offset..][0..length.*];
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -136,7 +138,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
};
|
||||
|
||||
const PE = struct {
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32;
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u64;
|
||||
pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
|
||||
|
||||
pub fn getData() ?[]const u8 {
|
||||
@@ -296,7 +298,9 @@ pub const StandaloneModuleGraph = struct {
|
||||
pub const Flags = packed struct(u32) {
|
||||
disable_default_env_files: bool = false,
|
||||
disable_autoload_bunfig: bool = false,
|
||||
_padding: u30 = 0,
|
||||
disable_autoload_tsconfig: bool = false,
|
||||
disable_autoload_package_json: bool = false,
|
||||
_padding: u28 = 0,
|
||||
};
|
||||
|
||||
const trailer = "\n---- Bun! ----\n";
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn create(bytes: []const u8) bun.sys.Maybe(bun.webcore.Blob.Store.Bytes) {
|
||||
const label = std.fmt.bufPrintZ(&label_buf, "memfd-num-{d}", .{memfd_counter.fetchAdd(1, .monotonic)}) catch "";
|
||||
|
||||
// Using huge pages was slower.
|
||||
const fd = switch (bun.sys.memfd_create(label, std.os.linux.MFD.CLOEXEC)) {
|
||||
const fd = switch (bun.sys.memfd_create(label, .non_executable)) {
|
||||
.err => |err| return .{ .err = bun.sys.Error.fromCode(err.getErrno(), .open) },
|
||||
.result => |fd| fd,
|
||||
};
|
||||
|
||||
@@ -1655,6 +1655,9 @@ pub const api = struct {
|
||||
|
||||
drop: []const []const u8 = &.{},
|
||||
|
||||
/// feature_flags for dead-code elimination via `import { feature } from "bun:bundle"`
|
||||
feature_flags: []const []const u8 = &.{},
|
||||
|
||||
/// preserve_symlinks
|
||||
preserve_symlinks: ?bool = null,
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ fn deduplicatedImport(
|
||||
// Disable this one since an older record is getting used. It isn't
|
||||
// practical to delete this import record entry since an import or
|
||||
// require expression can exist.
|
||||
ir.is_unused = true;
|
||||
ir.flags.is_unused = true;
|
||||
|
||||
const stmt = ctx.stmts.items[gop.value_ptr.stmt_index].data.s_import;
|
||||
if (items.len > 0) {
|
||||
|
||||
@@ -312,8 +312,9 @@ pub fn getObject(expr: *const Expr, name: string) ?Expr {
|
||||
|
||||
pub fn getBoolean(expr: *const Expr, name: string) ?bool {
|
||||
if (expr.asProperty(name)) |query| {
|
||||
if (query.expr.data == .e_boolean) {
|
||||
return query.expr.data.e_boolean.value;
|
||||
switch (query.expr.data) {
|
||||
.e_boolean, .e_branch_boolean => |b| return b.value,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -510,9 +511,10 @@ pub inline fn asStringZ(expr: *const Expr, allocator: std.mem.Allocator) OOM!?st
|
||||
pub fn asBool(
|
||||
expr: *const Expr,
|
||||
) ?bool {
|
||||
if (expr.data != .e_boolean) return null;
|
||||
|
||||
return expr.data.e_boolean.value;
|
||||
return switch (expr.data) {
|
||||
.e_boolean, .e_branch_boolean => |b| b.value,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn asNumber(expr: *const Expr) ?f64 {
|
||||
@@ -1490,6 +1492,11 @@ pub const Tag = enum {
|
||||
e_private_identifier,
|
||||
e_commonjs_export_identifier,
|
||||
e_boolean,
|
||||
/// Like e_boolean, but produced by `feature()` from `bun:bundle`.
|
||||
/// This tag ensures feature() can only be used directly in conditional
|
||||
/// contexts (if statements, ternaries). Invalid usage is caught during
|
||||
/// the visit phase when this expression appears outside a branch condition.
|
||||
e_branch_boolean,
|
||||
e_number,
|
||||
e_big_int,
|
||||
e_string,
|
||||
@@ -1513,7 +1520,7 @@ pub const Tag = enum {
|
||||
// object, regex and array may have had side effects
|
||||
pub fn isPrimitiveLiteral(tag: Tag) bool {
|
||||
return switch (tag) {
|
||||
.e_null, .e_undefined, .e_string, .e_boolean, .e_number, .e_big_int => true,
|
||||
.e_null, .e_undefined, .e_string, .e_boolean, .e_branch_boolean, .e_number, .e_big_int => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -1522,7 +1529,7 @@ pub const Tag = enum {
|
||||
return switch (tag) {
|
||||
.e_array, .e_object, .e_null, .e_reg_exp => "object",
|
||||
.e_undefined => "undefined",
|
||||
.e_boolean => "boolean",
|
||||
.e_boolean, .e_branch_boolean => "boolean",
|
||||
.e_number => "number",
|
||||
.e_big_int => "bigint",
|
||||
.e_string => "string",
|
||||
@@ -1537,7 +1544,7 @@ pub const Tag = enum {
|
||||
.e_array => writer.writeAll("array"),
|
||||
.e_unary => writer.writeAll("unary"),
|
||||
.e_binary => writer.writeAll("binary"),
|
||||
.e_boolean => writer.writeAll("boolean"),
|
||||
.e_boolean, .e_branch_boolean => writer.writeAll("boolean"),
|
||||
.e_super => writer.writeAll("super"),
|
||||
.e_null => writer.writeAll("null"),
|
||||
.e_undefined => writer.writeAll("undefined"),
|
||||
@@ -1627,14 +1634,7 @@ pub const Tag = enum {
|
||||
}
|
||||
}
|
||||
pub fn isBoolean(self: Tag) bool {
|
||||
switch (self) {
|
||||
.e_boolean => {
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
return false;
|
||||
},
|
||||
}
|
||||
return self == .e_boolean or self == .e_branch_boolean;
|
||||
}
|
||||
pub fn isSuper(self: Tag) bool {
|
||||
switch (self) {
|
||||
@@ -1921,7 +1921,7 @@ pub const Tag = enum {
|
||||
|
||||
pub fn isBoolean(a: *const Expr) bool {
|
||||
return switch (a.data) {
|
||||
.e_boolean => true,
|
||||
.e_boolean, .e_branch_boolean => true,
|
||||
.e_if => |ex| ex.yes.isBoolean() and ex.no.isBoolean(),
|
||||
.e_unary => |ex| ex.op == .un_not or ex.op == .un_delete,
|
||||
.e_binary => |ex| switch (ex.op) {
|
||||
@@ -1978,8 +1978,8 @@ pub fn maybeSimplifyNot(expr: *const Expr, allocator: std.mem.Allocator) ?Expr {
|
||||
.e_null, .e_undefined => {
|
||||
return expr.at(E.Boolean, E.Boolean{ .value = true }, allocator);
|
||||
},
|
||||
.e_boolean => |b| {
|
||||
return expr.at(E.Boolean, E.Boolean{ .value = b.value }, allocator);
|
||||
.e_boolean, .e_branch_boolean => |b| {
|
||||
return expr.at(E.Boolean, E.Boolean{ .value = !b.value }, allocator);
|
||||
},
|
||||
.e_number => |n| {
|
||||
return expr.at(E.Boolean, E.Boolean{ .value = (n.value == 0 or std.math.isNan(n.value)) }, allocator);
|
||||
@@ -2049,7 +2049,7 @@ pub fn toStringExprWithoutSideEffects(expr: *const Expr, allocator: std.mem.Allo
|
||||
.e_null => "null",
|
||||
.e_string => return expr.*,
|
||||
.e_undefined => "undefined",
|
||||
.e_boolean => |data| if (data.value) "true" else "false",
|
||||
.e_boolean, .e_branch_boolean => |data| if (data.value) "true" else "false",
|
||||
.e_big_int => |bigint| bigint.value,
|
||||
.e_number => |num| if (num.toString(allocator)) |str|
|
||||
str
|
||||
@@ -2151,6 +2151,7 @@ pub const Data = union(Tag) {
|
||||
e_commonjs_export_identifier: E.CommonJSExportIdentifier,
|
||||
|
||||
e_boolean: E.Boolean,
|
||||
e_branch_boolean: E.Boolean,
|
||||
e_number: E.Number,
|
||||
e_big_int: *E.BigInt,
|
||||
e_string: *E.String,
|
||||
@@ -2589,7 +2590,7 @@ pub const Data = union(Tag) {
|
||||
const symbol = e.ref.getSymbol(symbol_table);
|
||||
hasher.update(symbol.original_name);
|
||||
},
|
||||
inline .e_boolean, .e_number => |e| {
|
||||
inline .e_boolean, .e_branch_boolean, .e_number => |e| {
|
||||
writeAnyToHasher(hasher, e.value);
|
||||
},
|
||||
inline .e_big_int, .e_reg_exp => |e| {
|
||||
@@ -2643,6 +2644,7 @@ pub const Data = union(Tag) {
|
||||
return switch (this) {
|
||||
.e_number,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_inlined_enum,
|
||||
@@ -2671,6 +2673,7 @@ pub const Data = union(Tag) {
|
||||
|
||||
.e_number,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
// .e_reg_exp,
|
||||
@@ -2696,7 +2699,7 @@ pub const Data = union(Tag) {
|
||||
// rope strings can throw when toString is called.
|
||||
.e_string => |str| str.next == null,
|
||||
|
||||
.e_number, .e_boolean, .e_undefined, .e_null => true,
|
||||
.e_number, .e_boolean, .e_branch_boolean, .e_undefined, .e_null => true,
|
||||
// BigInt is deliberately excluded as a large enough BigInt could throw an out of memory error.
|
||||
//
|
||||
|
||||
@@ -2707,7 +2710,7 @@ pub const Data = union(Tag) {
|
||||
pub fn knownPrimitive(data: Expr.Data) PrimitiveType {
|
||||
return switch (data) {
|
||||
.e_big_int => .bigint,
|
||||
.e_boolean => .boolean,
|
||||
.e_boolean, .e_branch_boolean => .boolean,
|
||||
.e_null => .null,
|
||||
.e_number => .number,
|
||||
.e_string => .string,
|
||||
@@ -2843,7 +2846,7 @@ pub const Data = union(Tag) {
|
||||
// +'1' => 1
|
||||
return stringToEquivalentNumberValue(str.slice8());
|
||||
},
|
||||
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
|
||||
.e_boolean, .e_branch_boolean => |b| @as(f64, if (b.value) 1.0 else 0.0),
|
||||
.e_number => data.e_number.value,
|
||||
.e_inlined_enum => |inlined| switch (inlined.value.data) {
|
||||
.e_number => |num| num.value,
|
||||
@@ -2862,7 +2865,7 @@ pub const Data = union(Tag) {
|
||||
|
||||
pub fn toFiniteNumber(data: Expr.Data) ?f64 {
|
||||
return switch (data) {
|
||||
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
|
||||
.e_boolean, .e_branch_boolean => |b| @as(f64, if (b.value) 1.0 else 0.0),
|
||||
.e_number => if (std.math.isFinite(data.e_number.value))
|
||||
data.e_number.value
|
||||
else
|
||||
@@ -2953,12 +2956,12 @@ pub const Data = union(Tag) {
|
||||
.ok = ok,
|
||||
};
|
||||
},
|
||||
.e_boolean => |l| {
|
||||
.e_boolean, .e_branch_boolean => |l| {
|
||||
switch (right) {
|
||||
.e_boolean => {
|
||||
.e_boolean, .e_branch_boolean => |r| {
|
||||
return .{
|
||||
.ok = true,
|
||||
.equal = l.value == right.e_boolean.value,
|
||||
.equal = l.value == r.value,
|
||||
};
|
||||
},
|
||||
.e_number => |num| {
|
||||
@@ -2996,7 +2999,7 @@ pub const Data = union(Tag) {
|
||||
.equal = l.value == r.value.data.e_number.value,
|
||||
};
|
||||
},
|
||||
.e_boolean => |r| {
|
||||
.e_boolean, .e_branch_boolean => |r| {
|
||||
if (comptime kind == .loose) {
|
||||
return .{
|
||||
.ok = true,
|
||||
@@ -3111,7 +3114,7 @@ pub const Data = union(Tag) {
|
||||
.e_string => |e| e.toJS(allocator, globalObject),
|
||||
.e_null => jsc.JSValue.null,
|
||||
.e_undefined => .js_undefined,
|
||||
.e_boolean => |boolean| if (boolean.value)
|
||||
.e_boolean, .e_branch_boolean => |boolean| if (boolean.value)
|
||||
.true
|
||||
else
|
||||
.false,
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn scan(
|
||||
const record: *ImportRecord = &p.import_records.items[st.import_record_index];
|
||||
|
||||
if (record.path.isMacro()) {
|
||||
record.is_unused = true;
|
||||
record.flags.is_unused = true;
|
||||
record.path.is_disabled = true;
|
||||
continue;
|
||||
}
|
||||
@@ -190,8 +190,8 @@ pub fn scan(
|
||||
{
|
||||
// internal imports are presumed to be always used
|
||||
// require statements cannot be stripped
|
||||
if (!record.is_internal and !record.was_originally_require) {
|
||||
record.is_unused = true;
|
||||
if (!record.flags.is_internal and !record.flags.was_originally_require) {
|
||||
record.flags.is_unused = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ pub fn scan(
|
||||
st.star_name_loc = null;
|
||||
}
|
||||
|
||||
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
|
||||
record.flags.contains_default_alias = record.flags.contains_default_alias or st.default_name != null;
|
||||
|
||||
const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse
|
||||
ImportItemForNamespaceMap.init(allocator);
|
||||
@@ -265,7 +265,7 @@ pub fn scan(
|
||||
) catch |err| bun.handleOom(err);
|
||||
|
||||
if (st.star_name_loc) |loc| {
|
||||
record.contains_import_star = true;
|
||||
record.flags.contains_import_star = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
namespace_ref,
|
||||
js_ast.NamedImport{
|
||||
@@ -279,7 +279,7 @@ pub fn scan(
|
||||
}
|
||||
|
||||
if (st.default_name) |default| {
|
||||
record.contains_default_alias = true;
|
||||
record.flags.contains_default_alias = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
default.ref.?,
|
||||
.{
|
||||
@@ -313,7 +313,7 @@ pub fn scan(
|
||||
// We do not know at this stage whether or not the import statement is bundled
|
||||
// This keeps track of the `namespace_alias` incase, at printing time, we determine that we should print it with the namespace
|
||||
for (st.items) |item| {
|
||||
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
record.flags.contains_default_alias = record.flags.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
@@ -327,7 +327,7 @@ pub fn scan(
|
||||
|
||||
// Make sure the printer prints this as a property access
|
||||
var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()];
|
||||
if (record.contains_import_star or st.star_name_loc != null)
|
||||
if (record.flags.contains_import_star or st.star_name_loc != null)
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.alias,
|
||||
@@ -336,7 +336,7 @@ pub fn scan(
|
||||
};
|
||||
}
|
||||
|
||||
if (record.was_originally_require) {
|
||||
if (record.flags.was_originally_require) {
|
||||
var symbol = &p.symbols.items[namespace_ref.innerIndex()];
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
@@ -349,12 +349,12 @@ pub fn scan(
|
||||
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
|
||||
record.contains_import_star = record.contains_import_star or st.star_name_loc != null;
|
||||
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
|
||||
record.flags.contains_import_star = record.flags.contains_import_star or st.star_name_loc != null;
|
||||
record.flags.contains_default_alias = record.flags.contains_default_alias or st.default_name != null;
|
||||
|
||||
for (st.items) |*item| {
|
||||
record.contains_default_alias = record.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
record.contains_es_module_alias = record.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule");
|
||||
record.flags.contains_default_alias = record.flags.contains_default_alias or strings.eqlComptime(item.alias, "default");
|
||||
record.flags.contains_es_module_alias = record.flags.contains_es_module_alias or strings.eqlComptime(item.alias, "__esModule");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -456,7 +456,7 @@ pub fn scan(
|
||||
});
|
||||
try p.recordExport(alias.loc, alias.original_name, st.namespace_ref);
|
||||
var record = &p.import_records.items[st.import_record_index];
|
||||
record.contains_import_star = true;
|
||||
record.flags.contains_import_star = true;
|
||||
} else {
|
||||
// "export * from 'path'"
|
||||
try p.export_star_import_records.append(allocator, st.import_record_index);
|
||||
@@ -482,9 +482,9 @@ pub fn scan(
|
||||
|
||||
var record = &p.import_records.items[st.import_record_index];
|
||||
if (strings.eqlComptime(item.original_name, "default")) {
|
||||
record.contains_default_alias = true;
|
||||
record.flags.contains_default_alias = true;
|
||||
} else if (strings.eqlComptime(item.original_name, "__esModule")) {
|
||||
record.contains_es_module_alias = true;
|
||||
record.flags.contains_es_module_alias = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -480,7 +480,7 @@ pub const Runner = struct {
|
||||
this.macro.vm.waitForPromise(promise);
|
||||
|
||||
const promise_result = promise.result(this.macro.vm.jsc_vm);
|
||||
const rejected = promise.status(this.macro.vm.jsc_vm) == .rejected;
|
||||
const rejected = promise.status() == .rejected;
|
||||
|
||||
if (promise_result.isUndefined() and this.is_top_level) {
|
||||
this.is_top_level = false;
|
||||
|
||||
@@ -185,6 +185,13 @@ pub fn NewParser_(
|
||||
/// it to the symbol so the code generated `e_import_identifier`'s
|
||||
bun_app_namespace_ref: Ref = Ref.None,
|
||||
|
||||
/// Used to track the `feature` function from `import { feature } from "bun:bundle"`.
|
||||
/// When visiting e_call, if the target ref matches this, we replace the call with
|
||||
/// a boolean based on whether the feature flag is enabled.
|
||||
bundler_feature_flag_ref: Ref = Ref.None,
|
||||
/// Set to true when visiting an if/ternary condition. feature() calls are only valid in this context.
|
||||
in_branch_condition: bool = false,
|
||||
|
||||
scopes_in_order_visitor_index: usize = 0,
|
||||
has_classic_runtime_warned: bool = false,
|
||||
macro_call_count: MacroCallCountType = 0,
|
||||
@@ -511,7 +518,7 @@ pub fn NewParser_(
|
||||
p.import_records.items[import_record_index].tag = tag;
|
||||
}
|
||||
|
||||
p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
|
||||
p.import_records.items[import_record_index].flags.handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
|
||||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||||
|
||||
return p.newExpr(E.Import{
|
||||
@@ -566,7 +573,7 @@ pub fn NewParser_(
|
||||
}
|
||||
|
||||
const import_record_index = p.addImportRecord(.require_resolve, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable);
|
||||
p.import_records.items[import_record_index].handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
|
||||
p.import_records.items[import_record_index].flags.handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
|
||||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||||
|
||||
return p.newExpr(
|
||||
@@ -618,7 +625,7 @@ pub fn NewParser_(
|
||||
|
||||
if (should_unwrap_require) {
|
||||
const import_record_index = p.addImportRecordByRangeAndPath(.stmt, p.source.rangeOfString(arg.loc), path);
|
||||
p.import_records.items[import_record_index].handles_import_errors = handles_import_errors;
|
||||
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
|
||||
|
||||
// Note that this symbol may be completely removed later.
|
||||
var path_name = fs.PathName.init(path.text);
|
||||
@@ -651,7 +658,7 @@ pub fn NewParser_(
|
||||
}
|
||||
|
||||
const import_record_index = p.addImportRecordByRangeAndPath(.require, p.source.rangeOfString(arg.loc), path);
|
||||
p.import_records.items[import_record_index].handles_import_errors = handles_import_errors;
|
||||
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
|
||||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||||
|
||||
return p.newExpr(E.RequireString{ .import_record_index = import_record_index }, arg.loc);
|
||||
@@ -1325,7 +1332,7 @@ pub fn NewParser_(
|
||||
var import_record: *ImportRecord = &p.import_records.items[import_record_i];
|
||||
if (comptime is_internal)
|
||||
import_record.path.namespace = "runtime";
|
||||
import_record.is_internal = is_internal;
|
||||
import_record.flags.is_internal = is_internal;
|
||||
const import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator);
|
||||
var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + prefix.len);
|
||||
const clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len);
|
||||
@@ -2621,7 +2628,7 @@ pub fn NewParser_(
|
||||
if (is_macro) {
|
||||
const id = p.addImportRecord(.stmt, path.loc, path.text);
|
||||
p.import_records.items[id].path.namespace = js_ast.Macro.namespace;
|
||||
p.import_records.items[id].is_unused = true;
|
||||
p.import_records.items[id].flags.is_unused = true;
|
||||
|
||||
if (stmt.default_name) |name_loc| {
|
||||
const name = p.loadNameFromRef(name_loc.ref.?);
|
||||
@@ -2653,13 +2660,41 @@ pub fn NewParser_(
|
||||
return p.s(S.Empty{}, loc);
|
||||
}
|
||||
|
||||
// Handle `import { feature } from "bun:bundle"` - this is a special import
|
||||
// that provides static feature flag checking at bundle time.
|
||||
// We handle it here at parse time (similar to macros) rather than at visit time.
|
||||
if (strings.eqlComptime(path.text, "bun:bundle")) {
|
||||
// Look for the "feature" import and validate specifiers
|
||||
for (stmt.items) |*item| {
|
||||
// In ClauseItem from parseImportClause:
|
||||
// - alias is the name from the source module ("feature")
|
||||
// - original_name is the local binding name
|
||||
// - name.ref is the ref for the local binding
|
||||
if (strings.eqlComptime(item.alias, "feature")) {
|
||||
// Check for duplicate imports of feature
|
||||
if (p.bundler_feature_flag_ref.isValid()) {
|
||||
try p.log.addError(p.source, item.alias_loc, "`feature` from \"bun:bundle\" may only be imported once");
|
||||
continue;
|
||||
}
|
||||
// Declare the symbol and store the ref
|
||||
const name = p.loadNameFromRef(item.name.ref.?);
|
||||
const ref = try p.declareSymbol(.other, item.name.loc, name);
|
||||
p.bundler_feature_flag_ref = ref;
|
||||
} else {
|
||||
try p.log.addErrorFmt(p.source, item.alias_loc, p.allocator, "\"bun:bundle\" has no export named \"{s}\"", .{item.alias});
|
||||
}
|
||||
}
|
||||
// Return empty statement - the import is completely removed
|
||||
return p.s(S.Empty{}, loc);
|
||||
}
|
||||
|
||||
const macro_remap = if (comptime allow_macros)
|
||||
p.options.macro_context.getRemap(path.text)
|
||||
else
|
||||
null;
|
||||
|
||||
stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text);
|
||||
p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import;
|
||||
p.import_records.items[stmt.import_record_index].flags.was_originally_bare_import = was_originally_bare_import;
|
||||
|
||||
if (stmt.star_name_loc) |star| {
|
||||
const name = p.loadNameFromRef(stmt.namespace_ref);
|
||||
@@ -2720,9 +2755,9 @@ pub fn NewParser_(
|
||||
});
|
||||
|
||||
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
|
||||
p.import_records.items[new_import_id].is_unused = true;
|
||||
p.import_records.items[new_import_id].flags.is_unused = true;
|
||||
if (comptime only_scan_imports_and_do_not_visit) {
|
||||
p.import_records.items[new_import_id].is_internal = true;
|
||||
p.import_records.items[new_import_id].flags.is_internal = true;
|
||||
p.import_records.items[new_import_id].path.is_disabled = true;
|
||||
}
|
||||
stmt.default_name = null;
|
||||
@@ -2781,9 +2816,9 @@ pub fn NewParser_(
|
||||
});
|
||||
|
||||
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
|
||||
p.import_records.items[new_import_id].is_unused = true;
|
||||
p.import_records.items[new_import_id].flags.is_unused = true;
|
||||
if (comptime only_scan_imports_and_do_not_visit) {
|
||||
p.import_records.items[new_import_id].is_internal = true;
|
||||
p.import_records.items[new_import_id].flags.is_internal = true;
|
||||
p.import_records.items[new_import_id].path.is_disabled = true;
|
||||
}
|
||||
remap_count += 1;
|
||||
@@ -2809,11 +2844,11 @@ pub fn NewParser_(
|
||||
|
||||
if (remap_count > 0 and stmt.items.len == 0 and stmt.default_name == null) {
|
||||
p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace;
|
||||
p.import_records.items[stmt.import_record_index].is_unused = true;
|
||||
p.import_records.items[stmt.import_record_index].flags.is_unused = true;
|
||||
|
||||
if (comptime only_scan_imports_and_do_not_visit) {
|
||||
p.import_records.items[stmt.import_record_index].path.is_disabled = true;
|
||||
p.import_records.items[stmt.import_record_index].is_internal = true;
|
||||
p.import_records.items[stmt.import_record_index].flags.is_internal = true;
|
||||
}
|
||||
|
||||
return p.s(S.Empty{}, loc);
|
||||
@@ -3932,6 +3967,7 @@ pub fn NewParser_(
|
||||
.e_undefined,
|
||||
.e_missing,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
|
||||
@@ -134,17 +134,17 @@ pub const Parser = struct {
|
||||
// - import 'foo';
|
||||
// - import("foo")
|
||||
// - require("foo")
|
||||
import_record.is_unused = import_record.is_unused or
|
||||
import_record.flags.is_unused = import_record.flags.is_unused or
|
||||
(import_record.kind == .stmt and
|
||||
!import_record.was_originally_bare_import and
|
||||
!import_record.calls_runtime_re_export_fn);
|
||||
!import_record.flags.was_originally_bare_import and
|
||||
!import_record.flags.calls_runtime_re_export_fn);
|
||||
}
|
||||
|
||||
var iter = scan_pass.used_symbols.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
const val = entry.value_ptr;
|
||||
if (val.used) {
|
||||
scan_pass.import_records.items[val.import_record_index].is_unused = false;
|
||||
scan_pass.import_records.items[val.import_record_index].flags.is_unused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,7 +1023,7 @@ pub const Parser = struct {
|
||||
|
||||
const import_record: ?*const ImportRecord = brk: {
|
||||
for (p.import_records.items) |*import_record| {
|
||||
if (import_record.is_internal or import_record.is_unused) continue;
|
||||
if (import_record.flags.is_internal or import_record.flags.is_unused) continue;
|
||||
if (import_record.kind == .stmt) break :brk import_record;
|
||||
}
|
||||
|
||||
@@ -1094,7 +1094,7 @@ pub const Parser = struct {
|
||||
// If they use an import statement, we say it's ESM because that's not allowed in CommonJS files.
|
||||
const uses_any_import_statements = brk: {
|
||||
for (p.import_records.items) |*import_record| {
|
||||
if (import_record.is_internal or import_record.is_unused) continue;
|
||||
if (import_record.flags.is_internal or import_record.flags.is_unused) continue;
|
||||
if (import_record.kind == .stmt) break :brk true;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ pub const SideEffects = enum(u1) {
|
||||
.e_undefined,
|
||||
.e_string,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_inlined_enum,
|
||||
@@ -88,6 +89,7 @@ pub const SideEffects = enum(u1) {
|
||||
.e_undefined,
|
||||
.e_missing,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
@@ -277,7 +279,7 @@ pub const SideEffects = enum(u1) {
|
||||
}
|
||||
}
|
||||
|
||||
properties_slice[end] = prop_;
|
||||
properties_slice[end] = prop;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
@@ -545,6 +547,7 @@ pub const SideEffects = enum(u1) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_boolean,
|
||||
.e_branch_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
@@ -651,7 +654,7 @@ pub const SideEffects = enum(u1) {
|
||||
}
|
||||
switch (exp) {
|
||||
// Never null or undefined
|
||||
.e_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
|
||||
.e_boolean, .e_branch_boolean, .e_number, .e_string, .e_reg_exp, .e_function, .e_arrow, .e_big_int => {
|
||||
return Result{ .value = false, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
@@ -770,7 +773,7 @@ pub const SideEffects = enum(u1) {
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_boolean => |e| {
|
||||
.e_boolean, .e_branch_boolean => |e| {
|
||||
return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_number => |e| {
|
||||
|
||||
@@ -327,7 +327,7 @@ pub fn ParseStmt(
|
||||
|
||||
if (comptime track_symbol_usage_during_parse_pass) {
|
||||
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
|
||||
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
|
||||
p.import_records.items[import_record_index].flags.calls_runtime_re_export_fn = true;
|
||||
}
|
||||
|
||||
try p.lexer.expectOrInsertSemicolon();
|
||||
@@ -381,7 +381,7 @@ pub fn ParseStmt(
|
||||
|
||||
if (comptime track_symbol_usage_during_parse_pass) {
|
||||
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
|
||||
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
|
||||
p.import_records.items[import_record_index].flags.calls_runtime_re_export_fn = true;
|
||||
}
|
||||
p.current_scope.is_after_const_local_prefix = true;
|
||||
p.has_es_module_syntax = true;
|
||||
|
||||
@@ -923,7 +923,10 @@ pub fn VisitExpr(
|
||||
const e_ = expr.data.e_if;
|
||||
const is_call_target = @as(Expr.Data, p.call_target) == .e_if and expr.data.e_if == p.call_target.e_if;
|
||||
|
||||
const prev_in_branch = p.in_branch_condition;
|
||||
p.in_branch_condition = true;
|
||||
e_.test_ = p.visitExpr(e_.test_);
|
||||
p.in_branch_condition = prev_in_branch;
|
||||
|
||||
e_.test_ = SideEffects.simplifyBoolean(p, e_.test_);
|
||||
|
||||
@@ -1277,6 +1280,15 @@ pub fn VisitExpr(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle `feature("FLAG_NAME")` calls from `import { feature } from "bun:bundle"`
|
||||
// Check if the bundler_feature_flag_ref is set before calling the function
|
||||
// to avoid stack memory usage from copying values back and forth.
|
||||
if (p.bundler_feature_flag_ref.isValid()) {
|
||||
if (maybeReplaceBundlerFeatureCall(p, e_, expr.loc)) |result| {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (e_.target.data == .e_require_call_target) {
|
||||
e_.can_be_unwrapped_if_unused = .never;
|
||||
|
||||
@@ -1631,6 +1643,66 @@ pub fn VisitExpr(
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/// Handles `feature("FLAG_NAME")` calls from `import { feature } from "bun:bundle"`.
|
||||
/// This enables statically analyzable dead-code elimination through feature gating.
|
||||
///
|
||||
/// When a feature flag is enabled via `--feature=FLAG_NAME`, `feature("FLAG_NAME")`
|
||||
/// is replaced with `true`, otherwise it's replaced with `false`. This allows
|
||||
/// bundlers to eliminate dead code branches at build time.
|
||||
///
|
||||
/// Returns the replacement expression if this is a feature() call, or null otherwise.
|
||||
/// Note: Caller must check `p.bundler_feature_flag_ref.isValid()` before calling.
|
||||
fn maybeReplaceBundlerFeatureCall(p: *P, e_: *E.Call, loc: logger.Loc) ?Expr {
|
||||
// Check if the target is the `feature` function from "bun:bundle"
|
||||
// It could be e_identifier (for unbound) or e_import_identifier (for imports)
|
||||
const target_ref: ?Ref = switch (e_.target.data) {
|
||||
.e_identifier => |ident| ident.ref,
|
||||
.e_import_identifier => |ident| ident.ref,
|
||||
else => null,
|
||||
};
|
||||
|
||||
if (target_ref == null or !target_ref.?.eql(p.bundler_feature_flag_ref)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If control flow is dead, just return false without validation errors
|
||||
if (p.is_control_flow_dead) {
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
|
||||
// Validate: exactly one argument required
|
||||
if (e_.args.len != 1) {
|
||||
p.log.addError(p.source, loc, "feature() requires exactly one string argument") catch unreachable;
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
|
||||
const arg = e_.args.slice()[0];
|
||||
|
||||
// Validate: argument must be a string literal
|
||||
if (arg.data != .e_string) {
|
||||
p.log.addError(p.source, arg.loc, "feature() argument must be a string literal") catch unreachable;
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
|
||||
// Check if the feature flag is enabled
|
||||
// Use the underlying string data directly without allocation.
|
||||
// Feature flag names should be ASCII identifiers, so UTF-16 is unexpected.
|
||||
const flag_string = arg.data.e_string;
|
||||
if (flag_string.is_utf16) {
|
||||
p.log.addError(p.source, arg.loc, "feature() flag name must be an ASCII string") catch unreachable;
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
|
||||
// feature() can only be used directly in an if statement or ternary condition
|
||||
if (!p.in_branch_condition) {
|
||||
p.log.addError(p.source, loc, "feature() from \"bun:bundle\" can only be used directly in an if statement or ternary condition") catch unreachable;
|
||||
return p.newExpr(E.Boolean{ .value = false }, loc);
|
||||
}
|
||||
|
||||
const is_enabled = p.options.features.bundler_feature_flags.map.contains(flag_string.data);
|
||||
return .{ .data = .{ .e_branch_boolean = .{ .value = is_enabled } }, .loc = loc };
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1000,7 +1000,10 @@ pub fn VisitStmt(
|
||||
try stmts.append(stmt.*);
|
||||
}
|
||||
pub fn s_if(noalias p: *P, noalias stmts: *ListManaged(Stmt), noalias stmt: *Stmt, noalias data: *S.If) !void {
|
||||
const prev_in_branch = p.in_branch_condition;
|
||||
p.in_branch_condition = true;
|
||||
data.test_ = p.visitExpr(data.test_);
|
||||
p.in_branch_condition = prev_in_branch;
|
||||
|
||||
if (p.options.features.minify_syntax) {
|
||||
data.test_ = SideEffects.simplifyBoolean(p, data.test_);
|
||||
|
||||
@@ -150,6 +150,7 @@ pub const FilePoll = struct {
|
||||
const StaticPipeWriter = Subprocess.StaticPipeWriter.Poll;
|
||||
const ShellStaticPipeWriter = bun.shell.ShellSubprocess.StaticPipeWriter.Poll;
|
||||
const FileSink = jsc.WebCore.FileSink.Poll;
|
||||
const TerminalPoll = bun.api.Terminal.Poll;
|
||||
const DNSResolver = bun.api.dns.Resolver;
|
||||
const GetAddrInfoRequest = bun.api.dns.GetAddrInfoRequest;
|
||||
const Request = bun.api.dns.internal.Request;
|
||||
@@ -181,6 +182,7 @@ pub const FilePoll = struct {
|
||||
// LifecycleScriptSubprocessOutputReader,
|
||||
Process,
|
||||
ShellBufferedWriter, // i do not know why, but this has to be here otherwise compiler will complain about dependency loop
|
||||
TerminalPoll,
|
||||
});
|
||||
|
||||
pub const AllocatorType = enum {
|
||||
@@ -414,6 +416,12 @@ pub const FilePoll = struct {
|
||||
Request.MacAsyncDNS.onMachportChange(loader);
|
||||
},
|
||||
|
||||
@field(Owner.Tag, @typeName(TerminalPoll)) => {
|
||||
log("onUpdate " ++ kqueue_or_epoll ++ " (fd: {f}) Terminal", .{poll.fd});
|
||||
var handler: *TerminalPoll = ptr.as(TerminalPoll);
|
||||
handler.onPoll(size_or_offset, poll.flags.contains(.hup));
|
||||
},
|
||||
|
||||
else => {
|
||||
const possible_name = Owner.typeNameFromTag(@intFromEnum(ptr.tag()));
|
||||
log("onUpdate " ++ kqueue_or_epoll ++ " (fd: {f}) disconnected? (maybe: {s})", .{ poll.fd, possible_name orelse "<unknown>" });
|
||||
|
||||
@@ -36,7 +36,7 @@ bakeModuleLoaderImportModule(JSC::JSGlobalObject* global,
|
||||
|
||||
if (!keyString) {
|
||||
auto promise = JSC::JSInternalPromise::create(vm, global->internalPromiseStructure());
|
||||
promise->reject(global, JSC::createError(global, "import() requires a string"_s));
|
||||
promise->reject(vm, global, JSC::createError(global, "import() requires a string"_s));
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
@@ -3626,7 +3626,7 @@ pub fn emitMemoryVisualizerMessageTimer(timer: *EventLoopTimer, _: *const bun.ti
|
||||
assert(dev.magic == .valid);
|
||||
dev.emitMemoryVisualizerMessage();
|
||||
timer.state = .FIRED;
|
||||
dev.vm.timer.update(timer, &bun.timespec.msFromNow(1000));
|
||||
dev.vm.timer.update(timer, &bun.timespec.msFromNow(.allow_mocked_time, 1000));
|
||||
}
|
||||
|
||||
pub fn emitMemoryVisualizerMessageIfNeeded(dev: *DevServer) void {
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn onMessage(s: *HmrSocket, ws: AnyWebSocket, msg: []const u8, opcode: uws.O
|
||||
bun.assert(s.dev.memory_visualizer_timer.state != .ACTIVE);
|
||||
s.dev.vm.timer.update(
|
||||
&s.dev.memory_visualizer_timer,
|
||||
&bun.timespec.msFromNow(1000),
|
||||
&bun.timespec.msFromNow(.allow_mocked_time, 1000),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -925,7 +925,7 @@ pub fn IncrementalGraph(comptime side: bake.Side) type {
|
||||
// When an import record is duplicated, it gets marked unused.
|
||||
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
|
||||
// There is still a case where deduplication must happen.
|
||||
if (import_record.is_unused) return .stop;
|
||||
if (import_record.flags.is_unused) return .stop;
|
||||
if (import_record.source_index.isRuntime()) return .stop;
|
||||
|
||||
const key = import_record.path.keyForIncrementalGraph();
|
||||
@@ -1044,7 +1044,7 @@ pub fn IncrementalGraph(comptime side: bake.Side) type {
|
||||
// When an import record is duplicated, it gets marked unused.
|
||||
// This happens in `ConvertESMExportsForHmr.deduplicatedImport`
|
||||
// There is still a case where deduplication must happen.
|
||||
if (import_record.is_unused) continue;
|
||||
if (import_record.flags.is_unused) continue;
|
||||
|
||||
if (!import_record.source_index.isRuntime()) try_index_record: {
|
||||
// TODO: move this block into a function
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user