mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 02:18:47 +00:00
Compare commits
180 Commits
nektro-pat
...
kai/mimall
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d565d267 | ||
|
|
55ca88b9b7 | ||
|
|
54ad155668 | ||
|
|
709d9f858a | ||
|
|
ea690d81d1 | ||
|
|
3e9842e1e1 | ||
|
|
cf0bdce7d4 | ||
|
|
b1417f494d | ||
|
|
d354714791 | ||
|
|
e7672b2d04 | ||
|
|
7110dc10a4 | ||
|
|
784271f85e | ||
|
|
9b363e4ef6 | ||
|
|
e6f6a487a1 | ||
|
|
2d86f46e07 | ||
|
|
f5ef9cda3c | ||
|
|
9ad5d3c6c3 | ||
|
|
decf84c416 | ||
|
|
2d36588609 | ||
|
|
67e44e25e8 | ||
|
|
d1562a7670 | ||
|
|
287c7adf76 | ||
|
|
0a13653707 | ||
|
|
a2c252256c | ||
|
|
45c34beb65 | ||
|
|
a2da900e40 | ||
|
|
883453391f | ||
|
|
0dc136149d | ||
|
|
8526b2512e | ||
|
|
29068c2118 | ||
|
|
b47d0bf960 | ||
|
|
e7aa548c42 | ||
|
|
cf868fd4c6 | ||
|
|
5fd3a42fe6 | ||
|
|
fecfe8082b | ||
|
|
57f799b6c2 | ||
|
|
f5077d6f7b | ||
|
|
2112ef5801 | ||
|
|
e020d2d953 | ||
|
|
586805ddb6 | ||
|
|
a25d7a8450 | ||
|
|
e5e9734c02 | ||
|
|
151cc59d53 | ||
|
|
dd7a639a6f | ||
|
|
99c3824b31 | ||
|
|
3cb1b5c7dd | ||
|
|
ecd74ac14c | ||
|
|
599947de28 | ||
|
|
53a3a67a0f | ||
|
|
50eaa755c7 | ||
|
|
0e13449e60 | ||
|
|
255a3dbd04 | ||
|
|
d7a725952d | ||
|
|
22a37b2791 | ||
|
|
a79b7c83f2 | ||
|
|
426c630d64 | ||
|
|
b7ec589a26 | ||
|
|
9fd5b20aa3 | ||
|
|
4fa69773a3 | ||
|
|
270f843f65 | ||
|
|
a33de51419 | ||
|
|
447f8446b8 | ||
|
|
0845231a1e | ||
|
|
7dd85f9dd4 | ||
|
|
50e7d5c26e | ||
|
|
edaa2e487a | ||
|
|
659f9365ea | ||
|
|
ff372f44cb | ||
|
|
7b31393d44 | ||
|
|
bbe7f81ebe | ||
|
|
33d4757321 | ||
|
|
5097b129c6 | ||
|
|
a2637497a4 | ||
|
|
504052d9b0 | ||
|
|
cf9761367e | ||
|
|
fac5e71a0c | ||
|
|
53b870af74 | ||
|
|
bf24d1b527 | ||
|
|
49f33c948a | ||
|
|
c106820a57 | ||
|
|
a4555ee3df | ||
|
|
8d40ee17ed | ||
|
|
d9742eece7 | ||
|
|
37a207e2a4 | ||
|
|
3cf6da9c9b | ||
|
|
0c83ff3f7e | ||
|
|
41b1efe12c | ||
|
|
4751f12678 | ||
|
|
98524943f1 | ||
|
|
b6b3dc7eb5 | ||
|
|
020fe12887 | ||
|
|
8e6184707d | ||
|
|
a57dee5721 | ||
|
|
e4beddb839 | ||
|
|
dd427a1c61 | ||
|
|
0b7a6024e0 | ||
|
|
35027a1399 | ||
|
|
cf8d3183b3 | ||
|
|
45ed0cb08e | ||
|
|
6ad208bc32 | ||
|
|
b0799da968 | ||
|
|
a67ba81e0b | ||
|
|
7cdc5d879c | ||
|
|
1dc9fdfd9b | ||
|
|
584946b0ce | ||
|
|
3766f183e6 | ||
|
|
19fac68e81 | ||
|
|
2f84949fe0 | ||
|
|
964d4dac2c | ||
|
|
a9d62d58ea | ||
|
|
0827add9a3 | ||
|
|
05cff5cfde | ||
|
|
f5c138d646 | ||
|
|
ee88c489ab | ||
|
|
46e1c5a0fa | ||
|
|
5893ae99c2 | ||
|
|
0c46791d28 | ||
|
|
aab14c161a | ||
|
|
d8e5f6106f | ||
|
|
428c8d4bbf | ||
|
|
92f896ddd7 | ||
|
|
3b1842723e | ||
|
|
f5b397c040 | ||
|
|
c3c2dccc55 | ||
|
|
a9a7526ed1 | ||
|
|
74b1462ad4 | ||
|
|
47c8a67b75 | ||
|
|
2ed5b0ffad | ||
|
|
0bf0d8420e | ||
|
|
df61e88dc0 | ||
|
|
c088a6838f | ||
|
|
4deeadd53a | ||
|
|
3652008b0d | ||
|
|
7c65c35f8f | ||
|
|
455f3a65b9 | ||
|
|
4d301cc3c4 | ||
|
|
e9dc25200a | ||
|
|
ccbd3f3575 | ||
|
|
a72d74e09a | ||
|
|
04883a8bdc | ||
|
|
fe28e00d53 | ||
|
|
da856dd347 | ||
|
|
25d490fb65 | ||
|
|
806d6c156f | ||
|
|
198d7c3b19 | ||
|
|
dfe1a1848a | ||
|
|
0612f459a4 | ||
|
|
408fda7ad2 | ||
|
|
7ad3049e70 | ||
|
|
ed6f099e5e | ||
|
|
258a2a2e3a | ||
|
|
4568258960 | ||
|
|
dd27ad7716 | ||
|
|
d3d08eeb2d | ||
|
|
47727bdbe3 | ||
|
|
be5c69df79 | ||
|
|
9785e37e10 | ||
|
|
4494353abf | ||
|
|
fa1ad54257 | ||
|
|
a0687c06f8 | ||
|
|
15578df7fc | ||
|
|
b6d3768038 | ||
|
|
1ac2391b20 | ||
|
|
276eee74eb | ||
|
|
deaef1882b | ||
|
|
711de8a667 | ||
|
|
a5af485354 | ||
|
|
73e737be56 | ||
|
|
68d322f05f | ||
|
|
39eccf89a8 | ||
|
|
a729a046bd | ||
|
|
9bb4a6af19 | ||
|
|
07ffde8a69 | ||
|
|
bb67f2b345 | ||
|
|
7c4c360431 | ||
|
|
4b39a9b07d | ||
|
|
d0edcc69ae | ||
|
|
0cf2b71ff1 | ||
|
|
40bff9fea8 | ||
|
|
7726e5c670 |
@@ -303,9 +303,34 @@ function getCppAgent(platform, options) {
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "c8g.16xlarge" : "c7i.16xlarge",
|
||||
cpuCount: 32,
|
||||
threadsPerCore: 1,
|
||||
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
* @returns {string}
|
||||
*/
|
||||
function getLinkBunAgent(platform, options) {
|
||||
const { os, arch, distro } = platform;
|
||||
|
||||
if (os === "darwin") {
|
||||
return {
|
||||
queue: `build-${os}`,
|
||||
os,
|
||||
arch,
|
||||
};
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.large" : "r7i.large",
|
||||
});
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.xlarge" : "r7i.xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,7 +381,7 @@ function getTestAgent(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: `dev-server-ssr-110.test.ts` and `next-build.test.ts` run out of memory at 8GB of memory, so use 16GB instead.
|
||||
// TODO: delete this block when we upgrade to mimalloc v3
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: "c7i.2xlarge",
|
||||
@@ -502,7 +527,7 @@ function getLinkBunStep(platform, options) {
|
||||
key: `${getTargetKey(platform)}-build-bun`,
|
||||
label: `${getTargetLabel(platform)} - build-bun`,
|
||||
depends_on: [`${getTargetKey(platform)}-build-cpp`, `${getTargetKey(platform)}-build-zig`],
|
||||
agents: getCppAgent(platform, options),
|
||||
agents: getLinkBunAgent(platform, options),
|
||||
retry: getRetry(),
|
||||
cancel_on_build_failing: isMergeQueue(),
|
||||
env: {
|
||||
|
||||
103
.github/workflows/CLAUDE.md
vendored
Normal file
103
.github/workflows/CLAUDE.md
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
# GitHub Actions Workflow Maintenance Guide
|
||||
|
||||
This document provides guidance for maintaining the GitHub Actions workflows in this repository.
|
||||
|
||||
## format.yml Workflow
|
||||
|
||||
### Overview
|
||||
The `format.yml` workflow runs code formatters (Prettier, clang-format, and Zig fmt) on pull requests and pushes to main. It's optimized for speed by running all formatters in parallel.
|
||||
|
||||
### Key Components
|
||||
|
||||
#### 1. Clang-format Script (`scripts/run-clang-format.sh`)
|
||||
- **Purpose**: Formats C++ source and header files
|
||||
- **What it does**:
|
||||
- Reads C++ files from `cmake/sources/CxxSources.txt`
|
||||
- Finds all header files in `src/` and `packages/`
|
||||
- Excludes third-party directories (libuv, napi, deps, vendor, sqlite, etc.)
|
||||
- Requires specific clang-format version (no fallbacks)
|
||||
|
||||
**Important exclusions**:
|
||||
- `src/napi/` - Node API headers (third-party)
|
||||
- `src/bun.js/bindings/libuv/` - libuv headers (third-party)
|
||||
- `src/bun.js/bindings/sqlite/` - SQLite headers (third-party)
|
||||
- `src/bun.js/api/ffi-*.h` - FFI headers (generated/third-party)
|
||||
- `src/deps/` - Dependencies (third-party)
|
||||
- Files in `vendor/`, `third_party/`, `generated/` directories
|
||||
|
||||
#### 2. Parallel Execution
|
||||
The workflow runs all three formatters simultaneously:
|
||||
- Each formatter outputs with a prefix (`[prettier]`, `[clang-format]`, `[zig]`)
|
||||
- Output is streamed in real-time without blocking
|
||||
- Uses GitHub Actions groups (`::group::`) for collapsible sections
|
||||
|
||||
#### 3. Tool Installation
|
||||
|
||||
##### Clang-format-19
|
||||
- Installs ONLY `clang-format-19` package (not the entire LLVM toolchain)
|
||||
- Uses `--no-install-recommends --no-install-suggests` to skip unnecessary packages
|
||||
- Quiet installation with `-qq` and `-o=Dpkg::Use-Pty=0`
|
||||
|
||||
##### Zig
|
||||
- Downloads from `oven-sh/zig` releases (musl build for static linking)
|
||||
- URL: `https://github.com/oven-sh/zig/releases/download/autobuild-{COMMIT}/bootstrap-x86_64-linux-musl.zip`
|
||||
- Extracts to temp directory to avoid polluting the repository
|
||||
- Directory structure: `bootstrap-x86_64-linux-musl/zig`
|
||||
|
||||
### Updating the Workflow
|
||||
|
||||
#### To update Zig version:
|
||||
1. Find the new commit hash from https://github.com/oven-sh/zig/releases
|
||||
2. Replace the hash in the wget URL (line 65 of format.yml)
|
||||
3. Test that the URL is valid and the binary works
|
||||
|
||||
#### To update clang-format version:
|
||||
1. Update `LLVM_VERSION_MAJOR` environment variable at the top of format.yml
|
||||
2. Update the version check in `scripts/run-clang-format.sh`
|
||||
|
||||
#### To add/remove file exclusions:
|
||||
1. Edit the exclusion patterns in `scripts/run-clang-format.sh` (lines 34-39)
|
||||
2. Test locally to ensure the right files are being formatted
|
||||
|
||||
### Performance Optimizations
|
||||
1. **Parallel execution**: All formatters run simultaneously
|
||||
2. **Minimal installations**: Only required packages, no extras
|
||||
3. **Temp directories**: Tools downloaded to temp dirs, cleaned up after use
|
||||
4. **Streaming output**: Real-time feedback without buffering
|
||||
5. **Early start**: Formatting begins immediately after each tool is ready
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**If formatters appear to run sequentially:**
|
||||
- Check if output is being buffered (should use `sed` for line prefixing)
|
||||
- Ensure background processes use `&` and proper wait commands
|
||||
|
||||
**If third-party files are being formatted:**
|
||||
- Review exclusion patterns in `scripts/run-clang-format.sh`
|
||||
- Check if new third-party directories were added that need exclusion
|
||||
|
||||
**If clang-format installation is slow:**
|
||||
- Ensure using minimal package installation flags
|
||||
- Check if apt cache needs updating
|
||||
- Consider caching the clang-format binary between runs
|
||||
|
||||
### Testing Changes Locally
|
||||
|
||||
```bash
|
||||
# Test the clang-format script
|
||||
export LLVM_VERSION_MAJOR=19
|
||||
./scripts/run-clang-format.sh format
|
||||
|
||||
# Test with check mode (no modifications)
|
||||
./scripts/run-clang-format.sh check
|
||||
|
||||
# Test specific file exclusions
|
||||
./scripts/run-clang-format.sh format 2>&1 | grep -E "(libuv|napi|deps)"
|
||||
# Should return nothing if exclusions work correctly
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
- The script defaults to **format** mode (modifies files)
|
||||
- Always test locally before pushing workflow changes
|
||||
- The musl Zig build works on glibc systems due to static linking
|
||||
- Keep the exclusion list updated as new third-party code is added
|
||||
24
.github/workflows/auto-label-claude-prs.yml
vendored
Normal file
24
.github/workflows/auto-label-claude-prs.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Auto-label Claude PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
if: github.event.pull_request.user.login == 'robobun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Add claude label to PRs from robobun
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['claude']
|
||||
});
|
||||
48
.github/workflows/claude.yml
vendored
48
.github/workflows/claude.yml
vendored
@@ -13,23 +13,55 @@ on:
|
||||
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: ubuntu-latest
|
||||
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
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
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@beta
|
||||
# TODO: switch this out once they merge their v1
|
||||
uses: km-anthropic/claude-code-action@v1-dev
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
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 }}
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -6,6 +6,8 @@ on:
|
||||
- "docs/**"
|
||||
- "packages/bun-types/**.d.ts"
|
||||
- "CONTRIBUTING.md"
|
||||
- "src/cli/install.sh"
|
||||
- "src/cli/install.ps1"
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
82
.github/workflows/format.yml
vendored
82
.github/workflows/format.yml
vendored
@@ -37,24 +37,72 @@ jobs:
|
||||
- name: Setup Dependencies
|
||||
run: |
|
||||
bun install
|
||||
- name: Install LLVM
|
||||
- name: Format Code
|
||||
run: |
|
||||
curl -fsSL https://apt.llvm.org/llvm.sh | sudo bash -s -- ${{ env.LLVM_VERSION_MAJOR }} all
|
||||
- name: Setup Zig
|
||||
uses: mlugg/setup-zig@v1
|
||||
with:
|
||||
version: 0.14.0
|
||||
- name: Zig Format
|
||||
run: |
|
||||
zig fmt src
|
||||
./scripts/sort-imports.ts src
|
||||
zig fmt src
|
||||
- name: Prettier Format
|
||||
run: |
|
||||
bun run prettier
|
||||
- name: Clang Format
|
||||
run: |
|
||||
bun run clang-format
|
||||
# Start prettier in background with prefixed output
|
||||
echo "::group::Prettier"
|
||||
(bun run prettier 2>&1 | sed 's/^/[prettier] /' || echo "[prettier] Failed with exit code $?") &
|
||||
PRETTIER_PID=$!
|
||||
|
||||
# Start clang-format installation and formatting in background with prefixed output
|
||||
echo "::group::Clang-format"
|
||||
(
|
||||
echo "[clang-format] Installing clang-format-${{ env.LLVM_VERSION_MAJOR }}..."
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
|
||||
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${{ env.LLVM_VERSION_MAJOR }} main" | sudo tee /etc/apt/sources.list.d/llvm.list > /dev/null
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq --no-install-recommends --no-install-suggests -o=Dpkg::Use-Pty=0 clang-format-${{ env.LLVM_VERSION_MAJOR }}
|
||||
echo "[clang-format] Running clang-format..."
|
||||
LLVM_VERSION_MAJOR=${{ env.LLVM_VERSION_MAJOR }} ./scripts/run-clang-format.sh format 2>&1 | sed 's/^/[clang-format] /'
|
||||
) &
|
||||
CLANG_PID=$!
|
||||
|
||||
# Setup Zig in temp directory and run zig fmt in background with prefixed output
|
||||
echo "::group::Zig fmt"
|
||||
(
|
||||
ZIG_TEMP=$(mktemp -d)
|
||||
echo "[zig] Downloading Zig (musl build)..."
|
||||
wget -q -O "$ZIG_TEMP/zig.zip" https://github.com/oven-sh/zig/releases/download/autobuild-d1a4e0b0ddc75f37c6a090b97eef0cbb6335556e/bootstrap-x86_64-linux-musl.zip
|
||||
unzip -q -d "$ZIG_TEMP" "$ZIG_TEMP/zig.zip"
|
||||
export PATH="$ZIG_TEMP/bootstrap-x86_64-linux-musl:$PATH"
|
||||
echo "[zig] Running zig fmt..."
|
||||
zig fmt src 2>&1 | sed 's/^/[zig] /'
|
||||
./scripts/sort-imports.ts src 2>&1 | sed 's/^/[zig] /'
|
||||
zig fmt src 2>&1 | sed 's/^/[zig] /'
|
||||
rm -rf "$ZIG_TEMP"
|
||||
) &
|
||||
ZIG_PID=$!
|
||||
|
||||
# Wait for all formatting tasks to complete
|
||||
echo ""
|
||||
echo "Running formatters in parallel..."
|
||||
FAILED=0
|
||||
|
||||
if ! wait $PRETTIER_PID; then
|
||||
echo "::error::Prettier failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if ! wait $CLANG_PID; then
|
||||
echo "::error::Clang-format failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
if ! wait $ZIG_PID; then
|
||||
echo "::error::Zig fmt failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
# Exit with error if any formatter failed
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "::error::One or more formatters failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ All formatters completed successfully"
|
||||
- name: Ban Words
|
||||
run: |
|
||||
bun ./test/internal/ban-words.test.ts
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -184,4 +184,6 @@ codegen-for-zig-team.tar.gz
|
||||
*.sock
|
||||
scratch*.{js,ts,tsx,cjs,mjs}
|
||||
|
||||
*.bun-build
|
||||
*.bun-build
|
||||
|
||||
scripts/lldb-inline
|
||||
3
.vscode/launch.json
generated
vendored
3
.vscode/launch.json
generated
vendored
@@ -22,6 +22,9 @@
|
||||
"BUN_DEBUG_QUIET_LOGS": "1",
|
||||
"BUN_DEBUG_jest": "1",
|
||||
"BUN_GARBAGE_COLLECTOR_LEVEL": "1",
|
||||
// "BUN_JSC_validateExceptionChecks": "1",
|
||||
// "BUN_JSC_dumpSimulatedThrows": "1",
|
||||
// "BUN_JSC_unexpectedExceptionStackTraceLimit": "20",
|
||||
},
|
||||
"console": "internalConsole",
|
||||
"sourceMap": {
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -168,5 +168,5 @@
|
||||
"WebKit/WebInspectorUI": true,
|
||||
},
|
||||
"git.detectSubmodules": false,
|
||||
"bun.test.customScript": "./build/debug/bun-debug test"
|
||||
"bun.test.customScript": "./build/debug/bun-debug test",
|
||||
}
|
||||
|
||||
43
CLAUDE.md
43
CLAUDE.md
@@ -6,7 +6,7 @@ This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed
|
||||
|
||||
- **Build debug version**: `bun bd`
|
||||
- Creates a debug build at `./build/debug/bun-debug`
|
||||
- Compilation takes ~5 minutes. Don't timeout, be patient.
|
||||
- **CRITICAL**: DO NOT set a build timeout. Compilation takes ~5 minutes. Be patient.
|
||||
- **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>`
|
||||
@@ -43,7 +43,12 @@ Tests use Bun's Jest-compatible test runner with proper test fixtures:
|
||||
|
||||
```typescript
|
||||
import { test, expect } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
import {
|
||||
bunEnv,
|
||||
bunExe,
|
||||
normalizeBunSnapshot,
|
||||
tempDirWithFiles,
|
||||
} from "harness";
|
||||
|
||||
test("my feature", async () => {
|
||||
// Create temp directory with test files
|
||||
@@ -56,19 +61,25 @@ test("my feature", async () => {
|
||||
cmd: [bunExe(), "index.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.stdout.text(),
|
||||
proc.stderr.text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toBe("hello\n");
|
||||
// Prefer snapshot tests over expect(stdout).toBe("hello\n");
|
||||
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"hello"`);
|
||||
});
|
||||
```
|
||||
|
||||
- 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.
|
||||
|
||||
## Code Architecture
|
||||
|
||||
### Language Structure
|
||||
@@ -133,7 +144,6 @@ test("my feature", async () => {
|
||||
When implementing JavaScript classes in C++:
|
||||
|
||||
1. Create three classes if there's a public constructor:
|
||||
|
||||
- `class Foo : public JSC::JSDestructibleObject` (if has C++ fields)
|
||||
- `class FooPrototype : public JSC::JSNonFinalObject`
|
||||
- `class FooConstructor : public JSC::InternalFunction`
|
||||
@@ -193,7 +203,6 @@ Built-in JavaScript modules use special syntax and are organized as:
|
||||
```
|
||||
|
||||
3. **Debug helpers**:
|
||||
|
||||
- `$debug()` - Like console.log but stripped in release builds
|
||||
- `$assert()` - Assertions stripped in release builds
|
||||
- `if($debug) {}` - Check if debug env var is set
|
||||
@@ -221,15 +230,17 @@ bun ci
|
||||
## Important Development Notes
|
||||
|
||||
1. **Never use `bun test` or `bun <file>` directly** - always use `bun bd test` or `bun bd <command>`. `bun bd` compiles & runs the debug build.
|
||||
2. **Use `await using`** for proper resource cleanup with Bun APIs (Bun.spawn, Bun.serve, Bun.connect, etc.)
|
||||
3. **Follow existing code style** - check neighboring files for patterns
|
||||
4. **Create regression tests** in `test/regression/issue/` when fixing bugs
|
||||
5. **Use absolute paths** - Always use absolute paths in file operations
|
||||
6. **Avoid shell commands** - Don't use `find` or `grep` in tests; use Bun's Glob and built-in tools
|
||||
7. **Memory management** - In Zig code, be careful with allocators and use defer for cleanup
|
||||
8. **Cross-platform** - Test on macOS, Linux, and Windows when making platform-specific changes
|
||||
9. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_<scope>=1` to enable specific scopes
|
||||
10. **Transpiled source** - Find transpiled files in `/tmp/bun-debug-src/` for debugging
|
||||
2. **All changes must be tested** - if you're not testing your changes, you're not done.
|
||||
3. **Get your tests to pass**. If you didn't run the tests, your code does not work.
|
||||
4. **Follow existing code style** - check neighboring files for patterns
|
||||
5. **Create tests in the right folder** in `test/` and the test must end in `.test.ts` or `.test.tsx`
|
||||
6. **Use absolute paths** - Always use absolute paths in file operations
|
||||
7. **Avoid shell commands** - Don't use `find` or `grep` in tests; use Bun's Glob and built-in tools
|
||||
8. **Memory management** - In Zig code, be careful with allocators and use defer for cleanup
|
||||
9. **Cross-platform** - Run `bun run zig:check-all` to compile the Zig code on all platforms when making platform-specific changes
|
||||
10. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_<scope>=1` to enable specific scopes
|
||||
11. **Be humble & honest** - NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user.
|
||||
12. **Branch names must start with `claude/`** - This is a requirement for the CI to work.
|
||||
|
||||
## Key APIs and Features
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
"eventemitter3": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"fast-glob": "3.3.1",
|
||||
"fastify": "^5.0.0",
|
||||
"fdir": "^6.1.0",
|
||||
"mitata": "^1.0.25",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"string-width": "7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zx": "^7.2.3",
|
||||
},
|
||||
@@ -93,6 +95,18 @@
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.14.54", "", { "os": "linux", "cpu": "none" }, "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw=="],
|
||||
|
||||
"@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.2", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ=="],
|
||||
|
||||
"@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
|
||||
|
||||
"@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
|
||||
|
||||
"@fastify/forwarded": ["@fastify/forwarded@3.0.0", "", {}, "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA=="],
|
||||
|
||||
"@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
|
||||
|
||||
"@fastify/proxy-addr": ["@fastify/proxy-addr@5.0.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.1.1", "", { "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.0", "", {}, "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="],
|
||||
@@ -143,10 +157,20 @@
|
||||
|
||||
"@types/which": ["@types/which@3.0.3", "", {}, "sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g=="],
|
||||
|
||||
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
|
||||
|
||||
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
|
||||
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||
|
||||
"avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
|
||||
|
||||
"benchmark": ["benchmark@2.1.4", "", { "dependencies": { "lodash": "^4.17.4", "platform": "^1.3.3" } }, "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ=="],
|
||||
|
||||
"braces": ["braces@3.0.2", "", { "dependencies": { "fill-range": "^7.0.1" } }, "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="],
|
||||
@@ -167,12 +191,16 @@
|
||||
|
||||
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
|
||||
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
|
||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||
@@ -233,10 +261,22 @@
|
||||
|
||||
"execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
|
||||
|
||||
"fast-json-stringify": ["fast-json-stringify@6.0.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^2.0.0", "rfdc": "^1.2.0" } }, "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg=="],
|
||||
|
||||
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||
|
||||
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
|
||||
|
||||
"fastify": ["fastify@5.5.0", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.0", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^9.0.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-ZWSWlzj3K/DcULCnCjEiC2zn2FBPdlZsSA/pnPa/dbUfLvxkD/Nqmb0XXMXLrWkeM4uQPUvjdJpwtXmTfriXqw=="],
|
||||
|
||||
"fastq": ["fastq@1.15.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw=="],
|
||||
|
||||
"fdir": ["fdir@6.1.0", "", { "peerDependencies": { "picomatch": "2.x" } }, "sha512-274qhz5PxNnA/fybOu6apTCUnM0GnO3QazB6VH+oag/7DQskdYq8lm07ZSm90kEQuWYH5GvjAxGruuHrEr0bcg=="],
|
||||
@@ -245,6 +285,8 @@
|
||||
|
||||
"fill-range": ["fill-range@7.0.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="],
|
||||
|
||||
"find-my-way": ["find-my-way@9.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"from": ["from@0.1.7", "", {}, "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g=="],
|
||||
@@ -273,6 +315,8 @@
|
||||
|
||||
"ignore": ["ignore@5.3.0", "", {}, "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
@@ -289,10 +333,16 @@
|
||||
|
||||
"jsesc": ["jsesc@2.5.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="],
|
||||
|
||||
"json-schema-ref-resolver": ["json-schema-ref-resolver@2.0.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
|
||||
|
||||
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
|
||||
|
||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
@@ -323,6 +373,8 @@
|
||||
|
||||
"npm-run-path": ["npm-run-path@5.2.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg=="],
|
||||
|
||||
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||
|
||||
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
@@ -335,24 +387,50 @@
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"pino": ["pino@9.9.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-zxsRIQG9HzG+jEljmvmZupOMDUQ0Jpj0yAgE28jQvvrdYTlEaiGwelJpdndMl/MBuRr70heIj83QyqJUWaU8mQ=="],
|
||||
|
||||
"pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
||||
|
||||
"pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="],
|
||||
|
||||
"platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="],
|
||||
|
||||
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||
|
||||
"ps-tree": ["ps-tree@1.2.0", "", { "dependencies": { "event-stream": "=3.3.4" }, "bin": { "ps-tree": "./bin/ps-tree.js" } }, "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
|
||||
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||
|
||||
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
|
||||
|
||||
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
|
||||
|
||||
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
|
||||
|
||||
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"secure-json-parse": ["secure-json-parse@4.0.0", "", {}, "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA=="],
|
||||
|
||||
"semver": ["semver@6.3.0", "", { "bin": { "semver": "./bin/semver.js" } }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
@@ -363,8 +441,12 @@
|
||||
|
||||
"slash": ["slash@4.0.0", "", {}, "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew=="],
|
||||
|
||||
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
|
||||
|
||||
"split": ["split@0.3.3", "", { "dependencies": { "through": "2" } }, "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"stream-combiner": ["stream-combiner@0.0.4", "", { "dependencies": { "duplexer": "~0.1.1" } }, "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw=="],
|
||||
|
||||
"string-width": ["string-width@7.1.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw=="],
|
||||
@@ -375,6 +457,8 @@
|
||||
|
||||
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
|
||||
|
||||
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
|
||||
|
||||
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
@@ -383,6 +467,8 @@
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
|
||||
|
||||
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
@@ -407,8 +493,14 @@
|
||||
|
||||
"ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"avvio/fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"fastify/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"string-width": "7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
|
||||
77
bench/postMessage/postMessage-string.mjs
Normal file
77
bench/postMessage/postMessage-string.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
// Benchmark for string fast path optimization in postMessage with Workers
|
||||
|
||||
import { bench, run } from "mitata";
|
||||
import { Worker, isMainThread, parentPort } from "node:worker_threads";
|
||||
|
||||
// Test strings of different sizes
|
||||
const strings = {
|
||||
small: "Hello world",
|
||||
medium: Buffer.alloc("Hello World!!!".length * 1024, "Hello World!!!").toString(),
|
||||
large: Buffer.alloc("Hello World!!!".length * 1024 * 256, "Hello World!!!").toString(),
|
||||
};
|
||||
|
||||
let worker;
|
||||
let receivedCount = new Int32Array(new SharedArrayBuffer(4));
|
||||
let sentCount = 0;
|
||||
|
||||
function createWorker() {
|
||||
const workerCode = `
|
||||
import { parentPort, workerData } from "node:worker_threads";
|
||||
|
||||
let int = workerData;
|
||||
|
||||
parentPort?.on("message", data => {
|
||||
Atomics.add(int, 0, 1);
|
||||
});
|
||||
`;
|
||||
|
||||
worker = new Worker(workerCode, { eval: true, workerData: receivedCount });
|
||||
|
||||
worker.on("message", confirmationId => {});
|
||||
|
||||
worker.on("error", error => {
|
||||
console.error("Worker error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize worker before running benchmarks
|
||||
createWorker();
|
||||
|
||||
function fmt(int) {
|
||||
if (int < 1000) {
|
||||
return `${int} chars`;
|
||||
}
|
||||
|
||||
if (int < 100000) {
|
||||
return `${(int / 1024) | 0} KB`;
|
||||
}
|
||||
|
||||
return `${(int / 1024 / 1024) | 0} MB`;
|
||||
}
|
||||
|
||||
// Benchmark postMessage with pure strings (uses fast path)
|
||||
bench("postMessage(" + fmt(strings.small.length) + " string)", async () => {
|
||||
sentCount++;
|
||||
worker.postMessage(strings.small);
|
||||
});
|
||||
|
||||
bench("postMessage(" + fmt(strings.medium.length) + " string)", async () => {
|
||||
sentCount++;
|
||||
worker.postMessage(strings.medium);
|
||||
});
|
||||
|
||||
bench("postMessage(" + fmt(strings.large.length) + " string)", async () => {
|
||||
sentCount++;
|
||||
worker.postMessage(strings.large);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
if (receivedCount[0] !== sentCount) {
|
||||
throw new Error("Expected " + receivedCount[0] + " to equal " + sentCount);
|
||||
}
|
||||
|
||||
// Cleanup worker
|
||||
worker?.terminate();
|
||||
56
bench/postMessage/structureClone-string.mjs
Normal file
56
bench/postMessage/structureClone-string.mjs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Benchmark for string fast path optimization in postMessage and structuredClone
|
||||
|
||||
import { bench, run } from "mitata";
|
||||
|
||||
// Test strings of different sizes
|
||||
const strings = {
|
||||
small: "Hello world",
|
||||
medium: "Hello World!!!".repeat(1024).split("").join(""),
|
||||
large: "Hello World!!!".repeat(1024).repeat(1024).split("").join(""),
|
||||
};
|
||||
|
||||
console.log("String fast path benchmark");
|
||||
console.log("Comparing pure strings (fast path) vs objects containing strings (traditional)");
|
||||
console.log("For structuredClone, pure strings should have constant time regardless of size.");
|
||||
console.log("");
|
||||
|
||||
// Benchmark structuredClone with pure strings (uses fast path)
|
||||
bench("structuredClone small string (fast path)", () => {
|
||||
structuredClone(strings.small);
|
||||
});
|
||||
|
||||
bench("structuredClone medium string (fast path)", () => {
|
||||
structuredClone(strings.medium);
|
||||
});
|
||||
|
||||
bench("structuredClone large string (fast path)", () => {
|
||||
structuredClone(strings.large);
|
||||
});
|
||||
|
||||
// Benchmark structuredClone with objects containing strings (traditional path)
|
||||
bench("structuredClone object with small string", () => {
|
||||
structuredClone({ str: strings.small });
|
||||
});
|
||||
|
||||
bench("structuredClone object with medium string", () => {
|
||||
structuredClone({ str: strings.medium });
|
||||
});
|
||||
|
||||
bench("structuredClone object with large string", () => {
|
||||
structuredClone({ str: strings.large });
|
||||
});
|
||||
|
||||
// Multiple string cloning benchmark
|
||||
bench("structuredClone 100 small strings", () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
structuredClone(strings.small);
|
||||
}
|
||||
});
|
||||
|
||||
bench("structuredClone 100 small objects", () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
structuredClone({ str: strings.small });
|
||||
}
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -28,10 +28,4 @@ bench("brotli compress stream", async () => {
|
||||
await pipeline(source, compress);
|
||||
});
|
||||
|
||||
bench("brotli decompress stream", async () => {
|
||||
const source = Readable.from([compressed]);
|
||||
const decompress = createBrotliDecompress();
|
||||
await pipeline(source, decompress);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
37
bench/snippets/strip-ansi.mjs
Normal file
37
bench/snippets/strip-ansi.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
import npmStripAnsi from "strip-ansi";
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
let bunStripANSI = null;
|
||||
if (!process.env.FORCE_NPM) {
|
||||
bunStripANSI = globalThis?.Bun?.stripANSI;
|
||||
}
|
||||
|
||||
const stripANSI = bunStripANSI || npmStripAnsi;
|
||||
const formatter = new Intl.NumberFormat();
|
||||
const format = n => {
|
||||
return formatter.format(n);
|
||||
};
|
||||
|
||||
const inputs = [
|
||||
["hello world", "no-ansi"],
|
||||
["\x1b[31mred\x1b[39m", "ansi"],
|
||||
["a".repeat(1024 * 16), "long-no-ansi"],
|
||||
["\x1b[31mred\x1b[39m".repeat(1024 * 16), "long-ansi"],
|
||||
];
|
||||
|
||||
const maxInputLength = Math.max(...inputs.map(([input]) => input.length));
|
||||
|
||||
for (const [input, textLabel] of inputs) {
|
||||
const label = bunStripANSI ? "Bun.stripANSI" : "npm/strip-ansi";
|
||||
const name = `${label} ${format(input.length).padStart(format(maxInputLength).length, " ")} chars ${textLabel}`;
|
||||
|
||||
bench(name, () => {
|
||||
stripANSI(input);
|
||||
});
|
||||
|
||||
if (bunStripANSI && bunStripANSI(input) !== npmStripAnsi(input)) {
|
||||
throw new Error("strip-ansi mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
await run();
|
||||
16
bun.lock
16
bun.lock
@@ -6,6 +6,7 @@
|
||||
"devDependencies": {
|
||||
"@lezer/common": "^1.2.3",
|
||||
"@lezer/cpp": "^1.1.3",
|
||||
"@types/bun": "workspace:*",
|
||||
"bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8",
|
||||
"esbuild": "^0.21.4",
|
||||
"mitata": "^0.1.11",
|
||||
@@ -15,7 +16,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"source-map-js": "^1.2.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "5.9.2",
|
||||
},
|
||||
},
|
||||
"packages/@types/bun": {
|
||||
@@ -32,7 +33,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19",
|
||||
"typescript": "^5.0.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19",
|
||||
@@ -40,8 +40,8 @@
|
||||
},
|
||||
},
|
||||
"overrides": {
|
||||
"@types/bun": "workspace:packages/@types/bun",
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
"@types/bun": "workspace:packages/@types/bun",
|
||||
},
|
||||
"packages": {
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
@@ -148,7 +148,7 @@
|
||||
|
||||
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
|
||||
|
||||
"@sentry/types": ["@sentry/types@7.120.3", "", {}, "sha512-C4z+3kGWNFJ303FC+FxAd4KkHvxpNFYAFN8iMIgBwJdpIl25KZ8Q/VdGn0MLLUEHNLvjob0+wvwlcRBBNLXOow=="],
|
||||
"@sentry/types": ["@sentry/types@7.120.4", "", {}, "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q=="],
|
||||
|
||||
"@types/aws-lambda": ["@types/aws-lambda@8.10.152", "", {}, "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw=="],
|
||||
|
||||
@@ -160,9 +160,9 @@
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="],
|
||||
"@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||
"@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="],
|
||||
|
||||
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
|
||||
|
||||
@@ -308,11 +308,11 @@
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
|
||||
|
||||
"universal-github-app-jwt": ["universal-github-app-jwt@1.2.0", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" } }, "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g=="],
|
||||
|
||||
|
||||
@@ -102,6 +102,11 @@ else()
|
||||
endif()
|
||||
|
||||
optionx(ENABLE_ASAN BOOL "If ASAN support should be enabled" DEFAULT ${DEFAULT_ASAN})
|
||||
optionx(ENABLE_ZIG_ASAN BOOL "If Zig ASAN support should be enabled" DEFAULT ${ENABLE_ASAN})
|
||||
|
||||
if (NOT ENABLE_ASAN)
|
||||
set(ENABLE_ZIG_ASAN OFF)
|
||||
endif()
|
||||
|
||||
if(RELEASE AND LINUX AND CI AND NOT ENABLE_ASSERTIONS AND NOT ENABLE_ASAN)
|
||||
set(DEFAULT_LTO ON)
|
||||
|
||||
@@ -42,6 +42,7 @@ src/bun.js/bindings/DOMURL.cpp
|
||||
src/bun.js/bindings/DOMWrapperWorld.cpp
|
||||
src/bun.js/bindings/DoubleFormatter.cpp
|
||||
src/bun.js/bindings/EncodeURIComponent.cpp
|
||||
src/bun.js/bindings/EncodingTables.cpp
|
||||
src/bun.js/bindings/ErrorCode.cpp
|
||||
src/bun.js/bindings/ErrorStackFrame.cpp
|
||||
src/bun.js/bindings/ErrorStackTrace.cpp
|
||||
@@ -192,7 +193,16 @@ src/bun.js/bindings/ServerRouteList.cpp
|
||||
src/bun.js/bindings/spawn.cpp
|
||||
src/bun.js/bindings/SQLClient.cpp
|
||||
src/bun.js/bindings/sqlite/JSSQLStatement.cpp
|
||||
src/bun.js/bindings/stripANSI.cpp
|
||||
src/bun.js/bindings/Strong.cpp
|
||||
src/bun.js/bindings/TextCodec.cpp
|
||||
src/bun.js/bindings/TextCodecCJK.cpp
|
||||
src/bun.js/bindings/TextCodecReplacement.cpp
|
||||
src/bun.js/bindings/TextCodecSingleByte.cpp
|
||||
src/bun.js/bindings/TextCodecUserDefined.cpp
|
||||
src/bun.js/bindings/TextCodecWrapper.cpp
|
||||
src/bun.js/bindings/TextEncoding.cpp
|
||||
src/bun.js/bindings/TextEncodingRegistry.cpp
|
||||
src/bun.js/bindings/Uint8Array.cpp
|
||||
src/bun.js/bindings/Undici.cpp
|
||||
src/bun.js/bindings/URLDecomposition.cpp
|
||||
|
||||
@@ -65,6 +65,12 @@ src/js/internal/linkedlist.ts
|
||||
src/js/internal/primordials.js
|
||||
src/js/internal/promisify.ts
|
||||
src/js/internal/shared.ts
|
||||
src/js/internal/sql/errors.ts
|
||||
src/js/internal/sql/postgres.ts
|
||||
src/js/internal/sql/query.ts
|
||||
src/js/internal/sql/shared.ts
|
||||
src/js/internal/sql/sqlite.ts
|
||||
src/js/internal/sql/utils.ts
|
||||
src/js/internal/stream.promises.ts
|
||||
src/js/internal/stream.ts
|
||||
src/js/internal/streams/add-abort-signal.ts
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
src/allocators.zig
|
||||
src/allocators/AllocationScope.zig
|
||||
src/allocators/basic.zig
|
||||
src/allocators/fallback.zig
|
||||
src/allocators/fallback/z.zig
|
||||
src/allocators/LinuxMemFdAllocator.zig
|
||||
src/allocators/MaxHeapAllocator.zig
|
||||
src/allocators/MemoryReportingAllocator.zig
|
||||
@@ -19,19 +21,43 @@ src/ast/base.zig
|
||||
src/ast/Binding.zig
|
||||
src/ast/BundledAst.zig
|
||||
src/ast/CharFreq.zig
|
||||
src/ast/ConvertESMExportsForHmr.zig
|
||||
src/ast/E.zig
|
||||
src/ast/Expr.zig
|
||||
src/ast/foldStringAddition.zig
|
||||
src/ast/G.zig
|
||||
src/ast/ImportScanner.zig
|
||||
src/ast/KnownGlobal.zig
|
||||
src/ast/Macro.zig
|
||||
src/ast/maybe.zig
|
||||
src/ast/NewStore.zig
|
||||
src/ast/Op.zig
|
||||
src/ast/P.zig
|
||||
src/ast/parse.zig
|
||||
src/ast/parseFn.zig
|
||||
src/ast/parseImportExport.zig
|
||||
src/ast/parseJSXElement.zig
|
||||
src/ast/parsePrefix.zig
|
||||
src/ast/parseProperty.zig
|
||||
src/ast/Parser.zig
|
||||
src/ast/parseStmt.zig
|
||||
src/ast/parseSuffix.zig
|
||||
src/ast/parseTypescript.zig
|
||||
src/ast/S.zig
|
||||
src/ast/Scope.zig
|
||||
src/ast/ServerComponentBoundary.zig
|
||||
src/ast/SideEffects.zig
|
||||
src/ast/skipTypescript.zig
|
||||
src/ast/Stmt.zig
|
||||
src/ast/Symbol.zig
|
||||
src/ast/symbols.zig
|
||||
src/ast/TS.zig
|
||||
src/ast/TypeScript.zig
|
||||
src/ast/UseDirective.zig
|
||||
src/ast/visit.zig
|
||||
src/ast/visitBinaryExpression.zig
|
||||
src/ast/visitExpr.zig
|
||||
src/ast/visitStmt.zig
|
||||
src/async/posix_event_loop.zig
|
||||
src/async/stub_event_loop.zig
|
||||
src/async/windows_event_loop.zig
|
||||
@@ -72,6 +98,11 @@ src/bun.js/api/bun/spawn.zig
|
||||
src/bun.js/api/bun/spawn/stdio.zig
|
||||
src/bun.js/api/bun/ssl_wrapper.zig
|
||||
src/bun.js/api/bun/subprocess.zig
|
||||
src/bun.js/api/bun/subprocess/Readable.zig
|
||||
src/bun.js/api/bun/subprocess/ResourceUsage.zig
|
||||
src/bun.js/api/bun/subprocess/StaticPipeWriter.zig
|
||||
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
|
||||
src/bun.js/api/bun/subprocess/Writable.zig
|
||||
src/bun.js/api/bun/udp_socket.zig
|
||||
src/bun.js/api/bun/x509.zig
|
||||
src/bun.js/api/BunObject.zig
|
||||
@@ -104,6 +135,7 @@ src/bun.js/api/server/StaticRoute.zig
|
||||
src/bun.js/api/server/WebSocketServerContext.zig
|
||||
src/bun.js/api/streams.classes.zig
|
||||
src/bun.js/api/Timer.zig
|
||||
src/bun.js/api/Timer/DateHeaderTimer.zig
|
||||
src/bun.js/api/Timer/EventLoopTimer.zig
|
||||
src/bun.js/api/Timer/ImmediateObject.zig
|
||||
src/bun.js/api/Timer/TimeoutObject.zig
|
||||
@@ -165,6 +197,7 @@ src/bun.js/bindings/SourceProvider.zig
|
||||
src/bun.js/bindings/SourceType.zig
|
||||
src/bun.js/bindings/static_export.zig
|
||||
src/bun.js/bindings/SystemError.zig
|
||||
src/bun.js/bindings/TextCodec.zig
|
||||
src/bun.js/bindings/URL.zig
|
||||
src/bun.js/bindings/URLSearchParams.zig
|
||||
src/bun.js/bindings/VM.zig
|
||||
@@ -250,7 +283,84 @@ src/bun.js/RuntimeTranspilerCache.zig
|
||||
src/bun.js/SavedSourceMap.zig
|
||||
src/bun.js/Strong.zig
|
||||
src/bun.js/test/diff_format.zig
|
||||
src/bun.js/test/diff/diff_match_patch.zig
|
||||
src/bun.js/test/diff/printDiff.zig
|
||||
src/bun.js/test/expect.zig
|
||||
src/bun.js/test/expect/toBe.zig
|
||||
src/bun.js/test/expect/toBeArray.zig
|
||||
src/bun.js/test/expect/toBeArrayOfSize.zig
|
||||
src/bun.js/test/expect/toBeBoolean.zig
|
||||
src/bun.js/test/expect/toBeCloseTo.zig
|
||||
src/bun.js/test/expect/toBeDate.zig
|
||||
src/bun.js/test/expect/toBeDefined.zig
|
||||
src/bun.js/test/expect/toBeEmpty.zig
|
||||
src/bun.js/test/expect/toBeEmptyObject.zig
|
||||
src/bun.js/test/expect/toBeEven.zig
|
||||
src/bun.js/test/expect/toBeFalse.zig
|
||||
src/bun.js/test/expect/toBeFalsy.zig
|
||||
src/bun.js/test/expect/toBeFinite.zig
|
||||
src/bun.js/test/expect/toBeFunction.zig
|
||||
src/bun.js/test/expect/toBeGreaterThan.zig
|
||||
src/bun.js/test/expect/toBeGreaterThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeInstanceOf.zig
|
||||
src/bun.js/test/expect/toBeInteger.zig
|
||||
src/bun.js/test/expect/toBeLessThan.zig
|
||||
src/bun.js/test/expect/toBeLessThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeNaN.zig
|
||||
src/bun.js/test/expect/toBeNegative.zig
|
||||
src/bun.js/test/expect/toBeNil.zig
|
||||
src/bun.js/test/expect/toBeNull.zig
|
||||
src/bun.js/test/expect/toBeNumber.zig
|
||||
src/bun.js/test/expect/toBeObject.zig
|
||||
src/bun.js/test/expect/toBeOdd.zig
|
||||
src/bun.js/test/expect/toBeOneOf.zig
|
||||
src/bun.js/test/expect/toBePositive.zig
|
||||
src/bun.js/test/expect/toBeString.zig
|
||||
src/bun.js/test/expect/toBeSymbol.zig
|
||||
src/bun.js/test/expect/toBeTrue.zig
|
||||
src/bun.js/test/expect/toBeTruthy.zig
|
||||
src/bun.js/test/expect/toBeTypeOf.zig
|
||||
src/bun.js/test/expect/toBeUndefined.zig
|
||||
src/bun.js/test/expect/toBeValidDate.zig
|
||||
src/bun.js/test/expect/toBeWithin.zig
|
||||
src/bun.js/test/expect/toContain.zig
|
||||
src/bun.js/test/expect/toContainAllKeys.zig
|
||||
src/bun.js/test/expect/toContainAllValues.zig
|
||||
src/bun.js/test/expect/toContainAnyKeys.zig
|
||||
src/bun.js/test/expect/toContainAnyValues.zig
|
||||
src/bun.js/test/expect/toContainEqual.zig
|
||||
src/bun.js/test/expect/toContainKey.zig
|
||||
src/bun.js/test/expect/toContainKeys.zig
|
||||
src/bun.js/test/expect/toContainValue.zig
|
||||
src/bun.js/test/expect/toContainValues.zig
|
||||
src/bun.js/test/expect/toEndWith.zig
|
||||
src/bun.js/test/expect/toEqual.zig
|
||||
src/bun.js/test/expect/toEqualIgnoringWhitespace.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalled.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledOnce.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledTimes.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenLastCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenNthCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveLastReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveLength.zig
|
||||
src/bun.js/test/expect/toHaveNthReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveProperty.zig
|
||||
src/bun.js/test/expect/toHaveReturned.zig
|
||||
src/bun.js/test/expect/toHaveReturnedTimes.zig
|
||||
src/bun.js/test/expect/toHaveReturnedWith.zig
|
||||
src/bun.js/test/expect/toInclude.zig
|
||||
src/bun.js/test/expect/toIncludeRepeated.zig
|
||||
src/bun.js/test/expect/toMatch.zig
|
||||
src/bun.js/test/expect/toMatchInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toMatchObject.zig
|
||||
src/bun.js/test/expect/toMatchSnapshot.zig
|
||||
src/bun.js/test/expect/toSatisfy.zig
|
||||
src/bun.js/test/expect/toStartWith.zig
|
||||
src/bun.js/test/expect/toStrictEqual.zig
|
||||
src/bun.js/test/expect/toThrow.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig
|
||||
src/bun.js/test/jest.zig
|
||||
src/bun.js/test/pretty_format.zig
|
||||
src/bun.js/test/snapshot.zig
|
||||
@@ -487,7 +597,6 @@ src/defines.zig
|
||||
src/deps/boringssl.translated.zig
|
||||
src/deps/brotli_c.zig
|
||||
src/deps/c_ares.zig
|
||||
src/deps/diffz/DiffMatchPatch.zig
|
||||
src/deps/libdeflate.zig
|
||||
src/deps/libuv.zig
|
||||
src/deps/lol-html.zig
|
||||
@@ -682,6 +791,9 @@ src/Progress.zig
|
||||
src/ptr.zig
|
||||
src/ptr/Cow.zig
|
||||
src/ptr/CowSlice.zig
|
||||
src/ptr/meta.zig
|
||||
src/ptr/owned.zig
|
||||
src/ptr/owned/maybe.zig
|
||||
src/ptr/ref_count.zig
|
||||
src/ptr/tagged_pointer.zig
|
||||
src/ptr/weak_ptr.zig
|
||||
@@ -706,8 +818,10 @@ src/s3/multipart.zig
|
||||
src/s3/simple_request.zig
|
||||
src/s3/storage_class.zig
|
||||
src/safety.zig
|
||||
src/safety/alloc_ptr.zig
|
||||
src/safety/alloc.zig
|
||||
src/safety/CriticalSection.zig
|
||||
src/safety/thread_id.zig
|
||||
src/safety/ThreadLock.zig
|
||||
src/semver.zig
|
||||
src/semver/ExternalString.zig
|
||||
src/semver/SemverObject.zig
|
||||
@@ -858,6 +972,10 @@ src/string/StringJoiner.zig
|
||||
src/string/WTFStringImpl.zig
|
||||
src/sys_uv.zig
|
||||
src/sys.zig
|
||||
src/sys/coreutils_error_map.zig
|
||||
src/sys/Error.zig
|
||||
src/sys/File.zig
|
||||
src/sys/libuv_error_map.zig
|
||||
src/system_timer.zig
|
||||
src/test/fixtures.zig
|
||||
src/test/recover.zig
|
||||
|
||||
@@ -618,7 +618,7 @@ register_command(
|
||||
-Doptimize=${ZIG_OPTIMIZE}
|
||||
-Dcpu=${ZIG_CPU}
|
||||
-Denable_logs=$<IF:$<BOOL:${ENABLE_LOGS}>,true,false>
|
||||
-Denable_asan=$<IF:$<BOOL:${ENABLE_ASAN}>,true,false>
|
||||
-Denable_asan=$<IF:$<BOOL:${ENABLE_ZIG_ASAN}>,true,false>
|
||||
-Dversion=${VERSION}
|
||||
-Dreported_nodejs_version=${NODEJS_VERSION}
|
||||
-Dcanary=${CANARY_REVISION}
|
||||
@@ -1034,7 +1034,6 @@ if(LINUX)
|
||||
--ld-path=${LLD_PROGRAM}
|
||||
-fno-pic
|
||||
-Wl,-no-pie
|
||||
-Wl,-icf=safe
|
||||
-Wl,--as-needed
|
||||
-Wl,-z,stack-size=12800000
|
||||
-Wl,--compress-debug-sections=zlib
|
||||
@@ -1060,6 +1059,13 @@ if(LINUX)
|
||||
-Wl,--gc-sections
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DEBUG AND NOT ENABLE_ASAN)
|
||||
target_link_options(${bun} PUBLIC
|
||||
-Wl,-icf=safe
|
||||
)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
# --- Symbols list ---
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
HdrHistogram/HdrHistogram_c
|
||||
COMMIT
|
||||
652d51bcc36744fd1a6debfeb1a8a5f58b14022c
|
||||
8dcce8f68512fca460b171bccc3a5afce0048779
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
libarchive/libarchive
|
||||
COMMIT
|
||||
7118f97c26bf0b2f426728b482f86508efc81d02
|
||||
9525f90ca4bd14c7b335e2f8c84a4607b0af6bdf
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/mimalloc
|
||||
COMMIT
|
||||
c1f17cd2538417620f60bff70bffe7e68d332aec
|
||||
c90e3981edcf6d75c8243e3d323e4300d700ebc6
|
||||
)
|
||||
|
||||
set(MIMALLOC_CMAKE_ARGS
|
||||
@@ -13,14 +13,52 @@ set(MIMALLOC_CMAKE_ARGS
|
||||
-DMI_BUILD_SHARED=OFF
|
||||
-DMI_BUILD_TESTS=OFF
|
||||
-DMI_USE_CXX=ON
|
||||
-DMI_OVERRIDE=OFF
|
||||
-DMI_OSX_ZONE=OFF
|
||||
-DMI_OSX_INTERPOSE=OFF
|
||||
-DMI_SKIP_COLLECT_ON_EXIT=ON
|
||||
|
||||
# ```
|
||||
# ❯ mimalloc_allow_large_os_pages=0 BUN_PORT=3004 mem bun http-hello.js
|
||||
# Started development server: http://localhost:3004
|
||||
#
|
||||
# Peak memory usage: 52 MB
|
||||
#
|
||||
# ❯ mimalloc_allow_large_os_pages=1 BUN_PORT=3004 mem bun http-hello.js
|
||||
# Started development server: http://localhost:3004
|
||||
#
|
||||
# Peak memory usage: 74 MB
|
||||
# ```
|
||||
#
|
||||
# ```
|
||||
# ❯ mimalloc_allow_large_os_pages=1 mem bun --eval 1
|
||||
#
|
||||
# Peak memory usage: 52 MB
|
||||
#
|
||||
# ❯ mimalloc_allow_large_os_pages=0 mem bun --eval 1
|
||||
#
|
||||
# Peak memory usage: 30 MB
|
||||
# ```
|
||||
-DMI_NO_THP=1
|
||||
)
|
||||
|
||||
if (ABI STREQUAL "musl")
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_LIBC_MUSL=ON)
|
||||
endif()
|
||||
|
||||
if(ENABLE_ASAN)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_TRACK_ASAN=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
|
||||
elseif(APPLE OR LINUX)
|
||||
if(APPLE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
else()
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(DEBUG)
|
||||
@@ -31,7 +69,19 @@ if(ENABLE_VALGRIND)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_VALGRIND=ON)
|
||||
endif()
|
||||
|
||||
if(DEBUG)
|
||||
# Enable SIMD optimizations when not building for baseline (older CPUs)
|
||||
if(NOT ENABLE_BASELINE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_ARCH=ON)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OPT_SIMD=ON)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static-debug)
|
||||
else()
|
||||
set(MIMALLOC_LIBRARY mimalloc-static)
|
||||
endif()
|
||||
elseif(DEBUG)
|
||||
if (ENABLE_ASAN)
|
||||
set(MIMALLOC_LIBRARY mimalloc-asan-debug)
|
||||
else()
|
||||
|
||||
@@ -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 642e2252f6298387edb6d2f991a0408fd0320466)
|
||||
set(WEBKIT_VERSION 53385bda2d2270223ac66f7b021a4aec3dd6df75)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
4026
completions/bun-cli.json
Normal file
4026
completions/bun-cli.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -320,7 +320,6 @@ Bun automatically sets the `Content-Type` header for request bodies when not exp
|
||||
|
||||
- For `Blob` objects, uses the blob's `type`
|
||||
- For `FormData`, sets appropriate multipart boundary
|
||||
- For JSON objects, sets `application/json`
|
||||
|
||||
## Debugging
|
||||
|
||||
|
||||
369
docs/api/sql.md
369
docs/api/sql.md
@@ -1,20 +1,20 @@
|
||||
Bun provides native bindings for working with PostgreSQL databases with a modern, Promise-based API. The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
|
||||
Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports both PostgreSQL and SQLite. The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
|
||||
|
||||
```ts
|
||||
import { sql } from "bun";
|
||||
import { sql, SQL } from "bun";
|
||||
|
||||
// PostgreSQL (default)
|
||||
const users = await sql`
|
||||
SELECT * FROM users
|
||||
WHERE active = ${true}
|
||||
LIMIT ${10}
|
||||
`;
|
||||
|
||||
// Select with multiple conditions
|
||||
const activeUsers = await sql`
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE active = ${true}
|
||||
AND age >= ${18}
|
||||
// With a a SQLite db
|
||||
const sqlite = new SQL("sqlite://myapp.db");
|
||||
const results = await sqlite`
|
||||
SELECT * FROM users
|
||||
WHERE active = ${1}
|
||||
`;
|
||||
```
|
||||
|
||||
@@ -44,6 +44,115 @@ const activeUsers = await sql`
|
||||
|
||||
{% /features %}
|
||||
|
||||
## Database Support
|
||||
|
||||
Bun.SQL provides a unified API for multiple database systems:
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
PostgreSQL is used when:
|
||||
|
||||
- The connection string doesn't match SQLite patterns (it's the fallback adapter)
|
||||
- The connection string explicitly uses `postgres://` or `postgresql://` protocols
|
||||
- No connection string is provided and environment variables point to PostgreSQL
|
||||
|
||||
```ts
|
||||
import { sql } from "bun";
|
||||
// Uses PostgreSQL if DATABASE_URL is not set or is a PostgreSQL URL
|
||||
await sql`SELECT ...`;
|
||||
|
||||
import { SQL } from "bun";
|
||||
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
|
||||
await pg`SELECT ...`;
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
SQLite support is now built into Bun.SQL, providing the same tagged template literal interface as PostgreSQL:
|
||||
|
||||
```ts
|
||||
import { SQL } from "bun";
|
||||
|
||||
// In-memory database
|
||||
const memory = new SQL(":memory:");
|
||||
const memory2 = new SQL("sqlite://:memory:");
|
||||
|
||||
// File-based database
|
||||
const db = new SQL("sqlite://myapp.db");
|
||||
|
||||
// Using options object
|
||||
const db2 = new SQL({
|
||||
adapter: "sqlite",
|
||||
filename: "./data/app.db",
|
||||
});
|
||||
|
||||
// For simple filenames, specify adapter explicitly
|
||||
const db3 = new SQL("myapp.db", { adapter: "sqlite" });
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>SQLite Connection String Formats</summary>
|
||||
|
||||
SQLite accepts various URL formats for connection strings:
|
||||
|
||||
```ts
|
||||
// Standard sqlite:// protocol
|
||||
new SQL("sqlite://path/to/database.db");
|
||||
new SQL("sqlite:path/to/database.db"); // Without slashes
|
||||
|
||||
// file:// protocol (also recognized as SQLite)
|
||||
new SQL("file://path/to/database.db");
|
||||
new SQL("file:path/to/database.db");
|
||||
|
||||
// Special :memory: database
|
||||
new SQL(":memory:");
|
||||
new SQL("sqlite://:memory:");
|
||||
new SQL("file://:memory:");
|
||||
|
||||
// Relative and absolute paths
|
||||
new SQL("sqlite://./local.db"); // Relative to current directory
|
||||
new SQL("sqlite://../parent/db.db"); // Parent directory
|
||||
new SQL("sqlite:///absolute/path.db"); // Absolute path
|
||||
|
||||
// With query parameters
|
||||
new SQL("sqlite://data.db?mode=ro"); // Read-only mode
|
||||
new SQL("sqlite://data.db?mode=rw"); // Read-write mode (no create)
|
||||
new SQL("sqlite://data.db?mode=rwc"); // Read-write-create mode (default)
|
||||
```
|
||||
|
||||
**Note:** Simple filenames without a protocol (like `"myapp.db"`) require explicitly specifying `{ adapter: "sqlite" }` to avoid ambiguity with PostgreSQL.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>SQLite-Specific Options</summary>
|
||||
|
||||
SQLite databases support additional configuration options:
|
||||
|
||||
```ts
|
||||
const db = new SQL({
|
||||
adapter: "sqlite",
|
||||
filename: "app.db",
|
||||
|
||||
// SQLite-specific options
|
||||
readonly: false, // Open in read-only mode
|
||||
create: true, // Create database if it doesn't exist
|
||||
readwrite: true, // Open for reading and writing
|
||||
|
||||
// Additional Bun:sqlite options
|
||||
strict: true, // Enable strict mode
|
||||
safeIntegers: false, // Use JavaScript numbers for integers
|
||||
});
|
||||
```
|
||||
|
||||
Query parameters in the URL are parsed to set these options:
|
||||
|
||||
- `?mode=ro` → `readonly: true`
|
||||
- `?mode=rw` → `readonly: false, create: false`
|
||||
- `?mode=rwc` → `readonly: false, create: true` (default)
|
||||
|
||||
</details>
|
||||
|
||||
### Inserting data
|
||||
|
||||
You can pass JavaScript values directly to the SQL template literal and escaping will be handled for you.
|
||||
@@ -251,14 +360,55 @@ await query;
|
||||
|
||||
## Database Environment Variables
|
||||
|
||||
`sql` connection parameters can be configured using environment variables. The client checks these variables in a specific order of precedence.
|
||||
`sql` connection parameters can be configured using environment variables. The client checks these variables in a specific order of precedence and automatically detects the database type based on the connection string format.
|
||||
|
||||
The following environment variables can be used to define the connection URL:
|
||||
### Automatic Database Detection
|
||||
|
||||
When using `Bun.sql()` without arguments or `new SQL()` with a connection string, the adapter is automatically detected based on the URL format. SQLite becomes the default adapter in these cases:
|
||||
|
||||
#### SQLite Auto-Detection
|
||||
|
||||
SQLite is automatically selected when the connection string matches these patterns:
|
||||
|
||||
- `:memory:` - In-memory database
|
||||
- `sqlite://...` - SQLite protocol URLs
|
||||
- `sqlite:...` - SQLite protocol without slashes
|
||||
- `file://...` - File protocol URLs
|
||||
- `file:...` - File protocol without slashes
|
||||
|
||||
```ts
|
||||
// These all use SQLite automatically (no adapter needed)
|
||||
const sql1 = new SQL(":memory:");
|
||||
const sql2 = new SQL("sqlite://app.db");
|
||||
const sql3 = new SQL("file://./database.db");
|
||||
|
||||
// Works with DATABASE_URL environment variable
|
||||
DATABASE_URL=":memory:" bun run app.js
|
||||
DATABASE_URL="sqlite://myapp.db" bun run app.js
|
||||
DATABASE_URL="file://./data/app.db" bun run app.js
|
||||
```
|
||||
|
||||
#### PostgreSQL Auto-Detection
|
||||
|
||||
PostgreSQL is the default for all other connection strings:
|
||||
|
||||
```bash
|
||||
# PostgreSQL is detected for these patterns
|
||||
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
|
||||
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js
|
||||
|
||||
# Or any URL that doesn't match SQLite patterns
|
||||
DATABASE_URL="localhost:5432/mydb" bun run app.js
|
||||
```
|
||||
|
||||
### PostgreSQL Environment Variables
|
||||
|
||||
The following environment variables can be used to define the PostgreSQL connection:
|
||||
|
||||
| Environment Variable | Description |
|
||||
| --------------------------- | ------------------------------------------ |
|
||||
| `POSTGRES_URL` | Primary connection URL for PostgreSQL |
|
||||
| `DATABASE_URL` | Alternative connection URL |
|
||||
| `DATABASE_URL` | Alternative connection URL (auto-detected) |
|
||||
| `PGURL` | Alternative connection URL |
|
||||
| `PG_URL` | Alternative connection URL |
|
||||
| `TLS_POSTGRES_DATABASE_URL` | SSL/TLS-enabled connection URL |
|
||||
@@ -274,6 +424,19 @@ If no connection URL is provided, the system checks for the following individual
|
||||
| `PGPASSWORD` | - | (empty) | Database password |
|
||||
| `PGDATABASE` | - | username | Database name |
|
||||
|
||||
### SQLite Environment Variables
|
||||
|
||||
SQLite connections can be configured via `DATABASE_URL` when it contains a SQLite-compatible URL:
|
||||
|
||||
```bash
|
||||
# These are all recognized as SQLite
|
||||
DATABASE_URL=":memory:"
|
||||
DATABASE_URL="sqlite://./app.db"
|
||||
DATABASE_URL="file:///absolute/path/to/db.sqlite"
|
||||
```
|
||||
|
||||
**Note:** PostgreSQL-specific environment variables (`POSTGRES_URL`, `PGHOST`, etc.) are ignored when using SQLite.
|
||||
|
||||
## Runtime Preconnection
|
||||
|
||||
Bun can preconnect to PostgreSQL at startup to improve performance by establishing database connections before your application code runs. This is useful for reducing connection latency on the first database query.
|
||||
@@ -293,16 +456,18 @@ The `--sql-preconnect` flag will automatically establish a PostgreSQL connection
|
||||
|
||||
## Connection Options
|
||||
|
||||
You can configure your database connection manually by passing options to the SQL constructor:
|
||||
You can configure your database connection manually by passing options to the SQL constructor. Options vary depending on the database adapter:
|
||||
|
||||
### PostgreSQL Options
|
||||
|
||||
```ts
|
||||
import { SQL } from "bun";
|
||||
|
||||
const db = new SQL({
|
||||
// Required
|
||||
// Connection details (adapter is auto-detected as PostgreSQL)
|
||||
url: "postgres://user:pass@localhost:5432/dbname",
|
||||
|
||||
// Optional configuration
|
||||
// Alternative connection parameters
|
||||
hostname: "localhost",
|
||||
port: 5432,
|
||||
database: "myapp",
|
||||
@@ -330,14 +495,53 @@ const db = new SQL({
|
||||
|
||||
// Callbacks
|
||||
onconnect: client => {
|
||||
console.log("Connected to database");
|
||||
console.log("Connected to PostgreSQL");
|
||||
},
|
||||
onclose: client => {
|
||||
console.log("Connection closed");
|
||||
console.log("PostgreSQL connection closed");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### SQLite Options
|
||||
|
||||
```ts
|
||||
import { SQL } from "bun";
|
||||
|
||||
const db = new SQL({
|
||||
// Required for SQLite
|
||||
adapter: "sqlite",
|
||||
filename: "./data/app.db", // or ":memory:" for in-memory database
|
||||
|
||||
// SQLite-specific access modes
|
||||
readonly: false, // Open in read-only mode
|
||||
create: true, // Create database if it doesn't exist
|
||||
readwrite: true, // Allow read and write operations
|
||||
|
||||
// SQLite data handling
|
||||
strict: true, // Enable strict mode for better type safety
|
||||
safeIntegers: false, // Use BigInt for integers exceeding JS number range
|
||||
|
||||
// Callbacks
|
||||
onconnect: client => {
|
||||
console.log("SQLite database opened");
|
||||
},
|
||||
onclose: client => {
|
||||
console.log("SQLite database closed");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>SQLite Connection Notes</summary>
|
||||
|
||||
- **Connection Pooling**: SQLite doesn't use connection pooling as it's a file-based database. Each `SQL` instance represents a single connection.
|
||||
- **Transactions**: SQLite supports nested transactions through savepoints, similar to PostgreSQL.
|
||||
- **Concurrent Access**: SQLite handles concurrent access through file locking. Use WAL mode for better concurrency.
|
||||
- **Memory Databases**: Using `:memory:` creates a temporary database that exists only for the connection lifetime.
|
||||
|
||||
</details>
|
||||
|
||||
## Dynamic passwords
|
||||
|
||||
When clients need to use alternative authentication schemes such as access tokens or connections to databases with rotating passwords, provide either a synchronous or asynchronous function that will resolve the dynamic password value at connection time.
|
||||
@@ -353,11 +557,66 @@ const sql = new SQL(url, {
|
||||
});
|
||||
```
|
||||
|
||||
## SQLite-Specific Features
|
||||
|
||||
### Query Execution
|
||||
|
||||
SQLite executes queries synchronously, unlike PostgreSQL which uses asynchronous I/O. However, the API remains consistent using Promises:
|
||||
|
||||
```ts
|
||||
const sqlite = new SQL("sqlite://app.db");
|
||||
|
||||
// Works the same as PostgreSQL, but executes synchronously under the hood
|
||||
const users = await sqlite`SELECT * FROM users`;
|
||||
|
||||
// Parameters work identically
|
||||
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;
|
||||
```
|
||||
|
||||
### SQLite Pragmas
|
||||
|
||||
You can use PRAGMA statements to configure SQLite behavior:
|
||||
|
||||
```ts
|
||||
const sqlite = new SQL("sqlite://app.db");
|
||||
|
||||
// Enable foreign keys
|
||||
await sqlite`PRAGMA foreign_keys = ON`;
|
||||
|
||||
// Set journal mode to WAL for better concurrency
|
||||
await sqlite`PRAGMA journal_mode = WAL`;
|
||||
|
||||
// Check integrity
|
||||
const integrity = await sqlite`PRAGMA integrity_check`;
|
||||
```
|
||||
|
||||
### Data Type Differences
|
||||
|
||||
SQLite has a more flexible type system than PostgreSQL:
|
||||
|
||||
```ts
|
||||
// SQLite stores data in 5 storage classes: NULL, INTEGER, REAL, TEXT, BLOB
|
||||
const sqlite = new SQL("sqlite://app.db");
|
||||
|
||||
// SQLite is more lenient with types
|
||||
await sqlite`
|
||||
CREATE TABLE flexible (
|
||||
id INTEGER PRIMARY KEY,
|
||||
data TEXT, -- Can store numbers as strings
|
||||
value NUMERIC, -- Can store integers, reals, or text
|
||||
blob BLOB -- Binary data
|
||||
)
|
||||
`;
|
||||
|
||||
// JavaScript values are automatically converted
|
||||
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;
|
||||
```
|
||||
|
||||
## Transactions
|
||||
|
||||
To start a new transaction, use `sql.begin`. This method reserves a dedicated connection for the duration of the transaction and provides a scoped `sql` instance to use within the callback function. Once the callback completes, `sql.begin` resolves with the return value of the callback.
|
||||
To start a new transaction, use `sql.begin`. This method works for both PostgreSQL and SQLite. For PostgreSQL, it reserves a dedicated connection from the pool. For SQLite, it begins a transaction on the single connection.
|
||||
|
||||
The `BEGIN` command is sent automatically, including any optional configurations you specify. If an error occurs during the transaction, a `ROLLBACK` is triggered to release the reserved connection and ensure the process continues smoothly.
|
||||
The `BEGIN` command is sent automatically, including any optional configurations you specify. If an error occurs during the transaction, a `ROLLBACK` is triggered to ensure the process continues smoothly.
|
||||
|
||||
### Basic Transactions
|
||||
|
||||
@@ -552,9 +811,34 @@ Note that disabling prepared statements may impact performance for queries that
|
||||
|
||||
## Error Handling
|
||||
|
||||
The client provides typed errors for different failure scenarios:
|
||||
The client provides typed errors for different failure scenarios. Errors are database-specific and extend from base error classes:
|
||||
|
||||
### Connection Errors
|
||||
### Error Classes
|
||||
|
||||
```ts
|
||||
import { SQL } from "bun";
|
||||
|
||||
try {
|
||||
await sql`SELECT * FROM users`;
|
||||
} catch (error) {
|
||||
if (error instanceof SQL.PostgresError) {
|
||||
// PostgreSQL-specific error
|
||||
console.log(error.code); // PostgreSQL error code
|
||||
console.log(error.detail); // Detailed error message
|
||||
console.log(error.hint); // Helpful hint from PostgreSQL
|
||||
} else if (error instanceof SQL.SQLiteError) {
|
||||
// SQLite-specific error
|
||||
console.log(error.code); // SQLite error code (e.g., "SQLITE_CONSTRAINT")
|
||||
console.log(error.errno); // SQLite error number
|
||||
console.log(error.byteOffset); // Byte offset in SQL statement (if available)
|
||||
} else if (error instanceof SQL.SQLError) {
|
||||
// Generic SQL error (base class)
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PostgreSQL Connection Errors
|
||||
|
||||
| Connection Errors | Description |
|
||||
| --------------------------------- | ---------------------------------------------------- |
|
||||
@@ -619,6 +903,50 @@ The client provides typed errors for different failure scenarios:
|
||||
| `ERR_POSTGRES_UNSAFE_TRANSACTION` | Unsafe transaction operation detected |
|
||||
| `ERR_POSTGRES_INVALID_TRANSACTION_STATE` | Invalid transaction state |
|
||||
|
||||
### SQLite-Specific Errors
|
||||
|
||||
SQLite errors provide error codes and numbers that correspond to SQLite's standard error codes:
|
||||
|
||||
<details>
|
||||
<summary>Common SQLite Error Codes</summary>
|
||||
|
||||
| Error Code | errno | Description |
|
||||
| ------------------- | ----- | ---------------------------------------------------- |
|
||||
| `SQLITE_CONSTRAINT` | 19 | Constraint violation (UNIQUE, CHECK, NOT NULL, etc.) |
|
||||
| `SQLITE_BUSY` | 5 | Database is locked |
|
||||
| `SQLITE_LOCKED` | 6 | Table in the database is locked |
|
||||
| `SQLITE_READONLY` | 8 | Attempt to write to a readonly database |
|
||||
| `SQLITE_IOERR` | 10 | Disk I/O error |
|
||||
| `SQLITE_CORRUPT` | 11 | Database disk image is malformed |
|
||||
| `SQLITE_FULL` | 13 | Database or disk is full |
|
||||
| `SQLITE_CANTOPEN` | 14 | Unable to open database file |
|
||||
| `SQLITE_PROTOCOL` | 15 | Database lock protocol error |
|
||||
| `SQLITE_SCHEMA` | 17 | Database schema has changed |
|
||||
| `SQLITE_TOOBIG` | 18 | String or BLOB exceeds size limit |
|
||||
| `SQLITE_MISMATCH` | 20 | Data type mismatch |
|
||||
| `SQLITE_MISUSE` | 21 | Library used incorrectly |
|
||||
| `SQLITE_AUTH` | 23 | Authorization denied |
|
||||
|
||||
Example error handling:
|
||||
|
||||
```ts
|
||||
const sqlite = new SQL("sqlite://app.db");
|
||||
|
||||
try {
|
||||
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
|
||||
await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // Duplicate ID
|
||||
} catch (error) {
|
||||
if (error instanceof SQL.SQLiteError) {
|
||||
if (error.code === "SQLITE_CONSTRAINT") {
|
||||
console.log("Constraint violation:", error.message);
|
||||
// Handle unique constraint violation
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Numbers and BigInt
|
||||
|
||||
Bun's SQL client includes special handling for large numbers that exceed the range of a 53-bit integer. Here's how it works:
|
||||
@@ -652,7 +980,6 @@ There's still some things we haven't finished yet.
|
||||
|
||||
- Connection preloading via `--db-preconnect` Bun CLI flag
|
||||
- MySQL support: [we're working on it](https://github.com/oven-sh/bun/pull/15274)
|
||||
- SQLite support: planned, but not started. Ideally, we implement it natively instead of wrapping `bun:sqlite`.
|
||||
- Column name transforms (e.g. `snake_case` to `camelCase`). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit's `WTF::String`.
|
||||
- Column type transforms
|
||||
|
||||
|
||||
@@ -208,8 +208,8 @@ export class ArrayBufferSink {
|
||||
*
|
||||
* This API might change later to separate Uint8ArraySink and ArrayBufferSink
|
||||
*/
|
||||
flush(): number | Uint8Array | ArrayBuffer;
|
||||
end(): ArrayBuffer | Uint8Array;
|
||||
flush(): number | Uint8Array<ArrayBuffer> | ArrayBuffer;
|
||||
end(): ArrayBuffer | Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -772,6 +772,65 @@ console.log(obj); // => { foo: "bar" }
|
||||
|
||||
Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer.
|
||||
|
||||
## `Bun.stripANSI()` ~6-57x faster `strip-ansi` alternative
|
||||
|
||||
`Bun.stripANSI(text: string): string`
|
||||
|
||||
Strip ANSI escape codes from a string. This is useful for removing colors and formatting from terminal output.
|
||||
|
||||
```ts
|
||||
const coloredText = "\u001b[31mHello\u001b[0m \u001b[32mWorld\u001b[0m";
|
||||
const plainText = Bun.stripANSI(coloredText);
|
||||
console.log(plainText); // => "Hello World"
|
||||
|
||||
// Works with various ANSI codes
|
||||
const formatted = "\u001b[1m\u001b[4mBold and underlined\u001b[0m";
|
||||
console.log(Bun.stripANSI(formatted)); // => "Bold and underlined"
|
||||
```
|
||||
|
||||
`Bun.stripANSI` is significantly faster than the popular [`strip-ansi`](https://www.npmjs.com/package/strip-ansi) npm package:
|
||||
|
||||
```js
|
||||
> bun bench/snippets/strip-ansi.mjs
|
||||
cpu: Apple M3 Max
|
||||
runtime: bun 1.2.21 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
------------------------------------------------------- ----------
|
||||
Bun.stripANSI 11 chars no-ansi 8.13 ns/iter 8.27 ns
|
||||
(7.45 ns … 33.59 ns) 10.29 ns
|
||||
|
||||
Bun.stripANSI 13 chars ansi 51.68 ns/iter 52.51 ns
|
||||
(46.16 ns … 113.71 ns) 57.71 ns
|
||||
|
||||
Bun.stripANSI 16,384 chars long-no-ansi 298.39 ns/iter 305.44 ns
|
||||
(281.50 ns … 331.65 ns) 320.70 ns
|
||||
|
||||
Bun.stripANSI 212,992 chars long-ansi 227.65 µs/iter 234.50 µs
|
||||
(216.46 µs … 401.92 µs) 262.25 µs
|
||||
```
|
||||
|
||||
```js
|
||||
> node bench/snippets/strip-ansi.mjs
|
||||
cpu: Apple M3 Max
|
||||
runtime: node 24.6.0 (arm64-darwin)
|
||||
|
||||
benchmark avg (min … max) p75 / p99
|
||||
-------------------------------------------------------- ---------
|
||||
npm/strip-ansi 11 chars no-ansi 466.79 ns/iter 468.67 ns
|
||||
(454.08 ns … 570.67 ns) 543.67 ns
|
||||
|
||||
npm/strip-ansi 13 chars ansi 546.77 ns/iter 550.23 ns
|
||||
(532.74 ns … 651.08 ns) 590.35 ns
|
||||
|
||||
npm/strip-ansi 16,384 chars long-no-ansi 4.85 µs/iter 4.89 µs
|
||||
(4.71 µs … 5.00 µs) 4.98 µs
|
||||
|
||||
npm/strip-ansi 212,992 chars long-ansi 1.36 ms/iter 1.38 ms
|
||||
(1.27 ms … 1.73 ms) 1.49 ms
|
||||
|
||||
```
|
||||
|
||||
## `estimateShallowMemoryUsageOf` in `bun:jsc`
|
||||
|
||||
The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% callout %}
|
||||
**🚧** — The `Worker` API is still experimental and should not be considered ready for production.
|
||||
**🚧** — The `Worker` API is still experimental (particularly for terminating workers). We are actively working on improving this.
|
||||
{% /callout %}
|
||||
|
||||
[`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) lets you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread.
|
||||
|
||||
@@ -14,7 +14,7 @@ if (typeof Bun !== "undefined") {
|
||||
|
||||
---
|
||||
|
||||
In TypeScript environments, the previous approach will result in a type error unless `bun-types` is globally installed. To avoid this, you can check `process.versions` instead.
|
||||
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.
|
||||
|
||||
```ts
|
||||
if (process.versions.bun) {
|
||||
|
||||
@@ -496,6 +496,36 @@ Whether to generate a non-Bun lockfile alongside `bun.lock`. (A `bun.lock` will
|
||||
print = "yarn"
|
||||
```
|
||||
|
||||
### `install.linker`
|
||||
|
||||
Configure the default linker strategy. Default `"hoisted"`.
|
||||
|
||||
For complete documentation refer to [Package manager > Isolated installs](https://bun.com/docs/install/isolated).
|
||||
|
||||
```toml
|
||||
[install]
|
||||
linker = "hoisted"
|
||||
```
|
||||
|
||||
Valid values are:
|
||||
|
||||
{% table %}
|
||||
|
||||
- Value
|
||||
- Description
|
||||
|
||||
---
|
||||
|
||||
- `"hoisted"`
|
||||
- Link dependencies in a shared `node_modules` directory.
|
||||
|
||||
---
|
||||
|
||||
- `"isolated"`
|
||||
- Link dependencies inside each package installation.
|
||||
|
||||
{% /table %}
|
||||
|
||||
<!-- ## Debugging -->
|
||||
|
||||
<!--
|
||||
|
||||
@@ -148,7 +148,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:vm`](https://nodejs.org/api/vm.html)
|
||||
|
||||
🟡 Core functionality works, but experimental VM ES modules are not implemented, including `vm.Module`, `vm.SourceTextModule`, `vm.SyntheticModule`,`importModuleDynamically`, and `vm.measureMemory`. Options like `timeout`, `breakOnSigint`, `cachedData` are not implemented yet.
|
||||
🟡 Core functionality and ES modules are implemented, including `vm.Script`, `vm.createContext`, `vm.runInContext`, `vm.runInNewContext`, `vm.runInThisContext`, `vm.compileFunction`, `vm.isContext`, `vm.Module`, `vm.SourceTextModule`, `vm.SyntheticModule`, and `importModuleDynamically` support. Options like `timeout` and `breakOnSigint` are fully supported. Missing `vm.measureMemory` and some `cachedData` functionality.
|
||||
|
||||
### [`node:wasi`](https://nodejs.org/api/wasi.html)
|
||||
|
||||
@@ -214,6 +214,10 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`Atomics`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel)
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
@@ -532,6 +532,74 @@ Hello World! pwd=C:\Users\Demo
|
||||
|
||||
Bun Shell is a small programming language in Bun that is implemented in Zig. It includes a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.
|
||||
|
||||
## Security in the Bun shell
|
||||
|
||||
By design, the Bun shell _does not invoke a system shell_ (like `/bin/sh`) and
|
||||
is instead a re-implementation of bash that runs in the same Bun process,
|
||||
designed with security in mind.
|
||||
|
||||
When parsing command arguments, it treats all _interpolated variables_ as single, literal strings.
|
||||
|
||||
This protects the Bun shell against **command injection**:
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "my-file.txt; rm -rf /";
|
||||
|
||||
// SAFE: `userInput` is treated as a single quoted string
|
||||
await $`ls ${userInput}`;
|
||||
```
|
||||
|
||||
In the above example, `userInput` is treated as a single string. This causes
|
||||
the `ls` command to try to read the contents of a single directory named
|
||||
"my-file; rm -rf /".
|
||||
|
||||
### Security considerations
|
||||
|
||||
While command injection is prevented by default, developers are still
|
||||
responsible for security in certain scenarios.
|
||||
|
||||
Similar to the `Bun.spawn` or `node:child_process.exec()` APIs, you can intentionally
|
||||
execute a command which spawns a new shell (e.g. `bash -c`) with arguments.
|
||||
|
||||
When you do this, you hand off control, and Bun's built-in protections no
|
||||
longer apply to the string interpreted by that new shell.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "world; touch /tmp/pwned";
|
||||
|
||||
// UNSAFE: You have explicitly started a new shell process with `bash -c`.
|
||||
// This new shell will execute the `touch` command. Any user input
|
||||
// passed this way must be rigorously sanitized.
|
||||
await $`bash -c "echo ${userInput}"`;
|
||||
```
|
||||
|
||||
### Argument injection
|
||||
|
||||
The Bun shell cannot know how an external command interprets its own
|
||||
command-line arguments. An attacker can supply input that the target program
|
||||
recognizes as one of its own options or flags, leading to unintended behavior.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
// Malicious input formatted as a Git command-line flag
|
||||
const branch = "--upload-pack=echo pwned";
|
||||
|
||||
// UNSAFE: While Bun safely passes the string as a single argument,
|
||||
// the `git` program itself sees and acts upon the malicious flag.
|
||||
await $`git ls-remote origin ${branch}`;
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Recommendation** — As is best practice in every language, always sanitize
|
||||
user-provided input before passing it as an argument to an external command.
|
||||
The responsibility for validating arguments rests with your application code.
|
||||
{% /callout %}
|
||||
|
||||
## Credits
|
||||
|
||||
Large parts of this API were inspired by [zx](https://github.com/google/zx), [dax](https://github.com/dsherret/dax), and [bnx](https://github.com/wobsoriano/bnx). Thank you to the authors of those projects.
|
||||
|
||||
@@ -677,17 +677,17 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap
|
||||
|
||||
---
|
||||
|
||||
- ❌
|
||||
- ✅
|
||||
- [`.toHaveReturnedWith()`](https://jestjs.io/docs/expect#tohavereturnedwithvalue)
|
||||
|
||||
---
|
||||
|
||||
- ❌
|
||||
- ✅
|
||||
- [`.toHaveLastReturnedWith()`](https://jestjs.io/docs/expect#tohavelastreturnedwithvalue)
|
||||
|
||||
---
|
||||
|
||||
- ❌
|
||||
- ✅
|
||||
- [`.toHaveNthReturnedWith()`](https://jestjs.io/docs/expect#tohaventhreturnedwithnthcall-value)
|
||||
|
||||
---
|
||||
|
||||
724
misctools/generate-cli-completions.ts
Normal file
724
misctools/generate-cli-completions.ts
Normal file
@@ -0,0 +1,724 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* CLI Flag Parser for Bun Commands
|
||||
*
|
||||
* This script reads the --help menu for every Bun command and generates JSON
|
||||
* containing all flag information, descriptions, and whether they support
|
||||
* positional or non-positional arguments.
|
||||
*
|
||||
* Handles complex cases like:
|
||||
* - Nested subcommands (bun pm cache rm)
|
||||
* - Command aliases (bun i = bun install, bun a = bun add)
|
||||
* - Dynamic completions (scripts, packages, files)
|
||||
* - Context-aware flags
|
||||
* - Special cases like bare 'bun' vs 'bun run'
|
||||
*
|
||||
* Output is saved to completions/bun-cli.json for use in generating
|
||||
* shell completions (fish, bash, zsh).
|
||||
*/
|
||||
|
||||
import { spawn } from "bun";
|
||||
import { mkdirSync, writeFileSync, mkdtempSync, rmSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
interface FlagInfo {
|
||||
name: string;
|
||||
shortName?: string;
|
||||
description: string;
|
||||
hasValue: boolean;
|
||||
valueType?: string;
|
||||
defaultValue?: string;
|
||||
choices?: string[];
|
||||
required?: boolean;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
interface SubcommandInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
flags?: FlagInfo[];
|
||||
subcommands?: Record<string, SubcommandInfo>;
|
||||
positionalArgs?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[];
|
||||
examples?: string[];
|
||||
}
|
||||
|
||||
interface CommandInfo {
|
||||
name: string;
|
||||
aliases?: string[];
|
||||
description: string;
|
||||
usage?: string;
|
||||
flags: FlagInfo[];
|
||||
positionalArgs: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[];
|
||||
examples: string[];
|
||||
subcommands?: Record<string, SubcommandInfo>;
|
||||
documentationUrl?: string;
|
||||
dynamicCompletions?: {
|
||||
scripts?: boolean;
|
||||
packages?: boolean;
|
||||
files?: boolean;
|
||||
binaries?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface CompletionData {
|
||||
version: string;
|
||||
commands: Record<string, CommandInfo>;
|
||||
globalFlags: FlagInfo[];
|
||||
specialHandling: {
|
||||
bareCommand: {
|
||||
description: string;
|
||||
canRunFiles: boolean;
|
||||
dynamicCompletions: {
|
||||
scripts: boolean;
|
||||
files: boolean;
|
||||
binaries: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
bunGetCompletes: {
|
||||
available: boolean;
|
||||
commands: {
|
||||
scripts: string; // "bun getcompletes s" or "bun getcompletes z"
|
||||
binaries: string; // "bun getcompletes b"
|
||||
packages: string; // "bun getcompletes a <prefix>"
|
||||
files: string; // "bun getcompletes j"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const BUN_EXECUTABLE = process.env.BUN_DEBUG_BUILD || "bun";
|
||||
|
||||
/**
|
||||
* Parse flag line from help output
|
||||
*/
|
||||
function parseFlag(line: string): FlagInfo | null {
|
||||
// Match patterns like:
|
||||
// -h, --help Display this menu and exit
|
||||
// --timeout=<val> Set the per-test timeout in milliseconds, default is 5000.
|
||||
// -r, --preload=<val> Import a module before other modules are loaded
|
||||
// --watch Automatically restart the process on file change
|
||||
|
||||
const patterns = [
|
||||
// Long flag with short flag and value: -r, --preload=<val>
|
||||
/^\s*(-[a-zA-Z]),\s+(--[a-zA-Z-]+)=(<[^>]+>)\s+(.+)$/,
|
||||
// Long flag with short flag: -h, --help
|
||||
/^\s*(-[a-zA-Z]),\s+(--[a-zA-Z-]+)\s+(.+)$/,
|
||||
// Long flag with value: --timeout=<val>
|
||||
/^\s+(--[a-zA-Z-]+)=(<[^>]+>)\s+(.+)$/,
|
||||
// Long flag without value: --watch
|
||||
/^\s+(--[a-zA-Z-]+)\s+(.+)$/,
|
||||
// Short flag only: -i
|
||||
/^\s+(-[a-zA-Z])\s+(.+)$/,
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = line.match(pattern);
|
||||
if (match) {
|
||||
let shortName: string | undefined;
|
||||
let longName: string;
|
||||
let valueSpec: string | undefined;
|
||||
let description: string;
|
||||
|
||||
if (match.length === 5) {
|
||||
// Pattern with short flag, long flag, and value
|
||||
[, shortName, longName, valueSpec, description] = match;
|
||||
} else if (match.length === 4) {
|
||||
if (match[1].startsWith("-") && match[1].length === 2) {
|
||||
// Short flag with long flag
|
||||
[, shortName, longName, description] = match;
|
||||
} else if (match[2].startsWith("<")) {
|
||||
// Long flag with value
|
||||
[, longName, valueSpec, description] = match;
|
||||
} else {
|
||||
// Long flag without value
|
||||
[, longName, description] = match;
|
||||
}
|
||||
} else if (match.length === 3) {
|
||||
if (match[1].length === 2) {
|
||||
// Short flag only
|
||||
[, shortName, description] = match;
|
||||
longName = shortName.replace("-", "--");
|
||||
} else {
|
||||
// Long flag without value
|
||||
[, longName, description] = match;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract additional info from description
|
||||
const hasValue = !!valueSpec;
|
||||
let valueType: string | undefined;
|
||||
let defaultValue: string | undefined;
|
||||
let choices: string[] | undefined;
|
||||
|
||||
if (valueSpec) {
|
||||
valueType = valueSpec.replace(/[<>]/g, "");
|
||||
}
|
||||
|
||||
// Look for default values in description
|
||||
const defaultMatch = description.match(/[Dd]efault(?:s?)\s*(?:is|to|:)\s*"?([^".\s,]+)"?/);
|
||||
if (defaultMatch) {
|
||||
defaultValue = defaultMatch[1];
|
||||
}
|
||||
|
||||
// Look for choices/enums
|
||||
const choicesMatch = description.match(/(?:One of|Valid (?:orders?|values?|options?)):?\s*"?([^"]+)"?/);
|
||||
if (choicesMatch) {
|
||||
choices = choicesMatch[1]
|
||||
.split(/[,\s]+/)
|
||||
.map(s => s.replace(/[",]/g, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
return {
|
||||
name: longName.replace(/^--/, ""),
|
||||
shortName: shortName?.replace(/^-/, ""),
|
||||
description: description.trim(),
|
||||
hasValue,
|
||||
valueType,
|
||||
defaultValue,
|
||||
choices,
|
||||
required: false, // We'll determine this from usage patterns
|
||||
multiple: description.toLowerCase().includes("multiple") || description.includes("[]"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse usage line to extract positional arguments
|
||||
*/
|
||||
function parseUsage(usage: string): {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[] {
|
||||
const args: {
|
||||
name: string;
|
||||
description?: string;
|
||||
required: boolean;
|
||||
multiple: boolean;
|
||||
type?: string;
|
||||
completionType?: string;
|
||||
}[] = [];
|
||||
|
||||
// Extract parts after command name
|
||||
const parts = usage.split(/\s+/).slice(2); // Skip "Usage:" and command name
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.startsWith("[") || part.startsWith("<") || part.includes("...")) {
|
||||
let name = part;
|
||||
let required = false;
|
||||
let multiple = false;
|
||||
let completionType: string | undefined;
|
||||
|
||||
// Clean up the argument name
|
||||
name = name.replace(/[\[\]<>]/g, "");
|
||||
|
||||
if (part.startsWith("<")) {
|
||||
required = true;
|
||||
}
|
||||
|
||||
if (part.includes("...") || name.includes("...")) {
|
||||
multiple = true;
|
||||
name = name.replace(/\.{3}/g, "");
|
||||
}
|
||||
|
||||
// Skip flags
|
||||
if (!name.startsWith("-") && name.length > 0) {
|
||||
// Determine completion type based on argument name
|
||||
if (name.toLowerCase().includes("package")) {
|
||||
completionType = "package";
|
||||
} else if (name.toLowerCase().includes("script")) {
|
||||
completionType = "script";
|
||||
} else if (name.toLowerCase().includes("file") || name.includes(".")) {
|
||||
completionType = "file";
|
||||
}
|
||||
|
||||
args.push({
|
||||
name,
|
||||
required,
|
||||
multiple,
|
||||
type: "string", // Default type
|
||||
completionType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
const temppackagejson = mkdtempSync("package");
|
||||
writeFileSync(
|
||||
join(temppackagejson, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "test",
|
||||
version: "1.0.0",
|
||||
scripts: {},
|
||||
}),
|
||||
);
|
||||
process.once("beforeExit", () => {
|
||||
rmSync(temppackagejson, { recursive: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Execute bun command and get help output
|
||||
*/
|
||||
async function getHelpOutput(command: string[]): Promise<string> {
|
||||
try {
|
||||
const proc = spawn({
|
||||
cmd: [BUN_EXECUTABLE, ...command, "--help"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
cwd: temppackagejson,
|
||||
});
|
||||
|
||||
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
||||
|
||||
await proc.exited;
|
||||
|
||||
return stdout || stderr || "";
|
||||
} catch (error) {
|
||||
console.error(`Failed to get help for command: ${command.join(" ")}`, error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PM subcommands from help output
|
||||
*/
|
||||
function parsePmSubcommands(helpText: string): Record<string, SubcommandInfo> {
|
||||
const lines = helpText.split("\n");
|
||||
const subcommands: Record<string, SubcommandInfo> = {};
|
||||
|
||||
let inCommands = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Commands:") {
|
||||
inCommands = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCommands && trimmed.startsWith("Learn more")) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inCommands && line.match(/^\s+bun pm \w+/)) {
|
||||
// Parse lines like: "bun pm pack create a tarball of the current workspace"
|
||||
const match = line.match(/^\s+bun pm (\S+)(?:\s+(.+))?$/);
|
||||
if (match) {
|
||||
const [, name, description = ""] = match;
|
||||
subcommands[name] = {
|
||||
name,
|
||||
description: description.trim(),
|
||||
flags: [],
|
||||
positionalArgs: [],
|
||||
};
|
||||
|
||||
// Special handling for subcommands with their own subcommands
|
||||
if (name === "cache") {
|
||||
subcommands[name].subcommands = {
|
||||
rm: {
|
||||
name: "rm",
|
||||
description: "clear the cache",
|
||||
},
|
||||
};
|
||||
} else if (name === "pkg") {
|
||||
subcommands[name].subcommands = {
|
||||
get: { name: "get", description: "get values from package.json" },
|
||||
set: { name: "set", description: "set values in package.json" },
|
||||
delete: { name: "delete", description: "delete keys from package.json" },
|
||||
fix: { name: "fix", description: "auto-correct common package.json errors" },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse help output into CommandInfo
|
||||
*/
|
||||
function parseHelpOutput(helpText: string, commandName: string): CommandInfo {
|
||||
const lines = helpText.split("\n");
|
||||
const command: CommandInfo = {
|
||||
name: commandName,
|
||||
description: "",
|
||||
flags: [],
|
||||
positionalArgs: [],
|
||||
examples: [],
|
||||
};
|
||||
|
||||
let currentSection = "";
|
||||
let inFlags = false;
|
||||
let inExamples = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
// Extract command description (usually the first non-usage line)
|
||||
if (
|
||||
!command.description &&
|
||||
trimmed &&
|
||||
!trimmed.startsWith("Usage:") &&
|
||||
!trimmed.startsWith("Alias:") &&
|
||||
currentSection === ""
|
||||
) {
|
||||
command.description = trimmed;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract aliases
|
||||
if (trimmed.startsWith("Alias:")) {
|
||||
const aliasMatch = trimmed.match(/Alias:\s*(.+)/);
|
||||
if (aliasMatch) {
|
||||
command.aliases = aliasMatch[1]
|
||||
.split(/[,\s]+/)
|
||||
.map(a => a.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract usage and positional args
|
||||
if (trimmed.startsWith("Usage:")) {
|
||||
command.usage = trimmed;
|
||||
command.positionalArgs = parseUsage(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track sections
|
||||
if (trimmed === "Flags:") {
|
||||
inFlags = true;
|
||||
currentSection = "flags";
|
||||
continue;
|
||||
} else if (trimmed === "Examples:") {
|
||||
inExamples = true;
|
||||
inFlags = false;
|
||||
currentSection = "examples";
|
||||
continue;
|
||||
} else if (
|
||||
trimmed.startsWith("Full documentation") ||
|
||||
trimmed.startsWith("Learn more") ||
|
||||
trimmed.startsWith("A full list")
|
||||
) {
|
||||
const urlMatch = trimmed.match(/https?:\/\/[^\s]+/);
|
||||
if (urlMatch) {
|
||||
command.documentationUrl = urlMatch[0];
|
||||
}
|
||||
inFlags = false;
|
||||
inExamples = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse flags
|
||||
if (inFlags && line.match(/^\s+(-|\s+--)/)) {
|
||||
const flag = parseFlag(line);
|
||||
if (flag) {
|
||||
command.flags.push(flag);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse examples
|
||||
if (inExamples && trimmed && !trimmed.startsWith("Full documentation")) {
|
||||
if (trimmed.startsWith("bun ") || trimmed.startsWith("./") || trimmed.startsWith("Bundle")) {
|
||||
command.examples.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for pm command
|
||||
if (commandName === "pm") {
|
||||
command.subcommands = parsePmSubcommands(helpText);
|
||||
}
|
||||
|
||||
// Add dynamic completion info based on command
|
||||
command.dynamicCompletions = {};
|
||||
if (commandName === "run") {
|
||||
command.dynamicCompletions.scripts = true;
|
||||
command.dynamicCompletions.files = true;
|
||||
command.dynamicCompletions.binaries = true;
|
||||
// Also add file type info for positional args
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("file") || arg.name.includes("script")) {
|
||||
arg.completionType = "javascript_files";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "add") {
|
||||
command.dynamicCompletions.packages = true;
|
||||
// Mark package args
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("package") || arg.name === "name") {
|
||||
arg.completionType = "package";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "remove") {
|
||||
command.dynamicCompletions.packages = true; // installed packages
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("package") || arg.name === "name") {
|
||||
arg.completionType = "installed_package";
|
||||
}
|
||||
}
|
||||
} else if (["test"].includes(commandName)) {
|
||||
command.dynamicCompletions.files = true;
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("pattern") || arg.name.includes("file")) {
|
||||
arg.completionType = "test_files";
|
||||
}
|
||||
}
|
||||
} else if (["build"].includes(commandName)) {
|
||||
command.dynamicCompletions.files = true;
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name === "entrypoint" || arg.name.includes("file")) {
|
||||
arg.completionType = "javascript_files";
|
||||
}
|
||||
}
|
||||
} else if (commandName === "create") {
|
||||
// Create has special template completions
|
||||
for (const arg of command.positionalArgs) {
|
||||
if (arg.name.includes("template")) {
|
||||
arg.completionType = "create_template";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of main commands from bun --help
|
||||
*/
|
||||
async function getMainCommands(): Promise<string[]> {
|
||||
const helpText = await getHelpOutput([]);
|
||||
const lines = helpText.split("\n");
|
||||
const commands: string[] = [];
|
||||
|
||||
let inCommands = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Commands:") {
|
||||
inCommands = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop when we hit the "Flags:" section
|
||||
if (inCommands && trimmed === "Flags:") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inCommands && line.match(/^\s+\w+/)) {
|
||||
// Extract command name (first word after whitespace)
|
||||
const match = line.match(/^\s+(\w+)/);
|
||||
if (match) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const commandsToRemove = ["lint"];
|
||||
|
||||
return commands.filter(a => {
|
||||
if (commandsToRemove.includes(a)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract global flags from main help
|
||||
*/
|
||||
function parseGlobalFlags(helpText: string): FlagInfo[] {
|
||||
const lines = helpText.split("\n");
|
||||
const flags: FlagInfo[] = [];
|
||||
|
||||
let inFlags = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === "Flags:") {
|
||||
inFlags = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFlags && (trimmed === "" || trimmed.startsWith("("))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inFlags && line.match(/^\s+(-|\s+--)/)) {
|
||||
const flag = parseFlag(line);
|
||||
if (flag) {
|
||||
flags.push(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add command aliases based on common patterns
|
||||
*/
|
||||
function addCommandAliases(commands: Record<string, CommandInfo>): void {
|
||||
const aliasMap: Record<string, string[]> = {
|
||||
"install": ["i"],
|
||||
"add": ["a"],
|
||||
"remove": ["rm"],
|
||||
"create": ["c"],
|
||||
"x": ["bunx"], // bunx is an alias for bun x
|
||||
};
|
||||
|
||||
for (const [command, aliases] of Object.entries(aliasMap)) {
|
||||
if (commands[command]) {
|
||||
commands[command].aliases = aliases;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to generate completion data
|
||||
*/
|
||||
async function generateCompletions(): Promise<void> {
|
||||
console.log("🔍 Discovering Bun commands...");
|
||||
|
||||
// Get main help and extract commands
|
||||
const mainHelpText = await getHelpOutput([]);
|
||||
const mainCommands = await getMainCommands();
|
||||
const globalFlags = parseGlobalFlags(mainHelpText);
|
||||
|
||||
console.log(`📋 Found ${mainCommands.length} main commands: ${mainCommands.join(", ")}`);
|
||||
|
||||
const completionData: CompletionData = {
|
||||
version: "1.1.0",
|
||||
commands: {},
|
||||
globalFlags,
|
||||
specialHandling: {
|
||||
bareCommand: {
|
||||
description: "Run JavaScript/TypeScript files directly or access package scripts and binaries",
|
||||
canRunFiles: true,
|
||||
dynamicCompletions: {
|
||||
scripts: true,
|
||||
files: true,
|
||||
binaries: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
bunGetCompletes: {
|
||||
available: true,
|
||||
commands: {
|
||||
scripts: "bun getcompletes s", // or "bun getcompletes z" for scripts with descriptions
|
||||
binaries: "bun getcompletes b",
|
||||
packages: "bun getcompletes a", // takes prefix as argument
|
||||
files: "bun getcompletes j", // JavaScript/TypeScript files
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Parse each command
|
||||
for (const commandName of mainCommands) {
|
||||
console.log(`📖 Parsing help for: ${commandName}`);
|
||||
|
||||
try {
|
||||
const helpText = await getHelpOutput([commandName]);
|
||||
if (helpText.trim()) {
|
||||
const commandInfo = parseHelpOutput(helpText, commandName);
|
||||
completionData.commands[commandName] = commandInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to parse ${commandName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add common aliases
|
||||
addCommandAliases(completionData.commands);
|
||||
|
||||
// Also check some common subcommands that might have their own help
|
||||
const additionalCommands = ["pm"];
|
||||
for (const commandName of additionalCommands) {
|
||||
if (!completionData.commands[commandName]) {
|
||||
console.log(`📖 Parsing help for additional command: ${commandName}`);
|
||||
|
||||
try {
|
||||
const helpText = await getHelpOutput([commandName]);
|
||||
if (helpText.trim() && !helpText.includes("error:") && !helpText.includes("Error:")) {
|
||||
const commandInfo = parseHelpOutput(helpText, commandName);
|
||||
completionData.commands[commandName] = commandInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to parse ${commandName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure completions directory exists
|
||||
const completionsDir = join(process.cwd(), "completions");
|
||||
try {
|
||||
mkdirSync(completionsDir, { recursive: true });
|
||||
} catch (error) {
|
||||
// Directory might already exist
|
||||
}
|
||||
|
||||
// Write the JSON file
|
||||
const outputPath = join(completionsDir, "bun-cli.json");
|
||||
const jsonData = JSON.stringify(completionData, null, 2);
|
||||
|
||||
writeFileSync(outputPath, jsonData, "utf8");
|
||||
|
||||
console.log(`✅ Generated CLI completion data at: ${outputPath}`);
|
||||
console.log(`📊 Statistics:`);
|
||||
console.log(` - Commands: ${Object.keys(completionData.commands).length}`);
|
||||
console.log(` - Global flags: ${completionData.globalFlags.length}`);
|
||||
|
||||
let totalFlags = 0;
|
||||
let totalExamples = 0;
|
||||
let totalSubcommands = 0;
|
||||
for (const [name, cmd] of Object.entries(completionData.commands)) {
|
||||
totalFlags += cmd.flags.length;
|
||||
totalExamples += cmd.examples.length;
|
||||
const subcommandCount = cmd.subcommands ? Object.keys(cmd.subcommands).length : 0;
|
||||
totalSubcommands += subcommandCount;
|
||||
|
||||
const aliasInfo = cmd.aliases ? ` (aliases: ${cmd.aliases.join(", ")})` : "";
|
||||
const subcommandInfo = subcommandCount > 0 ? `, ${subcommandCount} subcommands` : "";
|
||||
const dynamicInfo = cmd.dynamicCompletions ? ` [dynamic: ${Object.keys(cmd.dynamicCompletions).join(", ")}]` : "";
|
||||
|
||||
console.log(
|
||||
` - ${name}${aliasInfo}: ${cmd.flags.length} flags, ${cmd.positionalArgs.length} positional args, ${cmd.examples.length} examples${subcommandInfo}${dynamicInfo}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(` - Total command flags: ${totalFlags}`);
|
||||
console.log(` - Total examples: ${totalExamples}`);
|
||||
console.log(` - Total subcommands: ${totalSubcommands}`);
|
||||
}
|
||||
|
||||
// Run the script
|
||||
if (import.meta.main) {
|
||||
generateCompletions().catch(console.error);
|
||||
}
|
||||
121
misctools/lldb/README.md
Normal file
121
misctools/lldb/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# LLDB Pretty Printers for Bun
|
||||
|
||||
This directory contains LLDB pretty printers for various Bun data structures to improve the debugging experience.
|
||||
|
||||
## Files
|
||||
|
||||
- `bun_pretty_printer.py` - Pretty printers for Bun-specific types (bun.String, WTFStringImpl, ZigString, BabyList, etc.)
|
||||
- `lldb_pretty_printers.py` - Pretty printers for Zig language types from the Zig project
|
||||
- `lldb_webkit.py` - Pretty printers for WebKit/JavaScriptCore types
|
||||
- `init.lldb` - LLDB initialization commands
|
||||
|
||||
## Supported Types
|
||||
|
||||
### bun.String Types
|
||||
- `bun.String` (or just `String`) - The main Bun string type
|
||||
- `WTFStringImpl` - WebKit string implementation (Latin1/UTF16)
|
||||
- `ZigString` - Zig string type (UTF8/Latin1/UTF16 with pointer tagging)
|
||||
|
||||
### Display Format
|
||||
|
||||
The pretty printers show string content directly, with additional metadata:
|
||||
|
||||
```
|
||||
# bun.String examples:
|
||||
"Hello, World!" [latin1] # Regular ZigString
|
||||
"UTF-8 String 🎉" [utf8] # UTF-8 encoded
|
||||
"Static content" [latin1 static] # Static string
|
||||
"" # Empty string
|
||||
<dead> # Dead/invalid string
|
||||
|
||||
# WTFStringImpl examples:
|
||||
"WebKit String" # Shows the actual string content
|
||||
|
||||
# ZigString examples:
|
||||
"Some text" [utf16 global] # UTF16 globally allocated
|
||||
"ASCII text" [latin1] # Latin1 encoded
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Option 1: Manual Loading
|
||||
In your LLDB session:
|
||||
```lldb
|
||||
command script import /path/to/bun/misctools/lldb/bun_pretty_printer.py
|
||||
```
|
||||
|
||||
### Option 2: Add to ~/.lldbinit
|
||||
Add the following line to your `~/.lldbinit` file to load automatically:
|
||||
```lldb
|
||||
command script import /path/to/bun/misctools/lldb/bun_pretty_printer.py
|
||||
```
|
||||
|
||||
### Option 3: Use init.lldb
|
||||
```lldb
|
||||
command source /path/to/bun/misctools/lldb/init.lldb
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test the pretty printers:
|
||||
|
||||
1. Build a debug version of Bun:
|
||||
```bash
|
||||
bun bd
|
||||
```
|
||||
|
||||
2. Create a test file that uses bun.String types
|
||||
|
||||
3. Debug with LLDB:
|
||||
```bash
|
||||
lldb ./build/debug/bun-debug
|
||||
(lldb) command script import misctools/lldb/bun_pretty_printer.py
|
||||
(lldb) breakpoint set --file your_test.zig --line <line_number>
|
||||
(lldb) run your_test.zig
|
||||
(lldb) frame variable
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### ZigString Pointer Tagging
|
||||
ZigString uses pointer tagging in the upper bits:
|
||||
- Bit 63: 1 = UTF16, 0 = UTF8/Latin1
|
||||
- Bit 62: 1 = Globally allocated (mimalloc)
|
||||
- Bit 61: 1 = UTF8 encoding
|
||||
|
||||
The pretty printer automatically detects and handles these tags.
|
||||
|
||||
### WTFStringImpl Encoding
|
||||
WTFStringImpl uses flags in `m_hashAndFlags`:
|
||||
- Bit 2 (s_hashFlag8BitBuffer): 1 = Latin1, 0 = UTF16
|
||||
|
||||
### bun.String Tag Union
|
||||
bun.String is a tagged union with these variants:
|
||||
- Dead (0): Invalid/freed string
|
||||
- WTFStringImpl (1): WebKit string
|
||||
- ZigString (2): Regular Zig string
|
||||
- StaticZigString (3): Static/immortal string
|
||||
- Empty (4): Empty string ""
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the pretty printers don't work:
|
||||
|
||||
1. Verify the Python script loaded:
|
||||
```lldb
|
||||
(lldb) script print("Python works")
|
||||
```
|
||||
|
||||
2. Check if the category is enabled:
|
||||
```lldb
|
||||
(lldb) type category list
|
||||
```
|
||||
|
||||
3. Enable the Bun category manually:
|
||||
```lldb
|
||||
(lldb) type category enable bun
|
||||
```
|
||||
|
||||
4. For debugging the pretty printer itself, check for exceptions:
|
||||
- The pretty printers catch all exceptions and return `<error>`
|
||||
- Modify the code to print exceptions for debugging
|
||||
@@ -10,8 +10,8 @@ class bun_BabyList_SynthProvider:
|
||||
|
||||
try:
|
||||
self.ptr = self.value.GetChildMemberWithName('ptr')
|
||||
self.len = self.value.GetChildMemberWithName('len').unsigned
|
||||
self.cap = self.value.GetChildMemberWithName('cap').unsigned
|
||||
self.len = self.value.GetChildMemberWithName('len').GetValueAsUnsigned()
|
||||
self.cap = self.value.GetChildMemberWithName('cap').GetValueAsUnsigned()
|
||||
self.elem_type = self.ptr.type.GetPointeeType()
|
||||
self.elem_size = self.elem_type.size
|
||||
except:
|
||||
@@ -46,7 +46,7 @@ def bun_BabyList_SummaryProvider(value, _=None):
|
||||
value = value.GetNonSyntheticValue()
|
||||
len_val = value.GetChildMemberWithName('len')
|
||||
cap_val = value.GetChildMemberWithName('cap')
|
||||
return 'len=%d cap=%d' % (len_val.unsigned, cap_val.unsigned)
|
||||
return 'len=%d cap=%d' % (len_val.GetValueAsUnsigned(), cap_val.GetValueAsUnsigned())
|
||||
except:
|
||||
return 'len=? cap=?'
|
||||
|
||||
@@ -67,6 +67,241 @@ def add(debugger, *, category, regex=False, type, identifier=None, synth=False,
|
||||
type
|
||||
))
|
||||
|
||||
def WTFStringImpl_SummaryProvider(value, _=None):
|
||||
try:
|
||||
# Get the raw pointer (it's already a pointer type)
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
# Check if it's a pointer type and dereference if needed
|
||||
if value.type.IsPointerType():
|
||||
struct = value.deref
|
||||
else:
|
||||
struct = value
|
||||
|
||||
m_length = struct.GetChildMemberWithName('m_length').GetValueAsUnsigned()
|
||||
m_hashAndFlags = struct.GetChildMemberWithName('m_hashAndFlags').GetValueAsUnsigned()
|
||||
m_ptr = struct.GetChildMemberWithName('m_ptr')
|
||||
|
||||
# Check if it's 8-bit (latin1) or 16-bit (utf16) string
|
||||
s_hashFlag8BitBuffer = 1 << 2
|
||||
is_8bit = (m_hashAndFlags & s_hashFlag8BitBuffer) != 0
|
||||
|
||||
if m_length == 0:
|
||||
return '[%s] ""' % ('latin1' if is_8bit else 'utf16')
|
||||
|
||||
# Limit memory reads to 1MB for performance
|
||||
MAX_BYTES = 1024 * 1024 # 1MB
|
||||
MAX_DISPLAY_CHARS = 200 # Maximum characters to display
|
||||
|
||||
# Calculate how much to read
|
||||
bytes_per_char = 1 if is_8bit else 2
|
||||
total_bytes = m_length * bytes_per_char
|
||||
truncated = False
|
||||
|
||||
if total_bytes > MAX_BYTES:
|
||||
# Read only first part of very large strings
|
||||
chars_to_read = MAX_BYTES // bytes_per_char
|
||||
bytes_to_read = chars_to_read * bytes_per_char
|
||||
truncated = True
|
||||
else:
|
||||
chars_to_read = m_length
|
||||
bytes_to_read = total_bytes
|
||||
|
||||
if is_8bit:
|
||||
# Latin1 string
|
||||
latin1_ptr = m_ptr.GetChildMemberWithName('latin1')
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
ptr_addr = latin1_ptr.GetValueAsUnsigned()
|
||||
if ptr_addr:
|
||||
byte_data = process.ReadMemory(ptr_addr, min(chars_to_read, m_length), error)
|
||||
if error.Success():
|
||||
string_val = byte_data.decode('latin1', errors='replace')
|
||||
else:
|
||||
return '[latin1] <read error: %s>' % error
|
||||
else:
|
||||
return '[latin1] <null ptr>'
|
||||
else:
|
||||
# UTF16 string
|
||||
utf16_ptr = m_ptr.GetChildMemberWithName('utf16')
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
ptr_addr = utf16_ptr.GetValueAsUnsigned()
|
||||
if ptr_addr:
|
||||
byte_data = process.ReadMemory(ptr_addr, bytes_to_read, error)
|
||||
if error.Success():
|
||||
# Properly decode UTF16LE to string
|
||||
string_val = byte_data.decode('utf-16le', errors='replace')
|
||||
else:
|
||||
return '[utf16] <read error: %s>' % error
|
||||
else:
|
||||
return '[utf16] <null ptr>'
|
||||
|
||||
# Escape special characters
|
||||
string_val = string_val.replace('\\', '\\\\')
|
||||
string_val = string_val.replace('"', '\\"')
|
||||
string_val = string_val.replace('\n', '\\n')
|
||||
string_val = string_val.replace('\r', '\\r')
|
||||
string_val = string_val.replace('\t', '\\t')
|
||||
|
||||
# Truncate display if too long
|
||||
display_truncated = truncated or len(string_val) > MAX_DISPLAY_CHARS
|
||||
if len(string_val) > MAX_DISPLAY_CHARS:
|
||||
string_val = string_val[:MAX_DISPLAY_CHARS]
|
||||
|
||||
# Add encoding and size info at the beginning
|
||||
encoding = 'latin1' if is_8bit else 'utf16'
|
||||
|
||||
if display_truncated:
|
||||
size_info = ' %d chars' % m_length
|
||||
if total_bytes >= 1024 * 1024:
|
||||
size_info += ' (%.1fMB)' % (total_bytes / (1024.0 * 1024.0))
|
||||
elif total_bytes >= 1024:
|
||||
size_info += ' (%.1fKB)' % (total_bytes / 1024.0)
|
||||
return '[%s%s] "%s..." <truncated>' % (encoding, size_info, string_val)
|
||||
else:
|
||||
return '[%s] "%s"' % (encoding, string_val)
|
||||
except:
|
||||
return '<error>'
|
||||
|
||||
def ZigString_SummaryProvider(value, _=None):
|
||||
try:
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
ptr = value.GetChildMemberWithName('_unsafe_ptr_do_not_use').GetValueAsUnsigned()
|
||||
length = value.GetChildMemberWithName('len').GetValueAsUnsigned()
|
||||
|
||||
# Check encoding flags
|
||||
is_16bit = (ptr & (1 << 63)) != 0
|
||||
is_utf8 = (ptr & (1 << 61)) != 0
|
||||
is_global = (ptr & (1 << 62)) != 0
|
||||
|
||||
# Determine encoding
|
||||
encoding = 'utf16' if is_16bit else ('utf8' if is_utf8 else 'latin1')
|
||||
flags = ' global' if is_global else ''
|
||||
|
||||
if length == 0:
|
||||
return '[%s%s] ""' % (encoding, flags)
|
||||
|
||||
# Untag the pointer (keep only the lower 53 bits)
|
||||
untagged_ptr = ptr & ((1 << 53) - 1)
|
||||
|
||||
# Limit memory reads to 1MB for performance
|
||||
MAX_BYTES = 1024 * 1024 # 1MB
|
||||
MAX_DISPLAY_CHARS = 200 # Maximum characters to display
|
||||
|
||||
# Calculate how much to read
|
||||
bytes_per_char = 2 if is_16bit else 1
|
||||
total_bytes = length * bytes_per_char
|
||||
truncated = False
|
||||
|
||||
if total_bytes > MAX_BYTES:
|
||||
# Read only first part of very large strings
|
||||
chars_to_read = MAX_BYTES // bytes_per_char
|
||||
bytes_to_read = chars_to_read * bytes_per_char
|
||||
truncated = True
|
||||
else:
|
||||
bytes_to_read = total_bytes
|
||||
|
||||
# Read the string data
|
||||
process = value.process
|
||||
error = lldb.SBError()
|
||||
|
||||
byte_data = process.ReadMemory(untagged_ptr, bytes_to_read, error)
|
||||
if not error.Success():
|
||||
return '[%s%s] <read error>' % (encoding, flags)
|
||||
|
||||
# Decode based on encoding
|
||||
if is_16bit:
|
||||
string_val = byte_data.decode('utf-16le', errors='replace')
|
||||
elif is_utf8:
|
||||
string_val = byte_data.decode('utf-8', errors='replace')
|
||||
else:
|
||||
string_val = byte_data.decode('latin1', errors='replace')
|
||||
|
||||
# Escape special characters
|
||||
string_val = string_val.replace('\\', '\\\\')
|
||||
string_val = string_val.replace('"', '\\"')
|
||||
string_val = string_val.replace('\n', '\\n')
|
||||
string_val = string_val.replace('\r', '\\r')
|
||||
string_val = string_val.replace('\t', '\\t')
|
||||
|
||||
# Truncate display if too long
|
||||
display_truncated = truncated or len(string_val) > MAX_DISPLAY_CHARS
|
||||
if len(string_val) > MAX_DISPLAY_CHARS:
|
||||
string_val = string_val[:MAX_DISPLAY_CHARS]
|
||||
|
||||
# Build the output
|
||||
if display_truncated:
|
||||
size_info = ' %d chars' % length
|
||||
if total_bytes >= 1024 * 1024:
|
||||
size_info += ' (%.1fMB)' % (total_bytes / (1024.0 * 1024.0))
|
||||
elif total_bytes >= 1024:
|
||||
size_info += ' (%.1fKB)' % (total_bytes / 1024.0)
|
||||
return '[%s%s%s] "%s..." <truncated>' % (encoding, flags, size_info, string_val)
|
||||
else:
|
||||
return '[%s%s] "%s"' % (encoding, flags, string_val)
|
||||
except:
|
||||
return '<error>'
|
||||
|
||||
def bun_String_SummaryProvider(value, _=None):
|
||||
try:
|
||||
value = value.GetNonSyntheticValue()
|
||||
|
||||
# Debug: Show the actual type name LLDB sees
|
||||
type_name = value.GetTypeName()
|
||||
|
||||
tag = value.GetChildMemberWithName('tag')
|
||||
if not tag or not tag.IsValid():
|
||||
# Try alternate field names
|
||||
tag = value.GetChildMemberWithName('Tag')
|
||||
if not tag or not tag.IsValid():
|
||||
# Show type name to help debug
|
||||
return '<no tag field in type: %s>' % type_name
|
||||
|
||||
tag_value = tag.GetValueAsUnsigned()
|
||||
|
||||
# Map tag values to names
|
||||
tag_names = {
|
||||
0: 'Dead',
|
||||
1: 'WTFStringImpl',
|
||||
2: 'ZigString',
|
||||
3: 'StaticZigString',
|
||||
4: 'Empty'
|
||||
}
|
||||
|
||||
tag_name = tag_names.get(tag_value, 'Unknown')
|
||||
|
||||
if tag_name == 'Empty':
|
||||
return '""'
|
||||
elif tag_name == 'Dead':
|
||||
return '<dead>'
|
||||
elif tag_name == 'WTFStringImpl':
|
||||
value_union = value.GetChildMemberWithName('value')
|
||||
if not value_union or not value_union.IsValid():
|
||||
return '<no value field>'
|
||||
impl_value = value_union.GetChildMemberWithName('WTFStringImpl')
|
||||
if not impl_value or not impl_value.IsValid():
|
||||
return '<no WTFStringImpl field>'
|
||||
return WTFStringImpl_SummaryProvider(impl_value, _)
|
||||
elif tag_name == 'ZigString' or tag_name == 'StaticZigString':
|
||||
value_union = value.GetChildMemberWithName('value')
|
||||
if not value_union or not value_union.IsValid():
|
||||
return '<no value field>'
|
||||
field_name = 'ZigString' if tag_name == 'ZigString' else 'StaticZigString'
|
||||
zig_string_value = value_union.GetChildMemberWithName(field_name)
|
||||
if not zig_string_value or not zig_string_value.IsValid():
|
||||
return '<no %s field>' % field_name
|
||||
result = ZigString_SummaryProvider(zig_string_value, _)
|
||||
# Add static marker if needed
|
||||
if tag_name == 'StaticZigString':
|
||||
result = result.replace(']', ' static]')
|
||||
return result
|
||||
else:
|
||||
return '<unknown tag %d>' % tag_value
|
||||
except Exception as e:
|
||||
return '<error: %s>' % str(e)
|
||||
|
||||
def __lldb_init_module(debugger, _=None):
|
||||
# Initialize Bun Category
|
||||
debugger.HandleCommand('type category define --language c99 bun')
|
||||
@@ -74,5 +309,30 @@ def __lldb_init_module(debugger, _=None):
|
||||
# Initialize Bun Data Structures
|
||||
add(debugger, category='bun', regex=True, type='^baby_list\\.BabyList\\(.*\\)$', identifier='bun_BabyList', synth=True, expand=True, summary=True)
|
||||
|
||||
# Add WTFStringImpl pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='WTFStringImpl', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='*WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='string.WTFStringImpl', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='string.WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', type='*string.WTFStringImplStruct', identifier='WTFStringImpl', summary=True)
|
||||
|
||||
# Add ZigString pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='ZigString', identifier='ZigString', summary=True)
|
||||
add(debugger, category='bun', type='bun.js.bindings.ZigString', identifier='ZigString', summary=True)
|
||||
add(debugger, category='bun', type='bindings.ZigString', identifier='ZigString', summary=True)
|
||||
|
||||
# Add bun.String pretty printer - try multiple possible type names
|
||||
add(debugger, category='bun', type='String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun.String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='string.String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='BunString', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun::String', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', type='bun::string::String', identifier='bun_String', summary=True)
|
||||
|
||||
# Try regex patterns for more flexible matching
|
||||
add(debugger, category='bun', regex=True, type='.*String$', identifier='bun_String', summary=True)
|
||||
add(debugger, category='bun', regex=True, type='.*WTFStringImpl.*', identifier='WTFStringImpl', summary=True)
|
||||
add(debugger, category='bun', regex=True, type='.*ZigString.*', identifier='ZigString', summary=True)
|
||||
|
||||
# Enable the category
|
||||
debugger.HandleCommand('type category enable bun')
|
||||
16
package.json
16
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.2.20",
|
||||
"version": "1.2.21",
|
||||
"workspaces": [
|
||||
"./packages/bun-types",
|
||||
"./packages/@types/bun"
|
||||
],
|
||||
"devDependencies": {
|
||||
"bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8",
|
||||
"@lezer/common": "^1.2.3",
|
||||
"@lezer/cpp": "^1.1.3",
|
||||
"@types/bun": "workspace:*",
|
||||
"bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8",
|
||||
"esbuild": "^0.21.4",
|
||||
"mitata": "^0.1.11",
|
||||
"peechy": "0.4.34",
|
||||
@@ -18,7 +19,7 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"source-map-js": "^1.2.0",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"bun-types": "workspace:packages/bun-types",
|
||||
@@ -48,6 +49,9 @@
|
||||
"css-properties": "bun run src/css/properties/generate_properties.ts",
|
||||
"uv-posix-stubs": "bun run src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts",
|
||||
"bump": "bun ./scripts/bump.ts",
|
||||
"jsc:build": "bun ./scripts/build-jsc.ts release",
|
||||
"jsc:build:debug": "bun ./scripts/build-jsc.ts debug",
|
||||
"jsc:build:lto": "bun ./scripts/build-jsc.ts lto",
|
||||
"typecheck": "tsc --noEmit && cd test && bun run typecheck",
|
||||
"fmt": "bun run prettier",
|
||||
"fmt:cpp": "bun run clang-format",
|
||||
@@ -68,9 +72,9 @@
|
||||
"zig:check-windows": "bun run zig build check-windows --summary new",
|
||||
"analysis": "bun ./scripts/build.mjs -DCMAKE_BUILD_TYPE=Debug -DENABLE_ANALYSIS=ON -DENABLE_CCACHE=OFF -B build/analysis",
|
||||
"analysis:no-llvm": "bun run analysis -DENABLE_LLVM=OFF",
|
||||
"clang-format": "bun run analysis --target clang-format",
|
||||
"clang-format:check": "bun run analysis --target clang-format-check",
|
||||
"clang-format:diff": "bun run analysis --target clang-format-diff",
|
||||
"clang-format": "./scripts/run-clang-format.sh format",
|
||||
"clang-format:check": "./scripts/run-clang-format.sh check",
|
||||
"clang-format:diff": "./scripts/run-clang-format.sh diff",
|
||||
"clang-tidy": "bun run analysis --target clang-tidy",
|
||||
"clang-tidy:check": "bun run analysis --target clang-tidy-check",
|
||||
"clang-tidy:diff": "bun run analysis --target clang-tidy-diff",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "bun-plugin-svelte",
|
||||
"devDependencies": {
|
||||
"@threlte/core": "8.0.1",
|
||||
"bun-types": "canary",
|
||||
"@types/bun": "../bun-types",
|
||||
"svelte": "^5.20.4",
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -28,11 +28,13 @@
|
||||
|
||||
"@threlte/core": ["@threlte/core@8.0.1", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-vy1xRQppJFNmfPTeiRQue+KmYFsbPgVhwuYXRTvVrwPeD2oYz43gxUeOpe1FACeGKxrxZykeKJF5ebVvl7gBxw=="],
|
||||
|
||||
"@types/bun": ["bun-types@file:../bun-types", { "dependencies": { "@types/node": "*" }, "devDependencies": { "@types/react": "^19" }, "peerDependencies": { "@types/react": "^19" } }],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
||||
|
||||
"@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||
"@types/react": ["@types/react@19.1.9", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="],
|
||||
|
||||
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
|
||||
@@ -42,10 +44,10 @@
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.4-canary.20250226T140704", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-P8b2CGLtbvi/kQ4dPHBhU5qkguIjHMYCjNqjWDTKSnodWDTbcv9reBdktZJ7m5SF4m15JLthfFq2PtwKpA9a+w=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||
|
||||
"esrap": ["esrap@1.4.5", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g=="],
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"build:types": "tsc --emitDeclarationOnly --declaration --declarationDir ./dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "canary",
|
||||
"@types/bun": "../bun-types",
|
||||
"svelte": "^5.20.4",
|
||||
"@threlte/core": "8.0.1"
|
||||
},
|
||||
|
||||
922
packages/bun-types/bun.d.ts
vendored
922
packages/bun-types/bun.d.ts
vendored
File diff suppressed because it is too large
Load Diff
89
packages/bun-types/deprecated.d.ts
vendored
89
packages/bun-types/deprecated.d.ts
vendored
@@ -1,4 +1,88 @@
|
||||
declare module "bun" {
|
||||
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
|
||||
type Platform =
|
||||
| "aix"
|
||||
| "android"
|
||||
| "darwin"
|
||||
| "freebsd"
|
||||
| "haiku"
|
||||
| "linux"
|
||||
| "openbsd"
|
||||
| "sunos"
|
||||
| "win32"
|
||||
| "cygwin"
|
||||
| "netbsd";
|
||||
|
||||
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
|
||||
type Architecture = "arm" | "arm64" | "ia32" | "mips" | "mipsel" | "ppc" | "ppc64" | "s390" | "s390x" | "x64";
|
||||
|
||||
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
|
||||
type UncaughtExceptionListener = (error: Error, origin: UncaughtExceptionOrigin) => void;
|
||||
|
||||
/**
|
||||
* Most of the time the unhandledRejection will be an Error, but this should not be relied upon
|
||||
* as *anything* can be thrown/rejected, it is therefore unsafe to assume that the value is an Error.
|
||||
*
|
||||
* @deprecated This type is unused in Bun's types and might be removed in the near future
|
||||
*/
|
||||
type UnhandledRejectionListener = (reason: unknown, promise: Promise<unknown>) => void;
|
||||
|
||||
/** @deprecated This type is unused in Bun's types and might be removed in the near future */
|
||||
type MultipleResolveListener = (type: MultipleResolveType, promise: Promise<unknown>, value: unknown) => void;
|
||||
|
||||
/**
|
||||
* Consume all data from a {@link ReadableStream} until it closes or errors.
|
||||
*
|
||||
* Concatenate the chunks into a single {@link ArrayBuffer}.
|
||||
*
|
||||
* Each chunk must be a TypedArray or an ArrayBuffer. If you need to support
|
||||
* chunks of different types, consider {@link readableStreamToBlob}
|
||||
*
|
||||
* @param stream The stream to consume.
|
||||
* @returns A promise that resolves with the concatenated chunks or the concatenated chunks as a {@link Uint8Array}.
|
||||
*
|
||||
* @deprecated Use {@link ReadableStream.bytes}
|
||||
*/
|
||||
function readableStreamToBytes(
|
||||
stream: ReadableStream<ArrayBufferView | ArrayBufferLike>,
|
||||
): Promise<Uint8Array<ArrayBuffer>> | Uint8Array<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* Consume all data from a {@link ReadableStream} until it closes or errors.
|
||||
*
|
||||
* Concatenate the chunks into a single {@link Blob}.
|
||||
*
|
||||
* @param stream The stream to consume.
|
||||
* @returns A promise that resolves with the concatenated chunks as a {@link Blob}.
|
||||
*
|
||||
* @deprecated Use {@link ReadableStream.blob}
|
||||
*/
|
||||
function readableStreamToBlob(stream: ReadableStream): Promise<Blob>;
|
||||
|
||||
/**
|
||||
* Consume all data from a {@link ReadableStream} until it closes or errors.
|
||||
*
|
||||
* Concatenate the chunks into a single string. Chunks must be a TypedArray or an ArrayBuffer. If you need to support chunks of different types, consider {@link readableStreamToBlob}.
|
||||
*
|
||||
* @param stream The stream to consume.
|
||||
* @returns A promise that resolves with the concatenated chunks as a {@link String}.
|
||||
*
|
||||
* @deprecated Use {@link ReadableStream.text}
|
||||
*/
|
||||
function readableStreamToText(stream: ReadableStream): Promise<string>;
|
||||
|
||||
/**
|
||||
* Consume all data from a {@link ReadableStream} until it closes or errors.
|
||||
*
|
||||
* Concatenate the chunks into a single string and parse as JSON. Chunks must be a TypedArray or an ArrayBuffer. If you need to support chunks of different types, consider {@link readableStreamToBlob}.
|
||||
*
|
||||
* @param stream The stream to consume.
|
||||
* @returns A promise that resolves with the concatenated chunks as a {@link String}.
|
||||
*
|
||||
* @deprecated Use {@link ReadableStream.json}
|
||||
*/
|
||||
function readableStreamToJSON(stream: ReadableStream): Promise<any>;
|
||||
|
||||
interface BunMessageEvent<T> {
|
||||
/**
|
||||
* @deprecated
|
||||
@@ -31,6 +115,9 @@ declare module "bun" {
|
||||
*/
|
||||
type Errorlike = ErrorLike;
|
||||
|
||||
/** @deprecated This is unused in Bun's types and may be removed in the future */
|
||||
type ShellFunction = (input: Uint8Array<ArrayBuffer>) => Uint8Array<ArrayBuffer>;
|
||||
|
||||
interface TLSOptions {
|
||||
/**
|
||||
* File path to a TLS key
|
||||
@@ -59,7 +146,7 @@ declare module "bun" {
|
||||
}
|
||||
|
||||
/** @deprecated This type is unused in Bun's declarations and may be removed in the future */
|
||||
type ReadableIO = ReadableStream<Uint8Array> | number | undefined;
|
||||
type ReadableIO = ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined;
|
||||
}
|
||||
|
||||
declare namespace NodeJS {
|
||||
|
||||
22
packages/bun-types/fetch.d.ts
vendored
22
packages/bun-types/fetch.d.ts
vendored
@@ -1,19 +1,21 @@
|
||||
/*
|
||||
|
||||
This file does not declare any global types.
|
||||
|
||||
That should only happen in [./globals.d.ts](./globals.d.ts)
|
||||
so that our documentation generator can pick it up, as it
|
||||
expects all globals to be declared in one file.
|
||||
|
||||
* This file does not declare any global types.
|
||||
*
|
||||
* That should only happen in [./globals.d.ts](./globals.d.ts)
|
||||
* so that our documentation generator can pick it up, as it
|
||||
* expects all globals to be declared in one file.
|
||||
*
|
||||
* This may change in the future, which would be
|
||||
* a nice thing as it would allow us to split up
|
||||
* relevant types into their own files.
|
||||
*/
|
||||
|
||||
declare module "bun" {
|
||||
type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers;
|
||||
type BodyInit =
|
||||
| ReadableStream
|
||||
| Bun.XMLHttpRequestBodyInit
|
||||
| URLSearchParams
|
||||
// Extras that Bun supports:
|
||||
| AsyncIterable<string | ArrayBuffer | ArrayBufferView>
|
||||
| AsyncGenerator<string | ArrayBuffer | ArrayBufferView>
|
||||
| (() => AsyncGenerator<string | ArrayBuffer | ArrayBufferView>);
|
||||
|
||||
@@ -26,7 +28,7 @@ declare module "bun" {
|
||||
? {}
|
||||
: Omit<import("undici-types").RequestInit, "body" | "headers"> & {
|
||||
body?: Bun.BodyInit | null | undefined;
|
||||
headers?: Bun.HeadersInit;
|
||||
headers?: Bun.HeadersInit | undefined;
|
||||
};
|
||||
|
||||
interface BunHeadersOverride extends LibOrFallbackHeaders {
|
||||
|
||||
31
packages/bun-types/globals.d.ts
vendored
31
packages/bun-types/globals.d.ts
vendored
@@ -999,6 +999,7 @@ interface ArrayBuffer {
|
||||
* Read-only. The length of the ArrayBuffer (in bytes).
|
||||
*/
|
||||
readonly byteLength: number;
|
||||
|
||||
/**
|
||||
* Resize an ArrayBuffer in-place.
|
||||
*/
|
||||
@@ -1008,7 +1009,6 @@ interface ArrayBuffer {
|
||||
* Returns a section of an ArrayBuffer.
|
||||
*/
|
||||
slice(begin: number, end?: number): ArrayBuffer;
|
||||
readonly [Symbol.toStringTag]: string;
|
||||
}
|
||||
|
||||
interface SharedArrayBuffer {
|
||||
@@ -1284,7 +1284,7 @@ interface ImportMeta {
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
readonly main: boolean;
|
||||
main: boolean;
|
||||
|
||||
/** Alias of `import.meta.dir`. Exists for Node.js compatibility */
|
||||
dirname: string;
|
||||
@@ -1426,12 +1426,12 @@ interface Blob {
|
||||
/**
|
||||
* Returns a promise that resolves to the contents of the blob as a Uint8Array (array of bytes) its the same as `new Uint8Array(await blob.arrayBuffer())`
|
||||
*/
|
||||
bytes(): Promise<Uint8Array>;
|
||||
bytes(): Promise<Uint8Array<ArrayBuffer>>;
|
||||
|
||||
/**
|
||||
* Returns a readable stream of the blob's contents
|
||||
*/
|
||||
stream(): ReadableStream<Uint8Array>;
|
||||
stream(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
||||
}
|
||||
|
||||
declare var Blob: Bun.__internal.UseLibDomIfAvailable<
|
||||
@@ -1506,14 +1506,14 @@ interface Uint8ArrayConstructor {
|
||||
alphabet?: "base64" | "base64url";
|
||||
lastChunkHandling?: "loose" | "strict" | "stop-before-partial";
|
||||
},
|
||||
): Uint8Array;
|
||||
): Uint8Array<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* Create a new Uint8Array from a hex encoded string
|
||||
* @param hex The hex encoded string to convert to a Uint8Array
|
||||
* @returns A new Uint8Array containing the decoded data
|
||||
*/
|
||||
fromHex(hex: string): Uint8Array;
|
||||
fromHex(hex: string): Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
|
||||
interface BroadcastChannel extends Bun.__internal.LibEmptyOrBroadcastChannel {}
|
||||
@@ -1888,6 +1888,25 @@ interface BunFetchRequestInit extends RequestInit {
|
||||
* ```
|
||||
*/
|
||||
unix?: string;
|
||||
|
||||
/**
|
||||
* Control automatic decompression of the response body.
|
||||
* When set to `false`, the response body will not be automatically decompressed,
|
||||
* and the `Content-Encoding` header will be preserved. This can improve performance
|
||||
* when you need to handle compressed data manually or forward it as-is.
|
||||
* This is a custom property that is not part of the Fetch API specification.
|
||||
*
|
||||
* @default true
|
||||
* @example
|
||||
* ```js
|
||||
* // Disable automatic decompression for a proxy server
|
||||
* const response = await fetch("https://example.com/api", {
|
||||
* decompress: false
|
||||
* });
|
||||
* // response.headers.get('content-encoding') might be 'gzip' or 'br'
|
||||
* ```
|
||||
*/
|
||||
decompress?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
1
packages/bun-types/index.d.ts
vendored
1
packages/bun-types/index.d.ts
vendored
@@ -21,6 +21,7 @@
|
||||
/// <reference path="./redis.d.ts" />
|
||||
/// <reference path="./shell.d.ts" />
|
||||
/// <reference path="./experimental.d.ts" />
|
||||
/// <reference path="./sql.d.ts" />
|
||||
|
||||
/// <reference path="./bun.ns.d.ts" />
|
||||
|
||||
|
||||
11
packages/bun-types/overrides.d.ts
vendored
11
packages/bun-types/overrides.d.ts
vendored
@@ -6,14 +6,17 @@ declare module "stream/web" {
|
||||
* Consume a ReadableStream as text
|
||||
*/
|
||||
text(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Consume a ReadableStream as a Uint8Array
|
||||
*/
|
||||
bytes(): Promise<Uint8Array>;
|
||||
bytes(): Promise<Uint8Array<ArrayBuffer>>;
|
||||
|
||||
/**
|
||||
* Consume a ReadableStream as JSON
|
||||
*/
|
||||
json(): Promise<any>;
|
||||
|
||||
/**
|
||||
* Consume a ReadableStream as a Blob
|
||||
*/
|
||||
@@ -21,6 +24,12 @@ declare module "stream/web" {
|
||||
}
|
||||
}
|
||||
|
||||
declare module "url" {
|
||||
interface URLSearchParams {
|
||||
toJSON(): Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv extends Bun.Env {}
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
},
|
||||
"files": [
|
||||
"./*.d.ts",
|
||||
"vendor/**/*.d.ts",
|
||||
"docs/**/*.md",
|
||||
"docs/*.md",
|
||||
"CLAUDE.md",
|
||||
"README.md"
|
||||
"./vendor/**/*.d.ts",
|
||||
"./docs/**/*.md",
|
||||
"./docs/*.md",
|
||||
"./CLAUDE.md",
|
||||
"./README.md"
|
||||
],
|
||||
"homepage": "https://bun.com",
|
||||
"dependencies": {
|
||||
@@ -24,8 +24,7 @@
|
||||
"@types/react": "^19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19",
|
||||
"typescript": "^5.0.2"
|
||||
"@types/react": "^19"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "echo $(pwd)",
|
||||
|
||||
44
packages/bun-types/redis.d.ts
vendored
44
packages/bun-types/redis.d.ts
vendored
@@ -574,6 +574,50 @@ declare module "bun" {
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration in seconds
|
||||
* @param key The key to get
|
||||
* @param ex Set the specified expire time, in seconds
|
||||
* @param seconds The number of seconds until expiration
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, ex: "EX", seconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration in milliseconds
|
||||
* @param key The key to get
|
||||
* @param px Set the specified expire time, in milliseconds
|
||||
* @param milliseconds The number of milliseconds until expiration
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, px: "PX", milliseconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration at a specific Unix timestamp in seconds
|
||||
* @param key The key to get
|
||||
* @param exat Set the specified Unix time at which the key will expire, in seconds
|
||||
* @param timestampSeconds The Unix timestamp in seconds
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, exat: "EXAT", timestampSeconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and set its expiration at a specific Unix timestamp in milliseconds
|
||||
* @param key The key to get
|
||||
* @param pxat Set the specified Unix time at which the key will expire, in milliseconds
|
||||
* @param timestampMilliseconds The Unix timestamp in milliseconds
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, pxat: "PXAT", timestampMilliseconds: number): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Get the value of a key and remove its expiration
|
||||
* @param key The key to get
|
||||
* @param persist Remove the expiration from the key
|
||||
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
|
||||
*/
|
||||
getex(key: RedisClient.KeyLike, persist: "PERSIST"): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Ping the server
|
||||
* @returns Promise that resolves with "PONG" if the server is reachable, or throws an error if the server is not reachable
|
||||
|
||||
4
packages/bun-types/s3.d.ts
vendored
4
packages/bun-types/s3.d.ts
vendored
@@ -487,8 +487,8 @@ declare module "bun" {
|
||||
* // Process text chunk by chunk
|
||||
* }
|
||||
*/
|
||||
readonly readable: ReadableStream;
|
||||
stream(): ReadableStream;
|
||||
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>>;
|
||||
stream(): ReadableStream<Uint8Array<ArrayBuffer>>;
|
||||
|
||||
/**
|
||||
* The name or path of the file in the bucket.
|
||||
|
||||
6
packages/bun-types/shell.d.ts
vendored
6
packages/bun-types/shell.d.ts
vendored
@@ -1,6 +1,4 @@
|
||||
declare module "bun" {
|
||||
type ShellFunction = (input: Uint8Array) => Uint8Array;
|
||||
|
||||
type ShellExpression =
|
||||
| { toString(): string }
|
||||
| Array<ShellExpression>
|
||||
@@ -294,7 +292,7 @@ declare module "bun" {
|
||||
* console.log(output.bytes()); // Uint8Array { byteLength: 6 }
|
||||
* ```
|
||||
*/
|
||||
bytes(): Uint8Array;
|
||||
bytes(): Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
|
||||
interface ShellOutput {
|
||||
@@ -361,7 +359,7 @@ declare module "bun" {
|
||||
* console.log(output.bytes()); // Uint8Array { byteLength: 6 }
|
||||
* ```
|
||||
*/
|
||||
bytes(): Uint8Array;
|
||||
bytes(): Uint8Array<ArrayBuffer>;
|
||||
|
||||
/**
|
||||
* Read from stdout as a Blob
|
||||
|
||||
805
packages/bun-types/sql.d.ts
vendored
Normal file
805
packages/bun-types/sql.d.ts
vendored
Normal file
@@ -0,0 +1,805 @@
|
||||
import type * as BunSQLite from "bun:sqlite";
|
||||
|
||||
declare module "bun" {
|
||||
/**
|
||||
* Represents a reserved connection from the connection pool Extends SQL with
|
||||
* additional release functionality
|
||||
*/
|
||||
interface ReservedSQL extends SQL, Disposable {
|
||||
/**
|
||||
* Releases the client back to the connection pool
|
||||
*/
|
||||
release(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a client within a transaction context Extends SQL with savepoint
|
||||
* functionality
|
||||
*/
|
||||
interface TransactionSQL extends SQL {
|
||||
/**
|
||||
* Creates a savepoint within the current transaction
|
||||
*/
|
||||
savepoint<T>(name: string, fn: SQL.SavepointContextCallback<T>): Promise<T>;
|
||||
savepoint<T>(fn: SQL.SavepointContextCallback<T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* The reserve method pulls out a connection from the pool, and returns a
|
||||
* client that wraps the single connection.
|
||||
*
|
||||
* Using reserve() inside of a transaction will return a brand new
|
||||
* connection, not one related to the transaction. This matches the
|
||||
* behaviour of the `postgres` package.
|
||||
*/
|
||||
reserve(): Promise<ReservedSQL>;
|
||||
}
|
||||
|
||||
namespace SQL {
|
||||
class SQLError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
|
||||
class PostgresError extends SQLError {
|
||||
public readonly code: string;
|
||||
public readonly errno: string | undefined;
|
||||
public readonly detail: string | undefined;
|
||||
public readonly hint: string | undefined;
|
||||
public readonly severity: string | undefined;
|
||||
public readonly position: string | undefined;
|
||||
public readonly internalPosition: string | undefined;
|
||||
public readonly internalQuery: string | undefined;
|
||||
public readonly where: string | undefined;
|
||||
public readonly schema: string | undefined;
|
||||
public readonly table: string | undefined;
|
||||
public readonly column: string | undefined;
|
||||
public readonly dataType: string | undefined;
|
||||
public readonly constraint: string | undefined;
|
||||
public readonly file: string | undefined;
|
||||
public readonly line: string | undefined;
|
||||
public readonly routine: string | undefined;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
options: {
|
||||
code: string;
|
||||
errno?: string | undefined;
|
||||
detail?: string;
|
||||
hint?: string | undefined;
|
||||
severity?: string | undefined;
|
||||
position?: string | undefined;
|
||||
internalPosition?: string;
|
||||
internalQuery?: string;
|
||||
where?: string | undefined;
|
||||
schema?: string;
|
||||
table?: string | undefined;
|
||||
column?: string | undefined;
|
||||
dataType?: string | undefined;
|
||||
constraint?: string;
|
||||
file?: string | undefined;
|
||||
line?: string | undefined;
|
||||
routine?: string | undefined;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class SQLiteError extends SQLError {
|
||||
public readonly code: string;
|
||||
public readonly errno: number;
|
||||
public readonly byteOffset?: number | undefined;
|
||||
|
||||
constructor(message: string, options: { code: string; errno: number; byteOffset?: number | undefined });
|
||||
}
|
||||
|
||||
type AwaitPromisesArray<T extends Array<PromiseLike<any>>> = {
|
||||
[K in keyof T]: Awaited<T[K]>;
|
||||
};
|
||||
|
||||
type ContextCallbackResult<T> = T extends Array<PromiseLike<any>> ? AwaitPromisesArray<T> : Awaited<T>;
|
||||
type ContextCallback<T, SQL> = (sql: SQL) => Bun.MaybePromise<T>;
|
||||
|
||||
interface SQLiteOptions extends BunSQLite.DatabaseOptions {
|
||||
adapter?: "sqlite";
|
||||
|
||||
/**
|
||||
* Specify the path to the database file
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - `sqlite://:memory:`
|
||||
* - `sqlite://./path/to/database.db`
|
||||
* - `sqlite:///Users/bun/projects/my-app/database.db`
|
||||
* - `./dev.db`
|
||||
* - `:memory:`
|
||||
*
|
||||
* @default ":memory:"
|
||||
*/
|
||||
filename?: URL | ":memory:" | (string & {}) | undefined;
|
||||
|
||||
/**
|
||||
* Callback executed when a connection attempt completes (SQLite)
|
||||
* Receives an Error on failure, or null on success.
|
||||
*/
|
||||
onconnect?: ((err: Error | null) => void) | undefined;
|
||||
|
||||
/**
|
||||
* Callback executed when a connection is closed (SQLite)
|
||||
* Receives the closing Error or null.
|
||||
*/
|
||||
onclose?: ((err: Error | null) => void) | undefined;
|
||||
}
|
||||
|
||||
interface PostgresOptions {
|
||||
/**
|
||||
* Connection URL (can be string or URL object)
|
||||
*/
|
||||
url?: URL | string | undefined;
|
||||
|
||||
/**
|
||||
* Database server hostname
|
||||
* @default "localhost"
|
||||
*/
|
||||
host?: string | undefined;
|
||||
|
||||
/**
|
||||
* Database server hostname (alias for host)
|
||||
* @deprecated Prefer {@link host}
|
||||
* @default "localhost"
|
||||
*/
|
||||
hostname?: string | undefined;
|
||||
|
||||
/**
|
||||
* Database server port number
|
||||
* @default 5432
|
||||
*/
|
||||
port?: number | string | undefined;
|
||||
|
||||
/**
|
||||
* Database user for authentication
|
||||
* @default "postgres"
|
||||
*/
|
||||
username?: string | undefined;
|
||||
|
||||
/**
|
||||
* Database user for authentication (alias for username)
|
||||
* @deprecated Prefer {@link username}
|
||||
* @default "postgres"
|
||||
*/
|
||||
user?: string | undefined;
|
||||
|
||||
/**
|
||||
* Database password for authentication
|
||||
* @default ""
|
||||
*/
|
||||
password?: string | (() => MaybePromise<string>) | undefined;
|
||||
|
||||
/**
|
||||
* Database password for authentication (alias for password)
|
||||
* @deprecated Prefer {@link password}
|
||||
* @default ""
|
||||
*/
|
||||
pass?: string | (() => MaybePromise<string>) | undefined;
|
||||
|
||||
/**
|
||||
* Name of the database to connect to
|
||||
* @default The username value
|
||||
*/
|
||||
database?: string | undefined;
|
||||
|
||||
/**
|
||||
* Name of the database to connect to (alias for database)
|
||||
* @deprecated Prefer {@link database}
|
||||
* @default The username value
|
||||
*/
|
||||
db?: string | undefined;
|
||||
|
||||
/**
|
||||
* Database adapter/driver to use
|
||||
* @default "postgres"
|
||||
*/
|
||||
adapter?: "postgres";
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait for connection to become available
|
||||
* @default 0 (no timeout)
|
||||
*/
|
||||
idleTimeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait for connection to become available (alias for idleTimeout)
|
||||
* @deprecated Prefer {@link idleTimeout}
|
||||
* @default 0 (no timeout)
|
||||
*/
|
||||
idle_timeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait when establishing a connection
|
||||
* @default 30
|
||||
*/
|
||||
connectionTimeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait when establishing a connection (alias for connectionTimeout)
|
||||
* @deprecated Prefer {@link connectionTimeout}
|
||||
* @default 30
|
||||
*/
|
||||
connection_timeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait when establishing a connection (alias
|
||||
* for connectionTimeout)
|
||||
* @deprecated Prefer {@link connectionTimeout}
|
||||
* @default 30
|
||||
*/
|
||||
connectTimeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum time in seconds to wait when establishing a connection (alias
|
||||
* for connectionTimeout)
|
||||
* @deprecated Prefer {@link connectionTimeout}
|
||||
* @default 30
|
||||
*/
|
||||
connect_timeout?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum lifetime in seconds of a connection
|
||||
* @default 0 (no maximum lifetime)
|
||||
*/
|
||||
maxLifetime?: number | undefined;
|
||||
|
||||
/**
|
||||
* Maximum lifetime in seconds of a connection (alias for maxLifetime)
|
||||
* @deprecated Prefer {@link maxLifetime}
|
||||
* @default 0 (no maximum lifetime)
|
||||
*/
|
||||
max_lifetime?: number | undefined;
|
||||
|
||||
/**
|
||||
* Whether to use TLS/SSL for the connection
|
||||
* @default false
|
||||
*/
|
||||
tls?: TLSOptions | boolean | undefined;
|
||||
|
||||
/**
|
||||
* Whether to use TLS/SSL for the connection (alias for tls)
|
||||
* @default false
|
||||
*/
|
||||
ssl?: TLSOptions | boolean | undefined;
|
||||
|
||||
// `.path` is currently unsupported in Bun, the implementation is
|
||||
// incomplete.
|
||||
//
|
||||
// /**
|
||||
// * Unix domain socket path for connection
|
||||
// * @default ""
|
||||
// */
|
||||
// path?: string | undefined;
|
||||
|
||||
/**
|
||||
* Callback executed when a connection attempt completes
|
||||
* Receives an Error on failure, or null on success.
|
||||
*/
|
||||
onconnect?: ((err: Error | null) => void) | undefined;
|
||||
|
||||
/**
|
||||
* Callback executed when a connection is closed
|
||||
* Receives the closing Error or null.
|
||||
*/
|
||||
onclose?: ((err: Error | null) => void) | undefined;
|
||||
|
||||
/**
|
||||
* Postgres client runtime configuration options
|
||||
*
|
||||
* @see https://www.postgresql.org/docs/current/runtime-config-client.html
|
||||
*/
|
||||
connection?: Record<string, string | boolean | number> | undefined;
|
||||
|
||||
/**
|
||||
* Maximum number of connections in the pool
|
||||
* @default 10
|
||||
*/
|
||||
max?: number | undefined;
|
||||
|
||||
/**
|
||||
* By default values outside i32 range are returned as strings. If this is
|
||||
* true, values outside i32 range are returned as BigInts.
|
||||
* @default false
|
||||
*/
|
||||
bigint?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Automatic creation of prepared statements
|
||||
* @default true
|
||||
*/
|
||||
prepare?: boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for SQL client connection and behavior
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const config: Bun.SQL.Options = {
|
||||
* host: 'localhost',
|
||||
* port: 5432,
|
||||
* user: 'dbuser',
|
||||
* password: 'secretpass',
|
||||
* database: 'myapp',
|
||||
* idleTimeout: 30,
|
||||
* max: 20,
|
||||
* onconnect: (client) => {
|
||||
* console.log('Connected to database');
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
type Options = SQLiteOptions | PostgresOptions;
|
||||
|
||||
/**
|
||||
* Represents a SQL query that can be executed, with additional control
|
||||
* methods Extends Promise to allow for async/await usage
|
||||
*/
|
||||
interface Query<T> extends Promise<T> {
|
||||
/**
|
||||
* Indicates if the query is currently executing
|
||||
*/
|
||||
active: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if the query has been cancelled
|
||||
*/
|
||||
cancelled: boolean;
|
||||
|
||||
/**
|
||||
* Cancels the executing query
|
||||
*/
|
||||
cancel(): Query<T>;
|
||||
|
||||
/**
|
||||
* Executes the query as a simple query, no parameters are allowed but can
|
||||
* execute multiple commands separated by semicolons
|
||||
*/
|
||||
simple(): Query<T>;
|
||||
|
||||
/**
|
||||
* Executes the query
|
||||
*/
|
||||
execute(): Query<T>;
|
||||
|
||||
/**
|
||||
* Returns the raw query result
|
||||
*/
|
||||
raw(): Query<T>;
|
||||
|
||||
/**
|
||||
* Returns only the values from the query result
|
||||
*/
|
||||
values(): Query<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function type for transaction contexts
|
||||
* @param sql Function to execute SQL queries within the transaction
|
||||
*/
|
||||
type TransactionContextCallback<T> = ContextCallback<T, TransactionSQL>;
|
||||
|
||||
/**
|
||||
* Callback function type for savepoint contexts
|
||||
* @param sql Function to execute SQL queries within the savepoint
|
||||
*/
|
||||
type SavepointContextCallback<T> = ContextCallback<T, SavepointSQL>;
|
||||
|
||||
/**
|
||||
* SQL.Helper represents a parameter or serializable
|
||||
* value inside of a query.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const helper = sql(users, 'id');
|
||||
* await sql`insert into users ${helper}`;
|
||||
* ```
|
||||
*/
|
||||
interface Helper<T> {
|
||||
readonly value: T[];
|
||||
readonly columns: (keyof T)[];
|
||||
}
|
||||
}
|
||||
|
||||
interface SQL extends AsyncDisposable {
|
||||
/**
|
||||
* Executes a SQL query using template literals
|
||||
* @example
|
||||
* ```ts
|
||||
* const [user] = await sql<Users[]>`select * from users where id = ${1}`;
|
||||
* ```
|
||||
*/
|
||||
<T = any>(strings: TemplateStringsArray, ...values: unknown[]): SQL.Query<T>;
|
||||
|
||||
/**
|
||||
* Execute a SQL query using a string
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const users = await sql<User[]>`SELECT * FROM users WHERE id = ${1}`;
|
||||
* ```
|
||||
*/
|
||||
<T = any>(string: string): SQL.Query<T>;
|
||||
|
||||
/**
|
||||
* Helper function for inserting an object into a query
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Insert an object
|
||||
* const result = await sql`insert into users ${sql(users)} returning *`;
|
||||
*
|
||||
* // Or pick specific columns
|
||||
* const result = await sql`insert into users ${sql(users, "id", "name")} returning *`;
|
||||
*
|
||||
* // Or a single object
|
||||
* const result = await sql`insert into users ${sql(user)} returning *`;
|
||||
* ```
|
||||
*/
|
||||
<T extends { [Key in PropertyKey]: unknown }>(obj: T | T[] | readonly T[]): SQL.Helper<T>; // Contributor note: This is the same as the signature below with the exception of the columns and the Pick<T, Keys>
|
||||
|
||||
/**
|
||||
* Helper function for inserting an object into a query, supporting specific columns
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Insert an object
|
||||
* const result = await sql`insert into users ${sql(users)} returning *`;
|
||||
*
|
||||
* // Or pick specific columns
|
||||
* const result = await sql`insert into users ${sql(users, "id", "name")} returning *`;
|
||||
*
|
||||
* // Or a single object
|
||||
* const result = await sql`insert into users ${sql(user)} returning *`;
|
||||
* ```
|
||||
*/
|
||||
<T extends { [Key in PropertyKey]: unknown }, Keys extends keyof T = keyof T>(
|
||||
obj: T | T[] | readonly T[],
|
||||
...columns: readonly Keys[]
|
||||
): SQL.Helper<Pick<T, Keys>>; // Contributor note: This is the same as the signature above with the exception of this signature tracking keys
|
||||
|
||||
/**
|
||||
* Helper function for inserting any serializable value into a query
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;
|
||||
* ```
|
||||
*/
|
||||
<T>(value: T): SQL.Helper<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main SQL client interface providing connection and transaction management
|
||||
*/
|
||||
class SQL {
|
||||
/**
|
||||
* Creates a new SQL client instance
|
||||
*
|
||||
* @param connectionString - The connection string for the SQL client
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const sql = new SQL("postgres://localhost:5432/mydb");
|
||||
* const sql = new SQL(new URL("postgres://localhost:5432/mydb"));
|
||||
* ```
|
||||
*/
|
||||
constructor(connectionString: string | URL);
|
||||
|
||||
/**
|
||||
* Creates a new SQL client instance with options
|
||||
*
|
||||
* @param connectionString - The connection string for the SQL client
|
||||
* @param options - The options for the SQL client
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const sql = new SQL("postgres://localhost:5432/mydb", { idleTimeout: 1000 });
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
connectionString: string | URL,
|
||||
options: Bun.__internal.DistributedOmit<SQL.Options, "url" | "filename">,
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a new SQL client instance with options
|
||||
*
|
||||
* @param options - The options for the SQL client
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const sql = new SQL({ url: "postgres://localhost:5432/mydb", idleTimeout: 1000 });
|
||||
* ```
|
||||
*/
|
||||
constructor(options?: SQL.Options);
|
||||
|
||||
/**
|
||||
* Current client options
|
||||
*/
|
||||
options: Bun.__internal.DistributedMerge<SQL.Options>;
|
||||
|
||||
/**
|
||||
* Commits a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL
|
||||
*
|
||||
* @param name - The name of the distributed transaction
|
||||
*
|
||||
* @throws {Error} If the adapter does not support distributed transactions (e.g., SQLite)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await sql.commitDistributed("my_distributed_transaction");
|
||||
* ```
|
||||
*/
|
||||
commitDistributed(name: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Rolls back a distributed transaction also know as prepared transaction in postgres or XA transaction in MySQL
|
||||
*
|
||||
* @param name - The name of the distributed transaction
|
||||
*
|
||||
* @throws {Error} If the adapter does not support distributed transactions (e.g., SQLite)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await sql.rollbackDistributed("my_distributed_transaction");
|
||||
* ```
|
||||
*/
|
||||
rollbackDistributed(name: string): Promise<void>;
|
||||
|
||||
/** Waits for the database connection to be established
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await sql.connect();
|
||||
* ```
|
||||
*/
|
||||
connect(): Promise<SQL>;
|
||||
|
||||
/**
|
||||
* Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing.
|
||||
*
|
||||
* @param options - The options for the close
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await sql.close({ timeout: 1 });
|
||||
* ```
|
||||
*/
|
||||
close(options?: { timeout?: number }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Closes the database connection with optional timeout in seconds. If timeout is 0, it will close immediately, if is not provided it will wait for all queries to finish before closing.
|
||||
* This is an alias of {@link SQL.close}
|
||||
*
|
||||
* @param options - The options for the close
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* await sql.end({ timeout: 1 });
|
||||
* ```
|
||||
*/
|
||||
end(options?: { timeout?: number }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Flushes any pending operations
|
||||
*
|
||||
* @throws {Error} If the adapter does not support flushing (e.g., SQLite)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* sql.flush();
|
||||
* ```
|
||||
*/
|
||||
flush(): void;
|
||||
|
||||
/**
|
||||
* The reserve method pulls out a connection from the pool, and returns a client that wraps the single connection.
|
||||
*
|
||||
* This can be used for running queries on an isolated connection.
|
||||
* Calling reserve in a reserved Sql will return a new reserved connection, not the same connection (behavior matches postgres package).
|
||||
*
|
||||
* @throws {Error} If the adapter does not support connection pooling (e.g., SQLite)s
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const reserved = await sql.reserve();
|
||||
* await reserved`select * from users`;
|
||||
* await reserved.release();
|
||||
* // with in a production scenario would be something more like
|
||||
* const reserved = await sql.reserve();
|
||||
* try {
|
||||
* // ... queries
|
||||
* } finally {
|
||||
* await reserved.release();
|
||||
* }
|
||||
*
|
||||
* // Bun supports Symbol.dispose and Symbol.asyncDispose
|
||||
* // always release after context (safer)
|
||||
* using reserved = await sql.reserve()
|
||||
* await reserved`select * from users`
|
||||
* ```
|
||||
*/
|
||||
reserve(): Promise<ReservedSQL>;
|
||||
|
||||
/**
|
||||
* Begins a new transaction.
|
||||
*
|
||||
* Will reserve a connection for the transaction and supply a scoped sql instance for all transaction uses in the callback function. sql.begin will resolve with the returned value from the callback function.
|
||||
* BEGIN is automatically sent with the optional options, and if anything fails ROLLBACK will be called so the connection can be released and execution can continue.
|
||||
* @example
|
||||
* const [user, account] = await sql.begin(async sql => {
|
||||
* const [user] = await sql`
|
||||
* insert into users (
|
||||
* name
|
||||
* ) values (
|
||||
* 'Murray'
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* const [account] = await sql`
|
||||
* insert into accounts (
|
||||
* user_id
|
||||
* ) values (
|
||||
* ${ user.user_id }
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* return [user, account]
|
||||
* })
|
||||
*/
|
||||
begin<const T>(fn: SQL.TransactionContextCallback<T>): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/**
|
||||
* Begins a new transaction with options.
|
||||
*
|
||||
* Will reserve a connection for the transaction and supply a scoped sql instance for all transaction uses in the callback function. sql.begin will resolve with the returned value from the callback function.
|
||||
* BEGIN is automatically sent with the optional options, and if anything fails ROLLBACK will be called so the connection can be released and execution can continue.
|
||||
* @example
|
||||
* const [user, account] = await sql.begin("read write", async sql => {
|
||||
* const [user] = await sql`
|
||||
* insert into users (
|
||||
* name
|
||||
* ) values (
|
||||
* 'Murray'
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* const [account] = await sql`
|
||||
* insert into accounts (
|
||||
* user_id
|
||||
* ) values (
|
||||
* ${ user.user_id }
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* return [user, account]
|
||||
* })
|
||||
*/
|
||||
begin<const T>(options: string, fn: SQL.TransactionContextCallback<T>): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/**
|
||||
* Alternative method to begin a transaction.
|
||||
*
|
||||
* Will reserve a connection for the transaction and supply a scoped sql instance for all transaction uses in the callback function. sql.transaction will resolve with the returned value from the callback function.
|
||||
* BEGIN is automatically sent with the optional options, and if anything fails ROLLBACK will be called so the connection can be released and execution can continue.
|
||||
* @alias begin
|
||||
* @example
|
||||
* const [user, account] = await sql.transaction(async sql => {
|
||||
* const [user] = await sql`
|
||||
* insert into users (
|
||||
* name
|
||||
* ) values (
|
||||
* 'Murray'
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* const [account] = await sql`
|
||||
* insert into accounts (
|
||||
* user_id
|
||||
* ) values (
|
||||
* ${ user.user_id }
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* return [user, account]
|
||||
* })
|
||||
*/
|
||||
transaction<const T>(fn: SQL.TransactionContextCallback<T>): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/**
|
||||
* Alternative method to begin a transaction with options
|
||||
* Will reserve a connection for the transaction and supply a scoped sql instance for all transaction uses in the callback function. sql.transaction will resolve with the returned value from the callback function.
|
||||
* BEGIN is automatically sent with the optional options, and if anything fails ROLLBACK will be called so the connection can be released and execution can continue.
|
||||
*
|
||||
* @alias {@link begin}
|
||||
*
|
||||
* @example
|
||||
* const [user, account] = await sql.transaction("read write", async sql => {
|
||||
* const [user] = await sql`
|
||||
* insert into users (
|
||||
* name
|
||||
* ) values (
|
||||
* 'Murray'
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* const [account] = await sql`
|
||||
* insert into accounts (
|
||||
* user_id
|
||||
* ) values (
|
||||
* ${ user.user_id }
|
||||
* )
|
||||
* returning *
|
||||
* `
|
||||
* return [user, account]
|
||||
* });
|
||||
*/
|
||||
transaction<const T>(options: string, fn: SQL.TransactionContextCallback<T>): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/**
|
||||
* Begins a distributed transaction
|
||||
* Also know as Two-Phase Commit, in a distributed transaction, Phase 1 involves the coordinator preparing nodes by ensuring data is written and ready to commit, while Phase 2 finalizes with nodes committing or rolling back based on the coordinator's decision, ensuring durability and releasing locks.
|
||||
* In PostgreSQL and MySQL distributed transactions persist beyond the original session, allowing privileged users or coordinators to commit/rollback them, ensuring support for distributed transactions, recovery, and administrative tasks.
|
||||
* beginDistributed will automatic rollback if any exception are not caught, and you can commit and rollback later if everything goes well.
|
||||
* PostgreSQL natively supports distributed transactions using PREPARE TRANSACTION, while MySQL uses XA Transactions, and MSSQL also supports distributed/XA transactions. However, in MSSQL, distributed transactions are tied to the original session, the DTC coordinator, and the specific connection.
|
||||
* These transactions are automatically committed or rolled back following the same rules as regular transactions, with no option for manual intervention from other sessions, in MSSQL distributed transactions are used to coordinate transactions using Linked Servers.
|
||||
*
|
||||
* @throws {Error} If the adapter does not support distributed transactions (e.g., SQLite)
|
||||
*
|
||||
* @example
|
||||
* await sql.beginDistributed("numbers", async sql => {
|
||||
* await sql`create table if not exists numbers (a int)`;
|
||||
* await sql`insert into numbers values(1)`;
|
||||
* });
|
||||
* // later you can call
|
||||
* await sql.commitDistributed("numbers");
|
||||
* // or await sql.rollbackDistributed("numbers");
|
||||
*/
|
||||
beginDistributed<const T>(
|
||||
name: string,
|
||||
fn: SQL.TransactionContextCallback<T>,
|
||||
): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/** Alternative method to begin a distributed transaction
|
||||
* @alias {@link beginDistributed}
|
||||
*/
|
||||
distributed<const T>(name: string, fn: SQL.TransactionContextCallback<T>): Promise<SQL.ContextCallbackResult<T>>;
|
||||
|
||||
/**If you know what you're doing, you can use unsafe to pass any string you'd like.
|
||||
* Please note that this can lead to SQL injection if you're not careful.
|
||||
* You can also nest sql.unsafe within a safe sql expression. This is useful if only part of your fraction has unsafe elements.
|
||||
* @example
|
||||
* const result = await sql.unsafe(`select ${danger} from users where id = ${dragons}`)
|
||||
*/
|
||||
unsafe<T = any>(string: string, values?: any[]): SQL.Query<T>;
|
||||
|
||||
/**
|
||||
* Reads a file and uses the contents as a query.
|
||||
* Optional parameters can be used if the file includes $1, $2, etc
|
||||
* @example
|
||||
* const result = await sql.file("query.sql", [1, 2, 3]);
|
||||
*/
|
||||
file<T = any>(filename: string, values?: any[]): SQL.Query<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL client
|
||||
*/
|
||||
const sql: SQL;
|
||||
|
||||
/**
|
||||
* SQL client for PostgreSQL
|
||||
*
|
||||
* @deprecated Prefer {@link Bun.sql}
|
||||
*/
|
||||
const postgres: SQL;
|
||||
|
||||
/**
|
||||
* Represents a savepoint within a transaction
|
||||
*/
|
||||
interface SavepointSQL extends SQL {}
|
||||
}
|
||||
190
packages/bun-types/sqlite.d.ts
vendored
190
packages/bun-types/sqlite.d.ts
vendored
@@ -24,6 +24,66 @@
|
||||
* | `null` | `NULL` |
|
||||
*/
|
||||
declare module "bun:sqlite" {
|
||||
/**
|
||||
* Options for {@link Database}
|
||||
*/
|
||||
export interface DatabaseOptions {
|
||||
/**
|
||||
* Open the database as read-only (no write operations, no create).
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READONLY}
|
||||
*/
|
||||
readonly?: boolean;
|
||||
|
||||
/**
|
||||
* Allow creating a new database
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_CREATE}
|
||||
*/
|
||||
create?: boolean;
|
||||
|
||||
/**
|
||||
* Open the database as read-write
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READWRITE}
|
||||
*/
|
||||
readwrite?: boolean;
|
||||
|
||||
/**
|
||||
* When set to `true`, integers are returned as `bigint` types.
|
||||
*
|
||||
* When set to `false`, integers are returned as `number` types and truncated to 52 bits.
|
||||
*
|
||||
* @default false
|
||||
* @since v1.1.14
|
||||
*/
|
||||
safeIntegers?: boolean;
|
||||
|
||||
/**
|
||||
* When set to `false` or `undefined`:
|
||||
* - Queries missing bound parameters will NOT throw an error
|
||||
* - Bound named parameters in JavaScript need to exactly match the SQL query.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = new Database(":memory:", { strict: false });
|
||||
* db.run("INSERT INTO foo (name) VALUES ($name)", { $name: "foo" });
|
||||
* ```
|
||||
*
|
||||
* When set to `true`:
|
||||
* - Queries missing bound parameters will throw an error
|
||||
* - Bound named parameters in JavaScript no longer need to be `$`, `:`, or `@`. The SQL query will remain prefixed.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = new Database(":memory:", { strict: true });
|
||||
* db.run("INSERT INTO foo (name) VALUES ($name)", { name: "foo" });
|
||||
* ```
|
||||
* @since v1.1.14
|
||||
*/
|
||||
strict?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A SQLite3 database
|
||||
*
|
||||
@@ -53,8 +113,6 @@ declare module "bun:sqlite" {
|
||||
* ```ts
|
||||
* const db = new Database("mydb.sqlite", {readonly: true});
|
||||
* ```
|
||||
*
|
||||
* @category Database
|
||||
*/
|
||||
export class Database implements Disposable {
|
||||
/**
|
||||
@@ -63,96 +121,19 @@ declare module "bun:sqlite" {
|
||||
* @param filename The filename of the database to open. Pass an empty string (`""`) or `":memory:"` or undefined for an in-memory database.
|
||||
* @param options defaults to `{readwrite: true, create: true}`. If a number, then it's treated as `SQLITE_OPEN_*` constant flags.
|
||||
*/
|
||||
constructor(
|
||||
filename?: string,
|
||||
options?:
|
||||
| number
|
||||
| {
|
||||
/**
|
||||
* Open the database as read-only (no write operations, no create).
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READONLY}
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/**
|
||||
* Allow creating a new database
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_CREATE}
|
||||
*/
|
||||
create?: boolean;
|
||||
/**
|
||||
* Open the database as read-write
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READWRITE}
|
||||
*/
|
||||
readwrite?: boolean;
|
||||
|
||||
/**
|
||||
* When set to `true`, integers are returned as `bigint` types.
|
||||
*
|
||||
* When set to `false`, integers are returned as `number` types and truncated to 52 bits.
|
||||
*
|
||||
* @default false
|
||||
* @since v1.1.14
|
||||
*/
|
||||
safeIntegers?: boolean;
|
||||
|
||||
/**
|
||||
* When set to `false` or `undefined`:
|
||||
* - Queries missing bound parameters will NOT throw an error
|
||||
* - Bound named parameters in JavaScript need to exactly match the SQL query.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = new Database(":memory:", { strict: false });
|
||||
* db.run("INSERT INTO foo (name) VALUES ($name)", { $name: "foo" });
|
||||
* ```
|
||||
*
|
||||
* When set to `true`:
|
||||
* - Queries missing bound parameters will throw an error
|
||||
* - Bound named parameters in JavaScript no longer need to be `$`, `:`, or `@`. The SQL query will remain prefixed.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = new Database(":memory:", { strict: true });
|
||||
* db.run("INSERT INTO foo (name) VALUES ($name)", { name: "foo" });
|
||||
* ```
|
||||
* @since v1.1.14
|
||||
*/
|
||||
strict?: boolean;
|
||||
},
|
||||
);
|
||||
constructor(filename?: string, options?: number | DatabaseOptions);
|
||||
|
||||
/**
|
||||
* Open or create a SQLite3 databases
|
||||
*
|
||||
* @param filename The filename of the database to open. Pass an empty string (`""`) or `":memory:"` or undefined for an in-memory database.
|
||||
* @param options defaults to `{readwrite: true, create: true}`. If a number, then it's treated as `SQLITE_OPEN_*` constant flags.
|
||||
*
|
||||
* This is an alias of `new Database()`
|
||||
*
|
||||
* See {@link Database}
|
||||
*/
|
||||
static open(
|
||||
filename: string,
|
||||
options?:
|
||||
| number
|
||||
| {
|
||||
/**
|
||||
* Open the database as read-only (no write operations, no create).
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READONLY}
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/**
|
||||
* Allow creating a new database
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_CREATE}
|
||||
*/
|
||||
create?: boolean;
|
||||
/**
|
||||
* Open the database as read-write
|
||||
*
|
||||
* Equivalent to {@link constants.SQLITE_OPEN_READWRITE}
|
||||
*/
|
||||
readwrite?: boolean;
|
||||
},
|
||||
): Database;
|
||||
static open(filename: string, options?: number | DatabaseOptions): Database;
|
||||
|
||||
/**
|
||||
* Execute a SQL query **without returning any results**.
|
||||
@@ -203,8 +184,11 @@ declare module "bun:sqlite" {
|
||||
* @returns `Database` instance
|
||||
*/
|
||||
run<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
|
||||
|
||||
/**
|
||||
* This is an alias of {@link Database.run}
|
||||
*
|
||||
* @deprecated Prefer {@link Database.run}
|
||||
*/
|
||||
exec<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
|
||||
|
||||
@@ -351,6 +335,16 @@ declare module "bun:sqlite" {
|
||||
*/
|
||||
static setCustomSQLite(path: string): boolean;
|
||||
|
||||
/**
|
||||
* Closes the database when using the async resource proposal
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* using db = new Database("myapp.db");
|
||||
* doSomethingWithDatabase(db);
|
||||
* // Automatically closed when `db` goes out of scope
|
||||
* ```
|
||||
*/
|
||||
[Symbol.dispose](): void;
|
||||
|
||||
/**
|
||||
@@ -744,6 +738,30 @@ declare module "bun:sqlite" {
|
||||
*/
|
||||
values(...params: ParamsType): Array<Array<string | bigint | number | boolean | Uint8Array>>;
|
||||
|
||||
/**
|
||||
* Execute the prepared statement and return all results as arrays of
|
||||
* `Uint8Array`s.
|
||||
*
|
||||
* This is similar to `values()` but returns all values as Uint8Array
|
||||
* objects, regardless of their original SQLite type.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const stmt = db.prepare("SELECT * FROM foo WHERE bar = ?");
|
||||
*
|
||||
* stmt.raw("baz");
|
||||
* // => [[Uint8Array(24)]]
|
||||
*
|
||||
* stmt.raw();
|
||||
* // => [[Uint8Array(24)]]
|
||||
* ```
|
||||
*/
|
||||
raw(...params: ParamsType): Array<Array<Uint8Array | null>>;
|
||||
|
||||
/**
|
||||
* The names of the columns returned by the prepared statement.
|
||||
* @example
|
||||
|
||||
25
packages/bun-types/test.d.ts
vendored
25
packages/bun-types/test.d.ts
vendored
@@ -56,6 +56,11 @@ declare module "bun:test" {
|
||||
* Restore the previous value of mocks.
|
||||
*/
|
||||
restore(): void;
|
||||
|
||||
/**
|
||||
* Reset all mock function state (calls, results, etc.) without restoring their original implementation.
|
||||
*/
|
||||
clearAllMocks(): void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1642,6 +1647,26 @@ declare module "bun:test" {
|
||||
*/
|
||||
toHaveReturnedTimes(times: number): void;
|
||||
|
||||
/**
|
||||
* Ensures that a mock function has returned a specific value.
|
||||
* This matcher uses deep equality, like toEqual(), and supports asymmetric matchers.
|
||||
*/
|
||||
toHaveReturnedWith(expected: unknown): void;
|
||||
|
||||
/**
|
||||
* Ensures that a mock function has returned a specific value on its last invocation.
|
||||
* This matcher uses deep equality, like toEqual(), and supports asymmetric matchers.
|
||||
*/
|
||||
toHaveLastReturnedWith(expected: unknown): void;
|
||||
|
||||
/**
|
||||
* Ensures that a mock function has returned a specific value on the nth invocation.
|
||||
* This matcher uses deep equality, like toEqual(), and supports asymmetric matchers.
|
||||
* @param n The 1-based index of the function call
|
||||
* @param expected The expected return value
|
||||
*/
|
||||
toHaveNthReturnedWith(n: number, expected: unknown): void;
|
||||
|
||||
/**
|
||||
* Ensures that a mock function is called.
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,6 +130,7 @@ void us_internal_socket_context_unlink_socket(int ssl, struct us_socket_context_
|
||||
next->prev = prev;
|
||||
}
|
||||
}
|
||||
us_internal_disable_sweep_timer(context->loop);
|
||||
us_socket_context_unref(ssl, context);
|
||||
}
|
||||
void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_socket_context_t *context, struct us_connecting_socket_t *c) {
|
||||
@@ -147,6 +148,7 @@ void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_sock
|
||||
next->prev_pending = prev;
|
||||
}
|
||||
}
|
||||
us_internal_disable_sweep_timer(context->loop);
|
||||
us_socket_context_unref(ssl, context);
|
||||
}
|
||||
|
||||
@@ -172,6 +174,7 @@ void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket
|
||||
}
|
||||
context->head_connecting_sockets = c;
|
||||
us_socket_context_ref(ssl, context);
|
||||
us_internal_enable_sweep_timer(context->loop);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +188,7 @@ void us_internal_socket_context_link_socket(struct us_socket_context_t *context,
|
||||
}
|
||||
context->head_sockets = s;
|
||||
us_socket_context_ref(0, context);
|
||||
us_internal_enable_sweep_timer(context->loop);
|
||||
}
|
||||
|
||||
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {
|
||||
|
||||
58
packages/bun-usockets/src/crypto/default_ciphers.h
Normal file
58
packages/bun-usockets/src/crypto/default_ciphers.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// TLSv1.3 suites start with TLS_, and are the OpenSSL defaults, see:
|
||||
// https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_ciphersuites.html
|
||||
#ifndef DEFAULT_CIPHER_LIST
|
||||
#define DEFAULT_CIPHER_LIST \
|
||||
"ECDHE-RSA-AES128-GCM-SHA256:" \
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
|
||||
"ECDHE-RSA-AES256-GCM-SHA384:" \
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384:" \
|
||||
"ECDHE-RSA-AES128-SHA256:" \
|
||||
"ECDHE-RSA-AES256-SHA384:" \
|
||||
"HIGH:" \
|
||||
"!aNULL:" \
|
||||
"!eNULL:" \
|
||||
"!EXPORT:" \
|
||||
"!DES:" \
|
||||
"!RC4:" \
|
||||
"!MD5:" \
|
||||
"!PSK:" \
|
||||
"!SRP:" \
|
||||
"!CAMELLIA"
|
||||
#endif
|
||||
|
||||
// BoringSSL does not support legacy DHE ciphers and dont support SSL_CTX_set_cipher_list (see https://github.com/envoyproxy/envoy/issues/8848#issuecomment-548672667)
|
||||
// Node.js full list bellow
|
||||
|
||||
// In node.js they filter TLS_* ciphers and use SSL_CTX_set_cipher_list (TODO: Electron has a patch https://github.com/nodejs/node/issues/25890)
|
||||
// if passed to SSL_CTX_set_cipher_list it will be filtered out and not used in BoringSSL
|
||||
// "TLS_AES_256_GCM_SHA384:" \
|
||||
// "TLS_CHACHA20_POLY1305_SHA256:" \
|
||||
// "TLS_AES_128_GCM_SHA256:" \
|
||||
|
||||
// Supported by BoringSSL:
|
||||
// "ECDHE-RSA-AES128-GCM-SHA256:" \
|
||||
// "ECDHE-ECDSA-AES128-GCM-SHA256:" \
|
||||
// "ECDHE-RSA-AES256-GCM-SHA384:" \
|
||||
// "ECDHE-ECDSA-AES256-GCM-SHA384:" \
|
||||
// "ECDHE-RSA-AES128-SHA256:" \
|
||||
// "ECDHE-RSA-AES256-SHA384:" \
|
||||
|
||||
// Not supported by BoringSSL:
|
||||
// "ECDHE-RSA-AES256-SHA256:" \
|
||||
// "DHE-RSA-AES128-GCM-SHA256:" \
|
||||
// "DHE-RSA-AES128-SHA256:" \
|
||||
// "DHE-RSA-AES256-SHA384:" \
|
||||
// "DHE-RSA-AES256-SHA256:" \
|
||||
|
||||
|
||||
// Also present in Node.js and supported by BoringSSL:
|
||||
// "HIGH:" \
|
||||
// "!aNULL:" \
|
||||
// "!eNULL:" \
|
||||
// "!EXPORT:" \
|
||||
// "!DES:" \
|
||||
// "!RC4:" \
|
||||
// "!MD5:" \
|
||||
// "!PSK:" \
|
||||
// "!SRP:" \
|
||||
// "!CAMELLIA"
|
||||
@@ -45,7 +45,7 @@ void *sni_find(void *sni, const char *hostname);
|
||||
#endif
|
||||
|
||||
#include "./root_certs_header.h"
|
||||
|
||||
#include "./default_ciphers.h"
|
||||
struct loop_ssl_data {
|
||||
char *ssl_read_input, *ssl_read_output;
|
||||
unsigned int ssl_read_input_length;
|
||||
@@ -346,6 +346,7 @@ us_internal_ssl_socket_close(struct us_internal_ssl_socket_t *s, int code,
|
||||
|
||||
// check if we are already closed
|
||||
if (us_internal_ssl_socket_is_closed(s)) return s;
|
||||
us_internal_set_loop_ssl_data(s);
|
||||
us_internal_update_handshake(s);
|
||||
|
||||
if (s->handshake_state != HANDSHAKE_COMPLETED) {
|
||||
@@ -848,21 +849,24 @@ create_ssl_context_from_options(struct us_socket_context_options_t options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* OWASP Cipher String 'A+'
|
||||
* (https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet) */
|
||||
if (SSL_CTX_set_cipher_list(
|
||||
ssl_context,
|
||||
"DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-"
|
||||
"AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256") != 1) {
|
||||
if (!SSL_CTX_set_cipher_list(ssl_context, DEFAULT_CIPHER_LIST)) {
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ssl_ciphers) {
|
||||
if (SSL_CTX_set_cipher_list(ssl_context, options.ssl_ciphers) != 1) {
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
if (!SSL_CTX_set_cipher_list(ssl_context, options.ssl_ciphers)) {
|
||||
unsigned long ssl_err = ERR_get_error();
|
||||
if (!(strlen(options.ssl_ciphers) == 0 && ERR_GET_REASON(ssl_err) == SSL_R_NO_CIPHER_MATCH)) {
|
||||
// TLS1.2 ciphers were deliberately cleared, so don't consider
|
||||
// SSL_R_NO_CIPHER_MATCH to be an error (this is how _set_cipher_suites()
|
||||
// works). If the user actually sets a value (like "no-such-cipher"), then
|
||||
// that's actually an error.
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
}
|
||||
ERR_clear_error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1288,21 +1292,27 @@ SSL_CTX *create_ssl_context_from_bun_options(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* OWASP Cipher String 'A+'
|
||||
* (https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet) */
|
||||
if (SSL_CTX_set_cipher_list(
|
||||
ssl_context,
|
||||
"DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-"
|
||||
"AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256") != 1) {
|
||||
if (!SSL_CTX_set_cipher_list(ssl_context, DEFAULT_CIPHER_LIST)) {
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ssl_ciphers) {
|
||||
if (SSL_CTX_set_cipher_list(ssl_context, options.ssl_ciphers) != 1) {
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
if (!SSL_CTX_set_cipher_list(ssl_context, options.ssl_ciphers)) {
|
||||
unsigned long ssl_err = ERR_get_error();
|
||||
if (!(strlen(options.ssl_ciphers) == 0 && ERR_GET_REASON(ssl_err) == SSL_R_NO_CIPHER_MATCH)) {
|
||||
char error_msg[256];
|
||||
ERR_error_string_n(ERR_peek_last_error(), error_msg, sizeof(error_msg));
|
||||
// TLS1.2 ciphers were deliberately cleared, so don't consider
|
||||
// SSL_R_NO_CIPHER_MATCH to be an error (this is how _set_cipher_suites()
|
||||
// works). If the user actually sets a value (like "no-such-cipher"), then
|
||||
// that's actually an error.
|
||||
*err = CREATE_BUN_SOCKET_ERROR_INVALID_CIPHERS;
|
||||
free_ssl_context(ssl_context);
|
||||
return NULL;
|
||||
}
|
||||
ERR_clear_error();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "./internal/internal.h"
|
||||
#include <atomic>
|
||||
#include <string.h>
|
||||
#include "./default_ciphers.h"
|
||||
static const int root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]);
|
||||
|
||||
extern "C" void BUN__warn__extra_ca_load_failed(const char* filename, const char* error_msg);
|
||||
@@ -184,3 +185,6 @@ extern "C" X509_STORE *us_get_default_ca_store() {
|
||||
|
||||
return store;
|
||||
}
|
||||
extern "C" const char *us_get_default_ciphers() {
|
||||
return DEFAULT_CIPHER_LIST;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Maintaining the root certificates
|
||||
// Maintaining the root certificates
|
||||
//
|
||||
// `src/crypto/root_certs.h` contains a compiled-in set of root certificates used as trust anchors
|
||||
// for TLS certificate validation.
|
||||
@@ -23,76 +23,10 @@
|
||||
// `src/crypto/root_certs.h`.
|
||||
// * Using `git diff-files` to determine which certificate have been added and/or
|
||||
// removed.
|
||||
//
|
||||
//
|
||||
#include "libusockets.h"
|
||||
static struct us_cert_string_t root_certs[] = {
|
||||
|
||||
/* GlobalSign Root CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMC\n"
|
||||
"QkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNV\n"
|
||||
"BAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBa\n"
|
||||
"MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdS\n"
|
||||
"b290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUA\n"
|
||||
"A4IBDwAwggEKAoIBAQDaDuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtI\n"
|
||||
"K+6NiY6arymAZavpxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCO\n"
|
||||
"XkNz8kHp1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n"
|
||||
"snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3\n"
|
||||
"dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DP\n"
|
||||
"AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRg\n"
|
||||
"e2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEA1nPnfE920I2/7LqivjTF\n"
|
||||
"KDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY7\n"
|
||||
"76BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9\n"
|
||||
"LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr\n"
|
||||
"+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n"
|
||||
"HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"
|
||||
"-----END CERTIFICATE-----",.len=1258},
|
||||
|
||||
/* Entrust.net Premium 2048 Secure Server CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVz\n"
|
||||
"dC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJl\n"
|
||||
"Zi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0\n"
|
||||
"ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4\n"
|
||||
"KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0\n"
|
||||
"Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVm\n"
|
||||
"LiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl\n"
|
||||
"ZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp\n"
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtK\n"
|
||||
"TY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/EC\n"
|
||||
"DNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ\n"
|
||||
"/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzWnLLPKQP5L6RQstRIzgUyVYr9smRM\n"
|
||||
"DuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVC\n"
|
||||
"wQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n"
|
||||
"BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQAD\n"
|
||||
"ggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo\n"
|
||||
"U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6YfzX1XEC+b\n"
|
||||
"BAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKTJ1wD\n"
|
||||
"LW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e\n"
|
||||
"nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=\n"
|
||||
"-----END CERTIFICATE-----",.len=1501},
|
||||
|
||||
/* Baltimore CyberTrust Root */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAG\n"
|
||||
"A1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1v\n"
|
||||
"cmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjEL\n"
|
||||
"MAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEi\n"
|
||||
"MCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQAD\n"
|
||||
"ggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2ygu\n"
|
||||
"zmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo\n"
|
||||
"6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n"
|
||||
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3z\n"
|
||||
"yZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkC\n"
|
||||
"AwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1BE3wMBIGA1UdEwEB/wQIMAYB\n"
|
||||
"Af8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27\n"
|
||||
"TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukM\n"
|
||||
"JY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhS\n"
|
||||
"NzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67\n"
|
||||
"G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n"
|
||||
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n"
|
||||
"-----END CERTIFICATE-----",.len=1258},
|
||||
|
||||
/* Entrust Root Certification Authority */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAU\n"
|
||||
@@ -119,30 +53,6 @@ static struct us_cert_string_t root_certs[] = {
|
||||
"j2A781q0tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8\n"
|
||||
"-----END CERTIFICATE-----",.len=1639},
|
||||
|
||||
/* Comodo AAA Services root */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UE\n"
|
||||
"CAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21v\n"
|
||||
"ZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0\n"
|
||||
"MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdy\n"
|
||||
"ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENB\n"
|
||||
"IExpbWl0ZWQxITAfBgNVBAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZI\n"
|
||||
"hvcNAQEBBQADggEPADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686td\n"
|
||||
"UIoWMQuaBtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n"
|
||||
"3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8Ioa\n"
|
||||
"E+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULi\n"
|
||||
"mAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7S\n"
|
||||
"w4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYD\n"
|
||||
"VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDov\n"
|
||||
"L2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0\n"
|
||||
"dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG\n"
|
||||
"9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n"
|
||||
"GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLzRt0vxuBq\n"
|
||||
"w8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z8VlI\n"
|
||||
"MCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C\n"
|
||||
"12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n"
|
||||
"-----END CERTIFICATE-----",.len=1513},
|
||||
|
||||
/* QuoVadis Root CA 2 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNV\n"
|
||||
@@ -211,78 +121,6 @@ static struct us_cert_string_t root_certs[] = {
|
||||
"zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=\n"
|
||||
"-----END CERTIFICATE-----",.len=2349},
|
||||
|
||||
/* XRamp Global CA Root */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkG\n"
|
||||
"A1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJh\n"
|
||||
"bXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlm\n"
|
||||
"aWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjEL\n"
|
||||
"MAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMb\n"
|
||||
"WFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2Vy\n"
|
||||
"dGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY\n"
|
||||
"JB69FbS638eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP\n"
|
||||
"KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5df\n"
|
||||
"T2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3\n"
|
||||
"hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSP\n"
|
||||
"puIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJ\n"
|
||||
"KwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n"
|
||||
"BBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwu\n"
|
||||
"eHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcN\n"
|
||||
"AQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR\n"
|
||||
"vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxtqZ4Bfj8p\n"
|
||||
"zgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8nnxCb\n"
|
||||
"HIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz\n"
|
||||
"8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=\n"
|
||||
"-----END CERTIFICATE-----",.len=1509},
|
||||
|
||||
/* Go Daddy Class 2 CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UE\n"
|
||||
"ChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAy\n"
|
||||
"IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYy\n"
|
||||
"MFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjEx\n"
|
||||
"MC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAw\n"
|
||||
"DQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWiz\n"
|
||||
"V3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HF\n"
|
||||
"iH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi\n"
|
||||
"EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN\n"
|
||||
"f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44\n"
|
||||
"dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLEsNKR1EwRcbNhyz2h/t2oatTj\n"
|
||||
"MIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2oatTjoWekZTBjMQswCQYDVQQGEwJV\n"
|
||||
"UzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRk\n"
|
||||
"eSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJ\n"
|
||||
"KoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYX\n"
|
||||
"MP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P\n"
|
||||
"TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQHmyW74cN\n"
|
||||
"xA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VILs9R\n"
|
||||
"aRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b\n"
|
||||
"vZ8=\n"
|
||||
"-----END CERTIFICATE-----",.len=1445},
|
||||
|
||||
/* Starfield Class 2 CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n"
|
||||
"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENs\n"
|
||||
"YXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5\n"
|
||||
"MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2ll\n"
|
||||
"cywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRo\n"
|
||||
"b3JpdHkwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N\n"
|
||||
"78gDGIc/oav7PKaf8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMe\n"
|
||||
"j2YcOadN+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n"
|
||||
"X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4Umkhyn\n"
|
||||
"ArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W\n"
|
||||
"93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRb\n"
|
||||
"Vazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0fhvRbVazc1xDCDqmI56FspGowaDEL\n"
|
||||
"MAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAw\n"
|
||||
"BgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG\n"
|
||||
"A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1ep\n"
|
||||
"oXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n"
|
||||
"eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJlxy16paq8\n"
|
||||
"U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJDKVtH\n"
|
||||
"CN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3\n"
|
||||
"QBFGmh95DmK/D5fs4C8fF5Q=\n"
|
||||
"-----END CERTIFICATE-----",.len=1465},
|
||||
|
||||
/* DigiCert Assured ID Root CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYD\n"
|
||||
@@ -3625,6 +3463,52 @@ static struct us_cert_string_t root_certs[] = {
|
||||
"GJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==\n"
|
||||
"-----END CERTIFICATE-----",.len=2020},
|
||||
|
||||
/* TrustAsia TLS ECC Root CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkG\n"
|
||||
"A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNV\n"
|
||||
"BAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1\n"
|
||||
"MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2ll\n"
|
||||
"cywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49\n"
|
||||
"AgEGBSuBBAAiA2IABLh/pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPo\n"
|
||||
"XlfXTr4sP/MSpwDpguMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kz\n"
|
||||
"saNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw\n"
|
||||
"DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mds\n"
|
||||
"d0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlz\n"
|
||||
"g8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==\n"
|
||||
"-----END CERTIFICATE-----",.len=820},
|
||||
|
||||
/* TrustAsia TLS RSA Root CA */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDEL\n"
|
||||
"MAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAg\n"
|
||||
"BgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQw\n"
|
||||
"NTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xv\n"
|
||||
"Z2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJ\n"
|
||||
"KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwha\n"
|
||||
"GnrhB3YmH49pVr7+NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMm\n"
|
||||
"SoPGlbYJQ1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561\n"
|
||||
"HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5Eebuy\n"
|
||||
"jBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLd\n"
|
||||
"KY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0J\n"
|
||||
"wqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB3\n"
|
||||
"3PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+\n"
|
||||
"k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHU\n"
|
||||
"uPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMB\n"
|
||||
"AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT\n"
|
||||
"MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3Rz/Nyjuu\n"
|
||||
"jsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8veWv\n"
|
||||
"0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L\n"
|
||||
"TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqF\n"
|
||||
"VwHxtlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soa\n"
|
||||
"B82G39tp27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4\n"
|
||||
"GS/+X/jbh87qqA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjE\n"
|
||||
"Wn9hongPXvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe\n"
|
||||
"SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY\n"
|
||||
"99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNd\n"
|
||||
"dBWcwauwxzQ=\n"
|
||||
"-----END CERTIFICATE-----",.len=1964},
|
||||
|
||||
/* D-TRUST EV Root CA 2 2023 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYD\n"
|
||||
@@ -3655,4 +3539,35 @@ static struct us_cert_string_t root_certs[] = {
|
||||
"S5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5cs\n"
|
||||
"bBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==\n"
|
||||
"-----END CERTIFICATE-----",.len=2020},
|
||||
|
||||
/* SwissSign RSA TLS Root CA 2022 - 1 */
|
||||
{.str="-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTEL\n"
|
||||
"MAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWdu\n"
|
||||
"IFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4\n"
|
||||
"MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3\n"
|
||||
"aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4IC\n"
|
||||
"DwAwggIKAoICAQDLKmjiC8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFw\n"
|
||||
"B9+zBvKK8i5VUXu7LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t\n"
|
||||
"8qsCLqSX5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE\n"
|
||||
"EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+Idr\n"
|
||||
"eXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH\n"
|
||||
"5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5KaM2iYauC8BPY7kGWUleDsFp\n"
|
||||
"swrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7\n"
|
||||
"mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwG\n"
|
||||
"qnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+Ddz\n"
|
||||
"iZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMB\n"
|
||||
"Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4\n"
|
||||
"DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQELBQADggIB\n"
|
||||
"AKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6WlkDf\n"
|
||||
"DDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo\n"
|
||||
"8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK\n"
|
||||
"50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8li\n"
|
||||
"Nr3CjlvrzG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAW\n"
|
||||
"pO2Whi4Z2L6MOuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3P\n"
|
||||
"XtpOpvJpzv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td\n"
|
||||
"Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9\n"
|
||||
"A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT\n"
|
||||
"6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ\n"
|
||||
"-----END CERTIFICATE-----",.len=1988},
|
||||
};
|
||||
|
||||
@@ -246,8 +246,7 @@ void us_loop_run(struct us_loop_t *loop) {
|
||||
}
|
||||
}
|
||||
|
||||
extern void Bun__JSC_onBeforeWait(void*);
|
||||
extern void Bun__JSC_onAfterWait(void*);
|
||||
extern void Bun__JSC_onBeforeWait(void * _Nonnull jsc_vm);
|
||||
|
||||
void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout) {
|
||||
if (loop->num_polls == 0)
|
||||
@@ -264,8 +263,9 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
|
||||
/* Emit pre callback */
|
||||
us_internal_loop_pre(loop);
|
||||
|
||||
/* Safe if jsc_vm is NULL */
|
||||
Bun__JSC_onBeforeWait(loop->data.jsc_vm);
|
||||
|
||||
if (loop->data.jsc_vm)
|
||||
Bun__JSC_onBeforeWait(loop->data.jsc_vm);
|
||||
|
||||
/* Fetch ready polls */
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
@@ -276,7 +276,6 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
|
||||
} while (IS_EINTR(loop->num_ready_polls));
|
||||
#endif
|
||||
|
||||
Bun__JSC_onAfterWait(loop->data.jsc_vm);
|
||||
|
||||
/* Iterate ready polls, dispatching them by type */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
|
||||
@@ -116,6 +116,8 @@ extern struct addrinfo_result *Bun__addrinfo_getRequestResult(struct addrinfo_re
|
||||
/* Loop related */
|
||||
void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, int events);
|
||||
void us_internal_timer_sweep(us_loop_r loop);
|
||||
void us_internal_enable_sweep_timer(struct us_loop_t *loop);
|
||||
void us_internal_disable_sweep_timer(struct us_loop_t *loop);
|
||||
void us_internal_free_closed_sockets(us_loop_r loop);
|
||||
void us_internal_loop_link(struct us_loop_t *loop,
|
||||
struct us_socket_context_t *context);
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef void* zig_mutex_t;
|
||||
// IMPORTANT: When changing this, don't forget to update the zig version in uws.zig as well!
|
||||
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;
|
||||
|
||||
@@ -262,6 +262,7 @@ enum create_bun_socket_error_t {
|
||||
CREATE_BUN_SOCKET_ERROR_LOAD_CA_FILE,
|
||||
CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE,
|
||||
CREATE_BUN_SOCKET_ERROR_INVALID_CA,
|
||||
CREATE_BUN_SOCKET_ERROR_INVALID_CIPHERS,
|
||||
};
|
||||
|
||||
struct us_socket_context_t *us_create_bun_ssl_socket_context(struct us_loop_t *loop,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "libusockets.h"
|
||||
#include "internal/internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#ifndef WIN32
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
@@ -29,11 +30,32 @@ extern void __attribute((__noreturn__)) Bun__panic(const char* message, size_t l
|
||||
#define BUN_PANIC(message) Bun__panic(message, sizeof(message) - 1)
|
||||
#endif
|
||||
|
||||
extern void Bun__internal_ensureDateHeaderTimerIsEnabled(struct us_loop_t *loop);
|
||||
|
||||
void sweep_timer_cb(struct us_internal_callback_t *cb);
|
||||
|
||||
void us_internal_enable_sweep_timer(struct us_loop_t *loop) {
|
||||
loop->data.sweep_timer_count++;
|
||||
if (loop->data.sweep_timer_count == 1) {
|
||||
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
|
||||
Bun__internal_ensureDateHeaderTimerIsEnabled(loop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void us_internal_disable_sweep_timer(struct us_loop_t *loop) {
|
||||
loop->data.sweep_timer_count--;
|
||||
if (loop->data.sweep_timer_count == 0) {
|
||||
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* The loop has 2 fallthrough polls */
|
||||
void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct us_loop_t *loop),
|
||||
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop)) {
|
||||
// We allocate with calloc, so we only need to initialize the specific fields in use.
|
||||
loop->data.sweep_timer = us_create_timer(loop, 1, 0);
|
||||
loop->data.sweep_timer_count = 0;
|
||||
loop->data.recv_buf = malloc(LIBUS_RECV_BUFFER_LENGTH + LIBUS_RECV_BUFFER_PADDING * 2);
|
||||
loop->data.send_buf = malloc(LIBUS_SEND_BUFFER_LENGTH);
|
||||
loop->data.pre_cb = pre_cb;
|
||||
@@ -547,9 +569,9 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
}
|
||||
}
|
||||
|
||||
/* Integration only requires the timer to be set up */
|
||||
/* Integration only requires the timer to be set up, but not automatically enabled */
|
||||
void us_loop_integrate(struct us_loop_t *loop) {
|
||||
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
|
||||
/* Timer is now controlled dynamically by socket count, not enabled automatically */
|
||||
}
|
||||
|
||||
void *us_loop_ext(struct us_loop_t *loop) {
|
||||
|
||||
@@ -222,6 +222,78 @@ namespace uWS
|
||||
return std::string_view(nullptr, 0);
|
||||
}
|
||||
|
||||
struct TransferEncoding {
|
||||
bool has: 1 = false;
|
||||
bool chunked: 1 = false;
|
||||
bool invalid: 1 = false;
|
||||
};
|
||||
|
||||
TransferEncoding getTransferEncoding()
|
||||
{
|
||||
TransferEncoding te;
|
||||
|
||||
if (!bf.mightHave("transfer-encoding")) {
|
||||
return te;
|
||||
}
|
||||
|
||||
for (Header *h = headers; (++h)->key.length();) {
|
||||
if (h->key.length() == 17 && !strncmp(h->key.data(), "transfer-encoding", 17)) {
|
||||
// Parse comma-separated values, ensuring "chunked" is last if present
|
||||
const auto value = h->value;
|
||||
size_t pos = 0;
|
||||
size_t lastTokenStart = 0;
|
||||
size_t lastTokenLen = 0;
|
||||
|
||||
while (pos < value.length()) {
|
||||
// Skip leading whitespace
|
||||
while (pos < value.length() && (value[pos] == ' ' || value[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Remember start of this token
|
||||
size_t tokenStart = pos;
|
||||
|
||||
// Find end of token (until comma or end)
|
||||
while (pos < value.length() && value[pos] != ',') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Trim trailing whitespace from token
|
||||
size_t tokenEnd = pos;
|
||||
while (tokenEnd > tokenStart && (value[tokenEnd - 1] == ' ' || value[tokenEnd - 1] == '\t')) {
|
||||
tokenEnd--;
|
||||
}
|
||||
|
||||
size_t tokenLen = tokenEnd - tokenStart;
|
||||
if (tokenLen > 0) {
|
||||
lastTokenStart = tokenStart;
|
||||
lastTokenLen = tokenLen;
|
||||
}
|
||||
|
||||
// Move past comma if present
|
||||
if (pos < value.length() && value[pos] == ',') {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (te.chunked) [[unlikely]] {
|
||||
te.invalid = true;
|
||||
return te;
|
||||
}
|
||||
|
||||
te.has = lastTokenLen > 0;
|
||||
|
||||
// Check if the last token is "chunked"
|
||||
if (lastTokenLen == 7 && !strncmp(value.data() + lastTokenStart, "chunked", 7)) [[likely]] {
|
||||
te.chunked = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return te;
|
||||
}
|
||||
|
||||
|
||||
std::string_view getUrl()
|
||||
{
|
||||
@@ -771,14 +843,16 @@ namespace uWS
|
||||
* the Transfer-Encoding overrides the Content-Length. Such a message might indicate an attempt
|
||||
* to perform request smuggling (Section 11.2) or response splitting (Section 11.1) and
|
||||
* ought to be handled as an error. */
|
||||
std::string_view transferEncodingString = req->getHeader("transfer-encoding");
|
||||
std::string_view contentLengthString = req->getHeader("content-length");
|
||||
const std::string_view contentLengthString = req->getHeader("content-length");
|
||||
const auto contentLengthStringLen = contentLengthString.length();
|
||||
|
||||
/* Check Transfer-Encoding header validity and conflicts */
|
||||
HttpRequest::TransferEncoding transferEncoding = req->getTransferEncoding();
|
||||
|
||||
auto transferEncodingStringLen = transferEncodingString.length();
|
||||
auto contentLengthStringLen = contentLengthString.length();
|
||||
if (transferEncodingStringLen && contentLengthStringLen) {
|
||||
/* We could be smart and set an error in the context along with this, to indicate what
|
||||
* http error response we might want to return */
|
||||
transferEncoding.invalid = transferEncoding.invalid || (transferEncoding.has && (contentLengthStringLen || !transferEncoding.chunked));
|
||||
|
||||
if (transferEncoding.invalid) [[unlikely]] {
|
||||
/* Invalid Transfer-Encoding (multiple headers or chunked not last - request smuggling attempt) */
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_TRANSFER_ENCODING);
|
||||
}
|
||||
|
||||
@@ -789,7 +863,7 @@ namespace uWS
|
||||
// lets check if content len is valid before calling requestHandler
|
||||
if(contentLengthStringLen) {
|
||||
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
|
||||
if (remainingStreamingBytes == UINT64_MAX) {
|
||||
if (remainingStreamingBytes == UINT64_MAX) [[unlikely]] {
|
||||
/* Parser error */
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_CONTENT_LENGTH);
|
||||
}
|
||||
@@ -813,20 +887,8 @@ namespace uWS
|
||||
/* RFC 9112 6.3
|
||||
* If a message is received with both a Transfer-Encoding and a Content-Length header field,
|
||||
* the Transfer-Encoding overrides the Content-Length. */
|
||||
if (transferEncodingStringLen) {
|
||||
|
||||
/* If a proxy sent us the transfer-encoding header that 100% means it must be chunked or else the proxy is
|
||||
* not RFC 9112 compliant. Therefore it is always better to assume this is the case, since that entirely eliminates
|
||||
* all forms of transfer-encoding obfuscation tricks. We just rely on the header. */
|
||||
|
||||
/* RFC 9112 6.3
|
||||
* If a Transfer-Encoding header field is present in a request and the chunked transfer coding is not the
|
||||
* final encoding, the message body length cannot be determined reliably; the server MUST respond with the
|
||||
* 400 (Bad Request) status code and then close the connection. */
|
||||
|
||||
/* In this case we fail later by having the wrong interpretation (assuming chunked).
|
||||
* This could be made stricter but makes no difference either way, unless forwarding the identical message as a proxy. */
|
||||
|
||||
if (transferEncoding.has) {
|
||||
/* We already validated that chunked is last if present, before calling the handler */
|
||||
remainingStreamingBytes = STATE_IS_CHUNKED;
|
||||
/* If consume minimally, we do not want to consume anything but we want to mark this as being chunked */
|
||||
if constexpr (!ConsumeMinimally) {
|
||||
@@ -835,7 +897,7 @@ namespace uWS
|
||||
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes)) {
|
||||
dataHandler(user, chunk, chunk.length() == 0);
|
||||
}
|
||||
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
|
||||
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) [[unlikely]] {
|
||||
// TODO: what happen if we already responded?
|
||||
return HttpParserResult::error(HTTP_ERROR_400_BAD_REQUEST, HTTP_PARSER_ERROR_INVALID_CHUNKED_ENCODING);
|
||||
}
|
||||
|
||||
@@ -82,19 +82,6 @@ private:
|
||||
|
||||
static Loop *create(void *hint) {
|
||||
Loop *loop = ((Loop *) us_create_loop(hint, wakeupCb, preCb, postCb, sizeof(LoopData)))->init();
|
||||
|
||||
/* We also need some timers (should live off the one 4 second timer rather) */
|
||||
LoopData *loopData = (LoopData *) us_loop_ext((struct us_loop_t *) loop);
|
||||
loopData->dateTimer = us_create_timer((struct us_loop_t *) loop, 1, sizeof(LoopData *));
|
||||
loopData->updateDate();
|
||||
|
||||
memcpy(us_timer_ext(loopData->dateTimer), &loopData, sizeof(LoopData *));
|
||||
us_timer_set(loopData->dateTimer, [](struct us_timer_t *t) {
|
||||
LoopData *loopData;
|
||||
memcpy(&loopData, us_timer_ext(t), sizeof(LoopData *));
|
||||
loopData->updateDate();
|
||||
}, 1000, 1000);
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
@@ -146,10 +133,7 @@ public:
|
||||
/* Freeing the default loop should be done once */
|
||||
void free() {
|
||||
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
|
||||
|
||||
/* Stop and free dateTimer first */
|
||||
us_timer_close(loopData->dateTimer, 1);
|
||||
|
||||
|
||||
loopData->~LoopData();
|
||||
/* uSockets will track whether this loop is owned by us or a borrowed alien loop */
|
||||
us_loop_free((us_loop_t *) this);
|
||||
|
||||
@@ -151,8 +151,6 @@ public:
|
||||
ZlibContext *zlibContext = nullptr;
|
||||
InflationStream *inflationStream = nullptr;
|
||||
DeflationStream *deflationStream = nullptr;
|
||||
|
||||
us_timer_t *dateTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -163,8 +163,11 @@ export class BunTestController implements vscode.Disposable {
|
||||
const ignoreGlobs = await this.buildIgnoreGlobs(cancellationToken);
|
||||
const tests = await vscode.workspace.findFiles(
|
||||
this.customFilePattern(),
|
||||
"node_modules",
|
||||
undefined,
|
||||
"**/node_modules/**",
|
||||
// 5k tests is more than enough for most projects.
|
||||
// If they need more, they can manually open the files themself and it should be added to the test explorer.
|
||||
// This is needed because otherwise with too many tests, vscode OOMs.
|
||||
5_000,
|
||||
cancellationToken,
|
||||
);
|
||||
|
||||
|
||||
215
scripts/build-jsc.ts
Executable file
215
scripts/build-jsc.ts
Executable file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env bun
|
||||
import { spawnSync } from "child_process";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { arch, platform } from "os";
|
||||
import { join, resolve } from "path";
|
||||
|
||||
// Build configurations
|
||||
type BuildConfig = "debug" | "release" | "lto";
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const buildConfig: BuildConfig = (args[0] as BuildConfig) || "debug";
|
||||
const validConfigs = ["debug", "release", "lto"];
|
||||
|
||||
if (!validConfigs.includes(buildConfig)) {
|
||||
console.error(`Invalid build configuration: ${buildConfig}`);
|
||||
console.error(`Valid configurations: ${validConfigs.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Detect platform
|
||||
const OS_NAME = platform().toLowerCase();
|
||||
const ARCH_NAME_RAW = arch();
|
||||
const IS_MAC = OS_NAME === "darwin";
|
||||
const IS_LINUX = OS_NAME === "linux";
|
||||
const IS_ARM64 = ARCH_NAME_RAW === "arm64" || ARCH_NAME_RAW === "aarch64";
|
||||
|
||||
// Paths
|
||||
const ROOT_DIR = resolve(import.meta.dir, "..");
|
||||
const WEBKIT_DIR = resolve(ROOT_DIR, "vendor/WebKit");
|
||||
const WEBKIT_BUILD_DIR = join(WEBKIT_DIR, "WebKitBuild");
|
||||
const WEBKIT_RELEASE_DIR = join(WEBKIT_BUILD_DIR, "Release");
|
||||
const WEBKIT_DEBUG_DIR = join(WEBKIT_BUILD_DIR, "Debug");
|
||||
const WEBKIT_RELEASE_DIR_LTO = join(WEBKIT_BUILD_DIR, "ReleaseLTO");
|
||||
|
||||
// Homebrew prefix detection
|
||||
const HOMEBREW_PREFIX = IS_ARM64 ? "/opt/homebrew/" : "/usr/local/";
|
||||
|
||||
// Compiler detection
|
||||
function findExecutable(names: string[]): string | null {
|
||||
for (const name of names) {
|
||||
const result = spawnSync("which", [name], { encoding: "utf8" });
|
||||
if (result.status === 0) {
|
||||
return result.stdout.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const CC = findExecutable(["clang-19", "clang"]) || "clang";
|
||||
const CXX = findExecutable(["clang++-19", "clang++"]) || "clang++";
|
||||
|
||||
// Build directory based on config
|
||||
const getBuildDir = (config: BuildConfig) => {
|
||||
switch (config) {
|
||||
case "debug":
|
||||
return WEBKIT_DEBUG_DIR;
|
||||
case "lto":
|
||||
return WEBKIT_RELEASE_DIR_LTO;
|
||||
default:
|
||||
return WEBKIT_RELEASE_DIR;
|
||||
}
|
||||
};
|
||||
|
||||
// Common CMake flags
|
||||
const getCommonFlags = () => {
|
||||
const flags = [
|
||||
"-DPORT=JSCOnly",
|
||||
"-DENABLE_STATIC_JSC=ON",
|
||||
"-DALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS=ON",
|
||||
"-DUSE_THIN_ARCHIVES=OFF",
|
||||
"-DUSE_BUN_JSC_ADDITIONS=ON",
|
||||
"-DUSE_BUN_EVENT_LOOP=ON",
|
||||
"-DENABLE_FTL_JIT=ON",
|
||||
"-G",
|
||||
"Ninja",
|
||||
`-DCMAKE_C_COMPILER=${CC}`,
|
||||
`-DCMAKE_CXX_COMPILER=${CXX}`,
|
||||
];
|
||||
|
||||
if (IS_MAC) {
|
||||
flags.push(
|
||||
"-DENABLE_SINGLE_THREADED_VM_ENTRY_SCOPE=ON",
|
||||
"-DBUN_FAST_TLS=ON",
|
||||
"-DPTHREAD_JIT_PERMISSIONS_API=1",
|
||||
"-DUSE_PTHREAD_JIT_PERMISSIONS_API=ON",
|
||||
);
|
||||
} else if (IS_LINUX) {
|
||||
flags.push(
|
||||
"-DJSEXPORT_PRIVATE=WTF_EXPORT_DECLARATION",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Build-specific CMake flags
|
||||
const getBuildFlags = (config: BuildConfig) => {
|
||||
const flags = [...getCommonFlags()];
|
||||
|
||||
switch (config) {
|
||||
case "debug":
|
||||
flags.push(
|
||||
"-DCMAKE_BUILD_TYPE=Debug",
|
||||
"-DENABLE_BUN_SKIP_FAILING_ASSERTIONS=ON",
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
);
|
||||
|
||||
if (IS_MAC) {
|
||||
// Enable address sanitizer by default on Mac debug builds
|
||||
flags.push("-DENABLE_SANITIZERS=address");
|
||||
// To disable asan, comment the line above and uncomment:
|
||||
// flags.push("-DENABLE_MALLOC_HEAP_BREAKDOWN=ON");
|
||||
}
|
||||
break;
|
||||
|
||||
case "lto":
|
||||
flags.push("-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS=-flto=full", "-DCMAKE_CXX_FLAGS=-flto=full");
|
||||
break;
|
||||
|
||||
default: // release
|
||||
flags.push("-DCMAKE_BUILD_TYPE=RelWithDebInfo");
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Environment variables for the build
|
||||
const getBuildEnv = () => {
|
||||
const env = { ...process.env };
|
||||
|
||||
const cflags = ["-ffat-lto-objects"];
|
||||
const cxxflags = ["-ffat-lto-objects"];
|
||||
|
||||
if (IS_LINUX && buildConfig !== "lto") {
|
||||
cflags.push("-Wl,--whole-archive");
|
||||
cxxflags.push("-Wl,--whole-archive", "-DUSE_BUN_JSC_ADDITIONS=ON", "-DUSE_BUN_EVENT_LOOP=ON");
|
||||
}
|
||||
|
||||
env.CFLAGS = (env.CFLAGS || "") + " " + cflags.join(" ");
|
||||
env.CXXFLAGS = (env.CXXFLAGS || "") + " " + cxxflags.join(" ");
|
||||
|
||||
if (IS_MAC) {
|
||||
env.ICU_INCLUDE_DIRS = `${HOMEBREW_PREFIX}opt/icu4c/include`;
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
// Run a command with proper error handling
|
||||
function runCommand(command: string, args: string[], options: any = {}) {
|
||||
console.log(`Running: ${command} ${args.join(" ")}`);
|
||||
const result = spawnSync(command, args, {
|
||||
stdio: "inherit",
|
||||
...options,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Failed to execute command: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.error(`Command failed with exit code ${result.status}`);
|
||||
process.exit(result.status || 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main build function
|
||||
function buildJSC() {
|
||||
const buildDir = getBuildDir(buildConfig);
|
||||
const cmakeFlags = getBuildFlags(buildConfig);
|
||||
const env = getBuildEnv();
|
||||
|
||||
console.log(`Building JSC with configuration: ${buildConfig}`);
|
||||
console.log(`Build directory: ${buildDir}`);
|
||||
|
||||
// Create build directories
|
||||
if (!existsSync(buildDir)) {
|
||||
mkdirSync(buildDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!existsSync(WEBKIT_DIR)) {
|
||||
mkdirSync(WEBKIT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Configure with CMake
|
||||
console.log("\n📦 Configuring with CMake...");
|
||||
runCommand("cmake", [...cmakeFlags, WEBKIT_DIR, buildDir], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
// Build with CMake
|
||||
console.log("\n🔨 Building JSC...");
|
||||
const buildType = buildConfig === "debug" ? "Debug" : buildConfig === "lto" ? "Release" : "RelWithDebInfo";
|
||||
|
||||
runCommand("cmake", ["--build", buildDir, "--config", buildType, "--target", "jsc"], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
console.log(`\n✅ JSC build completed successfully!`);
|
||||
console.log(`Build output: ${buildDir}`);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
if (import.meta.main) {
|
||||
buildJSC();
|
||||
}
|
||||
176
scripts/github-metrics.ts
Executable file
176
scripts/github-metrics.ts
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { $ } from "bun";
|
||||
|
||||
interface ReleaseInfo {
|
||||
publishedAt: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface Issue {
|
||||
number: number;
|
||||
closedAt: string;
|
||||
stateReason: string;
|
||||
}
|
||||
|
||||
interface Reaction {
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get release information for a given tag
|
||||
*/
|
||||
async function getReleaseInfo(tag: string): Promise<ReleaseInfo> {
|
||||
try {
|
||||
const result = await $`gh release view ${tag} --json publishedAt,tagName`.json();
|
||||
return {
|
||||
publishedAt: result.publishedAt,
|
||||
tag: result.tagName,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get release info for ${tag}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count issues closed as completed since a given date
|
||||
*/
|
||||
async function countCompletedIssues(sinceDate: string): Promise<{ count: number; issues: number[] }> {
|
||||
try {
|
||||
const result =
|
||||
(await $`gh issue list --state closed --search "closed:>=${sinceDate} reason:completed" --limit 1000 --json number,closedAt,stateReason`.json()) as Issue[];
|
||||
|
||||
const completedIssues = result.filter(issue => issue.stateReason === "COMPLETED");
|
||||
|
||||
return {
|
||||
count: completedIssues.length,
|
||||
issues: completedIssues.map(issue => issue.number),
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to count completed issues: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get positive reactions for an issue (👍, ❤️, 🎉, 🚀)
|
||||
*/
|
||||
async function getIssueReactions(issueNumber: number): Promise<number> {
|
||||
try {
|
||||
const reactions = (await $`gh api "repos/oven-sh/bun/issues/${issueNumber}/reactions"`.json()) as Reaction[];
|
||||
return reactions.filter(r => ["+1", "heart", "hooray", "rocket"].includes(r.content)).length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get positive reactions for all comments on an issue
|
||||
*/
|
||||
async function getCommentReactions(issueNumber: number): Promise<number> {
|
||||
try {
|
||||
const comments = (await $`gh api "repos/oven-sh/bun/issues/${issueNumber}/comments"`.json()) as Comment[];
|
||||
|
||||
let totalReactions = 0;
|
||||
for (const comment of comments) {
|
||||
try {
|
||||
const reactions =
|
||||
(await $`gh api "repos/oven-sh/bun/issues/comments/${comment.id}/reactions"`.json()) as Reaction[];
|
||||
totalReactions += reactions.filter(r => ["+1", "heart", "hooray", "rocket"].includes(r.content)).length;
|
||||
} catch {
|
||||
// Skip if we can't get reactions for this comment
|
||||
}
|
||||
}
|
||||
|
||||
return totalReactions;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count total positive reactions for issues and their comments
|
||||
*/
|
||||
async function countReactions(issueNumbers: number[], verbose = false): Promise<number> {
|
||||
let totalReactions = 0;
|
||||
|
||||
for (const issueNumber of issueNumbers) {
|
||||
if (verbose) {
|
||||
console.log(`Processing issue #${issueNumber}...`);
|
||||
}
|
||||
|
||||
const [issueReactions, commentReactions] = await Promise.all([
|
||||
getIssueReactions(issueNumber),
|
||||
getCommentReactions(issueNumber),
|
||||
]);
|
||||
|
||||
const issueTotal = issueReactions + commentReactions;
|
||||
totalReactions += issueTotal;
|
||||
|
||||
if (verbose && issueTotal > 0) {
|
||||
console.log(
|
||||
` Issue #${issueNumber}: ${issueReactions} issue + ${commentReactions} comment = ${issueTotal} total`,
|
||||
);
|
||||
}
|
||||
|
||||
// Small delay to avoid rate limiting
|
||||
await Bun.sleep(50);
|
||||
}
|
||||
|
||||
return totalReactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to collect GitHub metrics
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const releaseTag = args[0];
|
||||
const verbose = args.includes("--verbose") || args.includes("-v");
|
||||
|
||||
if (!releaseTag) {
|
||||
console.error("Usage: bun run scripts/github-metrics.ts <release-tag> [--verbose]");
|
||||
console.error("Example: bun run scripts/github-metrics.ts bun-v1.2.19");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`📊 Collecting GitHub metrics since ${releaseTag}...`);
|
||||
|
||||
// Get release date
|
||||
const releaseInfo = await getReleaseInfo(releaseTag);
|
||||
const releaseDate = releaseInfo.publishedAt.split("T")[0]; // Extract date part
|
||||
|
||||
if (verbose) {
|
||||
console.log(`📅 Release date: ${releaseDate}`);
|
||||
}
|
||||
|
||||
// Count completed issues
|
||||
console.log("🔍 Counting completed issues...");
|
||||
const { count: issueCount, issues: issueNumbers } = await countCompletedIssues(releaseDate);
|
||||
|
||||
// Count reactions
|
||||
console.log("👍 Counting positive reactions...");
|
||||
const reactionCount = await countReactions(issueNumbers, verbose);
|
||||
|
||||
// Display results
|
||||
console.log("\n📈 Results:");
|
||||
console.log(`Issues closed as completed since ${releaseTag}: ${issueCount}`);
|
||||
console.log(`Total positive reactions (👍❤️🎉🚀): ${reactionCount}`);
|
||||
|
||||
if (issueCount > 0) {
|
||||
console.log(`Average reactions per completed issue: ${(reactionCount / issueCount).toFixed(1)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if this script is executed directly
|
||||
if (import.meta.main) {
|
||||
main();
|
||||
}
|
||||
409
scripts/lldb-inline-tool.cpp
Normal file
409
scripts/lldb-inline-tool.cpp
Normal file
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* LLDB Inline Debug Tool
|
||||
*
|
||||
* This tool allows you to add inline debug points in your code using comments:
|
||||
* // LOG: message here
|
||||
* // LOG: variable value is {variable_name}
|
||||
*
|
||||
* The tool will set non-stopping breakpoints at these locations and print
|
||||
* the messages when hit, without interrupting program execution.
|
||||
*
|
||||
* BUILD INSTRUCTIONS:
|
||||
* ------------------
|
||||
* On macOS with Homebrew LLVM:
|
||||
* c++ -std=c++17 -o lldb-inline lldb-inline-tool.cpp \
|
||||
* -llldb \
|
||||
* -L/opt/homebrew/opt/llvm/lib \
|
||||
* -I/opt/homebrew/opt/llvm/include \
|
||||
* -Wl,-rpath,/opt/homebrew/opt/llvm/lib
|
||||
*
|
||||
* On Linux:
|
||||
* c++ -std=c++17 -o lldb-inline lldb-inline-tool.cpp \
|
||||
* -llldb \
|
||||
* -L/usr/lib/llvm-14/lib \
|
||||
* -I/usr/lib/llvm-14/include
|
||||
*
|
||||
* USAGE:
|
||||
* ------
|
||||
* ./lldb-inline <executable> [args...]
|
||||
*
|
||||
* The tool searches for // LOG: comments in all source files listed in
|
||||
* cmake/sources/ *.txt and sets breakpoints at those locations.
|
||||
*/
|
||||
|
||||
#include <lldb/API/LLDB.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
#include <glob.h>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
using namespace lldb;
|
||||
|
||||
struct DebugPoint {
|
||||
std::string file;
|
||||
int line;
|
||||
int column;
|
||||
enum Type { LOG, VAR } type;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
std::vector<DebugPoint> debugPoints;
|
||||
|
||||
bool logpointCallback(void *baton, SBProcess &process, SBThread &thread, SBBreakpointLocation &location) {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto* point = static_cast<DebugPoint*>(baton);
|
||||
|
||||
std::cout << point->file << ":" << point->line << ":" << point->column << " ";
|
||||
|
||||
// Parse the log message for {expressions}
|
||||
std::string msg = point->data;
|
||||
size_t pos = 0;
|
||||
|
||||
while ((pos = msg.find('{', pos)) != std::string::npos) {
|
||||
size_t endPos = msg.find('}', pos);
|
||||
if (endPos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract expression
|
||||
std::string expr = msg.substr(pos + 1, endPos - pos - 1);
|
||||
|
||||
// Evaluate expression
|
||||
SBFrame frame = thread.GetFrameAtIndex(0);
|
||||
SBValue result = frame.EvaluateExpression(expr.c_str());
|
||||
|
||||
std::string value;
|
||||
if (result.GetError().Success() && result.GetValue()) {
|
||||
value = result.GetValue();
|
||||
} else {
|
||||
value = "<error>";
|
||||
}
|
||||
|
||||
// Replace {expression} with value
|
||||
msg.replace(pos, endPos - pos + 1, value);
|
||||
pos += value.length();
|
||||
}
|
||||
|
||||
std::cout << msg << std::endl;
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Breakpoint callback took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// Don't stop
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> getSourceFiles() {
|
||||
std::vector<std::string> files;
|
||||
|
||||
// Read cmake source files
|
||||
glob_t globbuf;
|
||||
if (glob("cmake/sources/*.txt", 0, nullptr, &globbuf) == 0) {
|
||||
for (size_t i = 0; i < globbuf.gl_pathc; i++) {
|
||||
std::ifstream file(globbuf.gl_pathv[i]);
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty() && line[0] != '#' && line.find("${") == std::string::npos) {
|
||||
files.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
globfree(&globbuf);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void findDebugPoints() {
|
||||
// Get source files
|
||||
auto files = getSourceFiles();
|
||||
|
||||
if (files.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create temp file with list of files
|
||||
std::string tmpfile = "/tmp/lldb-inline-files.txt";
|
||||
std::ofstream out(tmpfile);
|
||||
for (const auto& file : files) {
|
||||
out << file << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
// Use ripgrep with limited threads for speed
|
||||
std::string cmd = "cat " + tmpfile + " | xargs rg -j4 --line-number --column --no-heading --color=never '//\\s*LOG:'";
|
||||
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) {
|
||||
unlink(tmpfile.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
std::regex logRegex(".*//\\s*LOG:\\s*(.+)");
|
||||
|
||||
while (fgets(buffer, sizeof(buffer), pipe)) {
|
||||
std::string line(buffer);
|
||||
// Remove trailing newline
|
||||
if (!line.empty() && line.back() == '\n') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
// Parse ripgrep output: file:line:column:text
|
||||
size_t pos1 = line.find(':');
|
||||
if (pos1 == std::string::npos) continue;
|
||||
|
||||
size_t pos2 = line.find(':', pos1 + 1);
|
||||
if (pos2 == std::string::npos) continue;
|
||||
|
||||
size_t pos3 = line.find(':', pos2 + 1);
|
||||
if (pos3 == std::string::npos) continue;
|
||||
|
||||
DebugPoint point;
|
||||
point.file = line.substr(0, pos1);
|
||||
point.line = std::stoi(line.substr(pos1 + 1, pos2 - pos1 - 1));
|
||||
point.column = std::stoi(line.substr(pos2 + 1, pos3 - pos2 - 1));
|
||||
|
||||
std::string text = line.substr(pos3 + 1);
|
||||
|
||||
std::smatch match;
|
||||
if (std::regex_match(text, match, logRegex)) {
|
||||
point.type = DebugPoint::LOG;
|
||||
point.data = match[1]; // The message is in capture group 1
|
||||
// Trim whitespace
|
||||
point.data.erase(0, point.data.find_first_not_of(" \t"));
|
||||
point.data.erase(point.data.find_last_not_of(" \t") + 1);
|
||||
debugPoints.push_back(point);
|
||||
}
|
||||
}
|
||||
|
||||
pclose(pipe);
|
||||
unlink(tmpfile.c_str());
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* executable = argv[1];
|
||||
|
||||
// Find debug points
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
findDebugPoints();
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Ripgrep search took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
|
||||
if (debugPoints.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize LLDB
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
SBDebugger::Initialize();
|
||||
SBDebugger debugger = SBDebugger::Create(false); // Don't read .lldbinit
|
||||
debugger.SetAsync(true);
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "LLDB init took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// Keep LLDB's stdio handling enabled
|
||||
SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
|
||||
SBCommandReturnObject result;
|
||||
interpreter.HandleCommand("settings set target.disable-stdio false", result);
|
||||
interpreter.HandleCommand("settings set symbols.load-on-demand true", result);
|
||||
interpreter.HandleCommand("settings set target.preload-symbols false", result);
|
||||
interpreter.HandleCommand("settings set symbols.enable-external-lookup false", result);
|
||||
interpreter.HandleCommand("settings set target.auto-import-clang-modules false", result);
|
||||
interpreter.HandleCommand("settings set target.detach-on-error true", result);
|
||||
|
||||
// Create target
|
||||
SBError error;
|
||||
char cwd[PATH_MAX];
|
||||
getcwd(cwd, sizeof(cwd));
|
||||
|
||||
std::string fullPath = executable;
|
||||
if (fullPath[0] != '/') {
|
||||
fullPath = std::string(cwd) + "/" + executable;
|
||||
}
|
||||
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
SBTarget target = debugger.CreateTarget(fullPath.c_str(), nullptr, nullptr, false, error);
|
||||
if (!target.IsValid()) {
|
||||
std::cerr << "Failed to create target: " << error.GetCString() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Create target took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// Set breakpoints
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
for (auto& point : debugPoints) {
|
||||
std::string absPath = point.file;
|
||||
if (absPath[0] != '/') {
|
||||
absPath = std::string(cwd) + "/" + point.file;
|
||||
}
|
||||
|
||||
SBBreakpoint bp = target.BreakpointCreateByLocation(absPath.c_str(), point.line);
|
||||
if (bp.IsValid()) {
|
||||
bp.SetCallback(logpointCallback, &point);
|
||||
}
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Set breakpoints took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// Build args
|
||||
std::vector<const char*> args;
|
||||
for (int i = 2; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
|
||||
// Launch process with proper settings
|
||||
SBLaunchInfo launch_info(args.data());
|
||||
launch_info.SetWorkingDirectory(cwd);
|
||||
launch_info.SetLaunchFlags(0); // Don't disable stdio
|
||||
|
||||
// Pass through environment variables from parent
|
||||
SBEnvironment env = launch_info.GetEnvironment();
|
||||
for (char **p = environ; *p != nullptr; p++) {
|
||||
env.PutEntry(*p);
|
||||
}
|
||||
launch_info.SetEnvironment(env, false);
|
||||
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
SBProcess process = target.Launch(launch_info, error);
|
||||
if (!process.IsValid()) {
|
||||
std::cerr << "Failed to launch process: " << error.GetCString() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Launch process took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// lldb::pid_t launchedPid = process.GetProcessID();
|
||||
// std::cerr << "Launched process with PID: " << launchedPid << std::endl;
|
||||
|
||||
// Handle events properly
|
||||
SBListener listener = debugger.GetListener();
|
||||
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
auto lastOutput = start;
|
||||
bool done = false;
|
||||
bool gotOutput = false;
|
||||
|
||||
int eventCount = 0;
|
||||
while (!done) {
|
||||
SBEvent event;
|
||||
if (listener.WaitForEvent(0, event)) { // Non-blocking
|
||||
eventCount++;
|
||||
auto eventTime = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(eventTime - start);
|
||||
StateType state = SBProcess::GetStateFromEvent(event);
|
||||
// std::cerr << "Event #" << eventCount << " at " << elapsed.count() << "ms: state=" << state << std::endl;
|
||||
if (state == eStateExited) {
|
||||
auto exitTime = std::chrono::high_resolution_clock::now();
|
||||
auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(exitTime - start);
|
||||
// std::cerr << "Process exited with code: " << process.GetExitStatus() << " after " << totalTime.count() << "ms in event loop" << std::endl;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case eStateStopped:
|
||||
process.Continue();
|
||||
break;
|
||||
|
||||
case eStateRunning:
|
||||
break;
|
||||
|
||||
case eStateExited:
|
||||
case eStateCrashed:
|
||||
case eStateDetached:
|
||||
// std::cerr << "Exiting immediately on state: " << state << std::endl;
|
||||
// Just exit immediately - skip all cleanup
|
||||
exit(process.GetExitStatus());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No event, check if process is done
|
||||
StateType currentState = process.GetState();
|
||||
if (currentState == eStateExited || currentState == eStateCrashed || currentState == eStateDetached) {
|
||||
done = true;
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - lastOutput);
|
||||
// std::cerr << "Process already exited, detected by polling. Time from last output: " << duration.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Read and forward stdout/stderr
|
||||
char buffer[1024];
|
||||
size_t num_bytes;
|
||||
|
||||
bool hadStdout = false;
|
||||
while ((num_bytes = process.GetSTDOUT(buffer, sizeof(buffer)-1)) > 0) {
|
||||
buffer[num_bytes] = '\0';
|
||||
std::cout << buffer;
|
||||
std::cout.flush();
|
||||
lastOutput = std::chrono::high_resolution_clock::now();
|
||||
gotOutput = true;
|
||||
hadStdout = true;
|
||||
}
|
||||
|
||||
bool hadStderr = false;
|
||||
while ((num_bytes = process.GetSTDERR(buffer, sizeof(buffer)-1)) > 0) {
|
||||
buffer[num_bytes] = '\0';
|
||||
std::cerr << buffer;
|
||||
std::cerr.flush();
|
||||
lastOutput = std::chrono::high_resolution_clock::now();
|
||||
gotOutput = true;
|
||||
hadStderr = true;
|
||||
}
|
||||
|
||||
// Poll process state every iteration
|
||||
StateType currentState = process.GetState();
|
||||
if (currentState == eStateExited || currentState == eStateCrashed || currentState == eStateDetached) {
|
||||
// Process has exited, break out of loop
|
||||
done = true;
|
||||
} else {
|
||||
// Small sleep to avoid busy-waiting
|
||||
usleep(10000); // 10ms
|
||||
}
|
||||
}
|
||||
|
||||
int exit_code = process.GetExitStatus();
|
||||
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Total event loop time: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
// Cleanup
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
SBDebugger::Destroy(debugger);
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
// std::cerr << "Debugger destroy took: " << duration.count() << "ms" << std::endl;
|
||||
|
||||
SBDebugger::Terminate();
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
55
scripts/lldb-inline.sh
Executable file
55
scripts/lldb-inline.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# LLDB Inline Debug Tool Build & Run Script
|
||||
#
|
||||
# This script builds the lldb-inline tool if needed and runs it.
|
||||
# Usage: ./scripts/lldb-inline.sh <executable> [args...]
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TOOL_SOURCE="$SCRIPT_DIR/lldb-inline-tool.cpp"
|
||||
TOOL_BINARY="$SCRIPT_DIR/lldb-inline"
|
||||
|
||||
# Check if we need to rebuild
|
||||
if [ ! -f "$TOOL_BINARY" ] || [ "$TOOL_SOURCE" -nt "$TOOL_BINARY" ]; then
|
||||
# Detect OS and build accordingly
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS with Homebrew LLVM
|
||||
c++ -std=c++17 -o "$TOOL_BINARY" "$TOOL_SOURCE" \
|
||||
-llldb \
|
||||
-L/opt/homebrew/opt/llvm/lib \
|
||||
-I/opt/homebrew/opt/llvm/include \
|
||||
-Wl,-rpath,/opt/homebrew/opt/llvm/lib >/dev/null 2>&1
|
||||
else
|
||||
# Linux - try to find LLVM installation
|
||||
LLVM_DIR=""
|
||||
for version in 18 17 16 15 14 13 12; do
|
||||
if [ -d "/usr/lib/llvm-$version" ]; then
|
||||
LLVM_DIR="/usr/lib/llvm-$version"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$LLVM_DIR" ] && [ -d "/usr/lib/llvm" ]; then
|
||||
LLVM_DIR="/usr/lib/llvm"
|
||||
fi
|
||||
|
||||
if [ -z "$LLVM_DIR" ]; then
|
||||
# Try pkg-config as fallback
|
||||
LLDB_CFLAGS=$(pkg-config --cflags lldb 2>/dev/null)
|
||||
LLDB_LIBS=$(pkg-config --libs lldb 2>/dev/null)
|
||||
c++ -std=c++17 -o "$TOOL_BINARY" "$TOOL_SOURCE" \
|
||||
$LLDB_CFLAGS $LLDB_LIBS >/dev/null 2>&1
|
||||
else
|
||||
c++ -std=c++17 -o "$TOOL_BINARY" "$TOOL_SOURCE" \
|
||||
-llldb \
|
||||
-L"$LLVM_DIR/lib" \
|
||||
-I"$LLVM_DIR/include" >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the tool with all arguments
|
||||
exec "$TOOL_BINARY" "$@"
|
||||
126
scripts/run-clang-format.sh
Executable file
126
scripts/run-clang-format.sh
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Run clang-format on all C++ source and header files in the Bun project
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Get the project root directory (parent of scripts/)
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Default to format mode (modify files)
|
||||
MODE="${1:-format}"
|
||||
|
||||
# Use LLVM_VERSION_MAJOR from environment or default to 19
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-19}"
|
||||
|
||||
# Ensure we have the specific clang-format version
|
||||
CLANG_FORMAT="clang-format-${LLVM_VERSION}"
|
||||
if ! command -v "$CLANG_FORMAT" &> /dev/null; then
|
||||
echo "Error: $CLANG_FORMAT not found" >&2
|
||||
echo "Please install clang-format version $LLVM_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Array to hold all files to format
|
||||
declare -a FILES_TO_FORMAT
|
||||
|
||||
# Find all header files in src/ and packages/, excluding third-party and generated code
|
||||
echo "Finding header files..."
|
||||
while IFS= read -r -d '' file; do
|
||||
# Additional filtering for specific files and patterns
|
||||
if [[ "$file" =~ src/bun\.js/api/ffi- ]] || \
|
||||
[[ "$file" =~ src/napi/ ]] || \
|
||||
[[ "$file" =~ src/bun\.js/bindings/libuv/ ]] || \
|
||||
[[ "$file" =~ src/bun\.js/bindings/sqlite/ ]] || \
|
||||
[[ "$file" =~ packages/bun-usockets/.*libuv ]] || \
|
||||
[[ "$file" =~ src/deps/ ]]; then
|
||||
continue
|
||||
fi
|
||||
FILES_TO_FORMAT+=("$file")
|
||||
done < <(find src packages -type f \( -name "*.h" -o -name "*.hpp" \) \
|
||||
-not -path "*/vendor/*" \
|
||||
-not -path "*/third_party/*" \
|
||||
-not -path "*/thirdparty/*" \
|
||||
-not -path "*/generated/*" \
|
||||
-print0 2>/dev/null || true)
|
||||
|
||||
# Read C++ source files from CxxSources.txt
|
||||
echo "Reading C++ source files from CxxSources.txt..."
|
||||
if [ -f "cmake/sources/CxxSources.txt" ]; then
|
||||
while IFS= read -r file; do
|
||||
# Skip empty lines and comments
|
||||
if [[ -n "$file" && ! "$file" =~ ^[[:space:]]*# ]]; then
|
||||
# Check if file exists
|
||||
if [ -f "$file" ]; then
|
||||
FILES_TO_FORMAT+=("$file")
|
||||
fi
|
||||
fi
|
||||
done < "cmake/sources/CxxSources.txt"
|
||||
else
|
||||
echo "Warning: cmake/sources/CxxSources.txt not found" >&2
|
||||
fi
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
declare -a UNIQUE_FILES
|
||||
declare -A seen
|
||||
for file in "${FILES_TO_FORMAT[@]}"; do
|
||||
if [[ ! -v "seen[$file]" ]]; then
|
||||
seen["$file"]=1
|
||||
UNIQUE_FILES+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Processing ${#UNIQUE_FILES[@]} files..."
|
||||
|
||||
# Run clang-format based on mode
|
||||
if [ "$MODE" = "check" ]; then
|
||||
# Check mode - verify formatting without modifying files
|
||||
FAILED=0
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
# Find the nearest .clang-format file for this source file
|
||||
dir=$(dirname "$file")
|
||||
while [ "$dir" != "." ] && [ "$dir" != "/" ]; do
|
||||
if [ -f "$dir/.clang-format" ]; then
|
||||
break
|
||||
fi
|
||||
dir=$(dirname "$dir")
|
||||
done
|
||||
|
||||
if ! $CLANG_FORMAT --dry-run --Werror "$file" 2>/dev/null; then
|
||||
echo "Format check failed: $file"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "Some files need formatting. Run 'bun run clang-format' to fix."
|
||||
exit 1
|
||||
else
|
||||
echo "All files are properly formatted."
|
||||
fi
|
||||
elif [ "$MODE" = "format" ] || [ "$MODE" = "fix" ]; then
|
||||
# Format mode - modify files in place
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
echo "Formatting: $file"
|
||||
$CLANG_FORMAT -i "$file"
|
||||
done
|
||||
echo "Formatting complete."
|
||||
elif [ "$MODE" = "diff" ]; then
|
||||
# Diff mode - show what would change
|
||||
for file in "${UNIQUE_FILES[@]}"; do
|
||||
if ! $CLANG_FORMAT --dry-run --Werror "$file" 2>/dev/null; then
|
||||
echo "=== $file ==="
|
||||
diff -u "$file" <($CLANG_FORMAT "$file") || true
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Usage: $0 [check|format|fix|diff]" >&2
|
||||
echo " check - Check if files are formatted (default)" >&2
|
||||
echo " format - Format files in place" >&2
|
||||
echo " fix - Same as format" >&2
|
||||
echo " diff - Show formatting differences" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -82,6 +82,10 @@ function getNodeParallelTestTimeout(testPath) {
|
||||
return 10_000;
|
||||
}
|
||||
|
||||
process.on("SIGTRAP", () => {
|
||||
console.warn("Test runner received SIGTRAP. Doing nothing.");
|
||||
});
|
||||
|
||||
const { values: options, positionals: filters } = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
@@ -178,6 +182,37 @@ if (options["quiet"]) {
|
||||
isQuiet = true;
|
||||
}
|
||||
|
||||
let newFiles = [];
|
||||
let prFileCount = 0;
|
||||
if (isBuildkite) {
|
||||
try {
|
||||
console.log("on buildkite: collecting new files from PR");
|
||||
const per_page = 50;
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/oven-sh/bun/pulls/${process.env.BUILDKITE_PULL_REQUEST}/files?per_page=${per_page}&page=${i}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${getSecret("GITHUB_TOKEN")}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const doc = await res.json();
|
||||
console.log(`-> page ${i}, found ${doc.length} items`);
|
||||
if (doc.length === 0) break;
|
||||
if (doc.length < per_page) break;
|
||||
for (const { filename, status } of doc) {
|
||||
prFileCount += 1;
|
||||
if (status !== "added") continue;
|
||||
newFiles.push(filename);
|
||||
}
|
||||
}
|
||||
console.log(`- PR ${process.env.BUILDKITE_PULL_REQUEST}, ${prFileCount} files, ${newFiles.length} new files`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let coresDir;
|
||||
|
||||
if (options["coredump-upload"]) {
|
||||
@@ -270,6 +305,7 @@ const skipArray = (() => {
|
||||
}
|
||||
return readFileSync(path, "utf-8")
|
||||
.split("\n")
|
||||
.map(line => line.trim())
|
||||
.filter(line => !line.startsWith("#") && line.length > 0);
|
||||
})();
|
||||
|
||||
@@ -419,6 +455,7 @@ async function runTests() {
|
||||
if (attempt >= maxAttempts || isAlwaysFailure(error)) {
|
||||
flaky = false;
|
||||
failedResults.push(failure);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +515,7 @@ async function runTests() {
|
||||
console.log("run in", cwd);
|
||||
let exiting = false;
|
||||
|
||||
const server = spawn(execPath, ["run", "ci-remap-server", execPath, cwd, getCommit()], {
|
||||
const server = spawn(execPath, ["run", "--silent", "ci-remap-server", execPath, cwd, getCommit()], {
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
cwd, // run in main repo
|
||||
env: { ...process.env, BUN_DEBUG_QUIET_LOGS: "1", NO_COLOR: "1" },
|
||||
@@ -488,18 +525,22 @@ async function runTests() {
|
||||
server.on("exit", (code, signal) => {
|
||||
if (!exiting && (code !== 0 || signal !== null)) errorResolve(signal ? signal : "code " + code);
|
||||
});
|
||||
process.on("exit", () => {
|
||||
function onBeforeExit() {
|
||||
exiting = true;
|
||||
server.kill();
|
||||
});
|
||||
server.off("error");
|
||||
server.off("exit");
|
||||
server.kill?.();
|
||||
}
|
||||
process.once("beforeExit", onBeforeExit);
|
||||
const lines = createInterface(server.stdout);
|
||||
lines.on("line", line => {
|
||||
portResolve({ port: parseInt(line) });
|
||||
});
|
||||
|
||||
const result = await Promise.race([portPromise, errorPromise, setTimeoutPromise(5000, "timeout")]);
|
||||
if (typeof result.port != "number") {
|
||||
server.kill();
|
||||
const result = await Promise.race([portPromise, errorPromise.catch(e => e), setTimeoutPromise(5000, "timeout")]);
|
||||
if (typeof result?.port != "number") {
|
||||
process.off("beforeExit", onBeforeExit);
|
||||
server.kill?.();
|
||||
console.warn("ci-remap server did not start:", result);
|
||||
} else {
|
||||
console.log("crash reports parsed on port", result.port);
|
||||
@@ -524,6 +565,7 @@ async function runTests() {
|
||||
};
|
||||
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateExceptions(testPath)) {
|
||||
env.BUN_JSC_validateExceptionChecks = "1";
|
||||
env.BUN_JSC_dumpSimulatedThrows = "1";
|
||||
}
|
||||
return runTest(title, async () => {
|
||||
const { ok, error, stdout, crashes } = await spawnBun(execPath, {
|
||||
@@ -1247,6 +1289,7 @@ async function spawnBunTest(execPath, testPath, options = { cwd }) {
|
||||
};
|
||||
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateExceptions(relative(cwd, absPath))) {
|
||||
env.BUN_JSC_validateExceptionChecks = "1";
|
||||
env.BUN_JSC_dumpSimulatedThrows = "1";
|
||||
}
|
||||
|
||||
const { ok, error, stdout, crashes } = await spawnBun(execPath, {
|
||||
@@ -1976,6 +2019,9 @@ function formatTestToMarkdown(result, concise, retries) {
|
||||
if (retries > 0) {
|
||||
markdown += ` (${retries} ${retries === 1 ? "retry" : "retries"})`;
|
||||
}
|
||||
if (newFiles.includes(testTitle)) {
|
||||
markdown += ` (new)`;
|
||||
}
|
||||
|
||||
if (concise) {
|
||||
markdown += "</li>\n";
|
||||
@@ -2181,6 +2227,7 @@ function isAlwaysFailure(error) {
|
||||
error.includes("illegal instruction") ||
|
||||
error.includes("sigtrap") ||
|
||||
error.includes("error: addresssanitizer") ||
|
||||
error.includes("internal assertion failure") ||
|
||||
error.includes("core dumped") ||
|
||||
error.includes("crash reported")
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ const usage = String.raw`
|
||||
/_____ \____/|__| |__| /_____ \__|__|_| / __/ \____/|__| |__| /____ >
|
||||
\/ \/ \/|__| \/
|
||||
|
||||
Usage: bun scripts/sortImports [options] <files...>
|
||||
Usage: bun scripts/sort-imports [options] <files...>
|
||||
|
||||
Options:
|
||||
--help Show this help message
|
||||
@@ -22,7 +22,7 @@ Options:
|
||||
--keep-unused Don't remove unused imports
|
||||
|
||||
Examples:
|
||||
bun scripts/sortImports src
|
||||
bun scripts/sort-imports src
|
||||
`.slice(1);
|
||||
if (args.includes("--help")) {
|
||||
console.log(usage);
|
||||
|
||||
@@ -165,13 +165,7 @@ pub const versions = @import("./generated_versions_list.zig");
|
||||
// Keeping this code for:
|
||||
// 1. documentation that an attempt was made
|
||||
// 2. if I want to configure allocator later
|
||||
pub inline fn configureAllocator(_: AllocatorConfiguration) void {
|
||||
// if (comptime !use_mimalloc) return;
|
||||
// const mimalloc = bun.mimalloc;
|
||||
// mimalloc.mi_option_set_enabled(mimalloc.mi_option_verbose, config.verbose);
|
||||
// mimalloc.mi_option_set_enabled(mimalloc.mi_option_large_os_pages, config.long_running);
|
||||
// if (!config.long_running) mimalloc.mi_option_set(mimalloc.mi_option_reset_delay, 0);
|
||||
}
|
||||
pub inline fn configureAllocator(_: AllocatorConfiguration) void {}
|
||||
|
||||
pub fn notimpl() noreturn {
|
||||
@branchHint(.cold);
|
||||
|
||||
@@ -47,7 +47,7 @@ fn createImportRecord(this: *HTMLScanner, input_path: []const u8, kind: ImportKi
|
||||
try this.import_records.push(this.allocator, record);
|
||||
}
|
||||
|
||||
const debug = bun.Output.scoped(.HTMLScanner, true);
|
||||
const debug = bun.Output.scoped(.HTMLScanner, .hidden);
|
||||
|
||||
pub fn onWriteHTML(_: *HTMLScanner, bytes: []const u8) void {
|
||||
_ = bytes; // bytes are not written in scan phase
|
||||
|
||||
@@ -6,6 +6,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
bytes: []const u8 = "",
|
||||
files: bun.StringArrayHashMap(File),
|
||||
entry_point_id: u32 = 0,
|
||||
compile_exec_argv: []const u8 = "",
|
||||
|
||||
// We never want to hit the filesystem for these files
|
||||
// We use the `/$bunfs/` prefix to indicate that it's a virtual path
|
||||
@@ -54,7 +55,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
|
||||
// by normalized file path
|
||||
pub fn find(this: *const StandaloneModuleGraph, name: []const u8) ?*File {
|
||||
if (!isBunStandaloneFilePath(base_path)) {
|
||||
if (!isBunStandaloneFilePath(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -279,6 +280,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
byte_count: usize = 0,
|
||||
modules_ptr: bun.StringPointer = .{},
|
||||
entry_point_id: u32 = 0,
|
||||
compile_exec_argv_ptr: bun.StringPointer = .{},
|
||||
};
|
||||
|
||||
const trailer = "\n---- Bun! ----\n";
|
||||
@@ -323,6 +325,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
.bytes = raw_bytes[0..offsets.byte_count],
|
||||
.files = modules,
|
||||
.entry_point_id = offsets.entry_point_id,
|
||||
.compile_exec_argv = sliceToZ(raw_bytes, offsets.compile_exec_argv_ptr),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -338,14 +341,14 @@ pub const StandaloneModuleGraph = struct {
|
||||
return bytes[ptr.offset..][0..ptr.length :0];
|
||||
}
|
||||
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format) ![]u8 {
|
||||
pub fn toBytes(allocator: std.mem.Allocator, prefix: []const u8, output_files: []const bun.options.OutputFile, output_format: bun.options.Format, compile_exec_argv: []const u8) ![]u8 {
|
||||
var serialize_trace = bun.perf.trace("StandaloneModuleGraph.serialize");
|
||||
defer serialize_trace.end();
|
||||
|
||||
var entry_point_id: ?usize = null;
|
||||
var string_builder = bun.StringBuilder{};
|
||||
var module_count: usize = 0;
|
||||
for (output_files) |output_file| {
|
||||
for (output_files) |*output_file| {
|
||||
string_builder.countZ(output_file.dest_path);
|
||||
string_builder.countZ(prefix);
|
||||
if (output_file.value == .buffer) {
|
||||
@@ -379,6 +382,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
string_builder.cap += trailer.len;
|
||||
string_builder.cap += 16;
|
||||
string_builder.cap += @sizeOf(Offsets);
|
||||
string_builder.countZ(compile_exec_argv);
|
||||
|
||||
try string_builder.allocate(allocator);
|
||||
|
||||
@@ -391,7 +395,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
var source_map_arena = bun.ArenaAllocator.init(allocator);
|
||||
defer source_map_arena.deinit();
|
||||
|
||||
for (output_files) |output_file| {
|
||||
for (output_files) |*output_file| {
|
||||
if (!output_file.output_kind.isFileInStandaloneMode()) {
|
||||
continue;
|
||||
}
|
||||
@@ -463,6 +467,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
const offsets = Offsets{
|
||||
.entry_point_id = @as(u32, @truncate(entry_point_id.?)),
|
||||
.modules_ptr = string_builder.appendCount(std.mem.sliceAsBytes(modules.items)),
|
||||
.compile_exec_argv_ptr = string_builder.appendCountZ(compile_exec_argv),
|
||||
.byte_count = string_builder.len,
|
||||
};
|
||||
|
||||
@@ -491,6 +496,21 @@ pub const StandaloneModuleGraph = struct {
|
||||
windows_hide_console: bool = false,
|
||||
};
|
||||
|
||||
pub const CompileResult = union(enum) {
|
||||
success: void,
|
||||
error_message: []const u8,
|
||||
|
||||
pub fn fail(msg: []const u8) CompileResult {
|
||||
return .{ .error_message = msg };
|
||||
}
|
||||
|
||||
pub fn deinit(this: *const @This()) void {
|
||||
if (this.* == .error_message) {
|
||||
bun.default_allocator.free(this.error_message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions, target: *const CompileTarget) bun.FileDescriptor {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var zname: [:0]const u8 = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
|
||||
@@ -627,6 +647,7 @@ pub const StandaloneModuleGraph = struct {
|
||||
cleanup(zname, fd);
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
break :brk fd;
|
||||
};
|
||||
|
||||
@@ -816,7 +837,43 @@ pub const StandaloneModuleGraph = struct {
|
||||
var needs_download: bool = true;
|
||||
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
||||
if (needs_download) {
|
||||
try target.downloadToPath(env, allocator, dest_z);
|
||||
target.downloadToPath(env, allocator, dest_z) catch |err| {
|
||||
// For CLI, provide detailed error messages and exit
|
||||
switch (err) {
|
||||
error.TargetNotFound => {
|
||||
Output.errGeneric(
|
||||
\\Does this target and version of Bun exist?
|
||||
\\
|
||||
\\404 downloading {} from npm registry
|
||||
, .{target.*});
|
||||
},
|
||||
error.NetworkError => {
|
||||
Output.errGeneric(
|
||||
\\Failed to download cross-compilation target.
|
||||
\\
|
||||
\\Network error downloading {} from npm registry
|
||||
, .{target.*});
|
||||
},
|
||||
error.InvalidResponse => {
|
||||
Output.errGeneric(
|
||||
\\Failed to verify the integrity of the downloaded tarball.
|
||||
\\
|
||||
\\The downloaded content for {} appears to be corrupted
|
||||
, .{target.*});
|
||||
},
|
||||
error.ExtractionFailed => {
|
||||
Output.errGeneric(
|
||||
\\Failed to extract the downloaded tarball.
|
||||
\\
|
||||
\\Could not extract executable for {}
|
||||
, .{target.*});
|
||||
},
|
||||
else => {
|
||||
Output.errGeneric("Failed to download {}: {s}", .{ target.*, @errorName(err) });
|
||||
},
|
||||
}
|
||||
Global.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
return try allocator.dupeZ(u8, dest_z);
|
||||
@@ -833,27 +890,68 @@ pub const StandaloneModuleGraph = struct {
|
||||
output_format: bun.options.Format,
|
||||
windows_hide_console: bool,
|
||||
windows_icon: ?[]const u8,
|
||||
) !void {
|
||||
const bytes = try toBytes(allocator, module_prefix, output_files, output_format);
|
||||
if (bytes.len == 0) return;
|
||||
compile_exec_argv: []const u8,
|
||||
self_exe_path: ?[]const u8,
|
||||
) !CompileResult {
|
||||
const bytes = toBytes(allocator, module_prefix, output_files, output_format, compile_exec_argv) catch |err| {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to generate module graph bytes: {s}", .{@errorName(err)}) catch "failed to generate module graph bytes");
|
||||
};
|
||||
if (bytes.len == 0) return CompileResult.fail("no output files to bundle");
|
||||
defer allocator.free(bytes);
|
||||
|
||||
const fd = inject(
|
||||
var free_self_exe = false;
|
||||
const self_exe = if (self_exe_path) |path| brk: {
|
||||
free_self_exe = true;
|
||||
break :brk allocator.dupeZ(u8, path) catch bun.outOfMemory();
|
||||
} else if (target.isDefault())
|
||||
bun.selfExePath() catch |err| {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to get self executable path: {s}", .{@errorName(err)}) catch "failed to get self executable path");
|
||||
}
|
||||
else blk: {
|
||||
var exe_path_buf: bun.PathBuffer = undefined;
|
||||
var version_str_buf: [1024]u8 = undefined;
|
||||
const version_str = std.fmt.bufPrintZ(&version_str_buf, "{}", .{target}) catch {
|
||||
return CompileResult.fail("failed to format target version string");
|
||||
};
|
||||
var needs_download: bool = true;
|
||||
const dest_z = target.exePath(&exe_path_buf, version_str, env, &needs_download);
|
||||
|
||||
if (needs_download) {
|
||||
target.downloadToPath(env, allocator, dest_z) catch |err| {
|
||||
const msg = switch (err) {
|
||||
error.TargetNotFound => std.fmt.allocPrint(allocator, "Target platform '{}' is not available for download. Check if this version of Bun supports this target.", .{target}) catch "Target platform not available for download",
|
||||
error.NetworkError => std.fmt.allocPrint(allocator, "Network error downloading executable for '{}'. Check your internet connection and proxy settings.", .{target}) catch "Network error downloading executable",
|
||||
error.InvalidResponse => std.fmt.allocPrint(allocator, "Downloaded file for '{}' appears to be corrupted. Please try again.", .{target}) catch "Downloaded file is corrupted",
|
||||
error.ExtractionFailed => std.fmt.allocPrint(allocator, "Failed to extract executable for '{}'. The download may be incomplete.", .{target}) catch "Failed to extract downloaded executable",
|
||||
error.UnsupportedTarget => std.fmt.allocPrint(allocator, "Target '{}' is not supported", .{target}) catch "Unsupported target",
|
||||
else => std.fmt.allocPrint(allocator, "Failed to download '{}': {s}", .{ target, @errorName(err) }) catch "Download failed",
|
||||
};
|
||||
return CompileResult.fail(msg);
|
||||
};
|
||||
}
|
||||
|
||||
free_self_exe = true;
|
||||
break :blk allocator.dupeZ(u8, dest_z) catch bun.outOfMemory();
|
||||
};
|
||||
|
||||
defer if (free_self_exe) {
|
||||
allocator.free(self_exe);
|
||||
};
|
||||
|
||||
var fd = inject(
|
||||
bytes,
|
||||
if (target.isDefault())
|
||||
bun.selfExePath() catch |err| {
|
||||
Output.err(err, "failed to get self executable path", .{});
|
||||
Global.exit(1);
|
||||
}
|
||||
else
|
||||
download(allocator, target, env) catch |err| {
|
||||
Output.err(err, "failed to download cross-compiled bun executable", .{});
|
||||
Global.exit(1);
|
||||
},
|
||||
self_exe,
|
||||
.{ .windows_hide_console = windows_hide_console },
|
||||
target,
|
||||
);
|
||||
defer if (fd != bun.invalid_fd) fd.close();
|
||||
bun.debugAssert(fd.kind == .system);
|
||||
|
||||
if (Environment.isPosix) {
|
||||
// Set executable permissions (0o755 = rwxr-xr-x) - makes it executable for owner, readable/executable for group and others
|
||||
_ = Syscall.fchmod(fd, 0o755);
|
||||
}
|
||||
|
||||
if (Environment.isWindows) {
|
||||
var outfile_buf: bun.OSPathBuffer = undefined;
|
||||
const outfile_slice = brk: {
|
||||
@@ -865,52 +963,59 @@ pub const StandaloneModuleGraph = struct {
|
||||
};
|
||||
|
||||
bun.windows.moveOpenedFileAtLoose(fd, .fromStdDir(root_dir), outfile_slice, true).unwrap() catch |err| {
|
||||
if (err == error.EISDIR) {
|
||||
Output.errGeneric("{} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.utf16(outfile_slice)});
|
||||
} else {
|
||||
Output.err(err, "failed to move executable to result path", .{});
|
||||
}
|
||||
|
||||
_ = bun.windows.deleteOpenedFile(fd);
|
||||
|
||||
Global.exit(1);
|
||||
if (err == error.EISDIR) {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
|
||||
} else {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to move executable to result path: {s}", .{@errorName(err)}) catch "failed to move executable");
|
||||
}
|
||||
};
|
||||
|
||||
fd.close();
|
||||
fd = bun.invalid_fd;
|
||||
|
||||
if (windows_icon) |icon_utf8| {
|
||||
var icon_buf: bun.OSPathBuffer = undefined;
|
||||
const icon = bun.strings.toWPathNormalized(&icon_buf, icon_utf8);
|
||||
bun.windows.rescle.setIcon(outfile_slice, icon) catch {
|
||||
Output.warn("Failed to set executable icon", .{});
|
||||
bun.windows.rescle.setIcon(outfile_slice, icon) catch |err| {
|
||||
Output.debug("Warning: Failed to set Windows icon for executable: {s}", .{@errorName(err)});
|
||||
};
|
||||
}
|
||||
return;
|
||||
return .success;
|
||||
}
|
||||
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
const temp_location = bun.getFdPath(fd, &buf) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get path for fd: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to get path for fd: {s}", .{@errorName(err)}) catch "failed to get path for file descriptor");
|
||||
};
|
||||
const temp_posix = std.posix.toPosixPath(temp_location) catch |err| {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "path too long: {s}", .{@errorName(err)}) catch "path too long");
|
||||
};
|
||||
const outfile_basename = std.fs.path.basename(outfile);
|
||||
const outfile_posix = std.posix.toPosixPath(outfile_basename) catch |err| {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "outfile name too long: {s}", .{@errorName(err)}) catch "outfile name too long");
|
||||
};
|
||||
|
||||
bun.sys.moveFileZWithHandle(
|
||||
fd,
|
||||
bun.FD.cwd(),
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(temp_location)), 0),
|
||||
bun.sliceTo(&temp_posix, 0),
|
||||
.fromStdDir(root_dir),
|
||||
bun.sliceTo(&(try std.posix.toPosixPath(std.fs.path.basename(outfile))), 0),
|
||||
bun.sliceTo(&outfile_posix, 0),
|
||||
) catch |err| {
|
||||
if (err == error.IsDir or err == error.EISDIR) {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> {} is a directory. Please choose a different --outfile or delete the directory", .{bun.fmt.quote(outfile)});
|
||||
} else {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) });
|
||||
}
|
||||
_ = Syscall.unlink(
|
||||
&(try std.posix.toPosixPath(temp_location)),
|
||||
);
|
||||
fd.close();
|
||||
fd = bun.invalid_fd;
|
||||
|
||||
Global.exit(1);
|
||||
_ = Syscall.unlink(&temp_posix);
|
||||
|
||||
if (err == error.IsDir or err == error.EISDIR) {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
|
||||
} else {
|
||||
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to rename {s} to {s}: {s}", .{ temp_location, outfile, @errorName(err) }) catch "failed to rename file");
|
||||
}
|
||||
};
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn fromExecutable(allocator: std.mem.Allocator) !?StandaloneModuleGraph {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const Watcher = @This();
|
||||
|
||||
const DebugLogScope = bun.Output.Scoped(.watcher, false);
|
||||
const DebugLogScope = bun.Output.Scoped(.watcher, .visible);
|
||||
const log = DebugLogScope.log;
|
||||
|
||||
// This will always be [max_count]WatchEvent,
|
||||
@@ -32,7 +32,7 @@ ctx: *anyopaque,
|
||||
onFileUpdate: *const fn (this: *anyopaque, events: []WatchEvent, changed_files: []?[:0]u8, watchlist: WatchList) void,
|
||||
onError: *const fn (this: *anyopaque, err: bun.sys.Error) void,
|
||||
|
||||
thread_lock: bun.DebugThreadLock = bun.DebugThreadLock.unlocked,
|
||||
thread_lock: bun.safety.ThreadLock = .initUnlocked(),
|
||||
|
||||
pub const max_count = 128;
|
||||
pub const requires_file_descriptors = switch (Environment.os) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub const c_allocator = @import("./allocators/basic.zig").c_allocator;
|
||||
pub const z_allocator = @import("./allocators/basic.zig").z_allocator;
|
||||
pub const c_allocator = basic.c_allocator;
|
||||
pub const z_allocator = basic.z_allocator;
|
||||
pub const freeWithoutSize = basic.freeWithoutSize;
|
||||
pub const mimalloc = @import("./allocators/mimalloc.zig");
|
||||
pub const MimallocArena = @import("./allocators/MimallocArena.zig");
|
||||
pub const AllocationScope = @import("./allocators/AllocationScope.zig");
|
||||
@@ -225,7 +226,6 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
|
||||
}
|
||||
};
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
@@ -311,7 +311,6 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
|
||||
|
||||
return struct {
|
||||
pub const Overflow = OverflowList([]const u8, count / 4);
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
|
||||
backing_buf: [count * item_length]u8,
|
||||
@@ -495,7 +494,6 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
|
||||
pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_keys: bool, comptime estimated_key_length: usize, comptime remove_trailing_slashes: bool) type {
|
||||
const max_index = count - 1;
|
||||
const BSSMapType = struct {
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Self = @This();
|
||||
const Overflow = OverflowList(ValueType, count / 4);
|
||||
|
||||
@@ -772,8 +770,44 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isDefault(allocator: Allocator) bool {
|
||||
return allocator.vtable == c_allocator.vtable;
|
||||
}
|
||||
|
||||
/// Allocate memory for a value of type `T` using the provided allocator, and initialize the memory
|
||||
/// with `value`.
|
||||
///
|
||||
/// If `allocator` is `bun.default_allocator`, this will internally use `bun.tryNew` to benefit from
|
||||
/// the added assertions.
|
||||
pub fn create(comptime T: type, allocator: Allocator, value: T) OOM!*T {
|
||||
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
||||
return bun.tryNew(T, value);
|
||||
}
|
||||
const ptr = try allocator.create(T);
|
||||
ptr.* = value;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Free memory previously allocated by `create`.
|
||||
///
|
||||
/// The memory must have been allocated by the `create` function in this namespace, not
|
||||
/// directly by `allocator.create`.
|
||||
pub fn destroy(allocator: Allocator, ptr: anytype) void {
|
||||
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
||||
bun.destroy(ptr);
|
||||
} else {
|
||||
allocator.destroy(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
const basic = if (bun.use_mimalloc)
|
||||
@import("./allocators/basic.zig")
|
||||
else
|
||||
@import("./allocators/fallback.zig");
|
||||
|
||||
const Environment = @import("./env.zig");
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const bun = @import("bun");
|
||||
const OOM = bun.OOM;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const MemoryReportingAllocator = @This();
|
||||
|
||||
const log = bun.Output.scoped(.MEM, false);
|
||||
const log = bun.Output.scoped(.MEM, .visible);
|
||||
|
||||
child_allocator: std.mem.Allocator,
|
||||
memory_cost: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
|
||||
|
||||
@@ -1,29 +1,58 @@
|
||||
const Self = @This();
|
||||
|
||||
heap: ?*mimalloc.Heap = null,
|
||||
heap: HeapPtr,
|
||||
|
||||
const log = bun.Output.scoped(.mimalloc, true);
|
||||
const HeapPtr = if (safety_checks) *DebugHeap else *mimalloc.Heap;
|
||||
|
||||
const DebugHeap = struct {
|
||||
inner: *mimalloc.Heap,
|
||||
thread_lock: bun.safety.ThreadLock,
|
||||
};
|
||||
|
||||
fn getMimallocHeap(self: Self) *mimalloc.Heap {
|
||||
return if (comptime safety_checks) self.heap.inner else self.heap;
|
||||
}
|
||||
|
||||
fn fromOpaque(ptr: *anyopaque) Self {
|
||||
return .{ .heap = bun.cast(HeapPtr, ptr) };
|
||||
}
|
||||
|
||||
fn assertThreadLock(self: Self) void {
|
||||
if (comptime safety_checks) self.heap.thread_lock.assertLocked();
|
||||
}
|
||||
|
||||
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
|
||||
|
||||
fn getThreadHeap() HeapPtr {
|
||||
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
|
||||
if (thread_heap == null) {
|
||||
thread_heap = .{
|
||||
.inner = mimalloc.mi_heap_get_default(),
|
||||
.thread_lock = .initLocked(),
|
||||
};
|
||||
}
|
||||
return &thread_heap.?;
|
||||
}
|
||||
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
|
||||
/// Internally, mimalloc calls mi_heap_get_default()
|
||||
/// to get the default heap.
|
||||
/// It uses pthread_getspecific to do that.
|
||||
/// We can save those extra calls if we just do it once in here
|
||||
pub fn getThreadlocalDefault() Allocator {
|
||||
return Allocator{ .ptr = mimalloc.mi_heap_get_default(), .vtable = &c_allocator_vtable };
|
||||
pub fn getThreadLocalDefault() Allocator {
|
||||
return Allocator{ .ptr = getThreadHeap(), .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn backingAllocator(self: Self) Allocator {
|
||||
var arena = Self{ .heap = self.heap.?.backing() };
|
||||
return arena.allocator();
|
||||
pub fn backingAllocator(_: Self) Allocator {
|
||||
return getThreadLocalDefault();
|
||||
}
|
||||
|
||||
pub fn allocator(self: Self) Allocator {
|
||||
@setRuntimeSafety(false);
|
||||
return Allocator{ .ptr = self.heap.?, .vtable = &c_allocator_vtable };
|
||||
return Allocator{ .ptr = self.heap, .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn dumpThreadStats(self: *Self) void {
|
||||
_ = self;
|
||||
pub fn dumpThreadStats(_: *Self) void {
|
||||
const dump_fn = struct {
|
||||
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
|
||||
const text = bun.span(textZ);
|
||||
@@ -34,8 +63,7 @@ pub fn dumpThreadStats(self: *Self) void {
|
||||
bun.Output.flush();
|
||||
}
|
||||
|
||||
pub fn dumpStats(self: *Self) void {
|
||||
_ = self;
|
||||
pub fn dumpStats(_: *Self) void {
|
||||
const dump_fn = struct {
|
||||
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
|
||||
const text = bun.span(textZ);
|
||||
@@ -47,37 +75,51 @@ pub fn dumpStats(self: *Self) void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
mimalloc.mi_heap_destroy(bun.take(&self.heap).?);
|
||||
const mimalloc_heap = self.getMimallocHeap();
|
||||
if (comptime safety_checks) {
|
||||
bun.destroy(self.heap);
|
||||
}
|
||||
mimalloc.mi_heap_destroy(mimalloc_heap);
|
||||
self.* = undefined;
|
||||
}
|
||||
pub fn init() !Self {
|
||||
return .{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
|
||||
|
||||
pub fn init() Self {
|
||||
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
|
||||
const heap = if (comptime safety_checks)
|
||||
bun.new(DebugHeap, .{
|
||||
.inner = mimalloc_heap,
|
||||
.thread_lock = .initLocked(),
|
||||
})
|
||||
else
|
||||
mimalloc_heap;
|
||||
return .{ .heap = heap };
|
||||
}
|
||||
|
||||
pub fn gc(self: Self) void {
|
||||
mimalloc.mi_heap_collect(self.heap orelse return, false);
|
||||
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
|
||||
}
|
||||
|
||||
pub inline fn helpCatchMemoryIssues(self: Self) void {
|
||||
if (comptime FeatureFlags.help_catch_memory_issues) {
|
||||
if (comptime bun.FeatureFlags.help_catch_memory_issues) {
|
||||
self.gc();
|
||||
bun.mimalloc.mi_collect(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
|
||||
return mimalloc.mi_heap_check_owned(self.heap.?, ptr);
|
||||
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
|
||||
}
|
||||
pub const supports_posix_memalign = true;
|
||||
|
||||
fn alignedAlloc(heap: *mimalloc.Heap, len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
fn alignedAlloc(self: Self, len: usize, alignment: Alignment) ?[*]u8 {
|
||||
log("Malloc: {d}\n", .{len});
|
||||
|
||||
const heap = self.getMimallocHeap();
|
||||
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
|
||||
else
|
||||
mimalloc.mi_heap_malloc(heap, len);
|
||||
|
||||
if (comptime Environment.isDebug) {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
const usable = mimalloc.mi_malloc_usable_size(ptr);
|
||||
if (usable < len) {
|
||||
std.debug.panic("mimalloc: allocated size is too small: {d} < {d}", .{ usable, len });
|
||||
@@ -94,30 +136,28 @@ fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
return mimalloc.mi_malloc_usable_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc(arena: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
const self = bun.cast(*mimalloc.Heap, arena);
|
||||
|
||||
return alignedAlloc(
|
||||
self,
|
||||
len,
|
||||
alignment,
|
||||
);
|
||||
fn alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return alignedAlloc(self, len, alignment);
|
||||
}
|
||||
|
||||
fn resize(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
fn resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
fn free(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: mem.Alignment,
|
||||
alignment: Alignment,
|
||||
_: usize,
|
||||
) void {
|
||||
// mi_free_size internally just asserts the size
|
||||
// so it's faster if we don't pass that value through
|
||||
// but its good to have that assertion
|
||||
if (comptime Environment.isDebug) {
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
assert(mimalloc.mi_is_in_heap_region(buf.ptr));
|
||||
if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_free_size_aligned(buf.ptr, buf.len, alignment.toByteUnits())
|
||||
@@ -147,9 +187,12 @@ fn free(
|
||||
/// `ret_addr` is optionally provided as the first return address of the
|
||||
/// allocation call stack. If the value is `0` it means no return address
|
||||
/// has been provided.
|
||||
fn remap(self: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
fn remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
const self = fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
const heap = self.getMimallocHeap();
|
||||
const aligned_size = alignment.toByteUnits();
|
||||
const value = mimalloc.mi_heap_realloc_aligned(@ptrCast(self), buf.ptr, new_len, aligned_size);
|
||||
const value = mimalloc.mi_heap_realloc_aligned(heap, buf.ptr, new_len, aligned_size);
|
||||
return @ptrCast(value);
|
||||
}
|
||||
|
||||
@@ -164,13 +207,12 @@ const c_allocator_vtable = Allocator.VTable{
|
||||
.free = &Self.free,
|
||||
};
|
||||
|
||||
const Environment = @import("../env.zig");
|
||||
const FeatureFlags = @import("../feature_flags.zig");
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const assert = bun.assert;
|
||||
const mimalloc = bun.mimalloc;
|
||||
const safety_checks = bun.Environment.ci_assert;
|
||||
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const log = bun.Output.scoped(.mimalloc, true);
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
|
||||
fn mimalloc_free(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: mem.Alignment,
|
||||
alignment: Alignment,
|
||||
_: usize,
|
||||
) void {
|
||||
if (comptime Environment.enable_logs)
|
||||
@@ -23,8 +23,7 @@ fn mimalloc_free(
|
||||
}
|
||||
|
||||
const MimallocAllocator = struct {
|
||||
pub const supports_posix_memalign = true;
|
||||
fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 {
|
||||
if (comptime Environment.enable_logs)
|
||||
log("mi_alloc({d}, {d})", .{ len, alignment.toByteUnits() });
|
||||
|
||||
@@ -49,15 +48,15 @@ const MimallocAllocator = struct {
|
||||
return mimalloc.mi_malloc_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
return alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
return @ptrCast(mimalloc.mi_realloc_aligned(buf.ptr, new_len, alignment.toByteUnits()));
|
||||
}
|
||||
|
||||
@@ -77,9 +76,7 @@ const c_allocator_vtable = &Allocator.VTable{
|
||||
};
|
||||
|
||||
const ZAllocator = struct {
|
||||
pub const supports_posix_memalign = true;
|
||||
|
||||
fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 {
|
||||
fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 {
|
||||
log("ZAllocator.alignedAlloc: {d}\n", .{len});
|
||||
|
||||
const ptr = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
@@ -103,11 +100,11 @@ const ZAllocator = struct {
|
||||
return mimalloc.mi_malloc_size(ptr);
|
||||
}
|
||||
|
||||
fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
|
||||
fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
return alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
|
||||
fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
if (new_len <= buf.len) {
|
||||
return true;
|
||||
}
|
||||
@@ -138,15 +135,20 @@ pub const z_allocator = Allocator{
|
||||
const z_allocator_vtable = Allocator.VTable{
|
||||
.alloc = &ZAllocator.alloc_with_z_allocator,
|
||||
.resize = &ZAllocator.resize_with_z_allocator,
|
||||
.remap = &std.mem.Allocator.noRemap,
|
||||
.remap = &Allocator.noRemap,
|
||||
.free = &ZAllocator.free_with_z_allocator,
|
||||
};
|
||||
|
||||
/// mimalloc can free allocations without being given their size.
|
||||
pub fn freeWithoutSize(ptr: ?*anyopaque) void {
|
||||
mimalloc.mi_free(ptr);
|
||||
}
|
||||
|
||||
const Environment = @import("../env.zig");
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const mimalloc = bun.mimalloc;
|
||||
|
||||
const mem = @import("std").mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
9
src/allocators/fallback.zig
Normal file
9
src/allocators/fallback.zig
Normal file
@@ -0,0 +1,9 @@
|
||||
pub const c_allocator = std.heap.c_allocator;
|
||||
pub const z_allocator = @import("./fallback/z.zig").allocator;
|
||||
|
||||
/// libc can free allocations without being given their size.
|
||||
pub fn freeWithoutSize(ptr: ?*anyopaque) void {
|
||||
std.c.free(ptr);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
43
src/allocators/fallback/z.zig
Normal file
43
src/allocators/fallback/z.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
/// A fallback zero-initializing allocator.
|
||||
pub const allocator = Allocator{
|
||||
.ptr = undefined,
|
||||
.vtable = &vtable,
|
||||
};
|
||||
|
||||
const vtable = Allocator.VTable{
|
||||
.alloc = alloc,
|
||||
.resize = resize,
|
||||
.remap = Allocator.noRemap, // the mimalloc z_allocator doesn't support remap
|
||||
.free = free,
|
||||
};
|
||||
|
||||
fn alloc(_: *anyopaque, len: usize, alignment: Alignment, return_address: usize) ?[*]u8 {
|
||||
const result = c_allocator.rawAlloc(len, alignment, return_address) orelse
|
||||
return null;
|
||||
@memset(result[0..len], 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn resize(
|
||||
_: *anyopaque,
|
||||
buf: []u8,
|
||||
alignment: Alignment,
|
||||
new_len: usize,
|
||||
return_address: usize,
|
||||
) bool {
|
||||
if (!c_allocator.rawResize(buf, alignment, new_len, return_address)) {
|
||||
return false;
|
||||
}
|
||||
@memset(buf.ptr[buf.len..new_len], 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn free(_: *anyopaque, buf: []u8, alignment: Alignment, return_address: usize) void {
|
||||
c_allocator.rawFree(buf, alignment, return_address);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const c_allocator = std.heap.c_allocator;
|
||||
|
||||
const Alignment = std.mem.Alignment;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -52,10 +52,6 @@ pub const Heap = opaque {
|
||||
return mi_heap_malloc(self, size);
|
||||
}
|
||||
|
||||
pub fn backing(_: *Heap) *Heap {
|
||||
return mi_heap_get_default();
|
||||
}
|
||||
|
||||
pub fn calloc(self: *Heap, count: usize, size: usize) ?*anyopaque {
|
||||
return mi_heap_calloc(self, count, size);
|
||||
}
|
||||
@@ -140,27 +136,45 @@ pub const Option = enum(c_uint) {
|
||||
show_stats = 1,
|
||||
verbose = 2,
|
||||
eager_commit = 3,
|
||||
deprecated_eager_region_commit = 4,
|
||||
deprecated_reset_decommits = 5,
|
||||
large_os_pages = 6,
|
||||
arena_eager_commit = 4,
|
||||
purge_decommits = 5,
|
||||
allow_large_os_pages = 6,
|
||||
reserve_huge_os_pages = 7,
|
||||
reserve_huge_os_pages_at = 8,
|
||||
reserve_os_memory = 9,
|
||||
deprecated_segment_cache = 10,
|
||||
page_reset = 11,
|
||||
abandoned_page_decommit = 12,
|
||||
deprecated_page_reset = 11,
|
||||
abandoned_page_purge = 12,
|
||||
deprecated_segment_reset = 13,
|
||||
eager_commit_delay = 14,
|
||||
decommit_delay = 15,
|
||||
purge_delay = 15,
|
||||
use_numa_nodes = 16,
|
||||
limit_os_alloc = 17,
|
||||
disallow_os_alloc = 17,
|
||||
os_tag = 18,
|
||||
max_errors = 19,
|
||||
max_warnings = 20,
|
||||
max_segment_reclaim = 21,
|
||||
allow_decommit = 22,
|
||||
segment_decommit_delay = 23,
|
||||
decommit_extend_delay = 24,
|
||||
deprecated_max_segment_reclaim = 21,
|
||||
destroy_on_exit = 22,
|
||||
arena_reserve = 23,
|
||||
arena_purge_mult = 24,
|
||||
deprecated_purge_extend_delay = 25,
|
||||
disallow_arena_alloc = 26,
|
||||
retry_on_oom = 27,
|
||||
visit_abandoned = 28,
|
||||
guarded_min = 29,
|
||||
guarded_max = 30,
|
||||
guarded_precise = 31,
|
||||
guarded_sample_rate = 32,
|
||||
guarded_sample_seed = 33,
|
||||
generic_collect = 34,
|
||||
page_reclaim_on_free = 35,
|
||||
page_full_retain = 36,
|
||||
page_max_candidates = 37,
|
||||
max_vabits = 38,
|
||||
pagemap_commit = 39,
|
||||
page_commit_on_demand = 40,
|
||||
page_max_reclaim = 41,
|
||||
page_cross_thread_max_reclaim = 42,
|
||||
};
|
||||
pub extern fn mi_option_is_enabled(option: Option) bool;
|
||||
pub extern fn mi_option_enable(option: Option) void;
|
||||
|
||||
@@ -111,6 +111,7 @@ pub const Features = struct {
|
||||
pub var csrf_generate: usize = 0;
|
||||
pub var unsupported_uv_function: usize = 0;
|
||||
pub var exited: usize = 0;
|
||||
pub var yarn_migration: usize = 0;
|
||||
|
||||
comptime {
|
||||
@export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" });
|
||||
|
||||
526
src/ast/ConvertESMExportsForHmr.zig
Normal file
526
src/ast/ConvertESMExportsForHmr.zig
Normal file
@@ -0,0 +1,526 @@
|
||||
last_part: *js_ast.Part,
|
||||
// files in node modules will not get hot updates, so the code generation
|
||||
// can be a bit more concise for re-exports
|
||||
is_in_node_modules: bool,
|
||||
imports_seen: bun.StringArrayHashMapUnmanaged(ImportRef) = .{},
|
||||
export_star_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
export_props: std.ArrayListUnmanaged(G.Property) = .{},
|
||||
stmts: std.ArrayListUnmanaged(Stmt) = .{},
|
||||
|
||||
const ImportRef = struct {
|
||||
/// Index into ConvertESMExportsForHmr.stmts
|
||||
stmt_index: u32,
|
||||
};
|
||||
|
||||
pub fn convertStmt(ctx: *ConvertESMExportsForHmr, p: anytype, stmt: Stmt) !void {
|
||||
const new_stmt = switch (stmt.data) {
|
||||
else => brk: {
|
||||
break :brk stmt;
|
||||
},
|
||||
.s_local => |st| stmt: {
|
||||
if (!st.is_export) {
|
||||
break :stmt stmt;
|
||||
}
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
var new_len: usize = 0;
|
||||
for (st.decls.slice()) |*decl_ptr| {
|
||||
const decl = decl_ptr.*; // explicit copy to avoid aliasinng
|
||||
const value = decl.value orelse {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
continue;
|
||||
};
|
||||
|
||||
switch (decl.binding.data) {
|
||||
.b_missing => {},
|
||||
|
||||
.b_identifier => |id| {
|
||||
const symbol = p.symbols.items[id.ref.inner_index];
|
||||
|
||||
// if the symbol is not used, we don't need to preserve
|
||||
// a binding in this scope. we can move it to the exports object.
|
||||
if (symbol.use_count_estimate == 0 and value.canBeMoved()) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = symbol.original_name }, decl.binding.loc),
|
||||
.value = value,
|
||||
});
|
||||
} else {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
st.decls.mut(new_len).* = decl;
|
||||
new_len += 1;
|
||||
try ctx.visitBindingToExport(p, decl.binding);
|
||||
},
|
||||
}
|
||||
}
|
||||
if (new_len == 0) {
|
||||
return;
|
||||
}
|
||||
st.decls.len = @intCast(new_len);
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_default => |st| stmt: {
|
||||
// When React Fast Refresh needs to tag the default export, the statement
|
||||
// cannot be moved, since a local reference is required.
|
||||
if (p.options.features.react_fast_refresh and
|
||||
st.value == .stmt and st.value.stmt.data == .s_function)
|
||||
fast_refresh_edge_case: {
|
||||
const symbol = st.value.stmt.data.s_function.func.name orelse
|
||||
break :fast_refresh_edge_case;
|
||||
const name = p.symbols.items[symbol.ref.?.inner_index].original_name;
|
||||
if (ReactRefresh.isComponentishName(name)) {
|
||||
// Lower to a function statement, and reference the function in the export list.
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(symbol.ref.?, stmt.loc),
|
||||
});
|
||||
break :stmt st.value.stmt;
|
||||
}
|
||||
// All other functions can be properly moved.
|
||||
}
|
||||
|
||||
// Try to move the export default expression to the end.
|
||||
const can_be_moved_to_inner_scope = switch (st.value) {
|
||||
.stmt => |s| switch (s.data) {
|
||||
.s_class => |c| c.class.canBeMoved() and (if (c.class.class_name) |name|
|
||||
p.symbols.items[name.ref.?.inner_index].use_count_estimate == 0
|
||||
else
|
||||
true),
|
||||
.s_function => |f| if (f.func.name) |name|
|
||||
p.symbols.items[name.ref.?.inner_index].use_count_estimate == 0
|
||||
else
|
||||
true,
|
||||
else => unreachable,
|
||||
},
|
||||
.expr => |e| switch (e.data) {
|
||||
.e_identifier => true,
|
||||
else => e.canBeMoved(),
|
||||
},
|
||||
};
|
||||
if (can_be_moved_to_inner_scope) {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
});
|
||||
// no statement emitted
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, an identifier must be exported
|
||||
switch (st.value) {
|
||||
.expr => {
|
||||
const temp_id = p.generateTempRef("default_export");
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = temp_id, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, temp_id, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, temp_id);
|
||||
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(temp_id, stmt.loc),
|
||||
});
|
||||
|
||||
break :stmt Stmt.alloc(S.Local, .{
|
||||
.kind = .k_const,
|
||||
.decls = try G.Decl.List.fromSlice(p.allocator, &.{
|
||||
.{
|
||||
.binding = Binding.alloc(p.allocator, B.Identifier{ .ref = temp_id }, stmt.loc),
|
||||
.value = st.value.toExpr(),
|
||||
},
|
||||
}),
|
||||
}, stmt.loc);
|
||||
},
|
||||
.stmt => |s| {
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = "default" }, stmt.loc),
|
||||
.value = Expr.initIdentifier(switch (s.data) {
|
||||
.s_class => |class| class.class.class_name.?.ref.?,
|
||||
.s_function => |func| func.func.name.?.ref.?,
|
||||
else => unreachable,
|
||||
}, stmt.loc),
|
||||
});
|
||||
break :stmt s;
|
||||
},
|
||||
}
|
||||
},
|
||||
.s_class => |st| stmt: {
|
||||
|
||||
// Strip the "export" keyword
|
||||
if (!st.is_export) {
|
||||
break :stmt stmt;
|
||||
}
|
||||
|
||||
// Export as CommonJS
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = p.symbols.items[st.class.class_name.?.ref.?.inner_index].original_name,
|
||||
}, stmt.loc),
|
||||
.value = Expr.initIdentifier(st.class.class_name.?.ref.?, stmt.loc),
|
||||
});
|
||||
|
||||
st.is_export = false;
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_function => |st| stmt: {
|
||||
// Strip the "export" keyword
|
||||
if (!st.func.flags.contains(.is_export)) break :stmt stmt;
|
||||
|
||||
st.func.flags.remove(.is_export);
|
||||
|
||||
try ctx.visitRefToExport(
|
||||
p,
|
||||
st.func.name.?.ref.?,
|
||||
null,
|
||||
stmt.loc,
|
||||
false,
|
||||
);
|
||||
|
||||
break :stmt stmt;
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref.?;
|
||||
try ctx.visitRefToExport(p, ref, item.alias, item.name.loc, false);
|
||||
}
|
||||
|
||||
return; // do not emit a statement here
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
st.items,
|
||||
stmt.loc,
|
||||
null,
|
||||
stmt.loc,
|
||||
);
|
||||
for (st.items) |*item| {
|
||||
const ref = item.name.ref.?;
|
||||
const symbol = &p.symbols.items[ref.innerIndex()];
|
||||
if (symbol.namespace_alias == null) {
|
||||
symbol.namespace_alias = .{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.original_name,
|
||||
.import_record_index = st.import_record_index,
|
||||
};
|
||||
}
|
||||
try ctx.visitRefToExport(
|
||||
p,
|
||||
ref,
|
||||
item.alias,
|
||||
item.name.loc,
|
||||
!ctx.is_in_node_modules, // live binding when this may be replaced
|
||||
);
|
||||
|
||||
// imports and export statements have their alias +
|
||||
// original_name swapped. this is likely a design bug in
|
||||
// the parser but since everything uses these
|
||||
// assumptions, this hack is simpler than making it
|
||||
// proper
|
||||
const alias = item.alias;
|
||||
item.alias = item.original_name;
|
||||
item.original_name = alias;
|
||||
}
|
||||
return;
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
const namespace_ref = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
&.{},
|
||||
stmt.loc,
|
||||
null,
|
||||
stmt.loc,
|
||||
);
|
||||
|
||||
if (st.alias) |alias| {
|
||||
// 'export * as ns from' creates one named property.
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{ .data = alias.original_name }, stmt.loc),
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
});
|
||||
} else {
|
||||
// 'export * from' creates a spread, hoisted at the top.
|
||||
try ctx.export_star_props.append(p.allocator, .{
|
||||
.kind = .spread,
|
||||
.value = Expr.initIdentifier(namespace_ref, stmt.loc),
|
||||
});
|
||||
}
|
||||
return;
|
||||
},
|
||||
// De-duplicate import statements. It is okay to disregard
|
||||
// named/default imports here as we always rewrite them as
|
||||
// full qualified property accesses (needed for live-bindings)
|
||||
.s_import => |st| {
|
||||
_ = try ctx.deduplicatedImport(
|
||||
p,
|
||||
st.import_record_index,
|
||||
st.namespace_ref,
|
||||
st.items,
|
||||
st.star_name_loc,
|
||||
st.default_name,
|
||||
stmt.loc,
|
||||
);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
try ctx.stmts.append(p.allocator, new_stmt);
|
||||
}
|
||||
|
||||
/// Deduplicates imports, returning a previously used Ref if present.
|
||||
fn deduplicatedImport(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
import_record_index: u32,
|
||||
namespace_ref: Ref,
|
||||
items: []js_ast.ClauseItem,
|
||||
star_name_loc: ?logger.Loc,
|
||||
default_name: ?js_ast.LocRef,
|
||||
loc: logger.Loc,
|
||||
) !Ref {
|
||||
const ir = &p.import_records.items[import_record_index];
|
||||
const gop = try ctx.imports_seen.getOrPut(p.allocator, ir.path.text);
|
||||
if (gop.found_existing) {
|
||||
// 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;
|
||||
|
||||
const stmt = ctx.stmts.items[gop.value_ptr.stmt_index].data.s_import;
|
||||
if (items.len > 0) {
|
||||
if (stmt.items.len == 0) {
|
||||
stmt.items = items;
|
||||
} else {
|
||||
stmt.items = try std.mem.concat(p.allocator, js_ast.ClauseItem, &.{ stmt.items, items });
|
||||
}
|
||||
}
|
||||
if (namespace_ref.isValid()) {
|
||||
if (!stmt.namespace_ref.isValid()) {
|
||||
stmt.namespace_ref = namespace_ref;
|
||||
return namespace_ref;
|
||||
} else {
|
||||
// Erase this namespace ref, but since it may be used in
|
||||
// existing AST trees, a link must be established.
|
||||
const symbol = &p.symbols.items[namespace_ref.innerIndex()];
|
||||
symbol.use_count_estimate = 0;
|
||||
symbol.link = stmt.namespace_ref;
|
||||
if (@hasField(@typeInfo(@TypeOf(p)).pointer.child, "symbol_uses")) {
|
||||
_ = p.symbol_uses.swapRemove(namespace_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stmt.star_name_loc == null) if (star_name_loc) |stl| {
|
||||
stmt.star_name_loc = stl;
|
||||
};
|
||||
if (stmt.default_name == null) if (default_name) |dn| {
|
||||
stmt.default_name = dn;
|
||||
};
|
||||
return stmt.namespace_ref;
|
||||
}
|
||||
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.Import, .{
|
||||
.import_record_index = import_record_index,
|
||||
.is_single_line = true,
|
||||
.default_name = default_name,
|
||||
.items = items,
|
||||
.namespace_ref = namespace_ref,
|
||||
.star_name_loc = star_name_loc,
|
||||
}, loc));
|
||||
|
||||
gop.value_ptr.* = .{ .stmt_index = @intCast(ctx.stmts.items.len - 1) };
|
||||
return namespace_ref;
|
||||
}
|
||||
|
||||
fn visitBindingToExport(ctx: *ConvertESMExportsForHmr, p: anytype, binding: Binding) !void {
|
||||
switch (binding.data) {
|
||||
.b_missing => {},
|
||||
.b_identifier => |id| {
|
||||
try ctx.visitRefToExport(p, id.ref, null, binding.loc, false);
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
try ctx.visitBindingToExport(p, item.binding);
|
||||
}
|
||||
},
|
||||
.b_object => |object| {
|
||||
for (object.properties) |item| {
|
||||
try ctx.visitBindingToExport(p, item.value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visitRefToExport(
|
||||
ctx: *ConvertESMExportsForHmr,
|
||||
p: anytype,
|
||||
ref: Ref,
|
||||
export_symbol_name: ?[]const u8,
|
||||
loc: logger.Loc,
|
||||
is_live_binding_source: bool,
|
||||
) !void {
|
||||
const symbol = p.symbols.items[ref.inner_index];
|
||||
const id = if (symbol.kind == .import)
|
||||
Expr.init(E.ImportIdentifier, .{ .ref = ref }, loc)
|
||||
else
|
||||
Expr.initIdentifier(ref, loc);
|
||||
if (is_live_binding_source or (symbol.kind == .import and !ctx.is_in_node_modules) or symbol.has_been_assigned_to) {
|
||||
// TODO (2024-11-24) instead of requiring getters for live-bindings,
|
||||
// a callback propagation system should be considered. mostly
|
||||
// because here, these might not even be live bindings, and
|
||||
// re-exports are so, so common.
|
||||
//
|
||||
// update(2025-03-05): HMRModule in ts now contains an exhaustive map
|
||||
// of importers. For local live bindings, these can just remember to
|
||||
// mutate the field in the exports object. Re-exports can just be
|
||||
// encoded into the module format, propagated in `replaceModules`
|
||||
const key = Expr.init(E.String, .{
|
||||
.data = export_symbol_name orelse symbol.original_name,
|
||||
}, loc);
|
||||
|
||||
// This is technically incorrect in that we've marked this as a
|
||||
// top level symbol. but all we care about is preventing name
|
||||
// collisions, not necessarily the best minificaiton (dev only)
|
||||
const arg1 = p.generateTempRef(symbol.original_name);
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = arg1, .is_top_level = true });
|
||||
try ctx.last_part.symbol_uses.putNoClobber(p.allocator, arg1, .{ .count_estimate = 1 });
|
||||
try p.current_scope.generated.push(p.allocator, arg1);
|
||||
|
||||
// 'get abc() { return abc }'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.kind = .get,
|
||||
.key = key,
|
||||
.value = Expr.init(E.Function, .{ .func = .{
|
||||
.body = .{
|
||||
.stmts = try p.allocator.dupe(Stmt, &.{
|
||||
Stmt.alloc(S.Return, .{ .value = id }, loc),
|
||||
}),
|
||||
.loc = loc,
|
||||
},
|
||||
} }, loc),
|
||||
});
|
||||
// no setter is added since live bindings are read-only
|
||||
} else {
|
||||
// 'abc,'
|
||||
try ctx.export_props.append(p.allocator, .{
|
||||
.key = Expr.init(E.String, .{
|
||||
.data = export_symbol_name orelse symbol.original_name,
|
||||
}, loc),
|
||||
.value = id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(ctx: *ConvertESMExportsForHmr, p: anytype, all_parts: []js_ast.Part) !void {
|
||||
if (ctx.export_star_props.items.len > 0) {
|
||||
if (ctx.export_props.items.len == 0) {
|
||||
ctx.export_props = ctx.export_star_props;
|
||||
} else {
|
||||
const export_star_len = ctx.export_star_props.items.len;
|
||||
try ctx.export_props.ensureUnusedCapacity(p.allocator, export_star_len);
|
||||
const len = ctx.export_props.items.len;
|
||||
ctx.export_props.items.len += export_star_len;
|
||||
bun.copy(G.Property, ctx.export_props.items[export_star_len..], ctx.export_props.items[0..len]);
|
||||
@memcpy(ctx.export_props.items[0..export_star_len], ctx.export_star_props.items);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.export_props.items.len > 0) {
|
||||
const obj = Expr.init(E.Object, .{
|
||||
.properties = G.Property.List.fromList(ctx.export_props),
|
||||
}, logger.Loc.Empty);
|
||||
|
||||
// `hmr.exports = ...`
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.assign(
|
||||
Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.hmr_api_ref, logger.Loc.Empty),
|
||||
.name = "exports",
|
||||
.name_loc = logger.Loc.Empty,
|
||||
}, logger.Loc.Empty),
|
||||
obj,
|
||||
),
|
||||
}, logger.Loc.Empty));
|
||||
|
||||
// mark a dependency on module_ref so it is renamed
|
||||
try ctx.last_part.symbol_uses.put(p.allocator, p.module_ref, .{ .count_estimate = 1 });
|
||||
try ctx.last_part.declared_symbols.append(p.allocator, .{ .ref = p.module_ref, .is_top_level = true });
|
||||
}
|
||||
|
||||
if (p.options.features.react_fast_refresh and p.react_refresh.register_used) {
|
||||
try ctx.stmts.append(p.allocator, Stmt.alloc(S.SExpr, .{
|
||||
.value = Expr.init(E.Call, .{
|
||||
.target = Expr.init(E.Dot, .{
|
||||
.target = Expr.initIdentifier(p.hmr_api_ref, .Empty),
|
||||
.name = "reactRefreshAccept",
|
||||
.name_loc = .Empty,
|
||||
}, .Empty),
|
||||
.args = .init(&.{}),
|
||||
}, .Empty),
|
||||
}, .Empty));
|
||||
}
|
||||
|
||||
// Merge all part metadata into the first part.
|
||||
for (all_parts[0 .. all_parts.len - 1]) |*part| {
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, part.declared_symbols);
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, part.import_record_indices.slice());
|
||||
for (part.symbol_uses.keys(), part.symbol_uses.values()) |k, v| {
|
||||
const gop = try ctx.last_part.symbol_uses.getOrPut(p.allocator, k);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = v;
|
||||
} else {
|
||||
gop.value_ptr.count_estimate += v.count_estimate;
|
||||
}
|
||||
}
|
||||
part.stmts = &.{};
|
||||
part.declared_symbols.entries.len = 0;
|
||||
part.tag = .dead_due_to_inlining;
|
||||
part.dependencies.clearRetainingCapacity();
|
||||
try part.dependencies.push(p.allocator, .{
|
||||
.part_index = @intCast(all_parts.len - 1),
|
||||
.source_index = p.source.index,
|
||||
});
|
||||
}
|
||||
|
||||
try ctx.last_part.import_record_indices.append(p.allocator, p.import_records_for_current_part.items);
|
||||
try ctx.last_part.declared_symbols.appendList(p.allocator, p.declared_symbols);
|
||||
|
||||
ctx.last_part.stmts = ctx.stmts.items;
|
||||
ctx.last_part.tag = .none;
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const logger = bun.logger;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const B = js_ast.B;
|
||||
const Binding = js_ast.Binding;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
const Property = G.Property;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ConvertESMExportsForHmr = js_parser.ConvertESMExportsForHmr;
|
||||
const ReactRefresh = js_parser.ReactRefresh;
|
||||
const Ref = js_parser.Ref;
|
||||
const options = js_parser.options;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
@@ -19,7 +19,7 @@ pub fn clone(this: Expr, allocator: std.mem.Allocator) !Expr {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deepClone(this: Expr, allocator: std.mem.Allocator) anyerror!Expr {
|
||||
pub fn deepClone(this: Expr, allocator: std.mem.Allocator) OOM!Expr {
|
||||
return .{
|
||||
.loc = this.loc,
|
||||
.data = try this.data.deepClone(allocator),
|
||||
|
||||
530
src/ast/ImportScanner.zig
Normal file
530
src/ast/ImportScanner.zig
Normal file
@@ -0,0 +1,530 @@
|
||||
stmts: []Stmt = &.{},
|
||||
kept_import_equals: bool = false,
|
||||
removed_import_equals: bool = false,
|
||||
|
||||
pub fn scan(
|
||||
comptime P: type,
|
||||
p: *P,
|
||||
stmts: []Stmt,
|
||||
will_transform_to_common_js: bool,
|
||||
comptime hot_module_reloading_transformations: bool,
|
||||
hot_module_reloading_context: if (hot_module_reloading_transformations) *ConvertESMExportsForHmr else void,
|
||||
) !ImportScanner {
|
||||
var scanner = ImportScanner{};
|
||||
var stmts_end: usize = 0;
|
||||
const allocator = p.allocator;
|
||||
const is_typescript_enabled: bool = comptime P.parser_features.typescript;
|
||||
|
||||
for (stmts) |_stmt| {
|
||||
var stmt = _stmt; // copy
|
||||
switch (stmt.data) {
|
||||
.s_import => |import_ptr| {
|
||||
var st = import_ptr.*;
|
||||
defer import_ptr.* = st;
|
||||
|
||||
const record: *ImportRecord = &p.import_records.items[st.import_record_index];
|
||||
|
||||
if (record.path.isMacro()) {
|
||||
record.is_unused = true;
|
||||
record.path.is_disabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The official TypeScript compiler always removes unused imported
|
||||
// symbols. However, we deliberately deviate from the official
|
||||
// TypeScript compiler's behavior doing this in a specific scenario:
|
||||
// we are not bundling, symbol renaming is off, and the tsconfig.json
|
||||
// "importsNotUsedAsValues" setting is present and is not set to
|
||||
// "remove".
|
||||
//
|
||||
// This exists to support the use case of compiling partial modules for
|
||||
// compile-to-JavaScript languages such as Svelte. These languages try
|
||||
// to reference imports in ways that are impossible for esbuild to know
|
||||
// about when esbuild is only given a partial module to compile. Here
|
||||
// is an example of some Svelte code that might use esbuild to convert
|
||||
// TypeScript to JavaScript:
|
||||
//
|
||||
// <script lang="ts">
|
||||
// import Counter from './Counter.svelte';
|
||||
// export let name: string = 'world';
|
||||
// </script>
|
||||
// <main>
|
||||
// <h1>Hello {name}!</h1>
|
||||
// <Counter />
|
||||
// </main>
|
||||
//
|
||||
// Tools that use esbuild to compile TypeScript code inside a Svelte
|
||||
// file like this only give esbuild the contents of the <script> tag.
|
||||
// These tools work around this missing import problem when using the
|
||||
// official TypeScript compiler by hacking the TypeScript AST to
|
||||
// remove the "unused import" flags. This isn't possible in esbuild
|
||||
// because esbuild deliberately does not expose an AST manipulation
|
||||
// API for performance reasons.
|
||||
//
|
||||
// We deviate from the TypeScript compiler's behavior in this specific
|
||||
// case because doing so is useful for these compile-to-JavaScript
|
||||
// languages and is benign in other cases. The rationale is as follows:
|
||||
//
|
||||
// * If "importsNotUsedAsValues" is absent or set to "remove", then
|
||||
// we don't know if these imports are values or types. It's not
|
||||
// safe to keep them because if they are types, the missing imports
|
||||
// will cause run-time failures because there will be no matching
|
||||
// exports. It's only safe keep imports if "importsNotUsedAsValues"
|
||||
// is set to "preserve" or "error" because then we can assume that
|
||||
// none of the imports are types (since the TypeScript compiler
|
||||
// would generate an error in that case).
|
||||
//
|
||||
// * If we're bundling, then we know we aren't being used to compile
|
||||
// a partial module. The parser is seeing the entire code for the
|
||||
// module so it's safe to remove unused imports. And also we don't
|
||||
// want the linker to generate errors about missing imports if the
|
||||
// imported file is also in the bundle.
|
||||
//
|
||||
// * If identifier minification is enabled, then using esbuild as a
|
||||
// partial-module transform library wouldn't work anyway because
|
||||
// the names wouldn't match. And that means we're minifying so the
|
||||
// user is expecting the output to be as small as possible. So we
|
||||
// should omit unused imports.
|
||||
//
|
||||
var did_remove_star_loc = false;
|
||||
const keep_unused_imports = !p.options.features.trim_unused_imports;
|
||||
// TypeScript always trims unused imports. This is important for
|
||||
// correctness since some imports might be fake (only in the type
|
||||
// system and used for type-only imports).
|
||||
if (!keep_unused_imports) {
|
||||
var found_imports = false;
|
||||
var is_unused_in_typescript = true;
|
||||
|
||||
if (st.default_name) |default_name| {
|
||||
found_imports = true;
|
||||
const symbol = p.symbols.items[default_name.ref.?.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[default_name.ref.?.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate == 0) {
|
||||
st.default_name = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the star import if it's unused
|
||||
if (st.star_name_loc) |_| {
|
||||
found_imports = true;
|
||||
const symbol = p.symbols.items[st.namespace_ref.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[st.namespace_ref.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate == 0) {
|
||||
// Make sure we don't remove this if it was used for a property
|
||||
// access while bundling
|
||||
var has_any = false;
|
||||
|
||||
if (p.import_items_for_namespace.get(st.namespace_ref)) |entry| {
|
||||
if (entry.count() > 0) {
|
||||
has_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_any) {
|
||||
st.star_name_loc = null;
|
||||
did_remove_star_loc = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove items if they are unused
|
||||
if (st.items.len > 0) {
|
||||
found_imports = true;
|
||||
var items_end: usize = 0;
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref.?;
|
||||
const symbol: Symbol = p.symbols.items[ref.innerIndex()];
|
||||
|
||||
// TypeScript has a separate definition of unused
|
||||
if (is_typescript_enabled and p.ts_use_counts.items[ref.innerIndex()] != 0) {
|
||||
is_unused_in_typescript = false;
|
||||
}
|
||||
|
||||
// Remove the symbol if it's never used outside a dead code region
|
||||
if (symbol.use_count_estimate != 0) {
|
||||
st.items[items_end] = item;
|
||||
items_end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
st.items = st.items[0..items_end];
|
||||
}
|
||||
|
||||
// -- Original Comment --
|
||||
// Omit this statement if we're parsing TypeScript and all imports are
|
||||
// unused. Note that this is distinct from the case where there were
|
||||
// no imports at all (e.g. "import 'foo'"). In that case we want to keep
|
||||
// the statement because the user is clearly trying to import the module
|
||||
// for side effects.
|
||||
//
|
||||
// This culling is important for correctness when parsing TypeScript
|
||||
// because a) the TypeScript compiler does this and we want to match it
|
||||
// and b) this may be a fake module that only exists in the type system
|
||||
// and doesn't actually exist in reality.
|
||||
//
|
||||
// We do not want to do this culling in JavaScript though because the
|
||||
// module may have side effects even if all imports are unused.
|
||||
// -- Original Comment --
|
||||
|
||||
// jarred: I think, in this project, we want this behavior, even in JavaScript.
|
||||
// I think this would be a big performance improvement.
|
||||
// The less you import, the less code you transpile.
|
||||
// Side-effect imports are nearly always done through identifier-less imports
|
||||
// e.g. `import 'fancy-stylesheet-thing/style.css';`
|
||||
// This is a breaking change though. We can make it an option with some guardrail
|
||||
// so maybe if it errors, it shows a suggestion "retry without trimming unused imports"
|
||||
if ((is_typescript_enabled and found_imports and is_unused_in_typescript and !p.options.preserve_unused_imports_ts) or
|
||||
(!is_typescript_enabled and p.options.features.trim_unused_imports and found_imports and st.star_name_loc == null and st.items.len == 0 and st.default_name == null))
|
||||
{
|
||||
// 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;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const namespace_ref = st.namespace_ref;
|
||||
const convert_star_to_clause = !p.options.bundle and (p.symbols.items[namespace_ref.innerIndex()].use_count_estimate == 0);
|
||||
|
||||
if (convert_star_to_clause and !keep_unused_imports) {
|
||||
st.star_name_loc = null;
|
||||
}
|
||||
|
||||
record.contains_default_alias = record.contains_default_alias or st.default_name != null;
|
||||
|
||||
const existing_items: ImportItemForNamespaceMap = p.import_items_for_namespace.get(namespace_ref) orelse
|
||||
ImportItemForNamespaceMap.init(allocator);
|
||||
|
||||
if (p.options.bundle) {
|
||||
if (st.star_name_loc != null and existing_items.count() > 0) {
|
||||
const sorted = try allocator.alloc(string, existing_items.count());
|
||||
defer allocator.free(sorted);
|
||||
for (sorted, existing_items.keys()) |*result, alias| {
|
||||
result.* = alias;
|
||||
}
|
||||
strings.sortDesc(sorted);
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, sorted.len) catch bun.outOfMemory();
|
||||
|
||||
// Create named imports for these property accesses. This will
|
||||
// cause missing imports to generate useful warnings.
|
||||
//
|
||||
// It will also improve bundling efficiency for internal imports
|
||||
// by still converting property accesses off the namespace into
|
||||
// bare identifiers even if the namespace is still needed.
|
||||
for (sorted) |alias| {
|
||||
const item = existing_items.get(alias).?;
|
||||
p.named_imports.put(
|
||||
p.allocator,
|
||||
item.ref.?,
|
||||
js_ast.NamedImport{
|
||||
.alias = alias,
|
||||
.alias_loc = item.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
const name: LocRef = item;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
// Make sure the printer prints this as a property access
|
||||
var symbol: *Symbol = &p.symbols.items[name_ref.innerIndex()];
|
||||
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = alias,
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name),
|
||||
};
|
||||
|
||||
// Also record these automatically-generated top-level namespace alias symbols
|
||||
p.declared_symbols.append(p.allocator, .{
|
||||
.ref = name_ref,
|
||||
.is_top_level = true,
|
||||
}) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
p.named_imports.ensureUnusedCapacity(
|
||||
p.allocator,
|
||||
st.items.len + @as(usize, @intFromBool(st.default_name != null)) + @as(usize, @intFromBool(st.star_name_loc != null)),
|
||||
) catch bun.outOfMemory();
|
||||
|
||||
if (st.star_name_loc) |loc| {
|
||||
record.contains_import_star = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
namespace_ref,
|
||||
js_ast.NamedImport{
|
||||
.alias_is_star = true,
|
||||
.alias = "",
|
||||
.alias_loc = loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (st.default_name) |default| {
|
||||
record.contains_default_alias = true;
|
||||
p.named_imports.putAssumeCapacity(
|
||||
default.ref.?,
|
||||
.{
|
||||
.alias = "default",
|
||||
.alias_loc = default.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (st.items) |item| {
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
p.named_imports.putAssumeCapacity(
|
||||
name_ref,
|
||||
js_ast.NamedImport{
|
||||
.alias = item.alias,
|
||||
.alias_loc = name.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// ESM requires live bindings
|
||||
// CommonJS does not require live bindings
|
||||
// We load ESM in browsers & in Bun.js
|
||||
// We have to simulate live bindings for cases where the code is bundled
|
||||
// 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");
|
||||
|
||||
const name: LocRef = item.name;
|
||||
const name_ref = name.ref.?;
|
||||
|
||||
try p.named_imports.put(p.allocator, name_ref, js_ast.NamedImport{
|
||||
.alias = item.alias,
|
||||
.alias_loc = name.loc,
|
||||
.namespace_ref = namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
});
|
||||
|
||||
// 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)
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = item.alias,
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = st.star_name_loc != null and existing_items.contains(symbol.original_name),
|
||||
};
|
||||
}
|
||||
|
||||
if (record.was_originally_require) {
|
||||
var symbol = &p.symbols.items[namespace_ref.innerIndex()];
|
||||
symbol.namespace_alias = G.NamespaceAlias{
|
||||
.namespace_ref = namespace_ref,
|
||||
.alias = "",
|
||||
.import_record_index = st.import_record_index,
|
||||
.was_originally_property_access = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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");
|
||||
}
|
||||
},
|
||||
|
||||
.s_function => |st| {
|
||||
if (st.func.flags.contains(.is_export)) {
|
||||
if (st.func.name) |name| {
|
||||
const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name;
|
||||
try p.recordExport(name.loc, original_name, name.ref.?);
|
||||
} else {
|
||||
try p.log.addRangeError(p.source, logger.Range{ .loc = st.func.open_parens_loc, .len = 2 }, "Exported functions must have a name");
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_class => |st| {
|
||||
if (st.is_export) {
|
||||
if (st.class.class_name) |name| {
|
||||
try p.recordExport(name.loc, p.symbols.items[name.ref.?.innerIndex()].original_name, name.ref.?);
|
||||
} else {
|
||||
try p.log.addRangeError(p.source, logger.Range{ .loc = st.class.body_loc, .len = 0 }, "Exported classes must have a name");
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_local => |st| {
|
||||
if (st.is_export) {
|
||||
for (st.decls.slice()) |decl| {
|
||||
p.recordExportedBinding(decl.binding);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused import-equals statements, since those likely
|
||||
// correspond to types instead of values
|
||||
if (st.was_ts_import_equals and !st.is_export and st.decls.len > 0) {
|
||||
var decl = st.decls.ptr[0];
|
||||
|
||||
// Skip to the underlying reference
|
||||
var value = decl.value;
|
||||
if (decl.value != null) {
|
||||
while (true) {
|
||||
if (@as(Expr.Tag, value.?.data) == .e_dot) {
|
||||
value = value.?.data.e_dot.target;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is this an identifier reference and not a require() call?
|
||||
if (value) |val| {
|
||||
if (@as(Expr.Tag, val.data) == .e_identifier) {
|
||||
// Is this import statement unused?
|
||||
if (@as(Binding.Tag, decl.binding.data) == .b_identifier and p.symbols.items[decl.binding.data.b_identifier.ref.innerIndex()].use_count_estimate == 0) {
|
||||
p.ignoreUsage(val.data.e_identifier.ref);
|
||||
|
||||
scanner.removed_import_equals = true;
|
||||
continue;
|
||||
} else {
|
||||
scanner.kept_import_equals = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.s_export_default => |st| {
|
||||
// This is defer'd so that we still record export default for identifiers
|
||||
defer {
|
||||
if (st.default_name.ref) |ref| {
|
||||
p.recordExport(st.default_name.loc, "default", ref) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite this export to be:
|
||||
// exports.default =
|
||||
// But only if it's anonymous
|
||||
if (!hot_module_reloading_transformations and will_transform_to_common_js and P != bun.bundle_v2.AstBuilder) {
|
||||
const expr = st.value.toExpr();
|
||||
var export_default_args = try p.allocator.alloc(Expr, 2);
|
||||
export_default_args[0] = p.@"module.exports"(expr.loc);
|
||||
export_default_args[1] = expr;
|
||||
stmt = p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc);
|
||||
}
|
||||
},
|
||||
.s_export_clause => |st| {
|
||||
for (st.items) |item| {
|
||||
try p.recordExport(item.alias_loc, item.alias, item.name.ref.?);
|
||||
}
|
||||
},
|
||||
.s_export_star => |st| {
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
|
||||
if (st.alias) |alias| {
|
||||
// "export * as ns from 'path'"
|
||||
try p.named_imports.put(p.allocator, st.namespace_ref, js_ast.NamedImport{
|
||||
.alias = null,
|
||||
.alias_is_star = true,
|
||||
.alias_loc = alias.loc,
|
||||
.namespace_ref = Ref.None,
|
||||
.import_record_index = st.import_record_index,
|
||||
.is_exported = true,
|
||||
});
|
||||
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;
|
||||
} else {
|
||||
// "export * from 'path'"
|
||||
try p.export_star_import_records.append(allocator, st.import_record_index);
|
||||
}
|
||||
},
|
||||
.s_export_from => |st| {
|
||||
try p.import_records_for_current_part.append(allocator, st.import_record_index);
|
||||
p.named_imports.ensureUnusedCapacity(p.allocator, st.items.len) catch unreachable;
|
||||
for (st.items) |item| {
|
||||
const ref = item.name.ref orelse p.panic("Expected export from item to have a name {any}", .{st});
|
||||
// Note that the imported alias is not item.Alias, which is the
|
||||
// exported alias. This is somewhat confusing because each
|
||||
// SExportFrom statement is basically SImport + SExportClause in one.
|
||||
try p.named_imports.put(p.allocator, ref, js_ast.NamedImport{
|
||||
.alias_is_star = false,
|
||||
.alias = item.original_name,
|
||||
.alias_loc = item.name.loc,
|
||||
.namespace_ref = st.namespace_ref,
|
||||
.import_record_index = st.import_record_index,
|
||||
.is_exported = true,
|
||||
});
|
||||
try p.recordExport(item.name.loc, item.alias, ref);
|
||||
|
||||
var record = &p.import_records.items[st.import_record_index];
|
||||
if (strings.eqlComptime(item.original_name, "default")) {
|
||||
record.contains_default_alias = true;
|
||||
} else if (strings.eqlComptime(item.original_name, "__esModule")) {
|
||||
record.contains_es_module_alias = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (hot_module_reloading_transformations) {
|
||||
try hot_module_reloading_context.convertStmt(p, stmt);
|
||||
} else {
|
||||
stmts[stmts_end] = stmt;
|
||||
stmts_end += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hot_module_reloading_transformations)
|
||||
scanner.stmts = stmts[0..stmts_end];
|
||||
|
||||
return scanner;
|
||||
}
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
const ImportRecord = bun.ImportRecord;
|
||||
const logger = bun.logger;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const Binding = js_ast.Binding;
|
||||
const Expr = js_ast.Expr;
|
||||
const G = js_ast.G;
|
||||
const LocRef = js_ast.LocRef;
|
||||
const S = js_ast.S;
|
||||
const Stmt = js_ast.Stmt;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const js_parser = bun.js_parser;
|
||||
const ConvertESMExportsForHmr = js_parser.ConvertESMExportsForHmr;
|
||||
const ImportItemForNamespaceMap = js_parser.ImportItemForNamespaceMap;
|
||||
const ImportScanner = js_parser.ImportScanner;
|
||||
const Ref = js_parser.Ref;
|
||||
const TypeScript = js_parser.TypeScript;
|
||||
const options = js_parser.options;
|
||||
210
src/ast/KnownGlobal.zig
Normal file
210
src/ast/KnownGlobal.zig
Normal file
@@ -0,0 +1,210 @@
|
||||
pub const KnownGlobal = enum {
|
||||
WeakSet,
|
||||
WeakMap,
|
||||
Date,
|
||||
Set,
|
||||
Map,
|
||||
Headers,
|
||||
Response,
|
||||
TextEncoder,
|
||||
TextDecoder,
|
||||
|
||||
pub const map = bun.ComptimeEnumMap(KnownGlobal);
|
||||
|
||||
pub noinline fn maybeMarkConstructorAsPure(noalias e: *E.New, symbols: []const Symbol) void {
|
||||
const id = if (e.target.data == .e_identifier) e.target.data.e_identifier.ref else return;
|
||||
const symbol = &symbols[id.innerIndex()];
|
||||
if (symbol.kind != .unbound)
|
||||
return;
|
||||
|
||||
const constructor = map.get(symbol.original_name) orelse return;
|
||||
|
||||
switch (constructor) {
|
||||
.WeakSet, .WeakMap => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new WeakSet()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_null, .e_undefined => {
|
||||
// "new WeakSet(null)" is pure
|
||||
// "new WeakSet(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
.e_array => |array| {
|
||||
if (array.items.len == 0) {
|
||||
// "new WeakSet([])" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
} else {
|
||||
// "new WeakSet([x])" is impure because an exception is thrown if "x" is not an object
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// "new WeakSet(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
.Date => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Date()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].knownPrimitive()) {
|
||||
.null, .undefined, .boolean, .number, .string => {
|
||||
// "new Date('')" is pure
|
||||
// "new Date(0)" is pure
|
||||
// "new Date(null)" is pure
|
||||
// "new Date(true)" is pure
|
||||
// "new Date(false)" is pure
|
||||
// "new Date(undefined)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Date(x)" is impure because the argument could be a string with side effects
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.Set => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Set()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_array, .e_null, .e_undefined => {
|
||||
// "new Set([a, b, c])" is pure
|
||||
// "new Set(null)" is pure
|
||||
// "new Set(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Set(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.Headers => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Headers()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
.Response => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Response()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].knownPrimitive()) {
|
||||
.null, .undefined, .boolean, .number, .string => {
|
||||
// "new Response('')" is pure
|
||||
// "new Response(0)" is pure
|
||||
// "new Response(null)" is pure
|
||||
// "new Response(true)" is pure
|
||||
// "new Response(false)" is pure
|
||||
// "new Response(undefined)" is pure
|
||||
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
else => {
|
||||
// "new Response(x)" is impure
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
.TextDecoder, .TextEncoder => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new TextEncoder()" is pure
|
||||
// "new TextDecoder()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We _could_ validate the encoding argument
|
||||
// But let's not bother
|
||||
},
|
||||
|
||||
.Map => {
|
||||
const n = e.args.len;
|
||||
|
||||
if (n == 0) {
|
||||
// "new Map()" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 1) {
|
||||
switch (e.args.ptr[0].data) {
|
||||
.e_null, .e_undefined => {
|
||||
// "new Map(null)" is pure
|
||||
// "new Map(void 0)" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
},
|
||||
.e_array => |array| {
|
||||
var all_items_are_arrays = true;
|
||||
for (array.items.slice()) |item| {
|
||||
if (item.data != .e_array) {
|
||||
all_items_are_arrays = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_items_are_arrays) {
|
||||
// "new Map([[a, b], [c, d]])" is pure
|
||||
e.can_be_unwrapped_if_unused = .if_unused;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// "new Map(x)" is impure because the iterator for "x" could have side effects
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const E = js_ast.E;
|
||||
const Symbol = js_ast.Symbol;
|
||||
|
||||
const std = @import("std");
|
||||
const Map = std.AutoHashMapUnmanaged;
|
||||
@@ -22,7 +22,7 @@ pub fn NewStore(comptime types: []const type, comptime count: usize) type {
|
||||
|
||||
const backing_allocator = bun.default_allocator;
|
||||
|
||||
const log = Output.scoped(.Store, true);
|
||||
const log = Output.scoped(.Store, .hidden);
|
||||
|
||||
return struct {
|
||||
const Store = @This();
|
||||
|
||||
6666
src/ast/P.zig
Normal file
6666
src/ast/P.zig
Normal file
File diff suppressed because it is too large
Load Diff
1528
src/ast/Parser.zig
Normal file
1528
src/ast/Parser.zig
Normal file
File diff suppressed because it is too large
Load Diff
887
src/ast/SideEffects.zig
Normal file
887
src/ast/SideEffects.zig
Normal file
@@ -0,0 +1,887 @@
|
||||
pub const SideEffects = enum(u1) {
|
||||
could_have_side_effects,
|
||||
no_side_effects,
|
||||
|
||||
pub const Result = struct {
|
||||
side_effects: SideEffects,
|
||||
ok: bool = false,
|
||||
value: bool = false,
|
||||
};
|
||||
|
||||
pub fn canChangeStrictToLoose(lhs: Expr.Data, rhs: Expr.Data) bool {
|
||||
const left = lhs.knownPrimitive();
|
||||
const right = rhs.knownPrimitive();
|
||||
return left == right and left != .unknown and left != .mixed;
|
||||
}
|
||||
|
||||
pub fn simplifyBoolean(p: anytype, expr: Expr) Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
|
||||
var result: Expr = expr;
|
||||
_simplifyBoolean(p, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn _simplifyBoolean(p: anytype, expr: *Expr) void {
|
||||
while (true) {
|
||||
switch (expr.data) {
|
||||
.e_unary => |e| {
|
||||
if (e.op == .un_not) {
|
||||
// "!!a" => "a"
|
||||
if (e.value.data == .e_unary and e.value.data.e_unary.op == .un_not) {
|
||||
expr.* = e.value.data.e_unary.value;
|
||||
continue;
|
||||
}
|
||||
|
||||
_simplifyBoolean(p, &e.value);
|
||||
}
|
||||
},
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
.bin_logical_and => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
if (effects.ok and effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything && truthyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
.bin_logical_or => {
|
||||
const effects = SideEffects.toBoolean(p, e.right.data);
|
||||
if (effects.ok and !effects.value and effects.side_effects == .no_side_effects) {
|
||||
// "if (anything || falsyNoSideEffects)" => "if (anything)"
|
||||
expr.* = e.left;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pub const toNumber = Expr.Data.toNumber;
|
||||
pub const typeof = Expr.Data.toTypeof;
|
||||
|
||||
pub fn isPrimitiveToReorder(data: Expr.Data) bool {
|
||||
return switch (data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_string,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_inlined_enum,
|
||||
.e_require_main,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn simplifyUnusedExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (!p.options.features.dead_code_elimination) return expr;
|
||||
switch (expr.data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_missing,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
.e_this,
|
||||
.e_reg_exp,
|
||||
.e_function,
|
||||
.e_arrow,
|
||||
.e_import_meta,
|
||||
.e_inlined_enum,
|
||||
=> return null,
|
||||
|
||||
.e_dot => |dot| {
|
||||
if (dot.can_be_removed_if_unused) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.e_identifier => |ident| {
|
||||
if (ident.must_keep_due_to_with_stmt) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (ident.can_be_removed_if_unused or p.symbols.items[ident.ref.innerIndex()].kind != .unbound) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
.e_if => |ternary| {
|
||||
ternary.yes = simplifyUnusedExpr(p, ternary.yes) orelse ternary.yes.toEmpty();
|
||||
ternary.no = simplifyUnusedExpr(p, ternary.no) orelse ternary.no.toEmpty();
|
||||
|
||||
// "foo() ? 1 : 2" => "foo()"
|
||||
if (ternary.yes.isEmpty() and ternary.no.isEmpty()) {
|
||||
return simplifyUnusedExpr(p, ternary.test_);
|
||||
}
|
||||
|
||||
// "foo() ? 1 : bar()" => "foo() || bar()"
|
||||
if (ternary.yes.isEmpty()) {
|
||||
return Expr.joinWithLeftAssociativeOp(
|
||||
.bin_logical_or,
|
||||
ternary.test_,
|
||||
ternary.no,
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
// "foo() ? bar() : 2" => "foo() && bar()"
|
||||
if (ternary.no.isEmpty()) {
|
||||
return Expr.joinWithLeftAssociativeOp(
|
||||
.bin_logical_and,
|
||||
ternary.test_,
|
||||
ternary.yes,
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
},
|
||||
.e_unary => |un| {
|
||||
// These operators must not have any type conversions that can execute code
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
switch (un.op) {
|
||||
.un_void, .un_not => {
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
},
|
||||
.un_typeof => {
|
||||
// "typeof x" must not be transformed into if "x" since doing so could
|
||||
// cause an exception to be thrown. Instead we can just remove it since
|
||||
// "typeof x" is special-cased in the standard to never throw.
|
||||
if (std.meta.activeTag(un.value.data) == .e_identifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return simplifyUnusedExpr(p, un.value);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
inline .e_call, .e_new => |call| {
|
||||
// A call that has been marked "__PURE__" can be removed if all arguments
|
||||
// can be removed. The annotation causes us to ignore the target.
|
||||
if (call.can_be_unwrapped_if_unused != .never) {
|
||||
if (call.args.len > 0) {
|
||||
const joined = Expr.joinAllWithCommaCallback(call.args.slice(), @TypeOf(p), p, comptime simplifyUnusedExpr, p.allocator);
|
||||
if (joined != null and call.can_be_unwrapped_if_unused == .if_unused_and_toString_safe) {
|
||||
@branchHint(.unlikely);
|
||||
// For now, only support this for 1 argument.
|
||||
if (joined.?.data.isSafeToString()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return joined;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.e_binary => |bin| {
|
||||
switch (bin.op) {
|
||||
// These operators must not have any type conversions that can execute code
|
||||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> return simplifyUnusedBinaryCommaExpr(p, expr),
|
||||
|
||||
// We can simplify "==" and "!=" even though they can call "toString" and/or
|
||||
// "valueOf" if we can statically determine that the types of both sides are
|
||||
// primitives. In that case there won't be any chance for user-defined
|
||||
// "toString" and/or "valueOf" to be called.
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
=> {
|
||||
if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) {
|
||||
return Expr.joinWithComma(
|
||||
simplifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(),
|
||||
simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
// If one side is a number, the number can be printed as
|
||||
// `0` since the result being unused doesnt matter, we
|
||||
// only care to invoke the coercion.
|
||||
if (bin.left.data == .e_number) {
|
||||
bin.left.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
} else if (bin.right.data == .e_number) {
|
||||
bin.right.data = .{ .e_number = .{ .value = 0.0 } };
|
||||
}
|
||||
},
|
||||
|
||||
.bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => {
|
||||
bin.right = simplifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty();
|
||||
// Preserve short-circuit behavior: the left expression is only unused if
|
||||
// the right expression can be completely removed. Otherwise, the left
|
||||
// expression is important for the branch.
|
||||
|
||||
if (bin.right.isEmpty())
|
||||
return simplifyUnusedExpr(p, bin.left);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
.e_object => {
|
||||
// Objects with "..." spread expressions can't be unwrapped because the
|
||||
// "..." triggers code evaluation via getters. In that case, just trim
|
||||
// the other items instead and leave the object expression there.
|
||||
var properties_slice = expr.data.e_object.properties.slice();
|
||||
var end: usize = 0;
|
||||
for (properties_slice) |spread| {
|
||||
end = 0;
|
||||
if (spread.kind == .spread) {
|
||||
// Spread properties must always be evaluated
|
||||
for (properties_slice) |prop_| {
|
||||
var prop = prop_;
|
||||
if (prop_.kind != .spread) {
|
||||
const value = simplifyUnusedExpr(p, prop.value.?);
|
||||
if (value != null) {
|
||||
prop.value = value;
|
||||
} else if (!prop.flags.contains(.is_computed)) {
|
||||
continue;
|
||||
} else {
|
||||
prop.value = p.newExpr(E.Number{ .value = 0.0 }, prop.value.?.loc);
|
||||
}
|
||||
}
|
||||
|
||||
properties_slice[end] = prop_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
properties_slice = properties_slice[0..end];
|
||||
expr.data.e_object.properties = G.Property.List.init(properties_slice);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
var result = Expr.init(E.Missing, E.Missing{}, expr.loc);
|
||||
|
||||
// Otherwise, the object can be completely removed. We only need to keep any
|
||||
// object properties with side effects. Apply this simplification recursively.
|
||||
for (properties_slice) |prop| {
|
||||
if (prop.flags.contains(.is_computed)) {
|
||||
// Make sure "ToString" is still evaluated on the key
|
||||
result = result.joinWithComma(
|
||||
p.newExpr(
|
||||
E.Binary{
|
||||
.op = .bin_add,
|
||||
.left = prop.key.?,
|
||||
.right = p.newExpr(E.String{}, prop.key.?.loc),
|
||||
},
|
||||
prop.key.?.loc,
|
||||
),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
result = result.joinWithComma(
|
||||
simplifyUnusedExpr(p, prop.value.?) orelse prop.value.?.toEmpty(),
|
||||
p.allocator,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
.e_array => {
|
||||
var items = expr.data.e_array.items.slice();
|
||||
|
||||
for (items) |item| {
|
||||
if (item.data == .e_spread) {
|
||||
var end: usize = 0;
|
||||
for (items) |item__| {
|
||||
const item_ = item__;
|
||||
if (item_.data != .e_missing) {
|
||||
items[end] = item_;
|
||||
end += 1;
|
||||
}
|
||||
|
||||
expr.data.e_array.items = ExprNodeList.init(items[0..end]);
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the array can be completely removed. We only need to keep any
|
||||
// array items with side effects. Apply this simplification recursively.
|
||||
return Expr.joinAllWithCommaCallback(
|
||||
items,
|
||||
@TypeOf(p),
|
||||
p,
|
||||
comptime simplifyUnusedExpr,
|
||||
p.allocator,
|
||||
);
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
pub const BinaryExpressionSimplifyVisitor = struct {
|
||||
bin: *E.Binary,
|
||||
};
|
||||
|
||||
///
|
||||
fn simplifyUnusedBinaryCommaExpr(p: anytype, expr: Expr) ?Expr {
|
||||
if (Environment.allow_assert) {
|
||||
assert(expr.data == .e_binary);
|
||||
assert(switch (expr.data.e_binary.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> true,
|
||||
else => false,
|
||||
});
|
||||
}
|
||||
const stack: *std.ArrayList(BinaryExpressionSimplifyVisitor) = &p.binary_expression_simplify_stack;
|
||||
const stack_bottom = stack.items.len;
|
||||
defer stack.shrinkRetainingCapacity(stack_bottom);
|
||||
|
||||
stack.append(.{ .bin = expr.data.e_binary }) catch bun.outOfMemory();
|
||||
|
||||
// Build stack up of expressions
|
||||
var left: Expr = expr.data.e_binary.left;
|
||||
while (left.data.as(.e_binary)) |left_bin| {
|
||||
switch (left_bin.op) {
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
.bin_comma,
|
||||
=> {
|
||||
stack.append(.{ .bin = left_bin }) catch bun.outOfMemory();
|
||||
left = left_bin.left;
|
||||
},
|
||||
else => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Ride the stack downwards
|
||||
var i = stack.items.len;
|
||||
var result = simplifyUnusedExpr(p, left) orelse Expr.empty;
|
||||
while (i > stack_bottom) {
|
||||
i -= 1;
|
||||
const top = stack.items[i];
|
||||
const visited_right = simplifyUnusedExpr(p, top.bin.right) orelse Expr.empty;
|
||||
result = result.joinWithComma(visited_right, p.allocator);
|
||||
}
|
||||
|
||||
return if (result.isMissing()) null else result;
|
||||
}
|
||||
|
||||
fn findIdentifiers(binding: Binding, decls: *std.ArrayList(G.Decl)) void {
|
||||
switch (binding.data) {
|
||||
.b_identifier => {
|
||||
decls.append(.{ .binding = binding }) catch unreachable;
|
||||
},
|
||||
.b_array => |array| {
|
||||
for (array.items) |item| {
|
||||
findIdentifiers(item.binding, decls);
|
||||
}
|
||||
},
|
||||
.b_object => |obj| {
|
||||
for (obj.properties) |item| {
|
||||
findIdentifiers(item.value, decls);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn shouldKeepStmtsInDeadControlFlow(stmts: []Stmt, allocator: Allocator) bool {
|
||||
for (stmts) |child| {
|
||||
if (shouldKeepStmtInDeadControlFlow(child, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// If this is in a dead branch, then we want to trim as much dead code as we
|
||||
/// can. Everything can be trimmed except for hoisted declarations ("var" and
|
||||
/// "function"), which affect the parent scope. For example:
|
||||
///
|
||||
/// function foo() {
|
||||
/// if (false) { var x; }
|
||||
/// x = 1;
|
||||
/// }
|
||||
///
|
||||
/// We can't trim the entire branch as dead or calling foo() will incorrectly
|
||||
/// assign to a global variable instead.
|
||||
///
|
||||
/// Caller is expected to first check `p.options.dead_code_elimination` so we only check it once.
|
||||
pub fn shouldKeepStmtInDeadControlFlow(stmt: Stmt, allocator: Allocator) bool {
|
||||
switch (stmt.data) {
|
||||
// Omit these statements entirely
|
||||
.s_empty, .s_expr, .s_throw, .s_return, .s_break, .s_continue, .s_class, .s_debugger => return false,
|
||||
|
||||
.s_local => |local| {
|
||||
if (local.kind != .k_var) {
|
||||
// Omit these statements entirely
|
||||
return false;
|
||||
}
|
||||
|
||||
// Omit everything except the identifiers
|
||||
|
||||
// common case: single var foo = blah, don't need to allocate
|
||||
if (local.decls.len == 1 and local.decls.ptr[0].binding.data == .b_identifier) {
|
||||
const prev = local.decls.ptr[0];
|
||||
stmt.data.s_local.decls.ptr[0] = G.Decl{ .binding = prev.binding };
|
||||
return true;
|
||||
}
|
||||
|
||||
var decls = std.ArrayList(G.Decl).initCapacity(allocator, local.decls.len) catch unreachable;
|
||||
for (local.decls.slice()) |decl| {
|
||||
findIdentifiers(decl.binding, &decls);
|
||||
}
|
||||
|
||||
local.decls.update(decls);
|
||||
return true;
|
||||
},
|
||||
|
||||
.s_block => |block| {
|
||||
return shouldKeepStmtsInDeadControlFlow(block.stmts, allocator);
|
||||
},
|
||||
|
||||
.s_try => |try_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(try_stmt.body, allocator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (try_stmt.catch_) |*catch_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(catch_stmt.body, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (try_stmt.finally) |*finally_stmt| {
|
||||
if (shouldKeepStmtsInDeadControlFlow(finally_stmt.stmts, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
.s_if => |_if_| {
|
||||
if (shouldKeepStmtInDeadControlFlow(_if_.yes, allocator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const no = _if_.no orelse return false;
|
||||
|
||||
return shouldKeepStmtInDeadControlFlow(no, allocator);
|
||||
},
|
||||
|
||||
.s_while => {
|
||||
return shouldKeepStmtInDeadControlFlow(stmt.data.s_while.body, allocator);
|
||||
},
|
||||
|
||||
.s_do_while => {
|
||||
return shouldKeepStmtInDeadControlFlow(stmt.data.s_do_while.body, allocator);
|
||||
},
|
||||
|
||||
.s_for => |__for__| {
|
||||
if (__for__.init) |init_| {
|
||||
if (shouldKeepStmtInDeadControlFlow(init_, allocator)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_for_in => |__for__| {
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_for_of => |__for__| {
|
||||
return shouldKeepStmtInDeadControlFlow(__for__.init, allocator) or shouldKeepStmtInDeadControlFlow(__for__.body, allocator);
|
||||
},
|
||||
|
||||
.s_label => |label| {
|
||||
return shouldKeepStmtInDeadControlFlow(label.stmt, allocator);
|
||||
},
|
||||
|
||||
else => return true,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if this expression is known to result in a primitive value (i.e.
|
||||
// null, undefined, boolean, number, bigint, or string), even if the expression
|
||||
// cannot be removed due to side effects.
|
||||
pub fn isPrimitiveWithSideEffects(data: Expr.Data) bool {
|
||||
switch (data) {
|
||||
.e_null,
|
||||
.e_undefined,
|
||||
.e_boolean,
|
||||
.e_number,
|
||||
.e_big_int,
|
||||
.e_string,
|
||||
.e_inlined_enum,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
.e_unary => |e| {
|
||||
switch (e.op) {
|
||||
// number or bigint
|
||||
.un_pos,
|
||||
.un_neg,
|
||||
.un_cpl,
|
||||
.un_pre_dec,
|
||||
.un_pre_inc,
|
||||
.un_post_dec,
|
||||
.un_post_inc,
|
||||
// boolean
|
||||
.un_not,
|
||||
.un_delete,
|
||||
// undefined
|
||||
.un_void,
|
||||
// string
|
||||
.un_typeof,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
// boolean
|
||||
.bin_lt,
|
||||
.bin_le,
|
||||
.bin_gt,
|
||||
.bin_ge,
|
||||
.bin_in,
|
||||
.bin_instanceof,
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
// string, number, or bigint
|
||||
.bin_add,
|
||||
.bin_add_assign,
|
||||
// number or bigint
|
||||
.bin_sub,
|
||||
.bin_mul,
|
||||
.bin_div,
|
||||
.bin_rem,
|
||||
.bin_pow,
|
||||
.bin_sub_assign,
|
||||
.bin_mul_assign,
|
||||
.bin_div_assign,
|
||||
.bin_rem_assign,
|
||||
.bin_pow_assign,
|
||||
.bin_shl,
|
||||
.bin_shr,
|
||||
.bin_u_shr,
|
||||
.bin_shl_assign,
|
||||
.bin_shr_assign,
|
||||
.bin_u_shr_assign,
|
||||
.bin_bitwise_or,
|
||||
.bin_bitwise_and,
|
||||
.bin_bitwise_xor,
|
||||
.bin_bitwise_or_assign,
|
||||
.bin_bitwise_and_assign,
|
||||
.bin_bitwise_xor_assign,
|
||||
=> {
|
||||
return true;
|
||||
},
|
||||
|
||||
// These always return one of the arguments unmodified
|
||||
.bin_logical_and,
|
||||
.bin_logical_or,
|
||||
.bin_nullish_coalescing,
|
||||
.bin_logical_and_assign,
|
||||
.bin_logical_or_assign,
|
||||
.bin_nullish_coalescing_assign,
|
||||
=> {
|
||||
return isPrimitiveWithSideEffects(e.left.data) and isPrimitiveWithSideEffects(e.right.data);
|
||||
},
|
||||
.bin_comma => {
|
||||
return isPrimitiveWithSideEffects(e.right.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_if => |e| {
|
||||
return isPrimitiveWithSideEffects(e.yes.data) and isPrimitiveWithSideEffects(e.no.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const toTypeOf = Expr.Data.typeof;
|
||||
|
||||
pub fn toNullOrUndefined(p: anytype, exp: Expr.Data) Result {
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
switch (exp) {
|
||||
// Never null or undefined
|
||||
.e_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 };
|
||||
},
|
||||
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .value = false, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
// always a null or undefined
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .value = true, .side_effects = .no_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
.e_unary => |e| {
|
||||
switch (e.op) {
|
||||
// Always number or bigint
|
||||
.un_pos,
|
||||
.un_neg,
|
||||
.un_cpl,
|
||||
.un_pre_dec,
|
||||
.un_pre_inc,
|
||||
.un_post_dec,
|
||||
.un_post_inc,
|
||||
|
||||
// Always boolean
|
||||
.un_not,
|
||||
.un_typeof,
|
||||
.un_delete,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
},
|
||||
|
||||
// Always undefined
|
||||
.un_void => {
|
||||
return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
.e_binary => |e| {
|
||||
switch (e.op) {
|
||||
// always string or number or bigint
|
||||
.bin_add,
|
||||
.bin_add_assign,
|
||||
// always number or bigint
|
||||
.bin_sub,
|
||||
.bin_mul,
|
||||
.bin_div,
|
||||
.bin_rem,
|
||||
.bin_pow,
|
||||
.bin_sub_assign,
|
||||
.bin_mul_assign,
|
||||
.bin_div_assign,
|
||||
.bin_rem_assign,
|
||||
.bin_pow_assign,
|
||||
.bin_shl,
|
||||
.bin_shr,
|
||||
.bin_u_shr,
|
||||
.bin_shl_assign,
|
||||
.bin_shr_assign,
|
||||
.bin_u_shr_assign,
|
||||
.bin_bitwise_or,
|
||||
.bin_bitwise_and,
|
||||
.bin_bitwise_xor,
|
||||
.bin_bitwise_or_assign,
|
||||
.bin_bitwise_and_assign,
|
||||
.bin_bitwise_xor_assign,
|
||||
// always boolean
|
||||
.bin_lt,
|
||||
.bin_le,
|
||||
.bin_gt,
|
||||
.bin_ge,
|
||||
.bin_in,
|
||||
.bin_instanceof,
|
||||
.bin_loose_eq,
|
||||
.bin_loose_ne,
|
||||
.bin_strict_eq,
|
||||
.bin_strict_ne,
|
||||
=> {
|
||||
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
},
|
||||
|
||||
.bin_comma => {
|
||||
const res = toNullOrUndefined(p, e.right.data);
|
||||
if (res.ok) {
|
||||
return Result{ .ok = true, .value = res.value, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toNullOrUndefined(p, inlined.value.data);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
|
||||
pub fn toBoolean(p: anytype, exp: Expr.Data) Result {
|
||||
// Only do this check once.
|
||||
if (!p.options.features.dead_code_elimination) {
|
||||
// value should not be read if ok is false, all existing calls to this function already adhere to this
|
||||
return Result{ .ok = false, .value = undefined, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
|
||||
return toBooleanWithoutDCECheck(exp);
|
||||
}
|
||||
|
||||
// Avoid passing through *P
|
||||
// This is a very recursive function.
|
||||
fn toBooleanWithoutDCECheck(exp: Expr.Data) Result {
|
||||
switch (exp) {
|
||||
.e_null, .e_undefined => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_boolean => |e| {
|
||||
return Result{ .ok = true, .value = e.value, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_number => |e| {
|
||||
return Result{ .ok = true, .value = e.value != 0.0 and !std.math.isNan(e.value), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_big_int => |e| {
|
||||
return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_string => |e| {
|
||||
return Result{ .ok = true, .value = e.isPresent(), .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_function, .e_arrow, .e_reg_exp => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .no_side_effects };
|
||||
},
|
||||
.e_object, .e_array, .e_class => {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.e_unary => |e_| {
|
||||
switch (e_.op) {
|
||||
.un_void => {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_typeof => {
|
||||
// Never an empty string
|
||||
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
},
|
||||
.un_not => {
|
||||
const result = toBooleanWithoutDCECheck(e_.value.data);
|
||||
if (result.ok) {
|
||||
return .{ .ok = true, .value = !result.value, .side_effects = result.side_effects };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_binary => |e_| {
|
||||
switch (e_.op) {
|
||||
.bin_logical_or => {
|
||||
// "anything || truthy" is truthy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = true, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_logical_and => {
|
||||
// "anything && falsy" is falsy
|
||||
const result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (!result.value and result.ok) {
|
||||
return Result{ .ok = true, .value = false, .side_effects = .could_have_side_effects };
|
||||
}
|
||||
},
|
||||
.bin_comma => {
|
||||
// "anything, truthy/falsy" is truthy/falsy
|
||||
var result = toBooleanWithoutDCECheck(e_.right.data);
|
||||
if (result.ok) {
|
||||
result.side_effects = .could_have_side_effects;
|
||||
return result;
|
||||
}
|
||||
},
|
||||
.bin_gt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num > right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_lt => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num < right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_le => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num <= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
.bin_ge => {
|
||||
if (e_.left.data.toFiniteNumber()) |left_num| {
|
||||
if (e_.right.data.toFiniteNumber()) |right_num| {
|
||||
return Result{ .ok = true, .value = left_num >= right_num, .side_effects = .no_side_effects };
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
.e_inlined_enum => |inlined| {
|
||||
return toBooleanWithoutDCECheck(inlined.value.data);
|
||||
},
|
||||
.e_special => |special| switch (special) {
|
||||
.module_exports,
|
||||
.resolved_specifier_string,
|
||||
.hot_data,
|
||||
=> {},
|
||||
.hot_accept,
|
||||
.hot_accept_visited,
|
||||
.hot_enabled,
|
||||
=> return .{ .ok = true, .value = true, .side_effects = .no_side_effects },
|
||||
.hot_disabled,
|
||||
=> return .{ .ok = true, .value = false, .side_effects = .no_side_effects },
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return Result{ .ok = false, .value = false, .side_effects = SideEffects.could_have_side_effects };
|
||||
}
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const options = @import("../options.zig");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const assert = bun.assert;
|
||||
const strings = bun.strings;
|
||||
|
||||
const js_ast = bun.ast;
|
||||
const Binding = js_ast.Binding;
|
||||
const E = js_ast.E;
|
||||
const Expr = js_ast.Expr;
|
||||
const ExprNodeList = js_ast.ExprNodeList;
|
||||
const Stmt = js_ast.Stmt;
|
||||
|
||||
const G = js_ast.G;
|
||||
const Decl = G.Decl;
|
||||
const Property = G.Property;
|
||||
|
||||
const std = @import("std");
|
||||
const List = std.ArrayListUnmanaged;
|
||||
const Allocator = std.mem.Allocator;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user