initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
exclude:
paths:
- test
- scripts
- bench
- packages/bun-lambda
- packages/bun-release
- packages/bun-vscode
- packages/bun-plugin-yaml
- packages/bun-plugin-svelte
- packages/bun-native-plugin-rs
- packages/bun-native-bundler-plugin-api
- packages/bun-inspector-protocol
- packages/bun-inspector-frontend
- packages/bun-error
- packages/bun-debug-adapter-protocol
- packages/bun-build-mdx-rs
- packages/@types/bun
+3
View File
@@ -0,0 +1,3 @@
selected_org: bun
pipelines:
- bun
+278
View File
@@ -0,0 +1,278 @@
ARG LLVM_VERSION="21"
ARG REPORTED_LLVM_VERSION="21.1.8"
ARG OLD_BUN_VERSION="1.3.13"
ARG BUILDKITE_AGENT_TAGS="queue=linux,os=linux,arch=${TARGETARCH}"
FROM --platform=$BUILDPLATFORM ubuntu:20.04 as base-arm64
FROM --platform=$BUILDPLATFORM ubuntu:20.04 as base-amd64
FROM base-$TARGETARCH as base
ARG LLVM_VERSION
ARG OLD_BUN_VERSION
ARG TARGETARCH
ARG REPORTED_LLVM_VERSION
ENV DEBIAN_FRONTEND=noninteractive \
CI=true \
DOCKER=true
RUN echo "Acquire::Queue-Mode \"host\";" > /etc/apt/apt.conf.d/99-apt-queue-mode.conf \
&& echo "Acquire::Timeout \"120\";" >> /etc/apt/apt.conf.d/99-apt-timeout.conf \
&& echo "Acquire::Retries \"3\";" >> /etc/apt/apt.conf.d/99-apt-retries.conf \
&& echo "APT::Install-Recommends \"false\";" >> /etc/apt/apt.conf.d/99-apt-install-recommends.conf \
&& echo "APT::Install-Suggests \"false\";" >> /etc/apt/apt.conf.d/99-apt-install-suggests.conf
RUN apt-get update && apt-get install -y --no-install-recommends \
wget curl git python3 python3-pip ninja-build \
software-properties-common apt-transport-https \
ca-certificates gnupg lsb-release unzip xz-utils \
libxml2-dev ruby ruby-dev bison gawk perl make golang ccache qemu-user-static \
nasm \
&& add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update \
&& apt-get install -y gcc-13 g++-13 libgcc-13-dev libstdc++-13-dev \
libasan6 libubsan1 libatomic1 libtsan0 liblsan0 \
libgfortran5 libc6-dev \
&& wget https://apt.llvm.org/llvm.sh \
&& chmod +x llvm.sh \
&& ./llvm.sh ${LLVM_VERSION} all \
&& rm llvm.sh \
&& rm -rf /var/lib/apt/lists/*
RUN --mount=type=tmpfs,target=/tmp \
cmake_version="3.30.5" && \
if [ "$TARGETARCH" = "arm64" ]; then \
cmake_url="https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-linux-aarch64.sh"; \
else \
cmake_url="https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-linux-x86_64.sh"; \
fi && \
wget -O /tmp/cmake.sh "$cmake_url" && \
sh /tmp/cmake.sh --skip-license --prefix=/usr
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 \
--slave /usr/bin/g++ g++ /usr/bin/g++-13 \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-13 \
--slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-13 \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-13
RUN echo "ARCH_PATH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64-linux-gnu" || echo "x86_64-linux-gnu")" >> /etc/environment \
&& echo "BUN_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x64")" >> /etc/environment
ENV LD_LIBRARY_PATH=/usr/lib/gcc/${ARCH_PATH}/13:/usr/lib/${ARCH_PATH} \
LIBRARY_PATH=/usr/lib/gcc/${ARCH_PATH}/13:/usr/lib/${ARCH_PATH} \
CPLUS_INCLUDE_PATH=/usr/include/c++/13:/usr/include/${ARCH_PATH}/c++/13 \
C_INCLUDE_PATH=/usr/lib/gcc/${ARCH_PATH}/13/include
RUN if [ "$TARGETARCH" = "arm64" ]; then \
export ARCH_PATH="aarch64-linux-gnu"; \
else \
export ARCH_PATH="x86_64-linux-gnu"; \
fi \
&& mkdir -p /usr/lib/gcc/${ARCH_PATH}/13 \
&& ln -sf /usr/lib/${ARCH_PATH}/libstdc++.so.6 /usr/lib/gcc/${ARCH_PATH}/13/ \
&& echo "/usr/lib/gcc/${ARCH_PATH}/13" > /etc/ld.so.conf.d/gcc-13.conf \
&& echo "/usr/lib/${ARCH_PATH}" >> /etc/ld.so.conf.d/gcc-13.conf \
&& ldconfig
RUN for f in /usr/lib/llvm-${LLVM_VERSION}/bin/*; do ln -sf "$f" /usr/bin; done \
&& ln -sf /usr/bin/clang-${LLVM_VERSION} /usr/bin/clang \
&& ln -sf /usr/bin/clang++-${LLVM_VERSION} /usr/bin/clang++ \
&& ln -sf /usr/bin/lld-${LLVM_VERSION} /usr/bin/lld \
&& ln -sf /usr/bin/lldb-${LLVM_VERSION} /usr/bin/lldb \
&& ln -sf /usr/bin/clangd-${LLVM_VERSION} /usr/bin/clangd \
&& ln -sf /usr/bin/llvm-ar-${LLVM_VERSION} /usr/bin/llvm-ar \
&& ln -sf /usr/bin/ld.lld /usr/bin/ld \
&& ln -sf /usr/bin/clang /usr/bin/cc \
&& ln -sf /usr/bin/clang++ /usr/bin/c++
ENV CC="clang" \
CXX="clang++" \
AR="llvm-ar-${LLVM_VERSION}" \
RANLIB="llvm-ranlib-${LLVM_VERSION}" \
LD="lld-${LLVM_VERSION}"
RUN --mount=type=tmpfs,target=/tmp \
bash -c '\
set -euxo pipefail && \
source /etc/environment && \
echo "Downloading bun-v${OLD_BUN_VERSION}/bun-linux-$BUN_ARCH.zip from https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/bun-v${OLD_BUN_VERSION}/bun-linux-$BUN_ARCH.zip" && \
curl -fsSL https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/bun-v${OLD_BUN_VERSION}/bun-linux-$BUN_ARCH.zip -o /tmp/bun.zip && \
unzip /tmp/bun.zip -d /tmp/bun && \
mv /tmp/bun/*/bun /usr/bin/bun && \
chmod +x /usr/bin/bun'
# Node — build scripts run under node (see scripts/build.ts). Version must
# match scripts/bootstrap.sh nodejs_version_exact() so container and bare-
# metal agents use the same runtime. .tar.gz (not .xz) to match bootstrap.sh
# and avoid needing xz-utils.
ARG NODE_VERSION="24.3.0"
RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "x64"; fi) && \
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCH}.tar.gz" \
| tar -xz -C /usr/local --strip-components=1 && \
node --version
ENV LLVM_VERSION=${REPORTED_LLVM_VERSION}
WORKDIR /workspace
FROM --platform=$BUILDPLATFORM base as buildkite
ARG BUILDKITE_AGENT_TAGS
# Install Rust nightly
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& export PATH=$HOME/.cargo/bin:$PATH \
&& rustup install nightly \
&& rustup default nightly \
&& rustup target add aarch64-linux-android x86_64-linux-android \
&& rustup target add x86_64-unknown-freebsd \
&& rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc \
&& rustup component add rust-src
# Android NDK — sysroot/libc++/compiler-rt for --abi=android cross-compile.
ARG ANDROID_NDK_VERSION="r27c"
RUN curl -fsSL "https://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_VERSION}-linux.zip" -o /tmp/ndk.zip \
&& unzip -q /tmp/ndk.zip -d /opt \
&& mv /opt/android-ndk-${ANDROID_NDK_VERSION} /opt/android-ndk \
&& rm /tmp/ndk.zip \
# Trim ~1.1GB we don't use (NDK clang/lld, lldb, non-android runtimes) —
# we only need sysroot + android compiler-rt. Dramatically shrinks the
# docker layer / AMI size.
&& rm -rf /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin \
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/python3 \
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/liblldb.so \
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/*-gnu \
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/*-musl* \
/opt/android-ndk/simpleperf /opt/android-ndk/shader-tools /opt/android-ndk/sources \
# Symlink NDK compiler-rt builtins + libunwind into host clang's resource
# dir — clang's driver hardcodes <resource-dir>/lib/<triple>/libclang_rt.*
# with no -L fallback. Done at image-build time (root) since the build
# user can't write to /usr.
&& RES=$(clang -print-resource-dir) \
&& NDK_RT=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/$(ls /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/)/lib/linux \
&& mkdir -p $RES/lib/linux \
&& for A in aarch64 x86_64; do \
ln -sf $NDK_RT/libclang_rt.builtins-${A}-android.a $RES/lib/linux/; \
mkdir -p $RES/lib/linux/${A}; \
ln -sf $NDK_RT/${A}/libunwind.a $RES/lib/linux/${A}/; \
DIR=$RES/lib/${A}-unknown-linux-android28; \
mkdir -p $DIR; \
ln -sf $NDK_RT/libclang_rt.builtins-${A}-android.a $DIR/libclang_rt.builtins.a; \
ln -sf $NDK_RT/${A}/libunwind.a $DIR/libunwind.a; \
done
ENV ANDROID_NDK_ROOT=/opt/android-ndk
# FreeBSD sysroot — extracted base.txz for --os=freebsd cross-compile.
# Only the matching arch is fetched (x64 image gets amd64 sysroot, etc.).
ARG FREEBSD_VERSION="14.3"
RUN FBSD_ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64"; fi) \
&& mkdir -p /opt/freebsd-sysroot \
&& curl -fsSL "https://download.freebsd.org/releases/${FBSD_ARCH}/${FREEBSD_VERSION}-RELEASE/base.txz" -o /tmp/base.txz \
&& tar -C /opt/freebsd-sysroot --no-same-owner -xJf /tmp/base.txz ./usr/include ./usr/lib ./lib \
&& rm /tmp/base.txz
ENV FREEBSD_SYSROOT=/opt/freebsd-sysroot
# Windows sysroot — xwin splat of the MSVC CRT/STL + Windows SDK + ATL (VS
# layout) for --os=windows cross-compile; clang-cl/lld-link consume it via
# /winsysroot (see scripts/build/config.ts `winsysroot`). Both target arches
# in one splat; --include-debug-libs so /MTd debug links work; --include-atl
# for <atlstr.h> (rescle.cpp).
# --accept-license accepts the Microsoft license terms for the SDK/CRT
# components, same as the Windows CI images do when installing VS Build Tools.
# If the image predates this layer, configure falls back to fetching the same
# splat at build time (scripts/build/winsysroot.ts — keep XWIN_VERSION in sync).
# The Include/Lib aliases exist because clang-cl/lld-link compose SDK paths
# in title case while the winsysroot-style splat writes lowercase.
ARG XWIN_VERSION="0.9.0"
RUN XWIN_ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "aarch64"; else echo "x86_64"; fi) \
&& curl -fsSL "https://github.com/Jake-Shadle/xwin/releases/download/${XWIN_VERSION}/xwin-${XWIN_VERSION}-${XWIN_ARCH}-unknown-linux-musl.tar.gz" \
-o /tmp/xwin.tar.gz \
&& tar -xzf /tmp/xwin.tar.gz -C /tmp \
&& /tmp/xwin-${XWIN_VERSION}-${XWIN_ARCH}-unknown-linux-musl/xwin --accept-license --arch x86_64,aarch64 --sdk-version 10.0.26100 --crt-version 14.44.17.14 --include-atl --cache-dir /tmp/xwin-cache \
splat --use-winsysroot-style --preserve-ms-arch-notation --include-debug-libs --output /opt/winsysroot \
> /dev/null \
&& ln -s include "/opt/winsysroot/Windows Kits/10/Include" \
&& ln -s lib "/opt/winsysroot/Windows Kits/10/Lib" \
&& rm -rf /tmp/xwin.tar.gz /tmp/xwin-${XWIN_VERSION}-${XWIN_ARCH}-unknown-linux-musl /tmp/xwin-cache
ENV WINDOWS_SYSROOT=/opt/winsysroot
RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64"; fi) && \
echo "Downloading buildkite" && \
curl -fsSL "https://github.com/buildkite/agent/releases/download/v3.87.0/buildkite-agent-linux-${ARCH}-3.87.0.tar.gz" -o /tmp/buildkite-agent.tar.gz && \
mkdir -p /tmp/buildkite-agent && \
tar -xzf /tmp/buildkite-agent.tar.gz -C /tmp/buildkite-agent && \
mv /tmp/buildkite-agent/buildkite-agent /usr/bin/buildkite-agent
RUN mkdir -p /var/cache/buildkite-agent /var/log/buildkite-agent /var/run/buildkite-agent /etc/buildkite-agent /var/lib/buildkite-agent/cache/bun
# Warm BUN_BUILD_PREFETCH_DIR (consulted by scripts/build/download.ts before
# any network fetch). Content-addressed by URL/identity, so a dep version bump
# in scripts/build/deps/ just misses the cache for that one dep — no image
# rebuild needed. The clone is only for scripts/prefetch-deps.ts + its
# scripts/build/ imports, which aren't in the docker context.
ARG BUN_REPO_REF=main
RUN set -e; \
if git clone --depth=1 --branch ${BUN_REPO_REF} https://github.com/oven-sh/bun.git /tmp/bun-clone \
&& [ -f /tmp/bun-clone/scripts/prefetch-deps.ts ]; then \
(cd /tmp/bun-clone && bun scripts/prefetch-deps.ts /opt/bun-prefetch); \
else \
echo "warning: prefetch-deps.ts unavailable at ${BUN_REPO_REF}; skipping warm cache"; \
fi; \
rm -rf /tmp/bun-clone
ENV BUN_BUILD_PREFETCH_DIR=/opt/bun-prefetch
# The following is necessary to configure buildkite to use a stable
# checkout directory for ccache to be effective.
RUN mkdir -p -m 755 /var/lib/buildkite-agent/hooks && \
cat <<'EOF' > /var/lib/buildkite-agent/hooks/environment
#!/bin/sh
set -efu
export BUILDKITE_BUILD_CHECKOUT_PATH=/var/lib/buildkite-agent/build
export BUN_BUILD_PREFETCH_DIR=/opt/bun-prefetch
EOF
RUN chmod 744 /var/lib/buildkite-agent/hooks/environment
COPY ../*/agent.mjs /var/bun/scripts/
ENV BUN_INSTALL_CACHE=/var/lib/buildkite-agent/cache/bun
ENV BUILDKITE_AGENT_TAGS=${BUILDKITE_AGENT_TAGS}
WORKDIR /var/bun/scripts
ENV PATH=/root/.cargo/bin:$PATH
CMD ["bun", "/var/bun/scripts/agent.mjs", "start"]
FROM --platform=$BUILDPLATFORM base as bun-build-linux-local
ARG LLVM_VERSION
WORKDIR /workspace/bun
COPY . /workspace/bun
# Install Rust nightly
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& export PATH=$HOME/.cargo/bin:$PATH \
&& rustup install nightly \
&& rustup default nightly \
&& rustup target add aarch64-linux-android x86_64-linux-android \
&& rustup target add x86_64-unknown-freebsd \
&& rustup component add rust-src
ENV PATH=/root/.cargo/bin:$PATH
ENV LLVM_VERSION=${REPORTED_LLVM_VERSION}
RUN --mount=type=tmpfs,target=/workspace/bun/build \
ls -la \
&& bun run build:release \
&& mkdir -p /target \
&& cp -r /workspace/bun/build/release/bun /target/bun
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "error: must run as root"
exit 1
fi
# Check OS compatibility
if ! command -v dnf &> /dev/null; then
echo "error: this script requires dnf (RHEL/Fedora/CentOS)"
exit 1
fi
# Ensure /tmp/agent.mjs, /tmp/Dockerfile are present
if [ ! -f /tmp/agent.mjs ] || [ ! -f /tmp/Dockerfile ]; then
# Print each missing file
if [ ! -f /tmp/agent.mjs ]; then
echo "error: /tmp/agent.mjs is missing"
fi
if [ ! -f /tmp/Dockerfile ]; then
echo "error: /tmp/Dockerfile is missing"
fi
exit 1
fi
# Install Docker
dnf update -y
dnf install -y docker
systemctl enable docker
systemctl start docker || {
echo "error: failed to start Docker"
exit 1
}
# Create builder
docker buildx create --name builder --driver docker-container --bootstrap --use || {
echo "error: failed to create Docker buildx builder"
exit 1
}
# Set up Docker to start on boot
cat << 'EOF' > /etc/systemd/system/buildkite-agent.service
[Unit]
Description=Buildkite Docker Container
After=docker.service network-online.target
Requires=docker.service network-online.target
[Service]
TimeoutStartSec=0
Restart=always
RestartSec=5
ExecStartPre=-/usr/bin/docker stop buildkite
ExecStartPre=-/usr/bin/docker rm buildkite
ExecStart=/usr/bin/docker run \
--name buildkite \
--restart=unless-stopped \
--network host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /tmp:/tmp \
buildkite:latest
[Install]
WantedBy=multi-user.target
EOF
echo "Building Buildkite image"
# Clean up any previous build artifacts
rm -rf /tmp/fakebun
mkdir -p /tmp/fakebun/scripts /tmp/fakebun/.buildkite
# Copy required files
cp /tmp/agent.mjs /tmp/fakebun/scripts/ || {
echo "error: failed to copy agent.mjs"
exit 1
}
cp /tmp/Dockerfile /tmp/fakebun/.buildkite/Dockerfile || {
echo "error: failed to copy Dockerfile"
exit 1
}
cd /tmp/fakebun || {
echo "error: failed to change directory"
exit 1
}
# Build the Buildkite image. BUN_REPO_REF tells the prefetch step which ref's
# dep versions to bake — passed through from machine.mjs via BUN_BOOTSTRAP_REPO_REF.
docker buildx build \
--platform $(uname -m | sed 's/aarch64/linux\/arm64/;s/x86_64/linux\/amd64/') \
--tag buildkite:latest \
--target buildkite \
--build-arg BUN_REPO_REF="${BUN_BOOTSTRAP_REPO_REF:-main}" \
-f .buildkite/Dockerfile \
--load \
. || {
echo "error: Docker build failed"
exit 1
}
# Pre-pull test docker images (postgres, mysql, redis, minio, …) into the host
# daemon so tests don't fetch them at runtime. /var/lib/docker is on the root
# volume and survives into the AMI. Best-effort — a missing script or docker
# hiccup shouldn't fail the bake.
if git clone --depth=1 --branch "${BUN_BOOTSTRAP_REPO_REF:-main}" \
https://github.com/oven-sh/bun.git /tmp/bun-test-docker; then
if [ -f /tmp/bun-test-docker/test/docker/prepare-ci.ts ]; then
(cd /tmp/bun-test-docker && bun test/docker/prepare-ci.ts) || \
echo "warning: prepare-ci.ts failed; test docker images not pre-pulled"
fi
rm -rf /tmp/bun-test-docker
fi
# Create container to ensure image is cached in AMI
docker container create \
--name buildkite \
--restart=unless-stopped \
buildkite:latest || {
echo "error: failed to create buildkite container"
exit 1
}
# Reload systemd to pick up new service
systemctl daemon-reload
# Enable the service, but don't start it yet
systemctl enable buildkite-agent || {
echo "error: failed to enable buildkite-agent service"
exit 1
}
echo "Bootstrap complete"
echo "To start the Buildkite agent, run: "
echo " systemctl start buildkite-agent"
+16
View File
@@ -0,0 +1,16 @@
# Uploads the latest CI workflow to Buildkite.
# https://buildkite.com/docs/pipelines/defining-steps
#
# Changes to this file must be manually edited here:
# https://buildkite.com/bun/bun/settings/steps
steps:
- if: "build.pull_request.repository.fork"
block: ":eyes:"
prompt: "Did you review the PR?"
blocked_state: "running"
- label: ":pipeline:"
agents:
queue: "build-darwin"
command:
- "node .buildkite/ci.mjs"
+1724
View File
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Fallback Buildkite annotation for infra failures.
#
# `scripts/runner.node.mjs` (test steps) and `scripts/build/ci.ts` (build steps)
# post their own failure annotations, then set build meta-data key
# `reported-$BUILDKITE_JOB_ID` via markBuildkiteStepReported() just before
# exiting. Anything that kills the step before that marker is written (agent
# command-hook failures such as a tart guest that never boots, artifact
# download failures, `node` missing, the runner/build script itself crashing)
# leaves the job red with nothing in the build's annotation list, so the
# failure is only discoverable by opening the raw log. This repository pre-exit
# hook posts a generic annotation for any such job so it shows up alongside
# test/build failures.
#
# The marker is build meta-data, which is server-side and so remains visible
# here even when the reporter ran inside an ephemeral VM (the darwin tart
# agents forward the Job API socket into the guest). Repository hooks run on
# every posix agent, and on Windows via Git Bash. Never exits non-zero.
set -uo pipefail
status="${BUILDKITE_COMMAND_EXIT_STATUS:-0}"
[ "$status" = "0" ] && exit 0
[ -n "${BUILDKITE_JOB_ID:-}" ] || exit 0
# Skip canceled/timed-out jobs: on cancel the agent SIGTERMs then SIGKILLs the
# command, so the reporter had no chance to set the marker, and a canceled
# build annotating every running job is noise, not signal. The agent's
# Executor.Cancel() sets BUILDKITE_JOB_CANCELLED=true in the shell env
# (buildkite/agent@3ab8ab31, v3.94.0+), and additionally
# BUILDKITE_JOB_TIMED_OUT=true when the cancel was a job-level timeout.
if [ "${BUILDKITE_JOB_CANCELLED:-}" = "true" ]; then exit 0; fi
# TODO(ci): the exit-code fallback below is only needed while agents older than
# v3.94.0 remain in the fleet (as of 2026-07: the linux queue=ci image is still
# on v3.87.0 and several bare-metal darwin boxes are on v3.87.0/v3.94.0;
# scripts/bootstrap.sh already pins 3.114.0, those images just need rebaking).
# -1 is the posix agent-killed code, 3221225786 is Windows STATUS_CONTROL_C_EXIT,
# both observed on cancel in build 76127. Delete this `case` once every agent is
# v3.94.0+.
case "$status" in -1|3221225786) exit 0 ;; esac
if buildkite-agent meta-data exists "reported-${BUILDKITE_JOB_ID}" 2>/dev/null; then
exit 0
fi
label="${BUILDKITE_LABEL:-${BUILDKITE_STEP_KEY:-job}}"
job_url="${BUILDKITE_BUILD_URL:-}#${BUILDKITE_JOB_ID}"
# darwin tart agents redirect `tart run` to /tmp/{bk,sc}-<job-id>.log on the
# host. A guest that booted and was cleanly stopped writes only
# `Stopping VM...`; anything else is tart reporting why it exited.
detail=""
for log in "/tmp/bk-${BUILDKITE_JOB_ID}.log" "/tmp/sc-${BUILDKITE_JOB_ID}.log"; do
[ -s "$log" ] || continue
# Cap per-log output so a pathological tart dump cannot push the shared
# `--append`ed annotation past Buildkite's 1 MiB body limit.
body=$(grep -v '^Stopping VM\.\.\.' "$log" 2>/dev/null | head -c 2048 || true)
[ -n "$body" ] && detail+="${detail:+$'\n'}${log##*/}: ${body}"
done
[ -n "$detail" ] || detail="see the job log for output"
# Match escapeCodeBlock() in scripts/runner.node.mjs: the body renders inside a
# ```terminal fence, so only backticks need escaping.
preview=$(printf '%s' "$detail" | sed 's/`/\\`/g')
printf '<details><summary><a><code>step failed outside runner</code></a> - exit %s on <a href="%s">%s</a></summary>\n\n```terminal\n%s\n```\n\n</details>\n\n' \
"$status" "$job_url" "$label" "$preview" \
| buildkite-agent annotate --append --style error --context step-failed-outside-runner --priority 5 2>&1 \
|| echo "pre-exit: buildkite-agent annotate failed (non-fatal)"
exit 0
@@ -0,0 +1,288 @@
# Batch Windows code signing for all bun-windows-*.zip Buildkite artifacts.
#
# This runs as a dedicated pipeline step on a Windows x64 agent after all
# Windows build-bun steps complete. Signing is done here instead of inline
# during each build because DigiCert smctl is x64-only and silently fails
# under ARM64 emulation.
#
# Each zip is downloaded, its exe signed in place, and the zip is re-packed
# with the same name so downstream steps (release, tests) see signed binaries.
param(
# Comma-separated list. powershell.exe -File passes everything as
# literal strings, so [string[]] with "a,b,c" becomes a 1-element array.
[Parameter(Mandatory=$true)]
[string]$Artifacts,
# Comma-separated, same length as Artifacts, mapping each zip to its source step.
[Parameter(Mandatory=$true)]
[string]$BuildSteps
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$ArtifactList = $Artifacts -split ","
$BuildStepList = $BuildSteps -split ","
# smctl shells out to signtool.exe which is only in PATH when the VS dev
# environment is loaded. Dot-source the existing helper to set it up.
. $PSScriptRoot\..\..\scripts\vs-shell.ps1
function Log-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Cyan
}
function Log-Success {
param([string]$Message)
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
}
function Log-Error {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
function Log-Debug {
param([string]$Message)
if ($env:DEBUG -eq "true" -or $env:DEBUG -eq "1") {
Write-Host "[DEBUG] $Message" -ForegroundColor Gray
}
}
function Get-BuildkiteSecret {
param([string]$Name)
$value = & buildkite-agent secret get $Name 2>&1
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrEmpty($value)) {
throw "Failed to fetch Buildkite secret: $Name"
}
return $value
}
function Ensure-Secrets {
Log-Info "Fetching signing secrets from Buildkite..."
$env:SM_API_KEY = Get-BuildkiteSecret "SM_API_KEY"
$env:SM_CLIENT_CERT_PASSWORD = Get-BuildkiteSecret "SM_CLIENT_CERT_PASSWORD"
$env:SM_CLIENT_CERT_FILE = Get-BuildkiteSecret "SM_CLIENT_CERT_FILE"
$env:SM_KEYPAIR_ALIAS = Get-BuildkiteSecret "SM_KEYPAIR_ALIAS"
$env:SM_HOST = Get-BuildkiteSecret "SM_HOST"
Log-Success "All signing secrets fetched"
}
function Setup-Certificate {
Log-Info "Decoding client certificate..."
try {
$tempCertPath = Join-Path $env:TEMP "digicert_cert_$(Get-Random).p12"
$certBytes = [System.Convert]::FromBase64String($env:SM_CLIENT_CERT_FILE)
[System.IO.File]::WriteAllBytes($tempCertPath, $certBytes)
$fileSize = (Get-Item $tempCertPath).Length
if ($fileSize -lt 100) {
throw "Decoded certificate too small: $fileSize bytes"
}
$env:SM_CLIENT_CERT_FILE = $tempCertPath
$script:TempCertPath = $tempCertPath
Log-Success "Certificate decoded ($fileSize bytes)"
} catch {
if (Test-Path $env:SM_CLIENT_CERT_FILE) {
Log-Info "Using certificate file path directly: $env:SM_CLIENT_CERT_FILE"
} else {
throw "SM_CLIENT_CERT_FILE is neither valid base64 nor an existing file"
}
}
}
function Install-KeyLocker {
Log-Info "Setting up DigiCert KeyLocker tools..."
$installDir = "C:\BuildTools\DigiCert"
$smctlPath = Join-Path $installDir "smctl.exe"
if (Test-Path $smctlPath) {
Log-Success "smctl already installed at $smctlPath"
$env:PATH = "$installDir;$env:PATH"
return $smctlPath
}
if (!(Test-Path $installDir)) {
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
}
# smctl is x64-only; this script must run on an x64 agent
$msiUrl = "https://bun-ci-assets.bun.sh/Keylockertools-windows-x64.msi"
$msiPath = Join-Path $env:TEMP "Keylockertools-windows-x64.msi"
Log-Info "Downloading KeyLocker MSI from $msiUrl"
if (Test-Path $msiPath) { Remove-Item $msiPath -Force }
(New-Object System.Net.WebClient).DownloadFile($msiUrl, $msiPath)
if (!(Test-Path $msiPath)) { throw "MSI download failed" }
Log-Info "Installing KeyLocker MSI..."
$proc = Start-Process -FilePath "msiexec.exe" -Wait -PassThru -NoNewWindow -ArgumentList @(
"/i", "`"$msiPath`"",
"/quiet", "/norestart",
"TARGETDIR=`"$installDir`"",
"INSTALLDIR=`"$installDir`"",
"ACCEPT_EULA=1",
"ADDLOCAL=ALL"
)
if ($proc.ExitCode -ne 0) {
throw "MSI install failed with exit code $($proc.ExitCode)"
}
if (!(Test-Path $smctlPath)) {
$found = Get-ChildItem -Path $installDir -Filter "smctl.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
if ($found) {
$smctlPath = $found.FullName
$installDir = $found.DirectoryName
} else {
throw "smctl.exe not found after install"
}
}
$env:PATH = "$installDir;$env:PATH"
Log-Success "smctl installed at $smctlPath"
return $smctlPath
}
function Configure-KeyLocker {
param([string]$Smctl)
Log-Info "Configuring KeyLocker..."
$version = & $Smctl --version 2>&1
Log-Debug "smctl version: $version"
$saveOut = & $Smctl credentials save $env:SM_API_KEY $env:SM_CLIENT_CERT_PASSWORD 2>&1 | Out-String
Log-Debug "credentials save: $saveOut"
$healthOut = & $Smctl healthcheck 2>&1 | Out-String
Log-Debug "healthcheck: $healthOut"
if ($healthOut -notlike "*Healthy*" -and $healthOut -notlike "*SUCCESS*" -and $LASTEXITCODE -ne 0) {
Log-Error "healthcheck output: $healthOut"
# Don't throw — healthcheck is sometimes flaky but signing still works
}
$syncOut = & $Smctl windows certsync 2>&1 | Out-String
Log-Debug "certsync: $syncOut"
Log-Success "KeyLocker configured"
}
function Download-Artifact {
param([string]$Name, [string]$StepKey)
Log-Info "Downloading $Name from step $StepKey"
& buildkite-agent artifact download $Name . --step $StepKey
if ($LASTEXITCODE -ne 0 -or !(Test-Path $Name)) {
throw "Failed to download artifact: $Name"
}
Log-Success "Downloaded $Name ($((Get-Item $Name).Length) bytes)"
}
function Sign-Exe {
param([string]$ExePath, [string]$Smctl)
$fileName = Split-Path $ExePath -Leaf
Log-Info "Signing $fileName ($((Get-Item $ExePath).Length) bytes)..."
$existing = Get-AuthenticodeSignature $ExePath
if ($existing.Status -eq "Valid") {
Log-Info "$fileName already signed by $($existing.SignerCertificate.Subject), skipping"
return
}
$out = & $Smctl sign --keypair-alias $env:SM_KEYPAIR_ALIAS --input $ExePath --verbose 2>&1 | Out-String
Log-Info "smctl output: $out"
# smctl exits 0 even on failure — must also check output text
if ($LASTEXITCODE -ne 0 -or $out -like "*FAILED*" -or $out -like "*error*") {
throw "Signing failed for $fileName (exit $LASTEXITCODE): $out"
}
$sig = Get-AuthenticodeSignature $ExePath
if ($sig.Status -ne "Valid") {
throw "$fileName signature verification failed: $($sig.Status) - $($sig.StatusMessage)"
}
Log-Success "$fileName signed by $($sig.SignerCertificate.Subject)"
}
function Sign-Artifact {
param([string]$ZipName, [string]$Smctl)
Write-Host "================================================" -ForegroundColor Cyan
Write-Host " Signing $ZipName" -ForegroundColor Cyan
Write-Host "================================================" -ForegroundColor Cyan
$extractDir = [System.IO.Path]::GetFileNameWithoutExtension($ZipName)
if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force }
Log-Info "Extracting $ZipName"
Expand-Archive -Path $ZipName -DestinationPath . -Force
if (!(Test-Path $extractDir)) {
throw "Expected directory $extractDir not found after extraction"
}
$exes = Get-ChildItem -Path $extractDir -Filter "*.exe"
if ($exes.Count -eq 0) {
throw "No .exe files found in $extractDir"
}
foreach ($exe in $exes) {
Sign-Exe -ExePath $exe.FullName -Smctl $Smctl
}
Log-Info "Re-packing $ZipName"
Remove-Item $ZipName -Force
# Use the same zip command as the build-bun step (scripts/build/ci.ts makeZip)
# so the signed archive's entry layout matches the original: forward-slash
# paths and a directory entry. Compress-Archive writes backslash separators,
# which violates the ZIP spec and triggers warnings in non-Windows unzip.
& cmake -E tar cfv $ZipName --format=zip $extractDir
if ($LASTEXITCODE -ne 0) {
throw "cmake -E tar failed for $ZipName"
}
Remove-Item $extractDir -Recurse -Force
Log-Info "Uploading signed $ZipName"
& buildkite-agent artifact upload $ZipName
if ($LASTEXITCODE -ne 0) {
throw "Failed to upload $ZipName"
}
Log-Success "$ZipName signed and uploaded"
}
# Main
try {
Write-Host "================================================" -ForegroundColor Cyan
Write-Host " Windows Artifact Code Signing" -ForegroundColor Cyan
Write-Host "================================================" -ForegroundColor Cyan
if ($ArtifactList.Count -ne $BuildStepList.Count) {
throw "Artifact count ($($ArtifactList.Count)) must match BuildStep count ($($BuildStepList.Count))"
}
Log-Info "Will sign $($ArtifactList.Count) artifacts: $($ArtifactList -join ', ')"
Ensure-Secrets
Setup-Certificate
$smctl = Install-KeyLocker
Configure-KeyLocker -Smctl $smctl
for ($i = 0; $i -lt $ArtifactList.Count; $i++) {
Download-Artifact -Name $ArtifactList[$i] -StepKey $BuildStepList[$i]
Sign-Artifact -ZipName $ArtifactList[$i] -Smctl $smctl
}
Write-Host "================================================" -ForegroundColor Green
Write-Host " All artifacts signed successfully" -ForegroundColor Green
Write-Host "================================================" -ForegroundColor Green
exit 0
} catch {
Log-Error "Signing failed: $_"
exit 1
} finally {
if ($script:TempCertPath -and (Test-Path $script:TempCertPath)) {
Remove-Item $script:TempCertPath -Force -ErrorAction SilentlyContinue
}
}
+409
View File
@@ -0,0 +1,409 @@
#!/bin/bash
set -eo pipefail
function assert_main() {
if [ -z "$BUILDKITE_REPO" ]; then
echo "error: Cannot find repository for this build"
exit 1
fi
if [ -z "$BUILDKITE_COMMIT" ]; then
echo "error: Cannot find commit for this build"
exit 1
fi
if [ -n "$BUILDKITE_PULL_REQUEST_REPO" ] && [ "$BUILDKITE_REPO" != "$BUILDKITE_PULL_REQUEST_REPO" ]; then
echo "error: Cannot upload release from a fork"
exit 1
fi
if [ "$BUILDKITE_PULL_REQUEST" != "false" ]; then
echo "error: Cannot upload release from a pull request"
exit 1
fi
if [ "$BUILDKITE_BRANCH" != "main" ]; then
echo "error: Cannot upload release from a branch other than main"
exit 1
fi
}
function assert_buildkite_agent() {
if ! command -v "buildkite-agent" &> /dev/null; then
echo "error: Cannot find buildkite-agent, please install it:"
echo "https://buildkite.com/docs/agent/v3/install"
exit 1
fi
}
function assert_github() {
assert_command "gh" "gh" "https://github.com/cli/cli#installation"
assert_buildkite_secret "GITHUB_TOKEN"
# gh expects the token in $GH_TOKEN
export GH_TOKEN="$GITHUB_TOKEN"
}
function assert_aws() {
assert_command "aws" "awscli" "https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
for secret in "AWS_ACCESS_KEY_ID" "AWS_SECRET_ACCESS_KEY" "AWS_ENDPOINT"; do
assert_buildkite_secret "$secret"
done
assert_buildkite_secret "AWS_BUCKET" --skip-redaction
}
function assert_sentry() {
assert_command "sentry-cli" "getsentry/tools/sentry-cli" "https://docs.sentry.io/cli/installation/"
for secret in "SENTRY_AUTH_TOKEN" "SENTRY_ORG" "SENTRY_PROJECT"; do
assert_buildkite_secret "$secret"
done
}
function run_command() {
set -x
"$@"
{ local status=$?; set +x; } 2>/dev/null
return "$status"
}
# Zips are read with unzip and written with cmake. Not one tool for both:
# `cmake -E tar xf` streams, so it exits 0 on a truncated archive and leaves a
# corrupt file behind where unzip exits 9, and `zip` is not on the agent image
# (which has no root to install it). cmake is what wrote these zips in the
# first place — scripts/build/ci.ts makeZip.
function assert_archive_tools() {
for tool in "unzip" "cmake"; do
if ! command -v "$tool" &> /dev/null; then
echo "error: Cannot find $tool"
echo ""
echo "hint: the agent image is supposed to have it; see scripts/bootstrap.sh"
exit 1
fi
done
}
# Tools this script installs go to a writable directory on PATH instead of
# /usr/local/bin, which needs root on most agents.
function ensure_tools_bin() {
if [ -n "$TOOLS_BIN" ]; then
return
fi
TOOLS_DIR="${HOME:-}/.cache/bun-release-tools"
if [ -z "$HOME" ] || ! mkdir -p "$TOOLS_DIR/bin" 2> /dev/null; then
TOOLS_DIR="$(mktemp -d)"
mkdir -p "$TOOLS_DIR/bin"
fi
TOOLS_BIN="$TOOLS_DIR/bin"
export PATH="$TOOLS_BIN:$PATH"
}
function install_gh_linux() {
local arch
case "$(uname -m)" in
x86_64 | amd64) arch="amd64" ;;
aarch64 | arm64) arch="arm64" ;;
*) echo "error: Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
# Resolve the version from the releases/latest redirect, not the REST API: the API is rate
# limited to 60 req/hour per IP (GITHUB_TOKEN is not exported yet), and piping curl into a
# short-circuiting reader such as `grep -m1` makes curl exit 23 (EPIPE) under pipefail.
local url version
url="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/cli/cli/releases/latest")"
version="${url##*/tag/v}"
if [ -z "$version" ] || [ "$version" == "$url" ]; then
echo "error: Cannot determine latest gh release version from: $url"
exit 1
fi
local dir
dir="$(mktemp -d)"
run_command curl -fsSL "https://github.com/cli/cli/releases/download/v${version}/gh_${version}_linux_${arch}.tar.gz" -o "$dir/gh.tar.gz"
run_command tar -xzf "$dir/gh.tar.gz" -C "$dir" --strip-components=1
ensure_tools_bin
run_command install -m 0755 "$dir/bin/gh" "$TOOLS_BIN/gh"
rm -rf "$dir"
}
function install_aws_linux() {
local dir
dir="$(mktemp -d)"
run_command curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip" -o "$dir/awscliv2.zip"
run_command unzip -q "$dir/awscliv2.zip" -d "$dir"
ensure_tools_bin
run_command "$dir/aws/install" --update -i "$TOOLS_DIR/aws-cli" -b "$TOOLS_BIN"
rm -rf "$dir"
}
function install_sentry_cli_linux() {
# The installer drops a single static binary into INSTALL_DIR.
ensure_tools_bin
run_command bash -c "curl -fsSL https://sentry.io/get-cli/ | INSTALL_DIR='$TOOLS_BIN' sh"
}
function assert_command() {
local command="$1"
local package="$2"
local help_url="$3"
if command -v "$command" &> /dev/null; then
return
fi
echo "warning: $command is not installed, installing..."
if command -v brew &> /dev/null; then
HOMEBREW_NO_AUTO_UPDATE=1 run_command brew install "$package"
elif [ "$(uname -s)" == "Linux" ]; then
case "$command" in
gh) install_gh_linux ;;
aws) install_aws_linux ;;
sentry-cli) install_sentry_cli_linux ;;
*) echo "error: Don't know how to install $command on Linux"; exit 1 ;;
esac
else
echo "error: Cannot install $command, please install it"
if [ -n "$help_url" ]; then
echo ""
echo "hint: See $help_url for help"
fi
exit 1
fi
if ! command -v "$command" &> /dev/null; then
echo "error: Failed to install $command"
if [ -n "$help_url" ]; then
echo ""
echo "hint: See $help_url for help"
fi
exit 1
fi
}
function assert_buildkite_secret() {
local key="$1"
local value=$(buildkite-agent secret get "$key" ${@:2})
if [ -z "$value" ]; then
echo "error: Cannot find $key secret"
echo ""
echo "hint: Create a secret named $key with a value:"
echo "https://buildkite.com/docs/pipelines/buildkite-secrets"
exit 1
fi
export "$key"="$value"
}
function release_tag() {
local version="$1"
if [ "$version" == "canary" ]; then
echo "canary"
else
echo "bun-v$version"
fi
}
function create_sentry_release() {
local version="$1"
local release="$version"
if [ "$version" == "canary" ]; then
release="$BUILDKITE_COMMIT-canary"
fi
run_command sentry-cli releases new "$release" --finalize
run_command sentry-cli releases set-commits "$release" --auto --ignore-missing
if [ "$version" == "canary" ]; then
run_command sentry-cli deploys new --env="canary" --release="$release"
fi
}
function download_buildkite_artifact() {
local name="$1"
local dir="$2"
if [ -z "$dir" ]; then
dir="."
fi
# When signing ran, Windows zips exist in two steps with the same name
# (build-bun unsigned, windows-sign signed). Pin to the sign step to
# guarantee we get the signed one.
local step_args=()
if [[ -n "$WINDOWS_ARTIFACT_STEP" && "$name" == bun-windows-* ]]; then
step_args=(--step "$WINDOWS_ARTIFACT_STEP")
fi
run_command buildkite-agent artifact download "$name" "$dir" "${step_args[@]}"
if [ ! -f "$dir/$name" ]; then
echo "error: Cannot find Buildkite artifact: $name"
exit 1
fi
}
function upload_github_assets() {
local tag="$(release_tag "$1")"
run_command gh release upload "$tag" "${@:2}" --clobber --repo "$BUILDKITE_REPO"
}
function update_github_release() {
local version="$1"
local tag="$(release_tag "$version")"
if [ "$tag" == "canary" ]; then
run_command gh release edit "$tag" --repo "$BUILDKITE_REPO" \
--notes "This release of Bun corresponds to the commit: $BUILDKITE_COMMIT"
fi
}
# S3 is a mirror; `bun upgrade` and install.sh read the GitHub release. A
# canary that made it to GitHub but not S3 has shipped, so don't fail it.
function upload_s3_files() {
local version="$1"
local files=("${@:2}")
local commit_folder="releases/$BUILDKITE_COMMIT"
if [ "$version" == "canary" ]; then
commit_folder="$commit_folder-canary"
fi
local status=0 file
for file in "${files[@]}"; do
run_command aws --endpoint-url="$AWS_ENDPOINT" s3 cp "$file" "s3://$AWS_BUCKET/$commit_folder/$file" || status=1
run_command aws --endpoint-url="$AWS_ENDPOINT" s3 cp "$file" "s3://$AWS_BUCKET/releases/$version/$file" || status=1
done
if [ "$status" -eq 0 ]; then
return 0
fi
if [ "$version" == "canary" ]; then
echo "warn: Some S3 uploads failed, ignoring since this is a canary release"
return 0
fi
echo "error: Some S3 uploads failed"
exit 1
}
function send_discord_announcement() {
local value=$(buildkite-agent secret get "BUN_ANNOUNCE_CANARY_WEBHOOK_URL")
if [ -z "$value" ]; then
echo "warn: BUN_ANNOUNCE_CANARY_WEBHOOK_URL not set, skipping Discord announcement"
return
fi
local version="$1"
local commit="$BUILDKITE_COMMIT"
local short_sha="${commit:0:7}"
local commit_url="https://github.com/oven-sh/bun/commit/$commit"
if [ "$version" == "canary" ]; then
local json_payload=$(cat <<EOF
{
"embeds": [{
"title": "New Bun Canary now available",
"description": "A new canary build of Bun has been automatically uploaded ([${short_sha}](${commit_url})). To upgrade, run:\n\n\`\`\`shell\nbun upgrade --canary\n\`\`\`\nCommit: \`${commit}\`",
"color": 16023551,
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}]
}
EOF
)
curl -H "Content-Type: application/json" \
-d "$json_payload" \
-sf \
"$value" >/dev/null
fi
}
function create_release() {
assert_main
assert_buildkite_agent
assert_archive_tools
assert_github
assert_aws
assert_sentry
local tag="$1" # 'canary' or 'x.y.z'
local artifacts=(
bun-darwin-aarch64.zip
bun-darwin-aarch64-profile.zip
bun-darwin-x64.zip
bun-darwin-x64-profile.zip
bun-linux-aarch64.zip
bun-linux-aarch64-profile.zip
bun-linux-x64.zip
bun-linux-x64-profile.zip
bun-linux-aarch64-musl.zip
bun-linux-aarch64-musl-profile.zip
bun-linux-x64-musl.zip
bun-linux-x64-musl-profile.zip
bun-linux-aarch64-android.zip
bun-linux-aarch64-android-profile.zip
bun-linux-x64-android.zip
bun-linux-x64-android-profile.zip
bun-freebsd-aarch64.zip
bun-freebsd-aarch64-profile.zip
bun-freebsd-x64.zip
bun-freebsd-x64-profile.zip
bun-windows-x64.zip
bun-windows-x64-profile.zip
bun-windows-aarch64.zip
bun-windows-aarch64-profile.zip
)
# x64 ships one nehalem binary under the plain name. Re-zip it under the
# historical `-baseline` name (inner dir renamed) so older `bun upgrade`
# clients that still request `-baseline` extract correctly.
function alias_baseline_artifact() {
local artifact="$1"
case "$artifact" in
bun-darwin-x64.zip) echo "bun-darwin-x64-baseline.zip" ;;
bun-darwin-x64-profile.zip) echo "bun-darwin-x64-baseline-profile.zip" ;;
bun-linux-x64.zip) echo "bun-linux-x64-baseline.zip" ;;
bun-linux-x64-profile.zip) echo "bun-linux-x64-baseline-profile.zip" ;;
bun-linux-x64-musl.zip) echo "bun-linux-x64-musl-baseline.zip" ;;
bun-linux-x64-musl-profile.zip) echo "bun-linux-x64-musl-baseline-profile.zip" ;;
bun-windows-x64.zip) echo "bun-windows-x64-baseline.zip" ;;
bun-windows-x64-profile.zip) echo "bun-windows-x64-baseline-profile.zip" ;;
*) echo "" ;;
esac
}
# Repack `$src_zip` (inner dir = basename of $src_zip) as `$dst_zip` with the
# inner dir renamed to match `$dst_zip`'s basename, which is what install.sh
# extracts. Not done in the build step's makeZip, where the staging dir is
# already in hand: the Windows zips are re-uploaded by the signing step, so
# an alias built there would carry the unsigned binary. Runs in a fresh
# mktemp dir so a caller-CWD change can't collide with the extracted names.
function rezip_as() {
local src_zip="$1" dst_zip="$2"
local src_dir="${src_zip%.zip}" dst_dir="${dst_zip%.zip}"
local abs_src="$PWD/$src_zip" abs_dst="$PWD/$dst_zip"
local work; work="$(mktemp -d)"
run_command unzip -q -d "$work" "$abs_src"
run_command mv "$work/$src_dir" "$work/$dst_dir"
(cd "$work" && run_command cmake -E tar cf "$abs_dst" --format=zip "$dst_dir")
run_command rm -rf "$work"
}
# Fetch everything up front so the GitHub release can take all assets in one
# `gh release upload`; per-file uploads raced on the same release.
local files=() pids=() artifact
for artifact in "${artifacts[@]}"; do
download_buildkite_artifact "$artifact" & pids+=("$!")
files+=("$artifact")
done
# Per-pid: a bare `wait` returns 0 however the children exited.
local pid status=0
for pid in "${pids[@]}"; do
wait "$pid" || status=1
done
if [ "$status" -ne 0 ]; then
echo "error: Failed to download one or more Buildkite artifacts"
exit 1
fi
for artifact in "${artifacts[@]}"; do
local alias="$(alias_baseline_artifact "$artifact")"
if [ -n "$alias" ]; then
rezip_as "$artifact" "$alias"
files+=("$alias")
fi
done
upload_github_assets "$tag" "${files[@]}"
update_github_release "$tag"
create_sentry_release "$tag"
send_discord_announcement "$tag"
upload_s3_files "$tag" "${files[@]}"
}
function assert_canary() {
if [ -z "$CANARY" ] || [ "$CANARY" == "0" ]; then
echo "warn: Skipping release because this is not a canary build"
exit 0
fi
}
assert_canary
create_release "canary"
+20
View File
@@ -0,0 +1,20 @@
# Regenerates test/expected-durations.json from recent Buildkite runs and
# uploads it as a build artifact. Attach a weekly schedule to this pipeline in
# the Buildkite UI (it is not wired into ci.mjs so it never runs on PRs).
#
# The runner uses the checked-in copy for sharding; refresh that copy by
# downloading this artifact and committing it when the shard balance drifts.
steps:
- label: ":stopwatch: update-test-durations"
if: build.source == "schedule" || build.source == "ui"
agents:
queue: build-linux
command: |
node scripts/update-test-durations.mjs --builds 5
node scripts/update-parallel-allowlist.mjs --builds 300
buildkite-agent artifact upload test/expected-durations.json
buildkite-agent artifact upload test/parallel-allowlist.json
env:
# The script reads BUILDKITE_API_TOKEN; the agent environment hook
# already exports a read-scoped token under this name.
BUILDKITE_API_TOKEN: "$BUILDKITE_API_TOKEN"
+9
View File
@@ -0,0 +1,9 @@
WarningsAsErrors: "*"
FormatStyle: webkit
Checks: >
-*,
clang-analyzer-*,
-clang-analyzer-optin.core.EnumCastOutOfRange
-clang-analyzer-webkit.UncountedLambdaCapturesChecker
-clang-analyzer-optin.core.EnumCastOutOfRange
-clang-analyzer-webkit.RefCntblBaseVirtualDtor
+43
View File
@@ -0,0 +1,43 @@
---
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
description: Find duplicate GitHub issues
---
# Issue deduplication command
Find up to 3 likely duplicate issues for a given GitHub issue.
To do this, follow these steps precisely:
1. Use an agent to check if the GitHub issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicate detection comment (check for the exact HTML marker `<!-- dedupe-bot:marker -->` in the issue comments - ignore other bot comments). If so, do not proceed.
2. Use an agent to view a GitHub issue, and ask the agent to return a summary of the issue
3. Then, launch 5 parallel agents to search GitHub for duplicates of this issue, using diverse keywords and search approaches, using the summary from Step 2. **IMPORTANT**: Always scope searches with `repo:owner/repo` to constrain results to the current repository only.
4. Next, feed the results from Steps 2 and 3 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
Notes (be sure to tell this to your agents, too):
- Use `gh` to interact with GitHub, rather than web fetch
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
- Make a todo list first
- Always scope searches with `repo:owner/repo` to prevent cross-repo false positives
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
---
Found 3 possible duplicate issues:
1. <link to issue>
2. <link to issue>
3. <link to issue>
This issue will be automatically closed as a duplicate in 3 days.
- If your issue is a duplicate, please close it and 👍 the existing issue instead
- To prevent auto-closure, add a comment or 👎 this comment
🤖 Generated with [Claude Code](https://claude.ai/code)
<!-- dedupe-bot:marker -->
---
+47
View File
@@ -0,0 +1,47 @@
---
allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*), Bash(gh search:*), Bash(gh pr list:*), Bash(gh api:*), Bash(gh pr comment:*), Bash(git diff:*), Bash(git log:*), Bash(git show:*), Bash(git status:*), Bash(git rev-parse:*), Bash(git ls-files:*), Bash(git grep:*), Bash(git branch:*), Bash(git remote:*)
description: Find open PRs that may duplicate a given PR
---
# Find duplicate PRs command
Find other open, unmerged pull requests that may be duplicates of (or substantially overlap with) a given PR.
To do this, follow these steps precisely:
1. Use an agent to check if the PR (a) is closed/merged, or (b) already has a duplicate-PR comment (check for the exact HTML marker `<!-- find-duplicate-prs-bot:marker -->` in the PR comments — ignore other bot comments). If so, do not proceed.
2. Use an agent to view the PR title, body, and diff (`gh pr view` and `gh pr diff`), and ask the agent to return a summary of:
- What the PR changes (files modified, functions changed, features added/fixed)
- Key technical terms, error messages, API names, or module names involved
- Any issue numbers referenced (e.g. "fixes #123") — two PRs that fix the same issue are likely duplicates
3. Then, launch 3 parallel agents to search GitHub for other **open, unmerged** pull requests that may duplicate this one, using diverse keywords derived from the summary in Step 2. **IMPORTANT**: Always scope searches with `repo:owner/repo is:pr is:open is:unmerged` to constrain results to the current repository. Each agent should try a different search strategy:
- Agent 1: Search using the PR title keywords and referenced issue numbers
- Agent 2: Search using `gh pr list` filtered by the same files/paths touched in the diff
- Agent 3: Search using feature/API/function names from the changed code
4. Next, feed the results from Steps 2 and 3 into another agent, so that it can filter out false positives that are not actually duplicates. Exclude the PR itself. Only keep PRs that change the same code area for the same purpose, or fix the same referenced issue. **If there are no likely duplicates remaining, do not comment at all** — silently exit.
5. Finally, if and only if at least one likely duplicate was found, comment on the PR.
Notes (be sure to tell this to your agents, too):
- Use `gh` to interact with GitHub, rather than web fetch
- You may also use read-only `git` commands (`git diff`, `git log`, `git show`, `git grep`, etc.) against the local checkout
- Do not use other tools beyond `gh` and `git` (eg. don't use other MCP servers, file edit, etc.)
- Make a todo list first
- Always scope searches with `repo:owner/repo` to prevent cross-repo false positives
- Only match against **open, unmerged** PRs — do not suggest closed, merged, or draft PRs
- Never include the input PR in the results
- **Do not post a comment if zero duplicates are found**
- For your comment, follow the following format precisely (assuming for this example that you found 2 likely duplicates):
---
This PR may be a duplicate of:
1. <link to PR> - <one-line summary of why it overlaps>
2. <link to PR> - <one-line summary of why it overlaps>
🤖 Generated with [Claude Code](https://claude.ai/code)
<!-- find-duplicate-prs-bot:marker -->
---
+57
View File
@@ -0,0 +1,57 @@
---
allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh issue view:*), Bash(gh api:*), Bash(gh pr comment:*), Bash(git diff:*), Bash(git log:*), Bash(git show:*), Bash(git status:*), Bash(git rev-parse:*), Bash(git ls-files:*), Bash(git grep:*), Bash(git branch:*), Bash(git remote:*)
description: Find GitHub issues that a PR might fix
---
# Find issues for PR command
Find open GitHub issues that a pull request might fix. Include all likely matches — do not artificially limit the number of results.
To do this, follow these steps precisely:
1. Use an agent to check if the PR (a) is closed/merged, or (b) already has a related-issues comment (check for the exact HTML marker `<!-- find-issues-bot:marker -->` in the PR comments - ignore other bot comments). If so, do not proceed.
2. Use an agent to view the PR title, body, and diff (`gh pr view` and `gh pr diff`), and ask the agent to return a summary of:
- What the PR changes (files modified, functions changed, features added/fixed)
- Key technical terms, error messages, API names, or module names involved
- Any issue numbers already referenced in the PR body or commit messages
3. Then, launch 5 parallel agents to search GitHub for open issues that this PR might fix, using diverse keywords and search approaches derived from the summary in Step 2. **IMPORTANT**: Always scope searches with `repo:owner/repo` to constrain results to the current repository only. Each agent should try a different search strategy:
- Agent 1: Search using error messages or symptoms described in the diff
- Agent 2: Search using feature/module names from the changed files
- Agent 3: Search using API names or function names that were modified
- Agent 4: Search using keywords from the PR title and description
- Agent 5: Search using broader terms related to the area of code changed
4. Next, feed the results from Steps 2 and 3 into another agent, so that it can filter out false positives that are likely not actually related to the PR's changes. Exclude issues already referenced in the PR body (e.g. "fixes #123", "closes #456", "resolves #789"). Only keep issues where the PR changes are clearly relevant to the issue. If there are no related issues remaining, do not proceed.
5. Finally, comment on the PR with all related open issues found (or zero, if there are no likely matches). Do not cap the number — list every issue that is a likely match.
Notes (be sure to tell this to your agents, too):
- Use `gh` to interact with GitHub, rather than web fetch
- You may also use read-only `git` commands (`git diff`, `git log`, `git show`, `git grep`, etc.) against the local checkout
- Do not use other tools beyond `gh` and `git` (eg. don't use other MCP servers, file edit, etc.)
- Make a todo list first
- Always scope searches with `repo:owner/repo` to prevent cross-repo false positives
- Only match against **open** issues - do not suggest closed issues
- Exclude issues that are already linked in the PR description
- For your comment, follow the following format precisely (assuming for this example that you found 3 related issues). The fenced block at the bottom must contain one `Fixes #<number>` line per issue, in the same order, so the PR author can copy-paste it directly into the PR description:
---
Found 3 issues this PR may fix:
1. <link to issue> - <one-line summary of why this PR is relevant>
2. <link to issue> - <one-line summary of why this PR is relevant>
3. <link to issue> - <one-line summary of why this PR is relevant>
> If this is helpful, copy the block below into the PR description to auto-close these issues on merge.
```
Fixes #<number>
Fixes #<number>
Fixes #<number>
```
🤖 Generated with [Claude Code](https://claude.ai/code)
<!-- find-issues-bot:marker -->
---
+101
View File
@@ -0,0 +1,101 @@
---
description: Upgrade Bun's BoringSSL fork (oven-sh/boringssl) to the latest upstream google/boringssl
---
Bun pins BoringSSL by **commit SHA** in `scripts/build/deps/boringssl.ts` (`BORINGSSL_COMMIT`). The build downloads a tarball from `oven-sh/boringssl` at that SHA — there is no submodule and `vendor/boringssl/` is git-ignored.
The fork carries a small patch set on top of upstream (see "Preserved patches" below). Upgrading means: merge `google/boringssl` into `oven-sh/boringssl@master`, push, then bump the SHA + regenerate source lists in Bun.
## Steps
### 1. Clone the fork and merge upstream
```sh
git clone https://github.com/oven-sh/boringssl.git /tmp/boringssl
cd /tmp/boringssl
git remote add upstream https://github.com/google/boringssl.git
git fetch upstream
git log --oneline $(git merge-base HEAD upstream/main)..HEAD # our patches
git merge upstream/main
```
Resolve conflicts **preserving the fork's additions**. Most conflicts are upstream's periodic `|...|` → `` `...` `` doc-comment restyle landing adjacent to a line we added — keep our line + upstream's comment style. For `include/openssl/nid.h`, keep upstream's new NIDs **and** ours (our NID numbers are from OpenSSL's range and don't collide with BoringSSL's sequential allocation).
### 2. Verify the merged tree builds
```sh
cmake -B build -GNinja -DCMAKE_BUILD_TYPE=Release
ninja -C build crypto ssl decrepit
```
This catches mis-resolved conflicts before they reach Bun's CI.
### 3. Push to the fork
The default branch is **`master`** (not `main`).
```sh
git push origin HEAD:master
NEW_SHA=$(git rev-parse HEAD)
```
### 4. Bump Bun
In the bun repo:
- `scripts/build/deps/boringssl.ts` — set `BORINGSSL_COMMIT` to `$NEW_SHA`.
- `test/js/node/process/process.test.js` — update the `boringssl:` entry in `expectedVersions` to `$NEW_SHA`.
- `src/js/node/tls.ts` — three hand-maintained mirrors of BoringSSL tables must be re-derived from the new pin (a test pins the current set, but cannot see upstream additions on its own): `SUPPORTED_ECDH_GROUPS` ← `ssl/ssl_key_share.cc` `kNamedGroups` (every `name` and non-empty `alias`), `_VALID_CIPHERS_SET` ← `ssl/ssl_cipher.cc` `kCiphers`, `CIPHER_LIST_SELECTORS` ← `ssl/ssl_cipher.cc` `kCipherAliases`.
- Regenerate the source lists (the file's header comment has the exact one-liner). Only `gen/sources.json` is authoritative — diff old vs new and apply the delta:
```sh
rm -rf vendor/boringssl # force re-fetch on next build
bun bd --target=clone-boringssl
bun -e 'const j=require("./vendor/boringssl/gen/sources.json");
const f=l=>l.map(JSON.stringify).join(", ");
for(const k of ["bcm","crypto","ssl","decrepit"]) console.log(k,"\n",f(j[k].srcs));
console.log("asm\n",f([...j.bcm.asm,...j.crypto.asm]));
console.log("nasm\n",f([...j.bcm.nasm,...j.crypto.nasm]))'
```
### 5. Build and test locally
```sh
rm -rf vendor/boringssl
bun bd -p 'require("crypto").createHash("sha3-256").update("hi").digest("hex")'
bun bd test test/js/node/crypto/ test/js/bun/crypto/
bun bd test test/js/node/tls/ test/js/web/fetch/fetch.tls.test.ts
```
### 6. Open the Bun PR
```sh
git checkout -b claude/boringssl-<upstream-short-sha>
git commit -am "deps: upgrade BoringSSL to <upstream-short-sha>"
git push -u origin HEAD
gh pr create
```
Then `bun run ci:watch` and fix anything that turns up.
## Preserved patches (what conflicts to expect)
`git diff $(git merge-base HEAD upstream/main)..HEAD --stat` — currently ~35 files, ~550 insertions:
- **SHA-512/224** — `crypto/fipsmodule/sha/sha512.cc.inc`, `crypto/sha/sha512.cc`, `include/openssl/{sha2,nid,digest}.h`
- **SHA3-224/256/384/512 as `EVP_MD`** — `crypto/digest/digest_extra.cc`, `crypto/fipsmodule/{digest/digests.cc.inc,keccak/*}`, `include/openssl/{digest,nid}.h`
- **HMAC-SHA3** — `crypto/hmac/hmac_test*.{cc,txt}`
- **BLAKE2b-512** — `crypto/blake2/blake2.cc`, `include/openssl/blake2.h`
- **RIPEMD160 in `crypto/` (not `decrepit/`) + `EVP_ripemd160` lookup** — `crypto/ripemd/ripemd.cc` (moved), `crypto/digest/digest_extra.cc`, `include/openssl/digest.h`, `gen/sources.*`, `build.json`
- **`EVP_PBE_validate_scrypt_params`** — `crypto/evp/scrypt.cc`, `include/openssl/evp.h`
- **Electron `SSL_want` / `EVP_CIPHER_do_all_sorted`** — `ssl/ssl_lib.cc` (return `rwstate` directly), `ssl/ssl_test.cc` (drops the corresponding test block), `decrepit/evp/evp_do_all.cc`, `crypto/cipher/get_cipher.cc`, `include/openssl/cipher.h`
- **MLDSA stack-frame pragma** — `crypto/fipsmodule/mldsa/mldsa.cc.inc`
If upstream upstreams any of these (check `git grep` on `upstream/main` before re-applying), drop the fork's copy.
## Things that have broken before
- **`SSL_CTX` / `SSL_ECH_KEYS` / `SSL_CREDENTIAL` made opaque** — Bun's Rust FFI (`src/boringssl_sys/boringssl.rs`) treats them as opaque already, so this is fine, but check `packages/bun-usockets/src/crypto/openssl.c` for any direct field access.
- **`BIO_read`/`BIO_write` error-value narrowing** — can change `SSL_read` error paths over memory BIOs (`SSLWrapper` for TLS-over-duplex). If `node-tls-connect.test.ts` crashes in `flush_pending_events`, see `src/runtime/socket/UpgradedDuplex.rs::teardown` and `WindowsNamedPipe.rs`'s `WRAPPER_BUSY` for the re-entrant-drop guard.
- **Per-handshake allocation churn (PQ key shares)** grows under ASAN quarantine; RSS-delta tests like `tls-connect-socket-churn.test.ts` may need their `isASAN` bound raised. The `sslCtxLiveCount` check is the real regression guard there — if that passes and LSAN is clean, raise the RSS bound.
- **`asn1_string_st` / `GENERAL_NAME_st` layout** — Bun mirrors these in `src/boringssl_sys/boringssl.rs`; diff `include/openssl/{asn1,x509v3}.h` for field changes.
+92
View File
@@ -0,0 +1,92 @@
# Upgrading Bun's Self-Reported Node.js Version
This guide explains how to upgrade the Node.js version that Bun reports for compatibility with Node.js packages and native addons.
## Overview
Bun reports a Node.js version for compatibility with the Node.js ecosystem. This affects:
- `process.version` output
- Node-API (N-API) compatibility
- Native addon ABI compatibility
- V8 API compatibility for addons using V8 directly
## Files That Always Need Updates
### 1. Bootstrap Scripts
- `scripts/bootstrap.sh` - Update `NODEJS_VERSION=`
- `scripts/bootstrap.ps1` - Update `$NODEJS_VERSION =`
### 2. CMake Configuration
- `cmake/Options.cmake`
- `NODEJS_VERSION` - The Node.js version string (e.g., "24.3.0")
- `NODEJS_ABI_VERSION` - The ABI version number (find using command below)
### 3. Version Strings
- `src/jsc/bindings/BunProcess.cpp`
- Update `Bun__versions_node` with the Node.js version
- Update `Bun__versions_v8` with the V8 version (find using command below)
### 4. N-API Version
- `src/runtime/napi/js_native_api_types.h`
- Update `NAPI_VERSION` define (check Node.js release notes; see `src/runtime/napi/README.md` for the header resync procedure)
## Files That May Need Updates
Only check these if the build fails or tests crash after updating version numbers:
- V8 compatibility files in `src/jsc/bindings/v8/` (if V8 API changed)
- Test files (if Node.js requires newer C++ standard)
## Quick Commands to Find Version Info
```bash
# Get latest Node.js version info
curl -s https://nodejs.org/dist/index.json | jq '.[0]'
# Get V8 version for a specific Node.js version (replace v24.3.0)
curl -s https://nodejs.org/dist/v24.3.0/node-v24.3.0-headers.tar.gz | tar -xzO node-v24.3.0/include/node/node_version.h | grep V8_VERSION
# Get ABI version for a specific Node.js version
curl -s https://nodejs.org/dist/v24.3.0/node-v24.3.0-headers.tar.gz | tar -xzO node-v24.3.0/include/node/node_version.h | grep NODE_MODULE_VERSION
# Or use the ABI registry
curl -s https://raw.githubusercontent.com/nodejs/node/main/doc/abi_version_registry.json | jq '.NODE_MODULE_VERSION."<version>"'
```
## Update Process
1. **Gather version info** using the commands above
2. **Update the required files** listed in the sections above
3. **Build and test**:
```bash
bun bd
bun bd -e "console.log(process.version)"
bun bd -e "console.log(process.versions.v8)"
bun bd test test/v8/v8.test.ts
bun bd test test/napi/napi.test.ts
```
4. **Check for V8 API changes** only if build fails or tests crash:
- Compare v8-function-callback.h between versions
- Check v8-internal.h for Isolate size changes
- Look for new required APIs in build errors
## If Build Fails or Tests Crash
The V8 API rarely has breaking changes between minor Node.js versions. If you encounter issues:
1. Check build errors for missing symbols or type mismatches
2. Compare V8 headers between old and new Node.js versions
3. Most issues can be resolved by implementing missing functions or adjusting structures
## Testing Checklist
- [ ] `process.version` returns correct version
- [ ] `process.versions.v8` returns correct V8 version
- [ ] `process.config.variables.node_module_version` returns correct ABI
- [ ] V8 tests pass
- [ ] N-API tests pass
## Notes
- Most upgrades only require updating version numbers
- Major V8 version changes (rare) may require API updates
- The V8 shim implements only APIs used by common native addons
+41
View File
@@ -0,0 +1,41 @@
---
description: Upgrade Bun's WebKit fork to the latest upstream version of WebKit
---
Upgrade Bun's WebKit fork (vendor/WebKit = oven-sh/WebKit) to the latest upstream WebKit.
Two modes — pick from ARGUMENTS:
- **Direct (default)**: push the merge straight to oven-sh/WebKit main. Confirm with the user before pushing.
- **Preview** (when ARGUMENTS contains `preview` or `pr`): never push to main. Open a PR on oven-sh/WebKit and use its auto-built preview release instead.
To do that:
- cd vendor/WebKit (must be a real clone with an `upstream` remote pointing at WebKit/WebKit)
- git fetch upstream
- OLD_BASE=$(git merge-base origin/main upstream/main) — save this for the changelog
- Preview mode: create a working branch (e.g. `bun/upgrade-to-<upstream-short-sha>`) instead of staying on main
- git merge upstream/main
- Fix the merge conflicts (preserve the fork's Bun-specific changes)
- bun run jsc:build:debug — from the bun repo root, builds just JSC
- While it compiles, in another task review the JSC commits between $OLD_BASE and upstream/main (Source/JavaScriptCore, Source/WTF, Source/bmalloc). Write up a summary in a file called "webkit-changes.md"
- bun run build:local — full Bun build against the local WebKit (reuses the JSC build above)
- After it compiles, run some code to make sure things work: `bun run build:local -p '42'`
- Publish the new WebKit:
- Direct: cd vendor/WebKit, commit, `git push origin main`. The push triggers a release tagged `autobuild-<full-sha>`.
- Preview: push the branch and open a PR on oven-sh/WebKit. CI publishes a prerelease tagged `autobuild-preview-pr-<PR#>-<first-8-chars-of-head-sha>`. (Auto-triggers only for authors with write access; otherwise `gh workflow run build-preview.yml --repo oven-sh/WebKit -f pr_number=<N>`.)
- Wait until the release exists: `gh release view <tag> --repo oven-sh/WebKit`. It is created only after ALL platform builds succeed (takes a while). Bun's CI downloads prebuilts from it, so don't open the bun PR before it's up.
- cd back to bun and update WEBKIT_VERSION in scripts/build/deps/webkit.ts:
- Direct: the new vendor/WebKit commit sha
- Preview: the full preview tag (`autobuild-preview-pr-...`)
- git checkout -b claude/webkit-upgrade-<sha> (branch must start with `claude/` for CI)
- commit + push (without adding the webkit-changes.md file)
- create a PR titled "Upgrade WebKit to <upstream-short-sha>", paste webkit-changes.md into the description
- Preview mode: also note in the description that WEBKIT_VERSION points at a preview build and must be bumped to the merge-commit's `autobuild-<sha>` after the oven-sh/WebKit PR merges — do that bump before merging the bun PR
- delete the webkit-changes.md file
Things to check for a successful upgrade:
- Did Source/JavaScriptCore/runtime/JSType.h change? The enum values must align with Bun's mirror in src/jsc/JSType.rs.
- Were there any changes to the WebCore code generator? If there are C++ compilation errors, check for differences in the generated reference code in vendor/WebKit/Source/WebCore/bindings/scripts/test/JS/
- If the merge touched the fork's .github/workflows, the release tarball names must still match prebuiltSuffix() in scripts/build/deps/webkit.ts
+68
View File
@@ -0,0 +1,68 @@
# Landing PRs: situational review guidance
Companion to the "Landing PRs: What Bun Reviewers Catch" section in CLAUDE.md. These sections apply at specific moments — read the relevant one when:
- **Node/Web compat** — touching `node:*` modules, Web-standard APIs, or anything under `src/runtime/node/` / `src/js/node/`.
- **API design** — adding or changing user-facing API surface (JS APIs, CLI flags, options).
- **Performance** — optimizing, touching hot paths, or making performance claims.
- **Cross-platform** — platform-gated code, FFI/ABI boundaries, or platform-sensitive tests.
- **Dependencies & vendoring** — bumping a dependency or vendored library, adding a dependency, editing anything under `vendor/`.
- **Docs, types, and comments** — touching docs, `.d.ts` files in `packages/bun-types/`, or JSDoc.
- **PR process** — before opening a PR, before requesting review, and when responding to review feedback.
## Node/Web compat
- **For `node:*` modules, real Node's observed behavior is the spec** — not docs, @types/node, or intuition. (For Web-standard APIs — fetch, URL, streams — the WHATWG/W3C spec and WPT are the bar instead; Node is not the reference there.) Run the exact scenario under current Node and paste the repro + output in the PR; read the nodejs/node source for the function being emulated and cite permalinks in code comments for every magic constant and counterintuitive choice — "it matches Node" without a link does not unblock a thread. Port bug-for-bug, including grammatically wrong messages; don't modernize or ship accidental extra capabilities. If your fix only works by diverging from Node, suspect the bug is in a different layer. Deliberate divergence is never silent: raise it with reviewers and comment what Node does and why Bun differs.
- **Match Node's full error contract.** The exact `ERR_*` code, the constructor class (TypeError vs RangeError — user code does instanceof), the verbatim message text, the full property set (syscall, errno, the full access path like `options.privateKeyEngine`), the check ORDERING that decides which error wins on doubly-invalid input, and the delivery channel (sync throw vs `error` event vs rejection — for Web APIs the spec dictates this). Use the shared validators (`$ERR_*`, validateString, ErrorCode.ts), never hand-rolled typeof checks. Never validate stricter than Node (extra args don't throw; `{opt: undefined}` equals omitted). Assert `err.code` in tests, not just instanceof.
- **The entire observable surface is compat API.** Property attributes (writable/enumerable/configurable — never tightened for convenience), constructor.name and prototype chains, undocumented underscore internals ecosystem code probes (`_writableState`), documented defaults verbatim (511 not 512), per-instance state where Node uses factories (never module-level singletons — verify with multiple simultaneous instances). Ordering and timing are contract: state mutations relative to event emission are observable because handlers re-enter (set `complete` BEFORE `push(null)`); match sync-vs-nextTick callback timing exactly. If you can't establish Node's exact timing from its source, leave the behavior out rather than approximate. Never let user-visible behavior change as a side effect of unrelated work — dependency upgrades get patched; crashes are never fixed by deleting the feature.
- **The upstream test suite is the bar.** Port Node's own test files (test/js/node/test/parallel/) or WPT instead of hand-writing a small case set; write compat tests with node:test/node:assert so the identical file passes under real Node, and show both outputs. Check whether previously-disabled upstream tests can now be enabled ("does this add any passing node tests?" is a standard review question). Ported upstream files are verbatim-diffable: no edits, not even typos — necessary deviations get an inline comment or upstream-commit citation. Mark known failures in ported suites `test.todo`, never bare `test.skip` — todo flips to a failure when fixed (platform-capability gates still use `test.skipIf` with a reason). Never weaken ported assertions.
- **Port the whole behavior, not the slice the issue mentioned.** Every sibling form (sync/callback/promises — "Don't forget about fs.exists"), the spec's complete enumerations, the full input space the reference accepts (alternative spellings 'IPv4'/'ipv4'/4, fractional and negative numbers — truncate toward zero BEFORE negative-index math, absent-vs-empty distinctions), and real-world values beyond the spec (HTTP 999 exists in the wild). Malformed external input must surface as a catchable error the way Node does — never a panic. Never stub a path with a panic without checking whether real npm packages exercise it.
## API design
- **Make invalid states unrepresentable; declarations exactly as strict as the domain.** Tagged unions over pairs of optionals; two-value enums over bare boolean parameters (nobody can read what a bare `false` means at a call site); explicit Option over in-band sentinels when the sentinel is a legal value; named enums on BOTH sides of FFI, never convention-interpreted ints; owned-vs-borrowed encoded in the type. A cast where the concrete type is statically known is a design smell — change the signature so misuse fails at compile time. Don't over-constrain either: accept every input form that costs nothing; never weaken a type-safety wrapper to silence a compile error — fix the call sites.
- **Manage public surface deliberately, both directions.** Internals behind private symbols or #fields (never underscore identifiers or Symbol.for — user code can forge them); no hidden options in Web-standard APIs; no new globals when an existing namespace fits. Ship no speculative surface: no options nobody asked for ("let someone complain about the lack of it" — an explicit ask in the issue is not speculative) — anything user-reachable becomes de-facto supported forever. But what you DO ship ships complete in the same PR: the natural surfaces for the feature (CLI flag AND programmatic API where both exist), .d.ts types, --help text, docs. Partial surface coverage is blocked at review, not deferred.
- **Every accepted option does what it claims or fails loudly.** Reject values the option cannot honor (a zero or negative where the semantics make it meaningless — not where it's a legal value like a hash seed) at parse time, before any I/O; throw on mutually-exclusive combinations rather than ignoring one; error when an option is accepted in a mode where it can't work; stubs return errors, not empty successes. Distinguish "user explicitly set X" from "X equals the default" before branching on it; undefined means "use the default", null means "explicitly off". Handle every accepted form identically (`--flag value` and `--flag=value`, NO_PROXY and no_proxy).
- **Name from established precedent, in priority order:** Web spec names for Web-standard features (lastModified, not mtime), Node's vocabulary for Node-equivalent features, npm/pnpm/yarn names for package-manager flags. Absent precedent, name for the concrete behavior, never the tool or customer that requested it. Keep camelCase JS options identical across native code, .d.ts, and docs. Public names are forever — propose the convention-matching name first.
- **Never silently break existing users.** Working behavior users plausibly rely on — even undocumented, even spec-noncompliant — cannot be removed or restricted as a side effect ("People use file descriptor numbers. It should be allowed"); the test is whether real code depends on it, not whether docs bless it. No new caps on application-controlled values. Renames of user-facing API keep deprecated aliases. Changing an existing default is a breaking change behind a flag; new behavior-changing options default OFF — if enabling by default breaks any existing test, it breaks users. Explicit user configuration beats newly-added inferred behavior; pin the precedence with a regression test. This contract covers only the user-observable surface: internal native code has no backwards-compat obligation — rename, restructure, and delete non-user-facing code freely.
## Performance: what reviewers block
- **Do each piece of work exactly once; keep the event loop responsive.** Fold new validation into existing loops over the same data — the validation must still happen; what gets blocked is a *separate* O(n) pass when an existing loop already visits the bytes. Combined operations (getIfPropertyExists over has-then-get, getOrPut). Hoist invariant work out of ALL enclosing loop levels (fixes that move it up only one get re-flagged). On per-file/per-entry loops (installs, directory walks), count syscalls — attempt-the-operation-and-branch-on-errno instead of preflight stat/exists probes. Order compound conditions cheapest-first. In production code, no shift()/orderedRemove(0) draining of unbounded queues — accidentally-quadratic use is flagged even on cold paths. Cap bytes per event-loop turn in unbounded write loops. Never run synchronous filesystem calls on the JS thread inside async completion paths.
- **Count the copies and allocations your native code makes.** Write directly into the final destination — never create a temporary and copy, never build a JSString just to read its contents back. Check whether the callee already dupes before duping yourself. Reuse one scratch buffer across loop iterations. Multi-KB scratch comes from the shared pool (`PathBufferPool`), never tens-of-KB stack frames. Preallocate exact capacity when computable. Treat the byte size of frequently-instantiated structs as reviewed: measure `size_of` and state it in the PR; pack bools into existing flag bits; never embed large rarely-used buffers inline.
- **The common case pays zero for rare features.** Gate new allocations, subsystem init, and per-access interception on the precise condition needing them — no Proxies or getOwnPropertySlot hooks on hot paths (defeats inline caching, 10-100x slower); decide once at setup, not per event. Route the disabled case through the pre-existing code path completely unchanged. Gate debug diagnostics behind compile-time flags (`cfg(debug_assertions)`, ASSERT_ENABLED) — an assertion whose condition calls across FFI is NOT compiled out. Fast paths must replicate every observable of the general path (shared-reference identity, state flags/locks, exotic inputs) — verify the precondition covers degenerate inputs or bail to the slow path. Never fix a rare-case bug by adding cost to the hot path.
- **Performance claims need numbers; complexity fixed at the root.** Before/after from the repo's bench suite (`bench/`) covering ALL input classes — both string encodings, short and long inputs; "faster on average" is rejected; must not regress ANY measured case; compare against the previous Bun release and Node. Never port V8/Node micro-optimizations assuming they transfer to JSC ("JavaScriptCore is a different engine. Do you have a benchmark?"). Never change optimization levels, LTO modes, or tuning knobs assuming higher is better — existing settings encode prior measurements. Treat existing perf mechanisms as load-bearing (inline directives, corking flags, odd API variants chosen to skip a copy) — re-add any fast path your rewrite drops. Never cap counts or rate-limit to hide quadratic behavior ("Do not solve quadratic behavior by limiting the count"). Verify complexity claims by doubling inputs and reporting the ratio; adversarially test any memo/cache against inputs that thrash it.
- **JSC binding C++: use the engine's cached fast paths.** LazyClassStructure/cached structures per global — a structure created per instance permanently defeats inline caching. `vm.propertyNames`/BuiltinNames instead of `Identifier::fromString` per call (takes a lock, shows up in profiles). MAKE_STATIC_STRING_IMPL for fixed strings. JSType tag checks over constructor-name comparisons. Internal state in WriteBarrier'd native fields, not observable JS properties. Function-local Meyers singletons over top-level statics. Throw scopes at the top of the function.
## Cross-platform
- **Never assume OS/ABI facts are portable.** errno meanings differ (EPERM is a sharing violation on Windows), event flags differ, Windows env vars are case-insensitive, Windows has no POSIX signals, blocking syscalls retry on EINTR. FFI/ABI: explicit calling conventions on BOTH sides; fixed-width or C-ABI types, never bare int read from c_ulong (LLP64 garbage on Windows); extern declarations diff'd parameter-by-parameter against definitions — they compile cleanly per side and crash only on the platform you didn't build; complete Windows error-translation tables with fallbacks instead of force-unwraps.
- **Platform parity is part of every fix.** When you fix one platform backend (POSIX vs kqueue vs epoll vs libuv), audit every sibling backend for the same defect and apply symmetrically — or state why a backend is unaffected ("kqueue register path is unhandled. You only patched unregister."). A POSIX-only API addition ships its Windows equivalent in the same PR. Enabling a feature on a new platform means grepping every gate, dispatch chain, parallel platform script, test skip, and allowlist. Platform-specific CI failures in files you touched are real merge-blocking bugs, never flakes. Comment WHY on every new platform exclusion.
- **Write tests to pass on every CI platform** (Windows, macOS x64+arm64, Linux glibc and musl). Split on `/\r?\n/` (Windows CRLF); normalize separators in path assertions; never spawn shell builtins (echo, sleep are not programs on Windows — use bunExe() -e); no hardcoded /tmp or /bin; incidental servers bind 127.0.0.1, never ::1 (CI Linux may lack IPv6 — IPv6-specific tests gate on the harness IPv6 helper); exit codes not signal names for "did not crash" (Windows has no signals); when probing a limit, exceed the LARGEST platform limit to trip the guard and stay under the SMALLEST when constructing inputs (macOS PATH_MAX is 1024). Before skipping a platform, verify it genuinely lacks the capability (Windows supports AF_UNIX). Skip narrowly via test.skipIf with a reason; never fix one platform by loosening assertions for all.
- **Decide explicitly: filesystem path or URL-like identifier.** Module specifiers, cache keys, sourcemap paths use forward slashes everywhere (posix path helpers). Filesystem paths use platform path APIs, never literal '/' concatenation. Windows: accept BOTH separators; drive-relative (C:foo) and UNC forms exist; PATH splits on ';'. On POSIX, backslash is a legal filename character. Splitting a posix-normalized string with the platform separator silently no-ops on Windows — feed the other separator style through every new API in tests.
- **Beyond `rust:check-all` (required by CLAUDE.md) for platform-gated code:** verify link-time symbol resolution (a POSIX extern must still resolve on Windows even if runtime-gated); audit enum switches duplicated across platform arms; distrust lint sweeps — a cast redundant on your host may be load-bearing on another target. Trick: flip the platform condition locally to force the other branch through the type-checker.
## Dependencies & vendoring
- **Version bumps are repo-wide, verified operations.** Never merge a pin to an ephemeral artifact (preview tags, unmerged-PR builds) — swap to the merged upstream SHA and verify prebuilt artifacts exist for every platform × flavor before merge. Grep the entire repo for the old version value — build scripts, CI configs, Dockerfiles, and deliberate assertion tables — and update every duplicate in one commit. For vendored bumps: rebase every local patch and verify fetch+patch+compile from a clean state; verify the exact replacement upstream chose before mass-renames (WTF::move, not std::move — a plausible-but-wrong substitution × 300 files cost a 1570-line fixup). Codegen steps declare their input files as dependencies so outputs regenerate; build caches are keyed by compile flags too, not just OS/arch.
- **Adding a dependency is a last resort.** Inline trivial utilities; use the platform's own API or JSC-backed implementation over wrapper packages; every dependency must be traceable to a concrete consumer ("where are they used?"). Include license attribution in the same PR for any copied open-source code. Vendored code (vendor/, WPT fixtures, Node test files) is read-only — no style or typo fixes (copies serve as conformance baselines); exclude vendored dirs from mechanical rewrites. Vendor patches stay small with a comment explaining what upstream behavior they correct, plus the upstream issue link.
- **Dependency ranges follow the audience.** Repo-internal manifests (test fixtures, tooling, CI images) pin exact versions — never ^ or ~, never "tidy" an exact pin into a range. Published packages do the opposite: `*` for @types/node in bun-types, peerDependencies for toolchains users already have, and bundle runtime deps into shipped artifacts — the end-user machine has no node_modules. Overrides/resolutions entries are load-bearing — find out what breakage one prevents before deleting it.
## Docs, types, and comments
- **Sweep the same PR for everything describing the old state.** When behavior, names, or contracts change — including mid-PR pivots — update or delete: comments beyond the hunk, sibling/mirror implementations, JSDoc, "see above" cross-references, READMEs, CLAUDE.md, --help text, error-message hints. A comment contradicting the code is a correctness bug, not a nit — a stale refcount comment invites a future maintainer to "restore" unref() and cause a double-free. Write comments about the code as it now is, never narrating the change.
- **Comments must be load-bearing and true.** Any line correct for a non-obvious reason gets a why-comment: special-case branches (with a triggering input), deliberate deviations from the reference, magic constants (cite the spec line), workarounds (link the upstream issue). When a reviewer asks "is this state possible?" — answer with a code comment, not just a thread reply; articulating the invariant routinely exposes that it doesn't hold. SAFETY comments state the precise invariant and where it's enforced — against every caller — and get re-verified after each refactor. Encode documented preconditions as debug assertions rather than prose.
- **Verify every documentation claim you publish, by execution.** Run each snippet end-to-end exactly as written; fetch every URL; check option names/defaults against the implementation on main; preview rendered markdown (an unbalanced fence swallows everything after it). Replace marketing language with the specific guaranteed property. Never claim full compatibility when partial — enumerate what works. Don't publish claims you haven't verified — verify, scope down, or drop them (an AI-drafted page with unverifiable claims was deleted wholesale, +9/-325). Existing docs you didn't touch are out of scope.
- **Docs prose follows the voice rules in `docs/project/contributing.mdx` ("Voice").** Short sentences, one point each (a sentence with several commas or a dash-separated aside becomes two sentences or a list); active voice with the actor named ("Bun reads X", not "X is read"); present tense for current behavior (no "will"); "you" for the reader, never tutorial "we"/"let's"; no "easy"/"simple"/"just"/"quick"; name the subject where a bare "this" is ambiguous; say what to do rather than what not to do. Docs-wide passes have been merged to remove exactly these patterns (#28788, #33112), and #38686 rewrote pages that had reintroduced them the same day they merged; prose that breaks these rules costs a follow-up PR.
- **TypeScript declarations mirror the runtime exactly, in the same PR.** Declare only what's implemented — verify by running the API, never docs or the PR description; no types for stubbed APIs. Literal unions for fixed string sets (`'A' | 'B' | (string & {})` for open sets); overloads so parameters are only accepted where the runtime accepts them; `prop?: T | undefined` for exactOptionalPropertyTypes; `Uint8Array<ArrayBuffer>` generics (TS 5.9+); new type parameters get defaults so existing call sites compile; no new globals colliding with lib.dom/@types/node (use the Bun namespace); never widen a type or `as any` to silence one call site. No `*/` inside JSDoc (glob patterns break the entire .d.ts parse). Validate by compiling realistic usage in bun-types fixtures under BOTH tsconfigs (with and without DOM).
- **Write JSDoc for a zero-context reader.** Option docs explain what the option DOES — semantics, edge behavior, sentinel meanings (0 = unlimited), when it has no effect, the equivalent CLI flag — never a wordier restatement of the name. The .d.ts JSDoc is the canonical IDE-tooltip surface; constraints documented only in .mdx are invisible at the point of discovery. Security-adjacent examples must be safe to copy verbatim (least privilege, never User=root).
## PR process
- **Re-read your entire diff line-by-line as a reviewer would, before requesting review.** Delete all development residue: debug prints, commented-out code, forced conditionals, scratch files, leftover `.only` and debugging skips (a committed `.only` silently disables every other test in the file in CI), unused imports, AI-generated explanatory padding. Not cleaned up yet → open as draft.
- **Audit the full diff for accidental ride-alongs.** Submodule pointer bumps, lockfile churn from rebases, regenerated snapshots, formatter churn on untouched code, stash leakage. After every merge/rebase with main, re-diff against main: conflict resolution can silently resurrect deleted code or drop your own headline fix while keeping its test (a "one-line test tweak" commit once touched 33 files and reverted the entire production fix). Every file in the diff must be explainable from the PR's stated purpose.
- **The PR description is the permanent squash-commit message — keep it true.** State the root cause and make the exact fixing line identifiable apart from refactoring ("Which line was the fix?"). Name the verifying tests and state they fail on the unfixed build. "Fixes #N" must match the issue's actual repro; a partial fix says so. Re-sync title/description whenever review reworks the change. Every hunk needs an articulable one-sentence justification — pre-empt it in the description or a code comment for anything a reviewer can't explain from context. On large mechanical diffs, leave self-review comments pointing at the load-bearing hunks. Never delete unrelated code, others' TODOs, or debug tooling in passing — deletions your change orphans are required (see "Delete dead code"), but each is intentional and named.
- **Treat every review suggestion — especially from bots — as an unverified hypothesis.** Reproduce or check it against actual API semantics before applying or dismissing. Apply real findings; decline wrong ones in-thread with checkable evidence (file:line, run transcripts) — evidence-backed rebuttals close threads, bare dismissals don't. Never blanket-apply (blindly-applied suggestions have reintroduced known ASAN failures), never resolve threads silently in bulk (a bulk-resolve once swallowed a genuine correctness bug). When a reviewer flags a pattern once, sweep and fix every instance — Jarred leaves one substantive comment then "ditto" on each clone; fixing only the commented line guarantees another round.
- **Green CI on every platform is a hard precondition.** Maintainers file changes-requested reviews consisting solely of "CI is failing". Regenerate checked-in codegen outputs — again after every rebase. Every failure on your branch but not on main is yours to root-cause; "probably a flake" requires a link to the same failure on main. Check per-job results, not the aggregate icon, and confirm CI actually executed your tests — path filters silently skip them.
- **One concern per PR, scoped to the narrowest change that fixes it.** Fixing every instance of the same bug class is ONE concern (see "Fix the whole class"); drive-by refactors, style cleanups of adjacent code, and vendored upgrades are not. If one part triggers design debate mid-review, carve it out so the uncontroversial part merges. Diff size itself is grounds for changes-requested.
- **Pre-existing bugs surfaced by review: acknowledge, scope, track.** Never silently ignore, never silently widen your diff. State the mechanism in-thread, note your PR doesn't change it, file a tracking issue — "out of scope" without a tracker is not accepted. Exception: if it's the exact bug class your PR claims to eliminate, fix all instances in the same PR ("pre-existing, will follow up" for the same crash class gets "no, fix it.").
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bun
import { extname } from "path";
import { spawnSync } from "child_process";
const input = await Bun.stdin.json();
const toolName = input.tool_name;
const toolInput = input.tool_input || {};
const filePath = toolInput.file_path;
// Only process Write, Edit, and MultiEdit tools
if (!["Write", "Edit", "MultiEdit"].includes(toolName)) {
process.exit(0);
}
const ext = extname(filePath);
// Only format known files
if (!filePath) {
process.exit(0);
}
function formatTypeScriptFile() {
try {
// Format only — NO organize-imports plugin. That plugin strips imports
// it thinks are unused, which breaks split edits (add import → use it
// in next edit). CI's `bun run prettier` runs the plugin, so imports
// still get cleaned up before merge.
const result = spawnSync("./node_modules/.bin/prettier", ["--config", ".prettierrc", "--write", filePath], {
cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
encoding: "utf-8",
});
} catch (error) {}
}
if (
[
".cjs",
".css",
".html",
".js",
".json",
".jsonc",
".jsx",
".less",
".mjs",
".pcss",
".postcss",
".sass",
".scss",
".styl",
".stylus",
".toml",
".ts",
".tsx",
".yaml",
].includes(ext)
) {
formatTypeScriptFile();
}
process.exit(0);
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env bun
import { basename, extname } from "path";
const input = await Bun.stdin.json();
const toolName = input.tool_name;
const toolInput = input.tool_input || {};
const command = toolInput.command || "";
const timeout = toolInput.timeout;
const cwd = input.cwd || "";
// Get environment variables from the hook context
// Note: We check process.env directly as env vars are inherited
let useSystemBun = process.env.USE_SYSTEM_BUN;
if (toolName !== "Bash" || !command) {
process.exit(0);
}
function denyWithReason(reason) {
const output = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: reason,
},
};
console.log(JSON.stringify(output));
process.exit(0);
}
// Parse the command to extract argv0 and positional args
let tokens;
try {
// Simple shell parsing - split on spaces but respect quotes (both single and double)
tokens = command.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map(t => t.replace(/^['"]|['"]$/g, "")) || [];
} catch {
process.exit(0);
}
if (tokens.length === 0) {
process.exit(0);
}
// Strip inline environment variable assignments (e.g., FOO=1 bun test)
const inlineEnv = new Map();
let commandStart = 0;
while (
commandStart < tokens.length &&
/^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[commandStart]) &&
!tokens[commandStart].includes("/")
) {
const [name, value = ""] = tokens[commandStart].split("=", 2);
inlineEnv.set(name, value);
commandStart++;
}
if (commandStart >= tokens.length) {
process.exit(0);
}
tokens = tokens.slice(commandStart);
useSystemBun = inlineEnv.get("USE_SYSTEM_BUN") ?? useSystemBun;
// Get the executable name (argv0)
const argv0 = basename(tokens[0], extname(tokens[0]));
// Disallow direct `rustfmt`: it doesn't read the workspace edition from
// Cargo.toml the way `cargo fmt` does, so its output can disagree with CI's
// Format job (`cargo fmt --all --check`).
if (argv0 === "rustfmt") {
denyWithReason("error: Don't run `rustfmt` directly. Run `cargo fmt --all` — it's what CI checks.");
}
// Check if argv0 is timeout and the command is "bun bd"
if (argv0 === "timeout") {
// Find the actual command after timeout and its arguments
const timeoutArgEndIndex = tokens.slice(1).findIndex(t => !t.startsWith("-") && !/^\d/.test(t));
if (timeoutArgEndIndex === -1) {
process.exit(0);
}
const actualCommandIndex = timeoutArgEndIndex + 1;
if (actualCommandIndex >= tokens.length) {
process.exit(0);
}
const actualCommand = basename(tokens[actualCommandIndex]);
const restArgs = tokens.slice(actualCommandIndex + 1);
// Check if it's "bun bd" or "bun-debug bd" without other positional args
if (actualCommand === "bun" || actualCommand.includes("bun-debug")) {
// Claude is a sneaky fucker
let positionalArgs = restArgs.filter(arg => !arg.startsWith("-"));
const redirectStderrToStdoutIndex = positionalArgs.findIndex(arg => arg === "2>&1");
if (redirectStderrToStdoutIndex !== -1) {
positionalArgs.splice(redirectStderrToStdoutIndex, 1);
}
const redirectStdoutToStderrIndex = positionalArgs.findIndex(arg => arg === "1>&2");
if (redirectStdoutToStderrIndex !== -1) {
positionalArgs.splice(redirectStdoutToStderrIndex, 1);
}
const redirectToFileIndex = positionalArgs.findIndex(arg => arg === ">");
if (redirectToFileIndex !== -1) {
positionalArgs.splice(redirectToFileIndex, 2);
}
const redirectToFileAppendIndex = positionalArgs.findIndex(arg => arg === ">>");
if (redirectToFileAppendIndex !== -1) {
positionalArgs.splice(redirectToFileAppendIndex, 2);
}
const redirectTOFileInlineIndex = positionalArgs.findIndex(arg => arg.startsWith(">"));
if (redirectTOFileInlineIndex !== -1) {
positionalArgs.splice(redirectTOFileInlineIndex, 1);
}
const pipeIndex = positionalArgs.findIndex(arg => arg === "|");
if (pipeIndex !== -1) {
positionalArgs = positionalArgs.slice(0, pipeIndex);
}
positionalArgs = positionalArgs.map(arg => arg.trim()).filter(Boolean);
if (positionalArgs.length === 1 && positionalArgs[0] === "bd") {
denyWithReason("error: Run `bun bd` without a timeout");
}
}
}
// Check if command is "bun .* test" or "bun-debug test" with -u/--update-snapshots AND -t/--test-name-pattern
if (argv0 === "bun" || argv0.includes("bun-debug")) {
const allArgs = tokens.slice(1);
// Check if "test" is in positional args or "bd" followed by "test"
const positionalArgs = allArgs.filter(arg => !arg.startsWith("-"));
const hasTest = positionalArgs.includes("test") || (positionalArgs[0] === "bd" && positionalArgs[1] === "test");
if (hasTest) {
const hasUpdateSnapshots = allArgs.some(arg => arg === "-u" || arg === "--update-snapshots");
const hasTestNamePattern = allArgs.some(arg => arg === "-t" || arg === "--test-name-pattern");
if (hasUpdateSnapshots && hasTestNamePattern) {
denyWithReason("error: Cannot use -u/--update-snapshots with -t/--test-name-pattern");
}
}
}
// Check if timeout option is set for "bun bd" command
if (timeout !== undefined && (argv0 === "bun" || argv0.includes("bun-debug"))) {
const positionalArgs = tokens.slice(1).filter(arg => !arg.startsWith("-"));
if (positionalArgs.length === 1 && positionalArgs[0] === "bd") {
denyWithReason("error: Run `bun bd` without a timeout");
}
}
// Check if running "bun test <file>" without USE_SYSTEM_BUN=1
if ((argv0 === "bun" || argv0.includes("bun-debug")) && useSystemBun !== "1") {
const allArgs = tokens.slice(1);
const positionalArgs = allArgs.filter(arg => !arg.startsWith("-"));
// Check if it's "test" (not "bd test")
if (positionalArgs.length >= 1 && positionalArgs[0] === "test" && positionalArgs[0] !== "bd") {
denyWithReason(
"error: In development, use `bun bd test <file>` to test your changes. If you meant to use a release version, set USE_SYSTEM_BUN=1",
);
}
}
// Check if running "bun bd test" from bun repo root or test folder without a file path
if (argv0 === "bun" || argv0.includes("bun-debug")) {
const allArgs = tokens.slice(1);
const positionalArgs = allArgs.filter(arg => !arg.startsWith("-"));
// Check if it's "bd test"
if (positionalArgs.length >= 2 && positionalArgs[0] === "bd" && positionalArgs[1] === "test") {
// Check if cwd is the bun repo root or test folder
const isBunRepoRoot = cwd === "/workspace/bun" || cwd.endsWith("/bun");
const isTestFolder = cwd.endsWith("/bun/test");
if (isBunRepoRoot || isTestFolder) {
// Check if there's a file path argument (looks like a path: contains / or has test extension)
const hasFilePath = positionalArgs
.slice(2)
.some(
arg =>
arg.includes("/") ||
arg.endsWith(".test.ts") ||
arg.endsWith(".test.js") ||
arg.endsWith(".test.tsx") ||
arg.endsWith(".test.jsx"),
);
if (!hasFilePath) {
denyWithReason(
"error: `bun bd test` from repo root or test folder will run all tests. Use `bun bd test <path>` with a specific test file.",
);
}
}
}
}
// Allow the command to proceed
process.exit(0);
+26
View File
@@ -0,0 +1,26 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-bash-guard.js"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-edit-format.js"
}
]
}
]
}
}
@@ -0,0 +1,184 @@
---
name: implementing-jsc-classes-cpp
description: Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.
---
# Implementing JavaScript Classes in C++
## Class Structure
For publicly accessible Constructor and Prototype, create 3 classes:
1. **`class Foo : public JSC::DestructibleObject`** - if C++ fields exist; otherwise use `JSC::constructEmptyObject` with `putDirectOffset`
2. **`class FooPrototype : public JSC::JSNonFinalObject`**
3. **`class FooConstructor : public JSC::InternalFunction`**
No public constructor? Only Prototype and class needed.
## Iso Subspaces
Classes with C++ fields need subspaces in:
- `src/jsc/bindings/webcore/DOMClientIsoSubspaces.h`
- `src/jsc/bindings/webcore/DOMIsoSubspaces.h`
```cpp
template<typename MyClassT, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) {
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMyClassT = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForMyClassT = std::forward<decltype(space)>(space); });
}
```
## Property Definitions
```cpp
static JSC_DECLARE_HOST_FUNCTION(jsFooProtoFuncMethod);
static JSC_DECLARE_CUSTOM_GETTER(jsFooGetter_property);
static const HashTableValue JSFooPrototypeTableValues[] = {
{ "property"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsFooGetter_property, 0 } },
{ "method"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFooProtoFuncMethod, 1 } },
};
```
## Prototype Class
```cpp
class JSFooPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSFooPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) {
JSFooPrototype* prototype = new (NotNull, allocateCell<JSFooPrototype>(vm)) JSFooPrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.plainObjectSpace(); }
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}
private:
JSFooPrototype(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) {}
void finishCreation(JSC::VM& vm);
};
void JSFooPrototype::finishCreation(VM& vm) {
Base::finishCreation(vm);
reifyStaticProperties(vm, JSFoo::info(), JSFooPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
```
## Getter/Setter/Function Definitions
```cpp
// Getter
JSC_DEFINE_CUSTOM_GETTER(jsFooGetter_prop, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSFoo* thisObject = jsDynamicCast<JSFoo*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "JSFoo"_s, "prop"_s);
return {};
}
return JSValue::encode(jsBoolean(thisObject->value()));
}
// Function
JSC_DEFINE_HOST_FUNCTION(jsFooProtoFuncMethod, (JSGlobalObject* globalObject, CallFrame* callFrame)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSFoo*>(callFrame->thisValue());
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "Foo"_s, "method"_s);
return {};
}
return JSValue::encode(thisObject->doSomething(vm, globalObject));
}
```
## Constructor Class
```cpp
class JSFooConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSFooConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) {
JSFooConstructor* constructor = new (NotNull, JSC::allocateCell<JSFooConstructor>(vm)) JSFooConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.internalFunctionSpace(); }
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSFooConstructor(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, callFoo, constructFoo) {}
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) {
Base::finishCreation(vm, 0, "Foo"_s);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
};
```
## Structure Caching
Add to `ZigGlobalObject.h`:
```cpp
JSC::LazyClassStructure m_JSFooClassStructure;
```
Initialize in `ZigGlobalObject.cpp`:
```cpp
m_JSFooClassStructure.initLater([](LazyClassStructure::Initializer& init) {
Bun::initJSFooClassStructure(init);
});
```
Visit in `visitChildrenImpl`:
```cpp
m_JSFooClassStructure.visit(visitor);
```
## Expose to Zig
```cpp
extern "C" JSC::EncodedJSValue Bun__JSFooConstructor(Zig::GlobalObject* globalObject) {
return JSValue::encode(globalObject->m_JSFooClassStructure.constructor(globalObject));
}
extern "C" EncodedJSValue Bun__Foo__toJS(Zig::GlobalObject* globalObject, Foo* foo) {
auto* structure = globalObject->m_JSFooClassStructure.get(globalObject);
return JSValue::encode(JSFoo::create(globalObject->vm(), structure, globalObject, WTFMove(foo)));
}
```
Include `#include "root.h"` at the top of C++ files.
@@ -0,0 +1,131 @@
---
name: implementing-jsc-classes-rust
description: Creates JavaScript classes using Bun's Rust bindings generator (.classes.ts). Use when implementing new JS APIs in Rust with JSC integration, prototypes, or constructors.
---
# Bun's JavaScriptCore Class Bindings Generator
Bridge JavaScript and Rust through `.classes.ts` definitions and Rust implementations.
## Architecture
1. **JavaScript Interface Definition** (`.classes.ts` files)
2. **Rust Implementation** (`.rs` files)
3. **Generated Code**`src/codegen/generate-classes.ts` emits C++ + Rust into `${BUN_CODEGEN_DIR}/generated_classes.rs`, `include!`d as `crate::generated_classes` in `bun_runtime`. Run `bun bd` to regenerate.
## Class Definition (.classes.ts)
```typescript
export default [
define({
name: "Glob",
construct: true,
finalize: true,
hasPendingActivity: true,
proto: {
scan: { fn: "scan", length: 1 },
match: { fn: "match", length: 1 },
},
}),
];
```
Options:
- `construct`: Has a public `new Foo()` constructor
- `finalize`: Needs cleanup beyond `Drop` (rarely — see Finalize below)
- `hasPendingActivity`: GC keep-alive while async work is in flight
- `proto`: Methods (`fn:`), getters (`getter: true`, optionally `cache: true`)
- `values: [...]`: WriteBarrier slots for JS values the native side holds (callbacks, buffers)
## Rust Implementation
```rust
use bun_jsc::{CallFrame, JSGlobalObject, JSValue, JsResult};
use std::sync::atomic::{AtomicUsize, Ordering};
#[bun_jsc::JsClass]
pub struct Glob {
pattern: Box<[u8]>,
has_pending_activity: AtomicUsize,
}
impl Glob {
pub fn constructor(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<Box<Glob>> {
let arg = frame.argument(0);
let pattern = bun_core::String::from_js(arg, global)?.to_utf8_bytes().into();
Ok(Box::new(Glob { pattern, has_pending_activity: AtomicUsize::new(0) }))
}
#[bun_jsc::host_fn(method)]
pub fn r#match(&self, global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSValue> {
// ...
Ok(JSValue::TRUE)
}
pub fn has_pending_activity(&self) -> bool {
self.has_pending_activity.load(Ordering::SeqCst) > 0
}
}
```
### Canonical signatures
| Hook | Signature |
| ------------------- | ------------------------------------------------------------------------------------ |
| constructor | `pub fn constructor(global: &JSGlobalObject, frame: &CallFrame) -> JsResult<Box<Self>>` |
| method (`fn:`) | `pub fn name(&self, global: &JSGlobalObject, frame: &CallFrame) -> JsResult<JSValue>` |
| getter | `pub fn get_x(this: &Self, global: &JSGlobalObject) -> JsResult<JSValue>` |
| finalize | `pub fn finalize(self: Box<Self>)` — or omit; the blanket `JsFinalize` just drops |
| hasPendingActivity | `pub fn has_pending_activity(&self) -> bool` |
A missing or mis-typed hook is a **compile error** in `cargo check -p bun_runtime` — the generated code calls the inherent method directly.
## Hooking into the generated module
`#[bun_jsc::JsClass]` on the struct implements the `JsClass` trait (`to_js`, `from_js`, `from_js_direct`, `get_constructor`) by binding the C++ externs. Attribute knobs: `no_constructor`, `no_finalize`, `estimated_size`.
The codegen also emits a `js_$T` module with the cached-value accessors. Re-export it when you need `*_set_cached` / `*_get_cached` or `detach_ptr`:
```rust
pub use crate::generated_classes::js_Glob as js;
// or
bun_jsc::impl_js_class_via_generated!(Archive => crate::generated_classes::js_Archive);
```
The `js_$T` module surface:
```rust
pub fn from_js(value: JSValue) -> Option<NonNull<T>>;
pub fn from_js_direct(value: JSValue) -> Option<NonNull<T>>;
pub fn get_constructor(global: &JSGlobalObject) -> JSValue;
pub fn to_js(this: *mut T, global: &JSGlobalObject) -> JSValue; // ownership transfer
pub fn detach_ptr(value: JSValue);
// per cached getter / `values: [...]` entry:
pub fn <field>_set_cached(this_value: JSValue, global: &JSGlobalObject, value: JSValue);
pub fn <field>_get_cached(this_value: JSValue) -> Option<JSValue>;
```
## Finalize
Most classes need nothing — `#[bun_jsc::JsClass]` wires the blanket `JsFinalize` whose default is `drop(Box<Self>)`. Override only when you must release a JS handle or defer to a heap helper:
```rust
pub fn finalize(self: Box<Self>) {
bun_ptr::finalize_js_box(self, |this| this.this_value.with_mut(|v| v.finalize()));
}
```
Override with an **inherent** method, never `impl JsFinalize for T`.
## Holding JS values
Never store raw `JSValue` in a struct field. Declare a slot in `.classes.ts` (`values: ["callback"]` or a `cache: true` getter) and read/write it through `js::callback_set_cached(this_value, global, v)` / `js::callback_get_cached(this_value)`. The slot is a `WriteBarrier` visited by the GC, so the value stays alive without a `Strong`.
## Reference implementations
- `src/runtime/api/glob.rs` + `Glob.classes.ts` — constructor, methods, `hasPendingActivity`, default finalize
- `src/runtime/api/cron.rs` + `cron.classes.ts``noConstructor`, cached getter, `values: [...]`, custom finalize
- `src/runtime/image/Image.rs:56` — the `pub use crate::generated_classes::js_Image as js;` one-liner
- `src/jsc/host_fn.rs` — the host-fn adapters the codegen dispatches through
- `src/jsc_macros/lib.rs``#[bun_jsc::JsClass]` proc-macro source
@@ -0,0 +1,366 @@
---
name: javascriptcore-garbage-collector
description: JSC GC reference for Bun. Use for use-after-free, JS object leaks, "collected too early", or when touching WriteBarrier, visitChildren, visitAdditionalChildren, JSRef, JSC::Strong/Weak, hasPendingActivity, ensureStillAlive, addOpaqueRoot, reportExtraMemoryAllocated, IsoSubspace, HeapAnalyzer, finalize.
---
# JavaScriptCore's Garbage Collector (Riptide)
Riptide is **non-moving, generational, parallel, mostly-concurrent, conservative-on-the-stack**. Understanding those five words prevents most GC bugs in Bun.
## The mental model
The heap is a graph. GC does a breadth-first search from **roots** → marks everything it reaches → everything unmarked is freed (lazily, on next allocation from that block). It does NOT compact or move objects — pointers stay stable for an object's lifetime.
**Two collection modes:**
- **Eden GC**: only scans newly-allocated objects + remembered set. Fast, frequent.
- **Full GC**: scans everything. Slower, rarer.
**It runs concurrently.** Marking happens on background threads _while JS is executing_; the mutator only stops at brief safepoints. `visitChildren` runs **off the main thread, racing with your code**.
## How the VM gathers roots
Roots are not a hardcoded list — they are **marking constraints** registered with `Heap::addMarkingConstraint()` and run to fixpoint. The built-in set lives in `Heap::addCoreConstraints()` (`vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp:2970`):
| Tag | Name | What it marks |
| ----- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Cs` | Conservative Scan | Native stack + registers of every JS thread, scanned word-by-word (`gatherStackRoots``ConservativeRoots`). Also JIT stub routines. World is stopped for this. |
| `Msr` | Misc Small Roots | `vm.smallStrings`, `m_protectedValues` (`JSValueProtect`/`gcProtect`), `MarkedArgumentBuffer` lists, `vm.exception()` / `lastException()` / `m_terminationException` |
| `Sh` | Strong Handles | `m_handleSet.visitStrongHandles()` — every `JSC::Strong<T>`. Also `vm().visitAggregate()` (atom string tables etc.) |
| `D` | Debugger | Sampling profiler, type profiler, ShadowChicken |
| `Ws` | Weak Sets | Iterates every `WeakBlock`; calls `WeakHandleOwner::isReachableFromOpaqueRoots()` to decide whether a weak ref should _become_ strong this cycle |
| `O` | Output | Calls `visitOutputConstraints()` on already-marked cells in output-constraint subspaces (executables, WeakMaps). This is the "re-run after marking discovers more" hook |
| `Jw` | JIT Worklist | CodeBlocks queued for compilation |
| `Cb` | CodeBlocks | Executing/compiling CodeBlocks |
Bun registers an additional constraint, `DOMGCOutputConstraint` (`src/jsc/bindings/BunGCOutputConstraint.cpp`), which calls `visitOutputConstraints` on every marked cell in Bun's output-constraint subspaces (event targets, generated classes with `visitAdditionalChildren`, etc.).
**Constraint volatility** controls when they re-run during the fixpoint:
- `GreyedByExecution` — may produce new grey cells whenever the mutator runs (re-run after every resume)
- `GreyedByMarking` — may produce new grey cells when _other_ marking happens (re-run after each drain)
- `SeldomGreyed` — usually doesn't add anything; run last
## Object layout: the 8-byte JSCell header
Every GC-managed object inherits `JSCell` (`runtime/JSCell.h`):
```
| StructureID (4) | indexingTypeAndMisc (1) | JSType (1) | flags (1) | cellState (1) |
```
- `StructureID` — compressed hidden-class pointer
- `indexingTypeAndMisc` — 2 bits are an embedded `WTF::Lock` (the **cell lock**); always CAS this byte
- `cellState` — inlined GC color, used by the write barrier
Out-of-line, in the `MarkedBlock` footer (or `PreciseAllocation` header for objects >~8KB):
- `isMarked` bit — survived last GC
- `isNewlyAllocated` bit — allocated since last GC
Liveness = `isMarked || isNewlyAllocated` (with logical-versioning so blocks aren't swept eagerly).
## CellState and the write barrier
`vendor/WebKit/Source/JavaScriptCore/heap/CellState.h`:
```cpp
PossiblyBlack = 0 // visited (or old-space-pending-rescan during full GC)
DefinitelyWhite = 1 // new / unmarked
PossiblyGrey = 2 // on the mark stack
```
Generational + concurrent GC share **one** retreating-wavefront barrier:
```cpp
// After: obj->field = newValue
if (obj->cellState <= blackThreshold) // 0 normally, bumped while GC is marking
writeBarrierSlowPath(obj); // → put obj on remembered set / revisit
```
**You almost never write this by hand.** Use `WriteBarrier<T>` as the field type and call `.set(vm, owner, value)` — it stores then barriers. A raw `JSCell*` / `JSValue` member without a `WriteBarrier` wrapper is a bug: eden GC will free the target out from under you.
`LazyProperty<Owner, T>`, `LazyClassStructure`, and `WriteBarrierStructureID` are barrier-aware variants for lazily-initialized fields and structures.
## Allocation: where objects live
`bmalloc/libpas` provides pages; JSC carves them up:
- **`MarkedBlock`** — 16KB block, fixed cell size (segregated free list). Footer holds bitvectors. 16-byte minimum cell alignment. `addr & ~(16KB-1)` → block, so liveness checks are O(1).
- **`PreciseAllocation`** — large objects (>~8KB), individually `malloc`'d, 96-byte GC header. Always returns addresses with `addr % 16 == 8` so `ptr & 8` distinguishes them from MarkedBlock cells.
- **`CompleteSubspace`** — size-segregated set of `BlockDirectory`s for general JS objects.
- **`IsoSubspace`** — one subspace per C++ type (security: a freed cell can only be reused for the _same_ type, defeating type-confusion UAF). **Every Bun class with native fields needs its own IsoSubspace**`subspaceFor<T>` in the header, slot in `BunClientData`/`DOMIsoSubspaces`.
**Allocation may trigger GC.** A safepoint exists at every allocation. Never assume "I just allocated X, so Y from before is still alive" unless Y is rooted.
## Conservative stack scanning — what it does and doesn't guarantee
`vendor/WebKit/Source/JavaScriptCore/heap/ConservativeRoots.cpp` walks the native stack/registers word-by-word (after `MachineThreads::tryCopyOtherThreadStacks` snapshots them). Any aligned word inside a live `MarkedBlock` cell or `PreciseAllocation` is a root.
**This means:** a `JSCell*` / `JSValue` in a C++/Rust local variable _usually_ keeps the object alive — no `Handle`/`Local` ceremony like V8.
**This does NOT mean you're always safe.** The compiler may dead-store-eliminate the local after its last visible use, or never spill it. If you extract an interior pointer (`string->characters8()`, butterfly storage, typed-array `vector()`) and then call something that can allocate, the original cell may no longer be on the stack:
```cpp
JSC::EnsureStillAliveScope keepAlive(cell); // RAII: forces cell onto stack until scope end
// ... use interior pointer, call things that allocate ...
```
or `ensureStillAliveHere(cell)`. In Rust: `value.ensure_still_alive()`.
## `visitChildren` — the per-cell tracing hook
```cpp
// In header:
DECLARE_VISIT_CHILDREN;
WriteBarrier<JSObject> m_callback;
WriteBarrier<Unknown> m_cachedValue;
// In .cpp:
template<typename Visitor>
void JSFoo::visitChildrenImpl(JSCell* cell, Visitor& visitor) {
auto* thisObject = jsCast<JSFoo*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor); // ALWAYS call base first
visitor.append(thisObject->m_callback);
visitor.append(thisObject->m_cachedValue);
}
DEFINE_VISIT_CHILDREN(JSFoo);
```
**Rules — runs concurrently on a GC thread:**
- No allocation. No `toJS`, no `jsString`, nothing that touches `vm.heap`.
- No `ref()`/`deref()` of `RefCounted` (not thread-safe).
- No locks the main thread might also take while allocating (deadlock).
- If a field can be torn by a racing mutator, take `Locker locker { thisObject->cellLock() }` in both `visitChildren` and the mutating site.
- Forgetting to `append()` a `WriteBarrier` field → use-after-free, often eden-GC-only, often only under load.
## `visitAdditionalChildren` and output constraints
`visitChildren` only sees the cell's own fields. When a JS wrapper's liveness should propagate to **other JS objects reachable through native state** (event listeners, observers, the JS values held inside a wrapped C++ object), Bun uses the WebCore pattern:
```cpp
// Custom hook called from BOTH places:
template<typename Visitor>
void JSFoo::visitAdditionalChildren(Visitor& visitor) {
wrapped().listeners().visitJSEventListeners(visitor);
visitor.addOpaqueRoot(&wrapped());
}
// 1) From visitChildren (normal marking):
DEFINE_VISIT_CHILDREN_WITH_MODIFIER(..., JSFoo) {
...
thisObject->visitAdditionalChildren(visitor);
}
// 2) From visitOutputConstraints (constraint fixpoint re-scan):
template<typename Visitor>
void JSFoo::visitOutputConstraints(JSCell* cell, Visitor& visitor) {
auto* thisObject = jsCast<JSFoo*>(cell);
Base::visitOutputConstraints(thisObject, visitor);
thisObject->visitAdditionalChildren(visitor);
}
```
**Why two entry points?** `visitChildren` runs once when the cell turns grey. But marking may later discover that some _other_ native object (an opaque root) is live, which retroactively makes more of _this_ cell's references live. `visitOutputConstraints` is re-invoked by `DOMGCOutputConstraint` during the constraint fixpoint to catch that.
To make a class participate, its IsoSubspace must be registered as an **output-constraint subspace** (`clientSubspaceFor*` with `outputConstraint` in `BunClientData` / generated `ZigGeneratedClasses.cpp`). The codegen does this automatically when `.classes.ts` has `hasPendingActivity`, `own` properties, or event-target semantics.
## Opaque roots — liveness through non-JSCell pointers
When native objects form a graph that should keep wrappers alive:
```cpp
// In some wrapper's visitAdditionalChildren:
visitor.addOpaqueRoot(nativePtr); // "nativePtr is reachable"
// Elsewhere, deciding whether ANOTHER wrapper survives:
bool JSBarOwner::isReachableFromOpaqueRoots(Handle<Unknown> h, void* ctx,
AbstractSlotVisitor& v, ASCIILiteral* reason) {
auto* bar = static_cast<Bar*>(ctx);
if (UNLIKELY(reason)) *reason = "Bar is in document tree"_s;
return v.containsOpaqueRoot(bar->ownerNode());
}
```
The opaque-root set is just a `HashSet<void*>` rebuilt each cycle. It's how DOM trees stay alive as a unit.
## `JSC::Weak<T>`, `WeakImpl`, `WeakBlock`, `WeakHandleOwner`
`JSC::Weak<T>` (`vendor/WebKit/Source/JavaScriptCore/heap/Weak.h`) is the GC-aware weak pointer. It does **not** keep its target alive; `.get()` returns `nullptr` after the target is collected.
Under the hood:
- Each `Weak<T>` owns a `WeakImpl*` (`vendor/WebKit/Source/JavaScriptCore/heap/WeakImpl.h`): `{ JSValue, WeakHandleOwner* (low bits = state), void* context }`. State is `Live → Dead → Finalized → Deallocated`.
- `WeakImpl`s are slab-allocated in 1KB **`WeakBlock`s** (`vendor/WebKit/Source/JavaScriptCore/heap/WeakBlock.h`, `blockSize = 1024`). Every `MarkedBlock` and `PreciseAllocation` has a `WeakSet` — a linked list of `WeakBlock`s for cells in that container.
- During the `Ws` constraint, each `WeakBlock::visit()` walks its `WeakImpl`s; for each one whose target is **not yet marked**, it calls `WeakHandleOwner::isReachableFromOpaqueRoots(handle, context, visitor, &reason)`. Return `true` → the target is marked (the weak ref is "upgraded" this cycle). This is how `hasPendingActivity()` and opaque-root reachability keep wrappers alive even when nothing strongly references them.
- After marking, `WeakBlock::reap()` flips unmarked `Live` impls to `Dead`. `WeakBlock::sweep()` later runs `WeakHandleOwner::finalize(handle, context)` on each `Dead` impl, then frees the slot. **`finalize` runs on the mutator thread but the cell is already dead — do not touch its JS fields.** Typical use: drop the wrapper from a native→JS wrapper cache.
```cpp
struct MyOwner final : public JSC::WeakHandleOwner {
bool isReachableFromOpaqueRoots(Handle<Unknown>, void* ctx,
AbstractSlotVisitor& v, ASCIILiteral*) override {
return static_cast<NativeThing*>(ctx)->hasPendingActivity();
}
void finalize(Handle<Unknown>, void* ctx) override {
static_cast<NativeThing*>(ctx)->m_wrapper = nullptr;
}
};
JSC::Weak<JSFoo> m_wrapper { jsFoo, &myOwnerSingleton, nativeThing };
```
`Weak<T>` is **move-only** (allocates a `WeakImpl`). Don't put it in a hot path; cache it.
## `JSRef` — the native↔wrapper reference pattern
When a native object needs to hold a reference back to its own JS wrapper, **use `JSRef`** (`src/jsc/JSRef.rs`), not `gcProtect`, not a raw `JSValue` field, and usually not `Strong` directly.
`JSRef` is a tagged union with three states:
- `Weak` — a bare `JSValue`. Does **not** keep the wrapper alive. Valid only because the wrapper's `finalize()` will flip this to `Finalized` before the cell is freed, so `try_get()` returns `None` instead of a dangling pointer. (This is _not_ a `JSC::Weak`; it's cheaper — no `WeakImpl` allocation.)
- `Strong` — wraps `bun_jsc::Strong` (a `JSC::Strong<Unknown>` root). Keeps the wrapper alive.
- `Finalized` — terminal; `try_get()` returns `None`.
Pattern: **strong while busy, weak while idle.**
```rust
this_value: JSRef, // initialized with JSRef::empty()
// On construction / when work starts:
self.this_value.set_strong(js_wrapper, global); // or .upgrade(global)
// When the last in-flight operation completes:
self.this_value.downgrade(); // Strong -> Weak, GC may now collect
// In any callback that needs the wrapper:
let Some(js_this) = self.this_value.try_get() else { return };
// In the codegen'd finalize():
self.this_value.finalize();
```
See `ServerWebSocket`, `UDPSocket`, `MySQLConnection`, `ValkeyClient` for real examples.
**`JSRef` requires a finalizer.** The `Weak` state is only sound because the codegen'd `finalize()` flips it to `Finalized` before the cell is reused. If your `.classes.ts` entry has `finalize: true` (almost all native-backed classes do), `JSRef` is the default choice for self-references.
**`JSRef` vs `hasPendingActivity`:** prefer `JSRef`. `hasPendingActivity: true` is a GC-thread-polled atomic predicate; its only real justification is when **many concurrent operations** independently keep the wrapper alive and there's no single place to call `upgrade()`/`downgrade()` — i.e., refcount-style liveness where the count is touched from multiple threads. That's uncommon. If you can identify "work started" / "work finished" edges, use `JSRef`. Don't add `hasPendingActivity` reflexively; it costs a constraint-fixpoint poll on every GC.
## `gcProtect` / `JSValueProtect` — almost never
`gcProtect()` / `JSValueProtect()` push into `Heap::m_protectedValues` (a ref-counted root map, visited by the `Msr` constraint). It's the legacy C-API mechanism. **Avoid it in Bun:**
- It's a raw global root with manual unprotect — easy to leak.
- It has no owner, so heap snapshots can't attribute the retention.
- `bun_jsc::Strong` / `JSRef` give the same guarantee with RAII and a destructor.
The only legitimate uses are inside the JSC C API shims themselves, or one-off debugging.
## Extra-memory reporting — `reportExtraMemoryAllocated` / `reportExtraMemoryVisited`
The GC schedules itself by bytes-allocated-since-last-GC. It only sees JSCell allocations, so a 32-byte wrapper around a 50MB native buffer looks like 32 bytes → GC never triggers → OOM.
**Contract — both halves are required:**
```cpp
// 1) When the native memory is allocated (or the wrapper takes ownership):
vm.heap.reportExtraMemoryAllocated(ownerCell, byteCount);
// 2) In visitChildren, every time the cell is visited:
visitor.reportExtraMemoryVisited(thisObject->wrapped().byteSize());
```
- `reportExtraMemoryAllocated` adds to the "since last GC" counter and may **immediately trigger a GC** (it's a safepoint). Call it _after_ the cell is fully constructed.
- `reportExtraMemoryVisited` adds to the "live bytes after this GC" counter, which sets the next trigger threshold. **If you forget this half**, the heap's high-water mark drifts down each cycle and you get back-to-back full GCs (the "GC death spiral").
- If the size changes over time, report the delta on growth (`reportExtraMemoryAllocated(cell, newSize - oldSize)`) and report the current size in `visitChildren`.
- `deprecatedReportExtraMemory` exists for callers that can't satisfy the visit-side half — avoid it.
In `.classes.ts`, `estimatedSize: true` generates the `reportExtraMemoryVisited` side; you implement `estimated_size()` in Rust. You still call `reportExtraMemoryAllocated` (or the binding's helper) at allocation time.
## `HeapAnalyzer` — heap snapshots and labelling
`vendor/WebKit/Source/JavaScriptCore/heap/HeapAnalyzer.h` is the abstract visitor used to build heap snapshots (Web Inspector "Heap Snapshot", and Bun's V8-compatible `BunV8HeapSnapshotBuilder`). When a snapshot is requested, marking runs with an analyzer attached and each cell's `analyzeHeap` static is called:
```cpp
void JSFoo::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) {
auto* thisObject = jsCast<JSFoo*>(cell);
Base::analyzeHeap(cell, analyzer);
analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());
analyzer.setLabelForCell(cell, thisObject->wrapped().url().string());
if (auto* child = thisObject->m_callback.get())
analyzer.analyzePropertyNameEdge(cell, child, vm.propertyNames->callback.impl());
}
```
API (`HeapAnalyzer`):
- `analyzeNode(cell)` — record a node
- `analyzeEdge(from, to, RootMarkReason)` / `analyzePropertyNameEdge` / `analyzeVariableNameEdge` / `analyzeIndexEdge` — record a labelled edge
- `setWrappedObjectForCell(cell, void*)` — link wrapper → native pointer
- `setLabelForCell(cell, String)` — human-readable name in the snapshot
- `setOpaqueRootReachabilityReasonForCell` — why a weakly-held wrapper survived
If your class shows up as an opaque blob in heap snapshots, implement `analyzeHeap`.
## How to keep things alive (decision table)
| Scenario | Mechanism |
| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| JSCell field pointing to another JSCell | `WriteBarrier<T>` member + `visitor.append(m_field)` in `visitChildren` |
| Native state inside the wrapped C++ object holds JS values | `visitAdditionalChildren` + register subspace as output-constraint |
| C++/Rust local across allocation/call | Conservative scan (free) — add `EnsureStillAliveScope` / `value.ensure_still_alive()` if extracting interior pointers or seeing release-only crashes |
| Native object holds its own JS wrapper (class has `finalize: true`) | **`JSRef`** — `upgrade()` when work starts, `downgrade()` when idle. **This is the default.** |
| Native object owns an arbitrary JS value (callback, options object) | `bun_jsc::Strong` — drop in `finalize()`. Watch for cycles |
| C++ non-GC object owns a JS value as a root | `JSC::Strong<T>`. **Danger:** cycle if the JS value can reach back → leak |
| Weak ref with resurrection predicate / finalize callback (C++) | `JSC::Weak<T>` + `WeakHandleOwner` |
| Wrapper kept alive by **many concurrent operations** with no single busy/idle edge | `.classes.ts` `hasPendingActivity: true` (atomic flag polled on GC thread). **Uncommon — prefer `JSRef` if you can.** |
| Group of wrappers share lifetime via a native graph | `visitor.addOpaqueRoot(ptr)` + `containsOpaqueRoot(ptr)` |
| Temporarily forbid GC in a critical section | `DeferGC deferGC(vm)` — defers until scope exit. Never hold across user JS |
| Tell GC about off-heap memory you own | `reportExtraMemoryAllocated` on alloc **and** `reportExtraMemoryVisited` in `visitChildren` |
| ~~Mark a value as root from C API~~ | ~~`gcProtect` / `JSValueProtect`~~**avoid**; use `bun_jsc::Strong` / `JSRef` instead |
## Destruction & finalizers
- `static constexpr bool needsDestruction = true` → C++ destructor runs when the cell is swept. Sweep is **lazy** (next allocation from that block, or `IncrementalSweeper`), so destruction is delayed arbitrarily. Do not rely on it for prompt resource release — expose explicit `close()`/`dispose()`.
- In `.classes.ts`, `finalize: true` → native `finalize()` called from the destructor. Same laziness applies.
- `WeakHandleOwner::finalize` runs earlier (at weak-reap time) but the cell is already dead; only use it to clear caches.
- Destructors run on the mutator thread but **other JS objects may already be swept** — do not dereference `WriteBarrier` fields in a destructor.
## Debugging GC issues
```bash
# Force synchronous, frequent GC — turns rare races into immediate crashes
BUN_JSC_collectContinuously=1 BUN_JSC_useConcurrentGC=0 bun-debug test.js
# Zero free cells so UAF reads are obvious
BUN_JSC_scribbleFreeCells=1
# Validate the GC's own bookkeeping
BUN_JSC_verifyGC=1 BUN_JSC_verboseVerifyGC=1
# See what's being collected / heap growth
BUN_JSC_logGC=2 BUN_JSC_showObjectStatistics=1
# Force GC from JS
Bun.gc(true) // sync full GC
require('bun:jsc').heapStats()
```
If a bug only reproduces with concurrent GC **on** → missing write barrier or `visitChildren` race.
If it only reproduces with `collectContinuously=1` → something isn't rooted across an allocation.
If memory grows but `heapStats().heapSize` doesn't → missing `reportExtraMemoryAllocated`.
If GC runs constantly with little garbage → missing `reportExtraMemoryVisited`.
## Key source files
- `vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp``collectImpl`, `addCoreConstraints` (root list)
- `vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp` / `SlotVisitorInlines.h``drain()`, `append`, `addOpaqueRoot`, `reportExtraMemoryVisited`
- `vendor/WebKit/Source/JavaScriptCore/heap/MarkedBlock.h`, `vendor/WebKit/Source/JavaScriptCore/heap/PreciseAllocation.h` — cell containers, `isLive`
- `vendor/WebKit/Source/JavaScriptCore/heap/CellState.h`, `runtime/WriteBarrier.h`, `runtime/WriteBarrierInlines.h`
- `vendor/WebKit/Source/JavaScriptCore/heap/ConservativeRoots.cpp`, `vendor/WebKit/Source/JavaScriptCore/heap/MachineStackMarker.cpp`
- `vendor/WebKit/Source/JavaScriptCore/heap/Weak.h`, `vendor/WebKit/Source/JavaScriptCore/heap/WeakImpl.h`, `vendor/WebKit/Source/JavaScriptCore/heap/WeakBlock.h`, `vendor/WebKit/Source/JavaScriptCore/heap/WeakSet.h`, `vendor/WebKit/Source/JavaScriptCore/heap/WeakHandleOwner.h`
- `vendor/WebKit/Source/JavaScriptCore/heap/HeapAnalyzer.h`, `vendor/WebKit/Source/JavaScriptCore/heap/HeapSnapshotBuilder.cpp`, Bun: `vendor/WebKit/Source/JavaScriptCore/heap/BunV8HeapSnapshotBuilder.cpp`
- `vendor/WebKit/Source/JavaScriptCore/heap/DeferGC.h`, `vendor/WebKit/Source/JavaScriptCore/heap/Strong.h`, `vendor/WebKit/Source/JavaScriptCore/heap/HandleSet.h`
- `runtime/JSCell.h` / `JSCellInlines.h` — header layout, `visitChildren` base
- Bun: `src/jsc/bindings/BunGCOutputConstraint.cpp`, `ZigGeneratedClasses.cpp` (codegen'd `visitChildren` / `visitOutputConstraints`)
+96
View File
@@ -0,0 +1,96 @@
---
name: rust-system-calls
description: Guides using bun_sys for system calls and file I/O in Rust. Use when implementing file operations, opening fds, or any syscall path instead of std::fs or libc.
---
# System Calls & File I/O in Rust
Use `bun_sys` instead of `std::fs` or raw `libc` for cross-platform syscalls with proper error handling.
## bun_sys::File (Preferred)
For most file operations, use the `bun_sys::File` wrapper. It owns the fd and closes on `Drop`.
```rust
use bun_sys::{File, Fd, O};
let file = File::openat(Fd::cwd(), b"path/to/file", O::RDONLY, 0)?;
let mut buf = vec![0u8; 4096];
let n = file.read_all(&mut buf)?; // loops until EOF or full
// `file` closes on Drop.
```
### Complete Example
```rust
use bun_sys::{File, Fd, O};
pub fn write_file(path: &[u8], data: &[u8]) -> Result<(), bun_sys::Error> {
let file = File::openat(Fd::cwd(), path, O::WRONLY | O::CREAT | O::TRUNC, 0o664)?;
file.write_all(data)?;
Ok(())
}
```
## Why bun_sys?
| Aspect | bun_sys | std::fs / libc |
| ----------- | ---------------------------------- | ---------------------- |
| Return Type | `Maybe<T>` with rich `Error` | `io::Error` (lossy) |
| Windows | Full support with libuv fallback | Incomplete/POSIX-ish |
| Error Info | errno, syscall tag, path, fd | errno only |
| EINTR | Automatic retry | Manual handling |
| Paths | `&[u8]` (WTF-8 safe) | `&Path` (UTF-8 lossy) |
## Error Handling with Maybe<T>
`bun_sys` functions return `Maybe<T> = Result<T, bun_sys::Error>`. Propagate with `?`; convert to a JS exception via `bun_sys_jsc::ErrorJsc::to_js`:
```rust
use bun_sys_jsc::ErrorJsc;
use bun_sys::{File, Fd, O};
let file = match File::openat(Fd::cwd(), path, O::RDONLY, 0) {
Ok(f) => f,
Err(err) => return Ok(err.to_js(global)?),
};
```
`bun_sys::Error` carries `errno`, `syscall: Tag`, and `path: Box<[u8]>`. To branch on errno:
```rust
match bun_sys::unlink(path) {
Ok(()) => {}
Err(e) if e.errno() == bun_c::ENOENT => {} // already gone
Err(e) => return Err(e),
}
```
## Key Types and Functions
- `Fd` (`bun_core::Fd`) — cross-platform file descriptor. `Fd::cwd()`, `Fd::stdin()/stdout()/stderr()`, `fd.close()`.
- `File::open(path: &ZStr, flags, mode)` / `File::openat(dir: Fd, path: &[u8], flags, mode)` / `File::make_open(...)` (creates parent dirs) / `File::create(dir, path, truncate)`
- `file.read(buf)` / `read_all(buf)` / `read_to_end()` / `read_to_end_small()` / `write(buf)` / `write_all(buf)`
- `bun_sys::open`, `read`, `write`, `pread`, `pwrite`, `stat`, `fstat`, `lstat`, `mkdir`, `unlink`, `rename`, `symlink`, `chmod` — free fns over `Fd`
- Open flags: `bun_sys::O::RDONLY`, `O::WRONLY | O::CREAT | O::TRUNC`, etc.
## Path Buffers
Use `bun_paths` for joining/normalization and the path-buffer pool to avoid 64 KB stack allocations on Windows:
```rust
use bun_paths::{path_buffer_pool, resolve_path::{self, platform}};
let mut buf = path_buffer_pool::get();
let joined = resolve_path::join_string_buf::<platform::Auto>(&mut *buf, &[dir, name]);
let file = File::openat(Fd::cwd(), joined, O::RDONLY, 0)?;
```
## Common Mistakes
- **Don't `.unwrap()`** a `bun_sys` result that user input or the OS can cause to fail at runtime — return the error.
- **Don't use `std::fs::File`** — it loses the syscall tag and path needed for Node-compatible error objects.
- **Don't allocate `PathBuffer` on the stack** in hot paths — use `path_buffer_pool::get()`.
- **Don't forget `Drop`** alone closes a `File` — never `file.fd().close()` while the `File` is still live (double close).
See `src/CLAUDE.md` for the full `bun_core`/`bun_sys` reference.
+50
View File
@@ -0,0 +1,50 @@
---
name: slowest-tests
description: Find the top-N slowest test files in CI from a recent BuildKite run, optionally posting the results to a Slack channel as a formatted table. Use when asked to find slow CI tests, "what's making CI slow", or to post a slow-test report to Slack.
---
# Slowest CI tests
Args: `[N] [#channel]` — both optional. `N` defaults to 500. If `#channel` is omitted, just print the table and stop.
## 1. Gather the data
Run the script. With no build number it auto-picks the most recent finished build from a merged PR.
```bash
bun run ci:slowest # top 500 from a recent merged-PR build → TSV on stdout
bun run ci:slowest 47324 100 # specific build, top 100
bun run ci:slowest --json > /tmp/slow.json
```
The script (`scripts/ci-slowest-tests.ts`) does the heavy lifting:
- Lists `test-bun` jobs from `bk build view <N>`, skipping `retried: true`.
- Fetches each job's `raw_log_url` directly with `Authorization: Bearer $BUILDKITE_TOKEN`. **Do not use `bk job log` — it hangs indefinitely on some Windows/alpine jobs.**
- Caches logs to `$TMPDIR/bun-ci-logs-<build>/` so re-runs are instant.
- Parses `_bk;t=<ms> ... --- [N/TOTAL] <file>` group headers, normalising backslashes to `/` and stripping `[attempt #N]` retries.
- Aggregates each file's duration as the **max across all platforms** (a file appears once per platform; shards within a platform are disjoint).
- Drops `package.json` / non-JS entries — those are setup steps, not tests.
If the script can't find a build automatically (rare — it walks the last 10 merged PRs), pick one yourself: `gh pr list --state merged --limit 10 --json number,headRefName`, then `bk build list --branch <headRefName>` and pass the first build with a `finished_at`. Merged-PR builds usually report `state: failed` because of flaky tests — that's fine, the timing data is still valid.
## 2. Post to Slack (only if a channel was given)
**`slack_send_message` does NOT support markdown tables** — its markdown→blocks converter rejects `| a | b |` syntax with `invalid_blocks`. Don't use Canvases either; they render tables but the MCP proxy 502s above ~10 KB and the result is clunky.
Procedure:
1. Look up the channel ID with `slack_search_channels` (the user gives a name, you need the `C…` ID).
2. Write the full N-row markdown table to `~/code/tmp/top<N>-slow-tests.md`, then upload it as a **secret gist**: `gh gist create <file> --desc "Bun CI: top N slowest test files (build #<num>)"`. (The Slack MCP has no file-upload tool; secret gist is the agreed fallback. Do **not** ask the user to attach anything manually.)
3. Post the **main** message: header (build link + gist link, "Rest in thread.") followed by the **top 20** bullets. Row format:
```
• 325s `test/js/bun/cron/in-process-cron.test.ts` 🐧 x64-baseline
• 96s `test/js/bun/http/serve-body-leak.test.ts` 🐧 x64-asan
```
- seconds: plain text, left-aligned, padded so the backticks line up
- filepath: code-font, **full path including `test/` prefix and extension** — do not strip anything
- platform: standard Unicode emoji only (🐧 linux, 🪟 windows, 🍎 macOS — never workspace-custom shortcodes) followed by the arch/variant **verbatim** from the job name (`x64-asan`, `aarch64`, `x64-baseline` — do not abbreviate)
4. Reply in-thread (`thread_ts` = the main message) with rows 21N in the same bullet format, packed into chunks under 4800 chars each (~70 rows per chunk). Post chunks sequentially so they stay ordered.
+79
View File
@@ -0,0 +1,79 @@
---
description: Re-sync src/react_compiler/ against upstream facebook/react. Use when bumping the React Compiler, when upstream lands a fix we need, or when src/react_compiler/UPSTREAM_PORTED is stale.
---
# Re-syncing the React Compiler
Bun integrates the React Compiler by **directly lowering Bun's AST into the
compiler's HIR** and **directly emitting Bun's AST from codegen**, skipping
upstream's Babel-shaped `react_compiler_ast` intermediate entirely. Nothing is
vendored — every upstream crate Bun uses has been ported into
`src/react_compiler/` and is built as part of the `bun_react_compiler` crate.
There are two kinds of port:
- **Whole-crate ports** (`hir/`, `diagnostics/`, `ssa/`, `inference/`,
`typeinference/`, `optimization/`, `validation/`, `reactive_scopes/`,
`utils/`) — byte-for-byte copies of the upstream crate's `src/`, modulo
crate-name/import rewrites. Upstream diffs apply mechanically.
- **AST-boundary ports** (`lowering/build_hir/`, `lowering/*.rs`, `codegen.rs`,
`pipeline.rs`, `program.rs`, `imports.rs`, `compile_result.rs`) — re-typed
onto `bun_ast` using the mapping in `src/react_compiler/DESIGN.md`. Upstream
diffs are re-ported by hand. Upstream's `gating.rs` is folded into
`program.rs`; `suppression.rs` is handled by the lexer
(`js_parser/lexer.rs`) and consumed in `program.rs`;
`identifier_loc_index.rs` is not needed because Bun's `Ref` already
provides binding identity.
`react_compiler_ast`, `react_compiler_lowering`, and the `react_compiler`
umbrella crate are **not** in Bun's tree at all — they exist upstream only as
the porting reference for the AST-boundary files.
## Sync procedure
1. **Produce the upstream diff.** The script sparse-fetches facebook/react into
a temp dir (nothing is written to the repo) and prints, per ported file, the
diff between `src/react_compiler/UPSTREAM_PORTED` and upstream's tip:
```sh
scripts/sync-react-compiler.sh # or pass an explicit <sha>
```
Output is grouped into three sections: whole-crate ports, AST-boundary
ports, and any new upstream file that newly references `react_compiler_ast`
(i.e. a new boundary file that needs a fresh Bun port).
2. **Apply whole-crate diffs mechanically.** For each hunk under the
whole-crate section, apply it to the corresponding `src/react_compiler/<dir>/`
file. The only systematic edit is import paths (`react_compiler_hir::` →
`crate::hir::`, etc.); everything else lands verbatim.
3. **Re-port AST-boundary diffs by hand.** For each hunk under the
AST-boundary section, re-port it into the named Bun file using the
type-mapping table in `src/react_compiler/DESIGN.md`: where upstream reads
`react_compiler_ast::expressions::Expression::Foo`, the Bun port reads
`bun_ast::expr::Data::EFoo`; where upstream constructs
`react_compiler_ast::statements::Statement::Foo { … }`, the Bun port calls
`Stmt::alloc(S::Foo { … }, loc)`. Keep control flow, pass ordering,
variable names, and comments 1:1 with upstream — only the AST reads/writes
change.
For large diffs, fan out one agent per file with the upstream diff + the Bun
port + DESIGN.md as context, then adversarially review each port.
4. **Handle new boundary files.** If the third section lists any file, write a
fresh Bun port of it under `src/react_compiler/`, add it to both arrays in
`scripts/sync-react-compiler.sh`, and add a row to the layout table in
`DESIGN.md`.
5. **Verify.**
```sh
cargo check -p bun_react_compiler
bun bd test test/bundler/transpiler/react-compiler.test.ts
```
Snapshots will change if codegen changed upstream — review the diff against
upstream's new fixture output and update with `bun bd test -u` if it
matches.
6. **Update the port marker** to the `UPSTREAM_HEAD` the script printed:
```sh
echo <new-sha> > src/react_compiler/UPSTREAM_PORTED
```
+46
View File
@@ -0,0 +1,46 @@
---
name: verify
description: Verify a Bun runtime change by driving the debug binary end-to-end.
---
# Verify a Bun runtime change
Build and drive the debug binary directly — never `bun test`, never import-and-call.
## Build
```sh
bun bd --version # builds ./build/debug/bun-debug and prints its version
```
## Drive
For any JS-visible change, run the debug binary with `-e` and observe stdout:
```sh
bun bd -e '<repro>' # builds, then runs; sets BUN_DEBUG_QUIET_LOGS for you
```
For worker/subprocess-shaped changes, spawn a subprocess (still `-e`) so worker teardown / event-loop-idle paths are exercised. Cross-check against `node -e '<same repro>'` for Node-compat changes.
## Gotchas
- **A `src/js/**` edit can silently not reach the binary.** `bundle-modules`
regenerates `build/<cfg>/codegen/InternalModuleRegistryConstants.h`, but the C++
TU that embeds it is not always recompiled, so the build succeeds while the
binary still runs the OLD JS. Gate on the binary, not the build: ask the binary
you just built — `bun bd -e 'console.log(<Class>.toString().includes("<new-id>"))'`
(or run `./build/<cfg>/bun` directly). Plain `bun` is the system Bun on $PATH and
never has your edit, so it answers about the wrong binary.
If false, `touch src/jsc/bindings/InternalModuleRegistry.cpp` and rebuild.
- **Prefix every `bun bd` with `PATH="$HOME/.cargo/bin:$PATH"`** — Homebrew's `rust`
formula shadows the pinned nightly, and `bun bd` dies with `the option 'Z' is only
accepted on the nightly compiler`. `bun bd` re-runs cargo on every invocation, so
this is needed for follow-up runs too, not just the first build.
- `node:cluster` changes can't be driven with `-e`: `cluster.fork()` re-execs `argv[1]`, so workers need a real file on disk. Write a scratch script and run `./build/debug/bun-debug <file>`.
- Only one `bun bd` per worktree at a time — a second one blocks on the build lock and looks like a runtime hang. Build once, then drive `./build/debug/bun-debug` directly under `timeout`.
- `BUN_DEBUG_QUIET_LOGS=1` suppresses debug-build log spam.
- Debug builds print `[cachefs]`/`[sys]` lines to stdout; filter them before diffing
output against `node`.
- MessagePort's `.on/.off` are added by requiring `worker_threads` — plain `new MessageChannel()` ports only have `addEventListener` until then.
- The debug+asan build is 10-100× slower than release; large-allocation stress tests can time out locally while passing in CI.
@@ -0,0 +1,222 @@
---
name: writing-bundler-tests
description: Guides writing bundler tests using itBundled/expectBundled in test/bundler/. Use when creating or modifying bundler, transpiler, or code transformation tests.
---
# Writing Bundler Tests
Bundler tests use `itBundled()` from `test/bundler/expectBundled.ts` to test Bun's bundler.
## Basic Usage
```typescript
import { describe } from "bun:test";
import { itBundled, dedent } from "./expectBundled";
describe("bundler", () => {
itBundled("category/TestName", {
files: {
"index.js": `console.log("hello");`,
},
run: {
stdout: "hello",
},
});
});
```
Test ID format: `category/TestName` (e.g., `banner/CommentBanner`, `minify/Empty`)
## File Setup
```typescript
{
files: {
"index.js": `console.log("test");`,
"lib.ts": `export const foo = 123;`,
"nested/file.js": `export default {};`,
},
entryPoints: ["index.js"], // defaults to first file
runtimeFiles: { // written AFTER bundling
"extra.js": `console.log("added later");`,
},
}
```
## Bundler Options
```typescript
{
outfile: "/out.js",
outdir: "/out",
format: "esm" | "cjs" | "iife",
target: "bun" | "browser" | "node",
// Minification
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
// Code manipulation
banner: "// copyright",
footer: "// end",
define: { "PROD": "true" },
external: ["lodash"],
// Advanced
sourceMap: "inline" | "external",
splitting: true,
treeShaking: true,
drop: ["console"],
}
```
## Runtime Verification
```typescript
{
run: {
stdout: "expected output", // exact match
stdout: /regex/, // pattern match
partialStdout: "contains this", // substring
stderr: "error output",
exitCode: 1,
env: { NODE_ENV: "production" },
runtime: "bun" | "node",
// Runtime errors
error: "ReferenceError: x is not defined",
},
}
```
## Bundle Errors/Warnings
```typescript
{
bundleErrors: {
"/file.js": ["error message 1", "error message 2"],
},
bundleWarnings: {
"/file.js": ["warning message"],
},
}
```
## Dead Code Elimination (DCE)
Add markers in source code:
```javascript
// KEEP - this should survive
const used = 1;
// REMOVE - this should be eliminated
const unused = 2;
```
```typescript
{
dce: true,
dceKeepMarkerCount: 5, // expected KEEP markers
}
```
## Capture Pattern
Verify exact transpilation with `capture()`:
```typescript
itBundled("string/Folding", {
files: {
"index.ts": `capture(\`\${1 + 1}\`);`,
},
capture: ['"2"'], // expected captured value
minifySyntax: true,
});
```
## Post-Bundle Assertions
```typescript
{
onAfterBundle(api) {
api.expectFile("out.js").toContain("console.log");
api.assertFileExists("out.js");
const content = api.readFile("out.js");
expect(content).toMatchSnapshot();
const values = api.captureFile("out.js");
expect(values).toEqual(["2"]);
},
}
```
## Common Patterns
**Simple output verification:**
```typescript
itBundled("banner/Comment", {
banner: "// copyright",
files: { "a.js": `console.log("Hello")` },
onAfterBundle(api) {
api.expectFile("out.js").toContain("// copyright");
},
});
```
**Multi-file CJS/ESM interop:**
```typescript
itBundled("cjs/ImportSyntax", {
files: {
"entry.js": `import lib from './lib.cjs'; console.log(lib);`,
"lib.cjs": `exports.foo = 'bar';`,
},
run: { stdout: '{"foo":"bar"}' },
});
```
**Error handling:**
```typescript
itBundled("edgecase/InvalidLoader", {
files: { "index.js": `...` },
bundleErrors: {
"index.js": ["Unsupported loader type"],
},
});
```
## Test Organization
```text
test/bundler/
├── bundler_banner.test.ts
├── bundler_string.test.ts
├── bundler_minify.test.ts
├── bundler_cjs.test.ts
├── bundler_edgecase.test.ts
├── bundler_splitting.test.ts
├── css/
├── transpiler/
└── expectBundled.ts
```
## Running Tests
```bash
bun bd test test/bundler/bundler_banner.test.ts
BUN_BUNDLER_TEST_FILTER="banner/Comment" bun bd test bundler_banner.test.ts
BUN_BUNDLER_TEST_DEBUG=1 bun bd test bundler_minify.test.ts
```
## Key Points
- Use `dedent` for readable multi-line code
- File paths are relative (e.g., `/index.js`)
- Use `capture()` to verify exact transpilation results
- Use `.toMatchSnapshot()` for complex outputs
- Pass array to `run` for multiple test scenarios
@@ -0,0 +1,94 @@
---
name: writing-dev-server-tests
description: Guides writing HMR/Dev Server tests in test/bake/. Use when creating or modifying dev server, hot reloading, or bundling tests.
---
# Writing HMR/Dev Server Tests
Dev server tests validate hot-reloading robustness and reliability.
## File Structure
- `test/bake/bake-harness.ts` - shared utilities: `devTest`, `prodTest`, `devAndProductionTest`, `Dev` class, `Client` class
- `test/bake/client-fixture.mjs` - subprocess for `Client` (page loading, IPC queries)
- `test/bake/dev/*.test.ts` - dev server and hot reload tests
- `test/bake/dev-and-prod.ts` - tests running on both dev and production mode
## Test Categories
- `bundle.test.ts` - DevServer-specific bundling bugs
- `css.test.ts` - CSS bundling issues
- `plugins.test.ts` - development mode plugins
- `ecosystem.test.ts` - library compatibility (prefer concrete bugs over full package tests)
- `esm.test.ts` - ESM features in development
- `html.test.ts` - HTML file handling
- `react-spa.test.ts` - React, react-refresh transform, server components
- `sourcemap.test.ts` - source map correctness
## devTest Basics
```ts
import { devTest, emptyHtmlFile } from "../bake-harness";
devTest("html file is watched", {
files: {
"index.html": emptyHtmlFile({
scripts: ["/script.ts"],
body: "<h1>Hello</h1>",
}),
"script.ts": `console.log("hello");`,
},
async test(dev) {
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
await dev.patch("index.html", { find: "Hello", replace: "World" });
await dev.fetch("/").expect.toInclude("<h1>World</h1>");
await using c = await dev.client("/");
await c.expectMessage("hello");
await c.expectReload(async () => {
await dev.patch("index.html", { find: "World", replace: "Bar" });
});
await c.expectMessage("hello");
},
});
```
## Key APIs
- **`files`**: Initial filesystem state
- **`dev.fetch()`**: HTTP requests
- **`dev.client()`**: Opens browser instance
- **`dev.write/patch/delete`**: Filesystem mutations (wait for hot-reload automatically)
- **`c.expectMessage()`**: Assert console.log output
- **`c.expectReload()`**: Wrap code that causes hard reload
**Important**: Use `dev.write/patch/delete` instead of `node:fs` - they wait for hot-reload.
## Testing Errors
```ts
devTest("import then create", {
files: {
"index.html": `<!DOCTYPE html><html><head></head><body><script type="module" src="/script.ts"></script></body></html>`,
"script.ts": `import data from "./data"; console.log(data);`,
},
async test(dev) {
const c = await dev.client("/", {
errors: ['script.ts:1:18: error: Could not resolve: "./data"'],
});
await c.expectReload(async () => {
await dev.write("data.ts", "export default 'data';");
});
await c.expectMessage("data");
},
});
```
Specify expected errors with the `errors` option:
```ts
await dev.delete("other.ts", {
errors: ['index.ts:1:16: error: Could not resolve: "./other"'],
});
```
+147
View File
@@ -0,0 +1,147 @@
language: en-US
issue_enrichment:
auto_enrich:
enabled: false
reviews:
profile: assertive
request_changes_workflow: false
high_level_summary: false
high_level_summary_placeholder: "@coderabbitai summary"
high_level_summary_in_walkthrough: true
auto_title_placeholder: "@coderabbitai"
review_status: false
commit_status: false
fail_commit_status: false
collapse_walkthrough: false
changed_files_summary: true
sequence_diagrams: false
estimate_code_review_effort: false
assess_linked_issues: true
related_issues: true
related_prs: true
suggested_labels: false
suggested_reviewers: true
in_progress_fortune: false
poem: false
abort_on_close: true
path_filters:
- "!test/js/node/test/"
auto_review:
enabled: true
auto_incremental_review: true
drafts: false
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
pre_merge_checks:
docstrings:
mode: off
title:
mode: warning
description:
mode: warning
issue_assessment:
mode: warning
tools:
shellcheck:
enabled: true
ruff:
enabled: true
markdownlint:
enabled: true
github-checks:
enabled: true
timeout_ms: 90000
languagetool:
enabled: true
enabled_only: false
level: default
biome:
enabled: true
hadolint:
enabled: true
swiftlint:
enabled: true
phpstan:
enabled: true
level: default
phpmd:
enabled: true
phpcs:
enabled: true
golangci-lint:
enabled: true
yamllint:
enabled: true
gitleaks:
enabled: true
checkov:
enabled: true
detekt:
enabled: true
eslint:
enabled: true
flake8:
enabled: true
rubocop:
enabled: true
buf:
enabled: true
regal:
enabled: true
actionlint:
enabled: true
pmd:
enabled: true
clang:
enabled: true
cppcheck:
enabled: true
semgrep:
enabled: true
circleci:
enabled: true
clippy:
enabled: true
sqlfluff:
enabled: true
prismaLint:
enabled: true
pylint:
enabled: true
oxc:
enabled: true
shopifyThemeCheck:
enabled: true
luacheck:
enabled: true
brakeman:
enabled: true
dotenvLint:
enabled: true
htmlhint:
enabled: true
checkmake:
enabled: true
osvScanner:
enabled: true
chat:
auto_reply: true
knowledge_base:
opt_out: false
code_guidelines:
enabled: true
filePatterns:
- "**/.cursor/rules/*.mdc"
- "**/CLAUDE.md"
+18
View File
@@ -0,0 +1,18 @@
**/*.a
**/*.o
**/.next
**/CMakeCache.txt
**/node_modules
.git
examples
node_modules
packages/**/bun
packages/**/bun-profile
src/bun.js/WebKit
src/bun.js/WebKit/LayoutTests
build
vendor
node_modules
*.trace
packages/bun-uws/fuzzing
+8
View File
@@ -0,0 +1,8 @@
# https://EditorConfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
+65
View File
@@ -0,0 +1,65 @@
*.css text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.js text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.jsx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.tsx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.ts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.c text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.cpp text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.cc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.yml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.toml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.rs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.h text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.json text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mdc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mdx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mjs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
# Patch files are line-ending-sensitive — `git apply` rejects CRLF as corrupt.
*.patch text eol=lf
*.lockb binary diff=lockb
# XML loader fixtures in specific byte encodings (BOMs, UTF-16, ISO-8859-1);
# any newline or encoding normalization would change what they test.
test/js/bun/resolve/xml/xml-utf16le-bom.xml -text
test/js/bun/resolve/xml/xml-utf16be-bom.xml -text
test/js/bun/resolve/xml/xml-utf8-bom.xml -text
test/js/bun/resolve/xml/xml-latin1.xml -text
.vscode/launch.json linguist-generated
fixture.*.c linguist-generated
*-fixture* linguist-generated
src/jsc/bindings/ZigGeneratedCode.h linguist-generated
src/jsc/bindings/ZigGeneratedCode.cpp linguist-generated
src/jsc/bindings/headers.h linguist-generated
packages/bun-uws/fuzzing/seed-corpus/**/* linguist-generated
test/expected-durations.json linguist-generated
src/jsc/bindings/sqlite/sqlite3.c linguist-vendored
src/jsc/bindings/sqlite/sqlite3_local.h linguist-vendored
src/simdutf_sys/bun-simdutf.cpp linguist-vendored
src/simdutf_sys/bun-simdutf.h linguist-vendored
docs/**/* linguist-documentation
# Don't count tests in the language stats - https://github.com/github-linguist/linguist/blob/master/docs/overrides.md
test/**/* linguist-documentation
bench/**/* linguist-documentation
examples/**/* linguist-documentation
vendor/*.c linguist-vendored
vendor/brotli/** linguist-vendored
test/js/node/test/fixtures linguist-vendored
test/js/node/test/common linguist-vendored
test/js/bun/css/files linguist-vendored
.vscode/*.json linguist-language=JSON-with-Comments
src/cli/init/tsconfig.default.json linguist-language=JSON-with-Comments
+9
View File
@@ -0,0 +1,9 @@
# Project
/.github/CODEOWNERS @Jarred-Sumner
# Tests
/test/expectations.txt @Jarred-Sumner
# Types
*.d.ts @alii
/packages/bun-types/ @alii
+47
View File
@@ -0,0 +1,47 @@
name: 🐛 Bug Report
description: Report an issue that should be fixed
labels:
- bug
- needs triage
body:
- type: markdown
attributes:
value: |
Thank you for submitting a bug report. It helps make Bun better.
If you need help or support using Bun, and are not reporting a bug, please
join our [Discord](https://discord.gg/CXdq2DP29u) server, where you can ask questions in the [`#help`](https://discord.gg/32EtH6p7HN) forum.
Make sure you are running the [latest](https://bun.com/docs/installation#upgrading) version of Bun.
The bug you are experiencing may already have been fixed.
Please try to include as much information as possible.
- type: input
attributes:
label: What version of Bun is running?
description: Copy the output of `bun --revision`
- type: input
attributes:
label: What platform is your computer?
description: |
For MacOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
attributes:
label: What do you see instead?
description: If possible, please provide text instead of a screenshot.
- type: textarea
attributes:
label: Additional information
description: Is there anything else you think we should know?
@@ -0,0 +1,45 @@
name: 🇹 TypeScript Type Bug Report
description: Report an issue with TypeScript types
labels: [bug, types]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a bug report. It helps make Bun better.
If you need help or support using Bun, and are not reporting a bug, please
join our [Discord](https://discord.gg/CXdq2DP29u) server, where you can ask questions in the [`#help`](https://discord.gg/32EtH6p7HN) forum.
Make sure you are running the [latest](https://bun.com/docs/installation#upgrading) version of Bun.
The bug you are experiencing may already have been fixed.
Please try to include as much information as possible.
- type: input
attributes:
label: What version of Bun is running?
description: Copy the output of `bun --revision`
- type: input
attributes:
label: What platform is your computer?
description: |
For MacOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
attributes:
label: What do you see instead?
description: If possible, please provide text instead of a screenshot.
- type: textarea
attributes:
label: Additional information
description: Is there anything else you think we should know?
@@ -0,0 +1,24 @@
name: 🚀 Feature Request
description: Suggest an idea, feature, or enhancement
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thank you for submitting an idea. It helps make Bun better.
If you want to discuss Bun, or learn how others are using Bun, please
join our [Discord](https://discord.gg/CXdq2DP29u) server, where you can share in the [`#feedback`](https://discord.gg/unwUnHBNqy) channel.
- type: textarea
attributes:
label: What is the problem this feature would solve?
validations:
required: true
- type: textarea
attributes:
label: What is the feature you are proposing to solve the problem?
validations:
required: true
- type: textarea
attributes:
label: What alternatives have you considered?
+29
View File
@@ -0,0 +1,29 @@
name: 📗 Documentation Issue
description: Tell us if there is missing or incorrect documentation
labels: [docs]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a documentation request. It helps make Bun better.
We are working on moving documentation from the [README](https://github.com/oven-sh/bun#table-of-contents) to a documentation website. Please report as many issues or missing content requests as you can so we can incoperate that in the new documentation.
- type: dropdown
attributes:
label: What is the type of issue?
multiple: true
options:
- Documentation is missing
- Documentation is incorrect
- Documentation is confusing
- Example code is not working
- Something else
- type: textarea
attributes:
label: What is the issue?
validations:
required: true
- type: textarea
attributes:
label: Where did you find it?
description: If possible, please provide the URL(s) where you found this issue.
+28
View File
@@ -0,0 +1,28 @@
name: Prefilled crash report
description: Report a crash in Bun
labels:
- crash
- needs triage
body:
- type: markdown
attributes:
value: |
**Thank you so much** for submitting a crash report. You're helping us make Bun more reliable for everyone!
- type: textarea
id: code
attributes:
label: How can we reproduce the crash?
description: Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a GitHub repository, [Replit](https://replit.com/@replit/Bun) or [CodeSandbox](https://codesandbox.io/templates/bun)
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be
automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: remapped_trace
attributes:
label: Stack Trace (bun.report)
validations:
required: true
@@ -0,0 +1,34 @@
name: bun install crash report
description: Report a crash in bun install
labels:
- npm
- crash
body:
- type: markdown
attributes:
value: |
**Thank you so much** for submitting a crash report. You're helping us make Bun more reliable for everyone!
- type: textarea
id: package_json
attributes:
label: "`package.json` file"
description: "Can you upload your `package.json` file? This helps us reproduce the crash."
render: json
- type: textarea
id: repro
attributes:
label: How can we reproduce the crash?
description: Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a GitHub repository, [Replit](https://replit.com/@replit/Bun) or [CodeSandbox](https://codesandbox.io/templates/bun)
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be
automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: remapped_trace
attributes:
label: Stack Trace (bun.report)
validations:
required: true
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 💬 Ask a Question
url: https://discord.com/invite/CXdq2DP29u
about: Join our Discord server for questions, support requests, or just to chat.
@@ -0,0 +1,77 @@
name: Rust lint setup
description: >
Shared setup for the jobs in rust-lints.yml: LLVM from apt.llvm.org, Bun,
an optional pinned Rust toolchain, `bun install`, and the configure + ninja
step that produces what cargo needs before it can resolve the workspace.
inputs:
bun-version:
description: "Bun release to install."
required: true
llvm-version:
description: "LLVM major version to install from apt.llvm.org."
required: true
toolchain:
description: >
Rust toolchain to install (minimal profile) and set as the directory
override. Empty leaves rustup alone; the job's own RUSTUP_TOOLCHAIN
applies.
default: ""
components:
description: "Space-separated rustup components to add to `toolchain`, e.g. `clippy` or `miri rust-src`."
default: ""
ninja-targets:
description: "Space-separated ninja targets to build after configure."
default: "clone-lolhtml"
runs:
using: composite
steps:
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ inputs.bun-version }}
- name: Setup system deps
# cmake/ninja for `--configure-only`; clang for toolchain detection
# (configure resolves cfg.cc/cxx even though nothing here compiles C++).
shell: bash
env:
LLVM_VERSION_MAJOR: ${{ inputs.llvm-version }}
run: |
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)-${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 cmake ninja-build clang-${LLVM_VERSION_MAJOR} lld-${LLVM_VERSION_MAJOR} llvm-${LLVM_VERSION_MAJOR}
- name: Setup Rust
if: inputs.toolchain != ''
shell: bash
env:
TOOLCHAIN: ${{ inputs.toolchain }}
COMPONENTS: ${{ inputs.components }}
run: |
args=()
for c in $COMPONENTS; do args+=(--component "$c"); done
rustup toolchain install "$TOOLCHAIN" --profile minimal "${args[@]}"
rustup override set "$TOOLCHAIN"
- name: Enable rustc annotations
# rustc/clippy diagnostics → inline PR annotations
shell: bash
run: echo "::add-matcher::.github/rust-matcher.json"
- name: Configure
# Cargo can't resolve the workspace until `clone-lolhtml` has extracted
# the pinned lol-html fork into vendor/lolhtml (the root Cargo.toml
# path-deps it) through the same ninja edge the real build uses, and
# bun_core/build.rs needs the build_options.rs that configure writes.
# `codegen` writes the include!() sources under build/debug/codegen that
# bun_runtime/bun_jsc/bun_core can't be checked without.
shell: bash
env:
NINJA_TARGETS: ${{ inputs.ninja-targets }}
run: |
bun install --frozen-lockfile
bun scripts/build.ts --configure-only
ninja -C build/debug $NINJA_TARGETS
+55
View File
@@ -0,0 +1,55 @@
name: Setup Bun
description: An internal version of the 'oven-sh/setup-bun' action.
inputs:
bun-version:
type: string
description: "The version of bun to install: 'latest', 'canary', 'bun-v1.2.0', etc."
default: latest
required: false
baseline:
type: boolean
description: "Whether to use the baseline version of bun."
default: false
required: false
download-url:
type: string
description: "The base URL to download bun from."
default: "https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases"
required: false
runs:
using: composite
steps:
- name: Setup Bun
shell: bash
env:
BUN_VERSION: ${{ inputs.bun-version }}
BUN_BASELINE: ${{ inputs.baseline }}
BUN_DOWNLOAD_URL: ${{ inputs.download-url }}
run: |
case "$(uname -s)" in
Linux*) os=linux;;
Darwin*) os=darwin;;
*) os=windows;;
esac
case "$(uname -m)" in
arm64 | aarch64) arch=aarch64;;
*) arch=x64;;
esac
case "$BUN_BASELINE" in
true | 1) target="bun-${os}-${arch}-baseline";;
*) target="bun-${os}-${arch}";;
esac
case "$BUN_VERSION" in
latest) release="latest";;
canary) release="canary";;
*) release="bun-v${BUN_VERSION}";;
esac
curl -LO "${BUN_DOWNLOAD_URL}/${release}/${target}.zip" --retry 5
unzip "${target}.zip"
mkdir -p "$RUNNER_TEMP/.bun/bin"
mv "${target}"/bun* "$RUNNER_TEMP/.bun/bin/"
chmod +x "$RUNNER_TEMP"/.bun/bin/*
ln -fs "$RUNNER_TEMP/.bun/bin/bun" "$RUNNER_TEMP/.bun/bin/bunx"
echo "$RUNNER_TEMP/.bun/bin" >> "$GITHUB_PATH"
+13
View File
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: github-actions
directories:
- /
- /.github/actions/*
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
actions:
patterns: ["*"]
+3
View File
@@ -0,0 +1,3 @@
### What does this PR do?
### How did you verify your code works?
+20
View File
@@ -0,0 +1,20 @@
{
"problemMatcher": [
{
"owner": "rust",
"pattern": [
{
"regexp": "^(error|warning)(\\[E\\d+\\])?: (.+)$",
"severity": 1,
"message": 3
},
{
"regexp": "^\\s+-->\\s+(\\S+):(\\d+):(\\d+)$",
"file": 1,
"line": 2,
"column": 3
}
]
}
]
}
+139
View File
@@ -0,0 +1,139 @@
# 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 `cargo fmt`) on pull requests and pushes to main. It's optimized for speed by running all formatters in parallel. It also regenerates the checked-in `*.generated.rs` string maps (`bun run codegen:string-maps`) before the formatters start: everything the step leaves modified, formatting or codegen, is what the autofix.ci action at the end of the job pushes back to the PR (failing the run when it had anything to push), so nothing that produces fixes may run after it, and nothing that only verifies should run before it.
### Key Components
#### 1. Clang-format Script (`scripts/run-clang-format.sh`)
- **Purpose**: Formats C++ source and header files
- **What it does**:
- Globs C++ files via `bun scripts/glob-sources.ts cxx`
- 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/runtime/napi/` - Node API headers (third-party)
- `src/jsc/bindings/libuv/` - libuv headers (third-party)
- `src/jsc/bindings/sqlite/` - SQLite headers (third-party)
- `src/runtime/ffi/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]`, `[rustfmt]`)
- Output is streamed in real-time without blocking
- Uses GitHub Actions groups (`::group::`) for collapsible sections
#### 3. Tool Installation
##### Clang-format-21
- Installs ONLY `clang-format-21` 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`
##### Rustfmt
- The pinned nightly is set via `RUSTUP_TOOLCHAIN` in the step `env:` (kept in sync with `channel` in `rust-toolchain.toml`); `cargo fmt --all` runs against the workspace at the repo root.
- `RUSTUP_TOOLCHAIN` makes rustup ignore `rust-toolchain.toml` entirely, so the workflow installs only the host toolchain + `rustfmt` (`rustup toolchain install --profile minimal --component rustfmt`) rather than the file's full cross-target list.
### Updating the Workflow
#### To update the Rust toolchain:
1. Bump `channel` in `rust-toolchain.toml` (and `Dockerfile`/`bootstrap.sh` to match).
2. Bump `RUSTUP_TOOLCHAIN` in the `Format Code` step's `env:` block in `format.yml` to the same value.
3. Bump `RUSTUP_TOOLCHAIN` in the workflow-level `env:` block in `rust-lints.yml` to the same value.
4. `cargo fmt` formatting can change between nightlies; run `cargo fmt --all` locally on the new toolchain and include the resulting diff in the same PR.
#### 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. **Streaming output**: Real-time feedback without buffering
4. **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
- Keep the exclusion list updated as new third-party code is added
## rust-lints.yml Workflow
Four independent jobs that each run one cargo command over the Rust workspace. They share `.github/actions/rust-lint-setup`, a composite action that installs LLVM from apt.llvm.org (configure resolves a clang even though nothing here compiles C++), Bun, optionally a pinned Rust toolchain plus components, runs `bun install`, then `bun scripts/build.ts --configure-only` and the ninja targets a job asks for: `clone-lolhtml` (cargo cannot resolve the workspace until the vendored `lol_html` path dependency exists) and, for jobs that check `bun_runtime`/`bun_jsc`/`bun_core`, `codegen` (their `include!()`d sources under `build/debug/codegen`).
| Job | Check name | Runs | Blocking |
| --------- | --------------------- | -------------------------------------------- | ------------------------------ |
| `clippy` | `cargo clippy` | `bun run rust:clippy` | yes |
| `miri` | `cargo miri test` | `bun run rust:miri` (`scripts/rust-miri.ts`) | yes |
| `lolhtml` | `lol-html cargo test` | `cargo test` in `vendor/lolhtml` | yes |
| `mordant` | `mordant` | `cargo dylint --all --workspace` | advisory (`continue-on-error`) |
- `clippy`, `miri` and `lolhtml` pin `RUSTUP_TOOLCHAIN` at the workflow level (kept in sync with `channel` in `rust-toolchain.toml`) so rustup does not install that file's cross-target list; the action installs the toolchain with `--profile minimal` plus the components the job names (`clippy`, `miri rust-src`, none).
- `lolhtml` exists because the vendored lol-html is a fork (oven-sh/lol-html, `bun` branch) whose own test suite is the only thing guarding the fork's invariants. It used to trigger only on `scripts/build/deps/lolhtml.ts`; it now shares the workflow's wider path filter.
- `mordant` runs the [mordant](https://github.com/scarletindustries/mordant) dylint pack. It sets `RUSTUP_TOOLCHAIN: stable` instead: mordant is built with, and lints us using, the nightly named in its own rust-toolchain file, which dylint fetches on demand, so the outer cargo only needs to exist. Because that nightly is older than ours, the job passes `-A unknown_lints` through `DYLINT_RUSTFLAGS`. Two caches cover the slow parts: `~/.cargo/bin/{cargo-dylint,dylint-link}` keyed on `DYLINT_VERSION`, and `~/.dylint_drivers` + `target/dylint/libraries` keyed on `DYLINT_VERSION` plus the pinned mordant rev read out of `Cargo.toml`. It is skipped on `merge_group`.
### mordant: pin, baseline, disabled lints
- The pack is pinned by commit in `Cargo.toml` under `[workspace.metadata.dylint]`. A bump can also fail if this workspace stops compiling on mordant's nightly.
- `dylint.toml`'s `[mordant]` table points `baseline` at `mordant-baseline.toml` (per-(lint, file) counts of the findings that predate the job) and lists the lints this repo has switched off under `disabled`, each with its reason.
- In baseline mode mordant prints findings over the baseline as warnings and writes them to `target/mordant/over-baseline.txt` (relative to the workspace root). The job deletes that file, runs dylint, and fails if the file is non-empty; absent or empty means clean. Fixing baselined findings needs no baseline update.
- Locally, `bun run rust:mordant` is the same dylint invocation and `bun run rust:mordant:baseline` regenerates the baseline (`MORDANT_BASELINE_WRITE=1`). Both need `cargo install cargo-dylint dylint-link` once, and expect `build/debug/codegen` and `vendor/lolhtml` to exist, which any normal `bun bd` leaves behind.
To bump mordant: change the `rev` in `Cargo.toml`, run `bun run rust:mordant`, fix what the new revision reports or regenerate `mordant-baseline.toml` with `bun run rust:mordant:baseline`, and put the triage in the PR description.
+23
View File
@@ -0,0 +1,23 @@
name: Auto Assign Types Issues
permissions:
contents: read
on:
issues:
types: [labeled]
jobs:
auto-assign:
runs-on: ubuntu-latest
if: github.event.label.name == 'types'
permissions:
issues: write
steps:
- name: Assign to alii
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
ISSUE: ${{ github.event.issue.number }}
run: |
gh issue edit "$ISSUE" --add-assignee alii
@@ -0,0 +1,34 @@
name: Auto-close duplicate issues
permissions:
contents: read
on:
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
jobs:
auto-close-duplicates:
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: auto-close-duplicates-${{ github.repository }}
cancel-in-progress: true
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Auto-close duplicate issues
run: bun run scripts/auto-close-duplicates.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
@@ -0,0 +1,27 @@
name: Auto-label Claude PRs
permissions:
contents: read
on:
pull_request:
types: [opened]
jobs:
auto-label:
if: github.event.pull_request.user.login == 'robobun' || contains(github.event.pull_request.body, '🤖 Generated with')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Add claude label to PRs from robobun
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['claude']
});
+45
View File
@@ -0,0 +1,45 @@
name: bun-types
permissions:
contents: read
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "packages/bun-types/**"
- "packages/@types/bun/**"
- "test/integration/bun-types/**"
- "src/cli/init/tsconfig.default.json"
- ".github/workflows/bun-types.yml"
pull_request:
paths:
- "packages/bun-types/**"
- "packages/@types/bun/**"
- "test/integration/bun-types/**"
- "src/cli/init/tsconfig.default.json"
- ".github/workflows/bun-types.yml"
env:
BUN_VERSION: "canary"
jobs:
typecheck:
name: "TypeScript types"
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install dependencies
run: |
bun install
bun install --cwd test
- name: Check types
run: bun test test/integration/bun-types/bun-types.test.ts
@@ -0,0 +1,67 @@
name: Cancel BuildKite on PR close
# When a PR is closed without merging, its BuildKite builds keep running.
# On the macOS queue (fixed, non-ephemeral runners) those orphaned jobs can
# sit for hours and burn slots that live PRs need. This cancels them.
#
# Intentionally does NOT fire for merged PRs — those builds may still carry
# useful signal and the merge-queue path handles them separately.
on:
pull_request_target:
types: [closed]
# This job does not need the GitHub token at all — it only talks to BuildKite.
permissions: {}
jobs:
cancel:
if: github.event.pull_request.merged == false
runs-on: ubuntu-latest
steps:
- name: Cancel running/scheduled BuildKite builds for this branch
env:
BUILDKITE_BUILDS_TOKEN: ${{ secrets.BUILDKITE_BUILDS_TOKEN }}
# Same-repo PRs build under the bare branch name; fork PRs build
# under "owner:branch" (head.label). Pick accordingly. head.repo can
# be null if the fork was deleted before close — fall back to label.
IS_FORK: ${{ github.event.pull_request.head.repo.fork || github.event.pull_request.head.repo == null }}
HEAD_LABEL: ${{ github.event.pull_request.head.label }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
set -euo pipefail
if [ -z "${BUILDKITE_BUILDS_TOKEN}" ]; then
echo "BUILDKITE_BUILDS_TOKEN secret not set; skipping."
exit 0
fi
if [ "${IS_FORK}" = "true" ]; then
BK_BRANCH="${HEAD_LABEL}"
else
BK_BRANCH="${HEAD_REF}"
fi
# Defence in depth: never touch protected/queue branches even if the
# event somehow resolves to one.
case "${BK_BRANCH}" in
"" | main | master | gh-readonly-queue/*)
echo "Refusing to cancel builds on protected branch '${BK_BRANCH}'"
exit 0
;;
esac
enc=$(jq -rn --arg b "$BK_BRANCH" '$b|@uri')
api="https://api.buildkite.com/v2/organizations/bun/pipelines/bun"
builds=$(curl -fsS -H "Authorization: Bearer ${BUILDKITE_BUILDS_TOKEN}" \
"${api}/builds?branch=${enc}&state[]=running&state[]=scheduled&state[]=failing&per_page=30")
count=$(jq 'length' <<<"$builds")
echo "Found ${count} active build(s) on branch '${BK_BRANCH}'"
[ "$count" -eq 0 ] && exit 0
jq -r '.[].number' <<<"$builds" | while read -r num; do
echo "Cancelling build #${num}"
curl -fsS -X PUT -H "Authorization: Bearer ${BUILDKITE_BUILDS_TOKEN}" \
"${api}/builds/${num}/cancel" >/dev/null
done
@@ -0,0 +1,39 @@
name: Claude Issue Dedupe
permissions:
contents: read
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to process for duplicate detection'
required: true
type: string
jobs:
claude-dedupe-issues:
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: claude-dedupe-issues-${{ github.event.issue.number || inputs.issue_number }}
cancel-in-progress: true
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run Claude Code slash command
uses: anthropics/claude-code-action/base-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
env:
ANTHROPIC_MODEL: claude-opus-5[1m]
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -0,0 +1,50 @@
name: Claude Find Issues for PR
permissions:
contents: read
on:
pull_request:
types: [opened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to find related issues for'
required: true
type: string
jobs:
claude-find-issues:
runs-on: ubuntu-latest
timeout-minutes: 20
concurrency:
group: claude-find-issues-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Find issues this PR may fix
uses: anthropics/claude-code-action/base-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
env:
ANTHROPIC_MODEL: claude-opus-5[1m]
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prompt: "/find-issues ${{ github.repository }}/pull/${{ github.event.pull_request.number || inputs.pr_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Find duplicate PRs
if: always()
uses: anthropics/claude-code-action/base-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
env:
ANTHROPIC_MODEL: claude-opus-5[1m]
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prompt: "/find-duplicate-prs ${{ github.repository }}/pull/${{ github.event.pull_request.number || inputs.pr_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -0,0 +1,33 @@
name: Close stale robobun PRs
permissions:
contents: read
on:
schedule:
- cron: "30 0 * * *"
workflow_dispatch:
jobs:
close-stale-robobun-prs:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pull-requests: write
steps:
- name: Close stale robobun PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
ninety_days_ago=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)
gh pr list \
--author robobun \
--state open \
--json number,updatedAt \
--limit 1000 \
--jq ".[] | select(.updatedAt < \"$ninety_days_ago\") | .number" |
while read -r pr_number; do
echo "Closing PR #$pr_number (last updated before $ninety_days_ago)"
gh pr close "$pr_number" --comment "Closing this PR because it has been inactive for more than 90 days."
done
+191
View File
@@ -0,0 +1,191 @@
name: Comment Cop
# Flags multi-line code comments added in src/ by claude-labeled PRs and
# asks for them to be deleted. Groups containing a SAFETY: marker are
# skipped. Runs entirely against the GitHub API (no checkout of PR code).
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
pull-requests: write
jobs:
comment-cop:
if: >
github.repository == 'oven-sh/bun' &&
contains(github.event.pull_request.labels.*.name, 'claude') &&
(github.event.action != 'labeled' || github.event.label.name == 'claude')
runs-on: ubuntu-latest
timeout-minutes: 5
concurrency:
group: comment-cop-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Scan diff for added multi-line comments
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const crypto = require('crypto');
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.payload.pull_request.number;
const headSha = context.payload.pull_request.head.sha;
const SRC_EXT = /\.(rs|c|cc|cpp|h|hpp|m|mm|ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
const MIN_LINES = 2;
function isCommentLine(line) {
const t = line.trimStart();
if (t.startsWith('//')) return true;
if (t.startsWith('/*')) return true;
if (t === '*' || t === '*/' || t.startsWith('* ')) return true;
return false;
}
function groupsFromPatch(path, patch) {
const out = [];
let newLine = 0;
let cur = null;
const flush = () => {
if (cur && cur.lines.length >= MIN_LINES) {
const text = cur.lines.join('\n');
if (!/SAFETY:/.test(text)) out.push({ path, start: cur.start, end: cur.end, text });
}
cur = null;
};
for (const raw of patch.split('\n')) {
if (raw.startsWith('@@')) {
flush();
const m = /\+(\d+)/.exec(raw);
newLine = m ? parseInt(m[1], 10) : 1;
} else if (raw.startsWith('+')) {
const content = raw.slice(1);
if (isCommentLine(content)) {
if (cur) {
cur.end = newLine;
cur.lines.push(content);
} else {
cur = { start: newLine, end: newLine, lines: [content] };
}
} else {
flush();
}
newLine++;
} else if (raw.startsWith('-')) {
flush();
} else if (raw.startsWith('\\')) {
// "\ No newline at end of file"
} else {
// context line (leading space, or blank)
flush();
newLine++;
}
}
flush();
return out;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number, per_page: 100,
});
const groups = [];
for (const f of files) {
if (f.status === 'removed') continue;
if (!f.filename.startsWith('src/')) continue;
if (!SRC_EXT.test(f.filename)) continue;
if (!f.patch) continue;
for (const g of groupsFromPatch(f.filename, f.patch)) groups.push(g);
}
const keyFor = g => `${g.path}:${crypto.createHash('sha256').update(g.text).digest('hex').slice(0, 12)}`;
const presentKeys = new Set(groups.map(keyFor));
// Fetch existing comment-cop review threads (for dedup + auto-resolve).
const threads = [];
{
const q = `
query($owner: String!, $repo: String!, $pr: Int!, $after: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $after) {
pageInfo { hasNextPage endCursor }
nodes { id isResolved comments(first: 1) { nodes { body } } }
}
}
}
}`;
let after = null;
for (;;) {
const res = await github.graphql(q, { owner, repo, pr: pull_number, after });
const page = res.repository.pullRequest.reviewThreads;
for (const t of page.nodes) threads.push(t);
if (!page.pageInfo.hasNextPage) break;
after = page.pageInfo.endCursor;
}
}
const seenKeys = new Set();
const toResolve = [];
for (const t of threads) {
const body = t.comments.nodes[0]?.body || '';
const m = /<!-- comment-cop:([^>\s]+) -->/.exec(body);
if (!m) continue;
const key = m[1];
seenKeys.add(key);
if (!t.isResolved && !presentKeys.has(key)) toResolve.push(t.id);
}
// Auto-resolve threads whose flagged block is gone from the current diff.
if (toResolve.length > 0) {
const mut = `
mutation($id: ID!) {
resolveReviewThread(input: { threadId: $id }) { thread { id } }
}`;
for (const id of toResolve) {
try {
await github.graphql(mut, { id });
} catch (e) {
core.warning(`resolveReviewThread failed for ${id}: ${e.message}`);
}
}
core.info(`Resolved ${toResolve.length} stale comment-cop thread(s).`);
}
// Post new line comments for groups not already flagged.
const fresh = groups.filter(g => !seenKeys.has(keyFor(g)));
if (fresh.length === 0) {
core.info(`No new comment groups to flag (${groups.length} present, all already flagged).`);
return;
}
const bodyFor = g =>
`<!-- comment-cop:${keyFor(g)} -->\n` +
`If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code\n\n`;
let posted = 0;
for (const g of fresh) {
const params = {
owner, repo, pull_number,
commit_id: headSha,
path: g.path,
line: g.end,
side: 'RIGHT',
body: bodyFor(g),
};
if (g.start < g.end) {
params.start_line = g.start;
params.start_side = 'RIGHT';
}
try {
await github.rest.pulls.createReviewComment(params);
posted++;
} catch (e) {
core.warning(`createReviewComment failed for ${g.path}:${g.start}-${g.end}: ${e.message}`);
}
}
core.info(`Posted ${posted} review comment(s).`);
+43
View File
@@ -0,0 +1,43 @@
name: Deploy bun.com
# bun.com (oven-sh/site) bakes a slice of this repository into its build:
# docs/** and guides (rendered natively), packages/bun-types (API reference),
# and the install scripts. Nothing on the site side watches this repo, so a
# push that only touches those paths would otherwise sit unpublished until the
# next unrelated site deploy. Poke the site's Vercel deploy hook instead.
on:
push:
branches:
- main
paths:
- "docs/**"
- "packages/bun-types/**"
- "src/runtime/cli/install.sh"
- "src/runtime/cli/install.ps1"
workflow_dispatch:
permissions: {}
concurrency:
group: deploy-site
cancel-in-progress: true
jobs:
deploy:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Trigger Vercel deployment
env:
VERCEL_DEPLOY_HOOK: ${{ secrets.SITE_VERCEL_DEPLOY_HOOK }}
run: |
if [ -z "$VERCEL_DEPLOY_HOOK" ]; then
echo "::error::SITE_VERCEL_DEPLOY_HOOK secret is not set"
exit 1
fi
curl --fail --silent --show-error \
--connect-timeout 10 \
--max-time 60 \
-X POST "$VERCEL_DEPLOY_HOOK"
echo "Vercel deployment triggered"
+123
View File
@@ -0,0 +1,123 @@
name: autofix.ci
permissions:
contents: read
on:
workflow_call:
workflow_dispatch:
pull_request:
merge_group:
env:
BUN_VERSION: "1.3.14"
LLVM_VERSION: "21.1.8"
LLVM_VERSION_MAJOR: "21"
jobs:
autofix:
name: Format
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Configure Git
run: |
git config --global core.autocrlf true
git config --global core.ignorecase true
git config --global core.precomposeUnicode true
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Setup Dependencies
run: |
bun install
- name: Format Code
env:
# Pin the toolchain explicitly so rustup ignores rust-toolchain.toml's
# `targets` list (11 cross triples ≈ 450 MB of prebuilt std we don't
# need just to run rustfmt). Keep this in sync with `channel` there.
RUSTUP_TOOLCHAIN: nightly-2026-07-20
run: |
# Without pipefail, `cmd | sed` always reports sed's exit status, so a
# failing formatter is invisible to the `wait $PID` checks below.
set -o pipefail
# `*.generated.rs` are deterministic outputs of `*.string-map.ts`; the
# source `.ts` is the truth. Regenerating here, alongside the
# formatters, makes a stale checked-in copy one more fix for the
# autofix step below to push back to the PR (it also fails the run
# whenever it has something to push, so staleness stays a red check
# where it can't push, e.g. the merge queue). Runs before the
# formatters start so prettier isn't rewriting a `.string-map.ts`
# while the generator imports it.
echo "::group::String maps"
bun run codegen:string-maps 2>&1 | sed 's/^/[string-maps] /'
echo "::endgroup::"
# Start prettier in background with prefixed output
echo "::group::Prettier"
(bun run prettier 2>&1 | sed 's/^/[prettier] /') &
PRETTIER_PID=$!
# Start clang-format installation and formatting in background with prefixed output
echo "::group::Clang-format"
(
echo "[clang-format] Installing clang-format-${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)-${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-${LLVM_VERSION_MAJOR}
echo "[clang-format] Running clang-format..."
./scripts/run-clang-format.sh format 2>&1 | sed 's/^/[clang-format] /'
) &
CLANG_PID=$!
# Run cargo fmt in background with prefixed output. RUSTUP_TOOLCHAIN
# (step env) overrides rust-toolchain.toml, so install only the host
# toolchain + rustfmt instead of the file's 11 cross-target std libs.
echo "::group::Cargo fmt"
(
echo "[rustfmt] Installing toolchain $RUSTUP_TOOLCHAIN..."
rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --component rustfmt --no-self-update 2>&1 | sed 's/^/[rustfmt] /'
echo "[rustfmt] Running cargo fmt --all..."
cargo fmt --all 2>&1 | sed 's/^/[rustfmt] /'
) &
RUST_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 $RUST_PID; then
echo "::error::cargo 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"
- uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a # v1.3.4
+70
View File
@@ -0,0 +1,70 @@
name: FreeBSD smoke test
# Runs the cross-compiled FreeBSD binary inside a FreeBSD VM to verify it
# actually starts and executes JavaScript. The build itself happens on
# BuildKite (cross-compiled from Linux); this just validates the result.
#
# Triggered manually with a BuildKite artifact URL.
permissions:
contents: read
on:
workflow_dispatch:
inputs:
artifact_url:
description: 'URL to bun-freebsd-x64.zip (from BuildKite artifacts)'
required: true
type: string
jobs:
smoke:
runs-on: ubuntu-latest
name: FreeBSD ${{ matrix.version }} smoke
strategy:
fail-fast: false
matrix:
version: ['14.3']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
sparse-checkout: |
test/js/bun
test/harness.ts
- name: Download bun-freebsd-x64
if: inputs.artifact_url != ''
env:
ARTIFACT_URL: ${{ inputs.artifact_url }}
run: |
curl -fsSL "$ARTIFACT_URL" -o bun-freebsd.zip
mkdir -p ./bin
unzip -j bun-freebsd.zip -d ./bin
# Normalize whatever name was in the zip to ./bin/bun.
for f in ./bin/bun-freebsd-* ./bin/bun-profile; do
[ -f "$f" ] && mv "$f" ./bin/bun && break
done
chmod +x ./bin/bun
file ./bin/bun
- name: Run in FreeBSD VM
uses: vmactions/freebsd-vm@77ed28d336d03fe19a3f4f7266c1d2c4714dd79d # v1
with:
release: ${{ matrix.version }}
usesh: true
copyback: false
run: |
set -e
if [ -x ./bin/bun ]; then BUN=./bin/bun;
else echo "::error::no bun binary found; pass artifact_url"; exit 1; fi
echo "=== version ==="
$BUN --revision
echo "=== eval ==="
$BUN -e 'console.log("hello from", process.platform, process.arch)'
echo "=== os ==="
$BUN -e 'const os=require("os"); console.log(os.type(), os.release(), os.cpus().length, "cpus")'
echo "=== fs ==="
$BUN -e 'require("fs").writeFileSync("/tmp/x","ok"); console.log(require("fs").readFileSync("/tmp/x","utf8"))'
echo "=== http ==="
$BUN -e 'const s=Bun.serve({port:0,fetch:()=>new Response("ok")}); fetch("http://localhost:"+s.port).then(r=>r.text()).then(t=>{console.log("got:",t);s.stop();process.exit(t==="ok"?0:1)})'
+29
View File
@@ -0,0 +1,29 @@
name: Lint
permissions:
contents: read
on:
pull_request:
workflow_dispatch:
env:
BUN_VERSION: "1.3.14"
jobs:
lint-js:
name: "Lint JavaScript"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Setup Dependencies
run: |
bun install --frozen-lockfile
- name: Lint
run: bun lint
+36
View File
@@ -0,0 +1,36 @@
name: Close AI Slop PRs
permissions:
contents: read
on:
pull_request_target:
types: [labeled]
jobs:
on-slop:
runs-on: ubuntu-latest
if: github.event.label.name == 'slop' && github.repository == 'oven-sh/bun'
permissions:
issues: write
pull-requests: write
steps:
- name: Comment and close PR
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: 'This PR has been closed because it was flagged as AI slop.\n\nMany AI-generated PRs are fine, but this one was identified as having one or more of the following issues:\n- Fails to verify the problem actually exists\n- Fails to test that the fix works\n- Makes incorrect assumptions about the codebase\n- Submits changes that are incomplete or misleading\n\nIf you believe this was done in error, please leave a comment explaining why.'
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
title: 'ai slop',
body: 'This PR has been marked as AI slop and the description has been updated to avoid confusion or misleading reviewers.\n\nMany AI PRs are fine, but sometimes they submit a PR too early, fail to test if the problem is real, fail to reproduce the problem, or fail to test that the problem is fixed. If you think this PR is not AI slop, please leave a comment.',
state: 'closed'
});
+60
View File
@@ -0,0 +1,60 @@
name: Packages CI
permissions:
contents: read
on:
push:
branches:
- main
paths:
- "packages/**"
- .prettierrc
- .prettierignore
- tsconfig.json
- oxlint.json
- "!**/*.md"
pull_request:
branches:
- main
paths:
- "packages/**"
- .prettierrc
- .prettierignore
- tsconfig.json
- oxlint.json
- "!**/*.md"
env:
BUN_VERSION: "canary"
jobs:
bun-plugin-svelte:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install dependencies
run: |
bun install
pushd ./packages/bun-plugin-svelte && bun install
- name: Lint
run: |
bunx [email protected] --format github --deny-warnings
bunx prettier --config ../../.prettierrc --check .
working-directory: ./packages/bun-plugin-svelte
- name: Check types
run: bun check:types
working-directory: ./packages/bun-plugin-svelte
- name: Test
run: bun test
working-directory: ./packages/bun-plugin-svelte
+364
View File
@@ -0,0 +1,364 @@
# TODO: Move this to bash scripts intead of Github Actions
# so it can be run from Buildkite, see: .buildkite/scripts/release.sh
name: Release
concurrency: release
env:
BUN_VERSION: ${{ github.event.inputs.tag || github.event.release.tag_name || 'canary' }}
BUN_LATEST: ${{ (inputs.is-latest || github.event.release.tag_name) && 'true' || 'false' }}
permissions:
contents: read
on:
release:
types:
- published
schedule:
- cron: "0 14 * * *" # every day at 6am PST
workflow_dispatch:
inputs:
is-latest:
description: Is this the latest release?
type: boolean
default: false
tag:
type: string
description: What is the release tag? (e.g. "1.0.2", "canary")
required: true
use-docker:
description: Should Docker images be released?
type: boolean
default: false
use-npm:
description: Should npm packages be published?
type: boolean
default: false
use-homebrew:
description: Should binaries be released to Homebrew?
type: boolean
default: false
use-s3:
description: Should binaries be uploaded to S3?
type: boolean
default: false
use-types:
description: Should types be released to npm?
type: boolean
default: false
use-definitelytyped:
description: "Should types be PR'd to DefinitelyTyped?"
type: boolean
default: false
jobs:
sign:
name: Sign Release
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'oven-sh' }}
permissions:
contents: write
defaults:
run:
working-directory: packages/bun-release
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup GPG
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: "1.3.14"
- name: Install Dependencies
run: bun install
- name: Sign Release
run: |
echo "$GPG_PASSPHRASE" | bun upload-assets -- "$BUN_VERSION"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
npm:
name: Release to NPM
runs-on: ubuntu-latest
needs: sign
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.use-npm == 'true' }}
permissions:
contents: read
defaults:
run:
working-directory: packages/bun-release
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# To workaround issue
ref: main
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: "1.3.14"
- name: Install Dependencies
run: bun install
- name: Release
run: bun upload-npm -- "$BUN_VERSION" publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
npm-types:
name: Release types to NPM
runs-on: ubuntu-latest
needs: sign
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.use-types == 'true' }}
permissions:
contents: read
defaults:
run:
working-directory: packages/bun-types
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: latest
- name: Setup Bun
if: ${{ env.BUN_VERSION != 'canary' }}
uses: ./.github/actions/setup-bun
with:
bun-version: "1.3.14"
- name: Setup Bun
if: ${{ env.BUN_VERSION == 'canary' }}
uses: ./.github/actions/setup-bun
with:
bun-version: "canary" # Must be 'canary' so tag is correct
- name: Install Dependencies
run: bun install
- name: Setup Tag
if: ${{ env.BUN_VERSION == 'canary' }}
run: |
VERSION=$(bun --version)
TAG="${VERSION}-canary.$(date +'%Y%m%dT%H%M%S')"
echo "Setup tag: ${TAG}"
echo "TAG=${TAG}" >> ${GITHUB_ENV}
- name: Build
run: bun run build
env:
BUN_VERSION: ${{ env.TAG || env.BUN_VERSION }}
- name: Release
if: ${{ env.BUN_VERSION == 'canary' || env.BUN_LATEST == 'true' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/.npmrc
NPM_TAG: ${{ env.BUN_VERSION == 'canary' && 'canary' || 'latest' }}
run: |
echo '//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}' > "$NPM_CONFIG_USERCONFIG"
VERSION=$(node -p 'require("./package.json").version')
if [ "$(npm view bun-types@"$VERSION" version 2>/dev/null)" = "$VERSION" ]; then
echo "bun-types@$VERSION already published, skipping"
exit 0
fi
npm publish --access public --tag "$NPM_TAG"
definitelytyped:
name: Make pr to DefinitelyTyped to update `bun-types` version
runs-on: ubuntu-latest
needs: npm-types
if: ${{ github.event_name == 'release' || github.event.inputs.use-definitelytyped == 'true' }}
permissions:
contents: read
steps:
- name: Checkout (DefinitelyTyped)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: DefinitelyTyped/DefinitelyTyped
- name: Checkout (bun)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
path: bun
- name: Setup Bun
uses: ./bun/.github/actions/setup-bun
with:
bun-version: "1.3.14"
- id: bun-version
run: echo "BUN_VERSION=${BUN_VERSION#bun-v}" >> "$GITHUB_OUTPUT"
- name: Update bun-types version in package.json
run: |
bun -e '
const file = Bun.file("./types/bun/package.json");
const json = await file.json();
const version = process.env.BUN_VERSION.replace(/^bun-v/, "");
json.dependencies["bun-types"] = version;
json.version = version.slice(0, version.lastIndexOf(".")) + ".9999";
await file.write(JSON.stringify(json, null, 4) + "\n");
'
- name: Create Pull Request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
if: ${{ env.BUN_LATEST == 'true' && env.BUN_VERSION != 'canary'}}
with:
token: ${{ secrets.ROBOBUN_TOKEN }}
add-paths: ./types/bun/package.json
title: "[bun] update to ${{ steps.bun-version.outputs.BUN_VERSION }}"
commit-message: "[bun] update to ${{ steps.bun-version.outputs.BUN_VERSION }}"
body: |
Update `bun-types` version to ${{ steps.bun-version.outputs.BUN_VERSION }}
https://bun.com/blog/${{ env.BUN_VERSION }}
push-to-fork: oven-sh/DefinitelyTyped
branch: ${{env.BUN_VERSION}}
docker:
name: Release to Dockerhub
runs-on: ubuntu-latest
needs: sign
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.use-docker == 'true' }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- variant: debian
suffix: ""
- variant: debian
suffix: -debian
- variant: slim
suffix: -slim
dir: debian-slim
- variant: alpine
suffix: -alpine
- variant: distroless
suffix: -distroless
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Docker emulator
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- id: buildx
name: Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
with:
platforms: linux/amd64,linux/arm64
- id: metadata
name: Setup Docker metadata
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: oven/bun
flavor: |
latest=false
tags: |
type=raw,value=latest,enable=${{ env.BUN_LATEST == 'true' && matrix.suffix == '' }}
type=raw,value=${{ matrix.variant }},enable=${{ env.BUN_LATEST == 'true' }}
type=match,pattern=(bun-v)?(canary|\d+.\d+.\d+),group=2,value=${{ env.BUN_VERSION }},suffix=${{ matrix.suffix }}
type=match,pattern=(bun-v)?(canary|\d+.\d+),group=2,value=${{ env.BUN_VERSION }},suffix=${{ matrix.suffix }}
type=match,pattern=(bun-v)?(canary|\d+),group=2,value=${{ env.BUN_VERSION }},suffix=${{ matrix.suffix }}
- name: Login to Docker
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push to Docker
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: ./dockerhub/${{ matrix.dir || matrix.variant }}
platforms: linux/amd64,linux/arm64
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
build-args: |
BUN_VERSION=${{ env.BUN_VERSION }}
homebrew:
name: Release to Homebrew
runs-on: ubuntu-latest
needs: sign
permissions:
contents: read
if: ${{ github.event_name == 'release' || github.event.inputs.use-homebrew == 'true' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: oven-sh/homebrew-bun
persist-credentials: false
- name: Setup GPG
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
git_committer_name: robobun
git_committer_email: [email protected]
- name: Setup Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
with:
ruby-version: "2.6"
- name: Update Tap
run: ruby scripts/release.rb "$BUN_VERSION"
- name: Commit Tap
env:
ROBOBUN_TOKEN: ${{ secrets.ROBOBUN_TOKEN }}
run: |
git add -A
git diff --cached --quiet && exit 0
git commit -m "Release $BUN_VERSION"
git push "https://x-access-token:${ROBOBUN_TOKEN}@github.com/oven-sh/homebrew-bun" HEAD:main
s3:
name: Upload to S3
runs-on: ubuntu-latest
needs: sign
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.use-s3 == 'true' }}
permissions:
contents: read
defaults:
run:
working-directory: packages/bun-release
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: "1.3.14"
- name: Install Dependencies
run: bun install
- name: Release
run: bun upload-s3 -- "$BUN_VERSION"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY}}
AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }}
AWS_BUCKET: bun
notify-sentry:
name: Notify Sentry
runs-on: ubuntu-latest
needs: s3
steps:
- name: Notify Sentry
uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3.7.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
ignore_missing: true
ignore_empty: true
version: ${{ env.BUN_VERSION }}
environment: production
+191
View File
@@ -0,0 +1,191 @@
name: Rust lints
permissions:
contents: read
on:
workflow_dispatch:
pull_request:
paths:
- "src/**/*.rs"
- "src/**/Cargo.toml"
- "src/**/*.classes.ts"
- "src/codegen/**"
- "scripts/build/**"
- "scripts/build.ts"
- "scripts/rust-miri.ts"
- "package.json"
- "Cargo.toml"
- "Cargo.lock"
- "clippy.toml"
- "dylint.toml"
- "mordant-baseline.toml"
- "rust-toolchain.toml"
- ".github/workflows/rust-lints.yml"
- ".github/actions/rust-lint-setup/**"
- ".github/actions/setup-bun/**"
- ".github/rust-matcher.json"
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
BUN_VERSION: "1.3.14"
LLVM_VERSION_MAJOR: "21"
# Pin the toolchain explicitly so rustup ignores rust-toolchain.toml's
# `targets` list (11 cross triples ≈ 450 MB of prebuilt std we don't need
# to lint the host). Keep in sync with `channel` in rust-toolchain.toml.
RUSTUP_TOOLCHAIN: nightly-2026-07-20
jobs:
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/rust-lint-setup
with:
bun-version: ${{ env.BUN_VERSION }}
llvm-version: ${{ env.LLVM_VERSION_MAJOR }}
toolchain: ${{ env.RUSTUP_TOOLCHAIN }}
components: clippy
ninja-targets: codegen clone-lolhtml
- name: cargo clippy
env:
BUN_CODEGEN_DIR: ${{ github.workspace }}/build/debug/codegen
run: bun run rust:clippy
miri:
name: cargo miri test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/rust-lint-setup
with:
bun-version: ${{ env.BUN_VERSION }}
llvm-version: ${{ env.LLVM_VERSION_MAJOR }}
toolchain: ${{ env.RUSTUP_TOOLCHAIN }}
components: miri rust-src
- name: cargo miri test
env:
BUN_CODEGEN_DIR: ${{ github.workspace }}/build/debug/codegen
run: bun run rust:miri
lolhtml:
# The vendored lol-html is a fork (oven-sh/lol-html, `bun` branch) carrying
# content-handler suspension, and its own test suite is the only thing that
# guards the fork's invariants: nothing in the Bun test suite reaches, for
# example, the parser's suspension bookkeeping or `Arena::compact()`.
name: lol-html cargo test
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/rust-lint-setup
with:
bun-version: ${{ env.BUN_VERSION }}
llvm-version: ${{ env.LLVM_VERSION_MAJOR }}
toolchain: ${{ env.RUSTUP_TOOLCHAIN }}
- name: cargo test
# `lol_html` is a path dependency, not a workspace member (it carries
# dev-dependencies the Bun workspace does not), so run it in place.
working-directory: vendor/lolhtml
run: cargo test
mordant:
name: mordant
runs-on: ubuntu-latest
if: github.event_name != 'merge_group'
# Advisory while the pack is new: shows up on the PR, does not block it.
continue-on-error: true
env:
DYLINT_VERSION: "6.0.3"
# Mordant (pinned in Cargo.toml's [workspace.metadata.dylint]) is built
# with, and lints us using, the nightly named in its own rust-toolchain
# file, which dylint selects itself; rustup fetches it on demand, so the
# pin is the only thing to bump. The outer cargo just needs to exist:
# point it at the runner's stable so rust-toolchain.toml's cross-target
# list is not installed.
RUSTUP_TOOLCHAIN: stable
RUSTUP_AUTO_INSTALL: "1"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup
uses: ./.github/actions/rust-lint-setup
with:
bun-version: ${{ env.BUN_VERSION }}
llvm-version: ${{ env.LLVM_VERSION_MAJOR }}
ninja-targets: codegen clone-lolhtml
- name: Cache the dylint binaries
# Compiling cargo-dylint and dylint-link is ~100s; they only change on
# a DYLINT_VERSION bump.
id: dylint-bin
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/bin/cargo-dylint
~/.cargo/bin/dylint-link
key: dylint-bin-${{ runner.os }}-${{ env.DYLINT_VERSION }}
- name: Build the dylint binaries
if: steps.dylint-bin.outputs.cache-hit != 'true'
run: cargo install --locked cargo-dylint@"$DYLINT_VERSION" dylint-link@"$DYLINT_VERSION"
- name: Read the mordant pin
# The driver and library dylint builds are tied to the pinned mordant
# revision, so it is their cache key.
id: pin
run: |
rev=$(sed -n 's/.*scarletindustries\/mordant", rev = "\([0-9a-f]*\)".*/\1/p' Cargo.toml)
test -n "$rev"
echo "rev=$rev" >> "$GITHUB_OUTPUT"
- name: Cache the dylint driver and the built mordant library
# Both are a pure function of the dylint version and the pinned
# revision (which names the nightly they are built for): ~50s and
# ~60s of the job. What remains uncached is checking bun itself.
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.dylint_drivers
target/dylint/libraries
key: dylint-lib-${{ runner.os }}-${{ env.DYLINT_VERSION }}-${{ steps.pin.outputs.rev }}
- name: mordant
env:
# Mordant's nightly is older than rust-toolchain.toml's, so `#[allow]`s
# of lints added since then are unknown to it.
DYLINT_RUSTFLAGS: "-A unknown_lints"
run: |
rm -f target/mordant/over-baseline.txt
cargo dylint --all --workspace -- --keep-going
- name: Fail on findings over the baseline
# In baseline mode mordant reports findings over mordant-baseline.toml
# as warnings and lists them in this file; absent or empty means clean.
run: test ! -s target/mordant/over-baseline.txt
+75
View File
@@ -0,0 +1,75 @@
name: source-lints
permissions:
contents: read
# Source-tree lints and build-script unit tests from test/internal/source-lints/.
# These never touch the built bun binary (no bun:internal-for-testing, no
# bunExe() spawns), so they can run against a released bun immediately on push
# instead of waiting for build-bun in every Buildkite test lane.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "src/**/*.rs"
- "src/**/*.classes.ts"
- "src/codegen/class-definitions.ts"
- "src/jsc/bindings/**"
- "packages/bun-types/redis.d.ts"
- "scripts/build/**"
- "scripts/glob-sources.ts"
- "scripts/utils.mjs"
- ".buildkite/ci.mjs"
- "rust-toolchain.toml"
- "test/harness.ts"
- "test/tsconfig.json"
- "test/_util/**"
- "test/internal/source-lints/**"
- ".github/workflows/source-lints.yml"
- ".github/actions/setup-bun/**"
pull_request:
paths:
- "src/**/*.rs"
- "src/**/*.classes.ts"
- "src/codegen/class-definitions.ts"
- "src/jsc/bindings/**"
- "packages/bun-types/redis.d.ts"
- "scripts/build/**"
- "scripts/glob-sources.ts"
- "scripts/utils.mjs"
- ".buildkite/ci.mjs"
- "rust-toolchain.toml"
- "test/harness.ts"
- "test/tsconfig.json"
- "test/_util/**"
- "test/internal/source-lints/**"
- ".github/workflows/source-lints.yml"
- ".github/actions/setup-bun/**"
merge_group:
env:
BUN_VERSION: "1.3.14"
jobs:
source-lints:
name: "Source lints"
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# PR pushes supersede earlier runs; main commits always run to completion.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
# No `bun install`: every test here imports only built-ins, relative
# paths, or `harness` (resolved via test/tsconfig.json `paths`), so a
# bare checkout is sufficient.
- name: Run source lints
run: bun test test/internal/source-lints/
+33
View File
@@ -0,0 +1,33 @@
name: Close inactive issues
permissions:
contents: read
on:
# schedule:
# - cron: "15 * * * *"
workflow_dispatch:
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
days-before-issue-close: 5
any-of-issue-labels: "needs repro,waiting-for-author"
exempt-issue-labels: "neverstale"
exempt-pr-labels: "neverstale"
remove-stale-when-updated: true
stale-issue-label: "stale"
stale-pr-label: "stale"
stale-issue-message: "This issue is stale and may be closed due to inactivity. If you're still running into this, please leave a comment."
close-issue-message: "This issue was closed because it has been inactive for 5 days since being marked as stale."
days-before-pr-stale: 30
days-before-pr-close: 14
stale-pr-message: "This pull request is stale and may be closed due to inactivity."
close-pr-message: "This pull request has been closed due to inactivity."
repo-token: ${{ github.token }}
operations-per-run: 1000
+106
View File
@@ -0,0 +1,106 @@
name: Update c-ares
permissions:
contents: read
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check c-ares version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/cares.ts
CURRENT_VERSION=$(sed -nE 's/^const CARES_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find CARES_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/c-ares/c-ares/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const CARES_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/cares.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/cares.ts
commit-message: "deps: update c-ares to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update c-ares to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-cares
body: |
## What does this PR do?
Updates c-ares to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/c-ares/c-ares/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-cares.yml)
+109
View File
@@ -0,0 +1,109 @@
name: Update hdrhistogram
permissions:
contents: read
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check hdrhistogram version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/hdrhistogram.ts
CURRENT_VERSION=$(sed -nE 's/^const HDRHISTOGRAM_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find HDRHISTOGRAM_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/HdrHistogram/HdrHistogram_c/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/HdrHistogram/HdrHistogram_c/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
# Try to get commit SHA from tag object (for annotated tags)
# If it fails, assume it's a lightweight tag pointing directly to commit
LATEST_SHA=$(curl -sL "https://api.github.com/repos/HdrHistogram/HdrHistogram_c/git/tags/$LATEST_TAG_SHA" 2>/dev/null | jq -r '.object.sha // empty')
if [ -z "$LATEST_SHA" ]; then
# Lightweight tag - SHA points directly to commit
LATEST_SHA="$LATEST_TAG_SHA"
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const HDRHISTOGRAM_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/hdrhistogram.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/hdrhistogram.ts
commit-message: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-hdrhistogram
body: |
## What does this PR do?
Updates hdrhistogram to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/HdrHistogram/HdrHistogram_c/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-hdrhistogram.yml)
+125
View File
@@ -0,0 +1,125 @@
name: Update highway
permissions:
contents: read
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check highway version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/highway.ts
CURRENT_VERSION=$(sed -nE 's/^const HIGHWAY_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find HIGHWAY_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/google/highway/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
TAG_REF=$(curl -sL "https://api.github.com/repos/google/highway/git/refs/tags/$LATEST_TAG")
if [ -z "$TAG_REF" ]; then
echo "Error: Could not fetch tag reference for $LATEST_TAG"
exit 1
fi
TAG_OBJECT_SHA=$(echo "$TAG_REF" | jq -r '.object.sha')
TAG_OBJECT_TYPE=$(echo "$TAG_REF" | jq -r '.object.type')
if [ -z "$TAG_OBJECT_SHA" ] || [ "$TAG_OBJECT_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
# Handle both lightweight tags (type: commit) and annotated tags (type: tag)
if [ "$TAG_OBJECT_TYPE" = "commit" ]; then
# Lightweight tag - object.sha is already the commit SHA
LATEST_SHA="$TAG_OBJECT_SHA"
elif [ "$TAG_OBJECT_TYPE" = "tag" ]; then
# Annotated tag - need to fetch the tag object to get the commit SHA
LATEST_SHA=$(curl -sL "https://api.github.com/repos/google/highway/git/tags/$TAG_OBJECT_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch commit SHA for annotated tag $LATEST_TAG @ $TAG_OBJECT_SHA"
exit 1
fi
else
echo "Error: Unexpected tag object type: $TAG_OBJECT_TYPE"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const HIGHWAY_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/highway.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/highway.ts
commit-message: "deps: update highway to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update highway to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-highway
body: |
## What does this PR do?
Updates highway to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/google/highway/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-highway.yml)
+106
View File
@@ -0,0 +1,106 @@
name: Update libarchive
permissions:
contents: read
on:
schedule:
- cron: "0 3 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check libarchive version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/libarchive.ts
CURRENT_VERSION=$(sed -nE 's/^const LIBARCHIVE_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find LIBARCHIVE_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/libarchive/libarchive/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const LIBARCHIVE_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/libarchive.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/libarchive.ts
commit-message: "deps: update libarchive to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update libarchive to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-libarchive
body: |
## What does this PR do?
Updates libarchive to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/libarchive/libarchive/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-libarchive.yml)
+106
View File
@@ -0,0 +1,106 @@
name: Update libdeflate
permissions:
contents: read
on:
schedule:
- cron: "0 2 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check libdeflate version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/libdeflate.ts
CURRENT_VERSION=$(sed -nE 's/^const LIBDEFLATE_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find LIBDEFLATE_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/ebiggers/libdeflate/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const LIBDEFLATE_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/libdeflate.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/libdeflate.ts
commit-message: "deps: update libdeflate to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update libdeflate to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-libdeflate
body: |
## What does this PR do?
Updates libdeflate to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/ebiggers/libdeflate/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-libdeflate.yml)
+118
View File
@@ -0,0 +1,118 @@
name: Update lolhtml
permissions:
contents: read
on:
schedule:
- cron: "0 1 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check lolhtml version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/lolhtml.ts
CURRENT_VERSION=$(sed -nE 's/^const LOLHTML_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find LOLHTML_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/cloudflare/lol-html/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
# Get the commit SHA that the tag points to
# This handles both lightweight tags (direct commit refs) and annotated tags (tag objects)
TAG_REF_RESPONSE=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/refs/tags/$LATEST_TAG")
LATEST_TAG_SHA=$(echo "$TAG_REF_RESPONSE" | jq -r '.object.sha')
TAG_OBJECT_TYPE=$(echo "$TAG_REF_RESPONSE" | jq -r '.object.type')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
if [ "$TAG_OBJECT_TYPE" = "tag" ]; then
# This is an annotated tag, we need to get the commit it points to
LATEST_SHA=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch commit SHA for annotated tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
else
# This is a lightweight tag pointing directly to a commit
LATEST_SHA="$LATEST_TAG_SHA"
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const LOLHTML_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/lolhtml.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/lolhtml.ts
commit-message: "deps: update lolhtml to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update lolhtml to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-lolhtml
body: |
## What does this PR do?
Updates lolhtml to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/cloudflare/lol-html/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-lolhtml.yml)
+123
View File
@@ -0,0 +1,123 @@
name: Update lshpack
permissions:
contents: read
on:
schedule:
- cron: "0 5 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check lshpack version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/lshpack.ts
CURRENT_VERSION=$(sed -nE 's/^const LSHPACK_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find LSHPACK_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/litespeedtech/ls-hpack/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
# Get the tag reference, which contains both SHA and type
TAG_REF=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/refs/tags/$LATEST_TAG")
if [ -z "$TAG_REF" ]; then
echo "Error: Could not fetch tag reference for $LATEST_TAG"
exit 1
fi
LATEST_TAG_SHA=$(echo "$TAG_REF" | jq -r '.object.sha')
TAG_TYPE=$(echo "$TAG_REF" | jq -r '.object.type')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
# If it's an annotated tag, we need to dereference it to get the commit SHA
# If it's a lightweight tag, the SHA already points to the commit
if [ "$TAG_TYPE" = "tag" ]; then
LATEST_SHA=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch commit SHA for annotated tag $LATEST_TAG"
exit 1
fi
else
# For lightweight tags, the SHA is already the commit SHA
LATEST_SHA="$LATEST_TAG_SHA"
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const LSHPACK_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/lshpack.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/lshpack.ts
commit-message: "deps: update lshpack to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update lshpack to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-lshpack
body: |
## What does this PR do?
Updates lshpack to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/litespeedtech/ls-hpack/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-lshpack.yml)
+103
View File
@@ -0,0 +1,103 @@
name: Update SQLite3
permissions:
contents: read
on:
schedule:
- cron: "0 6 * * 0" # Run weekly
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check SQLite version
id: check-version
run: |
set -euo pipefail
# Get current version from the header file using SQLITE_VERSION_NUMBER
CURRENT_VERSION_NUM=$(grep -o '#define SQLITE_VERSION_NUMBER [0-9]\+' src/jsc/bindings/sqlite/sqlite3_local.h | awk '{print $3}' | tr -d '\n\r')
if [ -z "$CURRENT_VERSION_NUM" ]; then
echo "Error: Could not find SQLITE_VERSION_NUMBER in sqlite3_local.h"
exit 1
fi
# Convert numeric version to semantic version for display
CURRENT_MAJOR=$((CURRENT_VERSION_NUM / 1000000))
CURRENT_MINOR=$((($CURRENT_VERSION_NUM / 1000) % 1000))
CURRENT_PATCH=$((CURRENT_VERSION_NUM % 1000))
CURRENT_VERSION="$CURRENT_MAJOR.$CURRENT_MINOR.$CURRENT_PATCH"
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "current_num=$CURRENT_VERSION_NUM" >> $GITHUB_OUTPUT
# Fetch SQLite download page
DOWNLOAD_PAGE=$(curl -sL https://sqlite.org/download.html)
if [ -z "$DOWNLOAD_PAGE" ]; then
echo "Error: Failed to fetch SQLite download page"
exit 1
fi
# Extract latest version and year from the amalgamation link
LATEST_INFO=$(echo "$DOWNLOAD_PAGE" | grep -o 'sqlite-amalgamation-[0-9]\{7\}.zip' | head -n1)
LATEST_YEAR=$(echo "$DOWNLOAD_PAGE" | grep -o '[0-9]\{4\}/sqlite-amalgamation-[0-9]\{7\}.zip' | head -n1 | cut -d'/' -f1 | tr -d '\n\r')
LATEST_VERSION_NUM=$(echo "$LATEST_INFO" | grep -o '[0-9]\{7\}' | tr -d '\n\r')
if [ -z "$LATEST_VERSION_NUM" ] || [ -z "$LATEST_YEAR" ]; then
echo "Error: Could not extract latest version info"
exit 1
fi
# Convert numeric version to semantic version for display
LATEST_MAJOR=$((10#$LATEST_VERSION_NUM / 1000000))
LATEST_MINOR=$((($LATEST_VERSION_NUM / 10000) % 100))
LATEST_PATCH=$((10#$LATEST_VERSION_NUM % 1000))
LATEST_VERSION="$LATEST_MAJOR.$LATEST_MINOR.$LATEST_PATCH"
echo "latest=$LATEST_VERSION" >> $GITHUB_OUTPUT
echo "latest_year=$LATEST_YEAR" >> $GITHUB_OUTPUT
echo "latest_num=$LATEST_VERSION_NUM" >> $GITHUB_OUTPUT
# Debug output
echo "Current version: $CURRENT_VERSION ($CURRENT_VERSION_NUM)"
echo "Latest version: $LATEST_VERSION ($LATEST_VERSION_NUM)"
- name: Update SQLite if needed
if: success() && steps.check-version.outputs.current_num < steps.check-version.outputs.latest_num
env:
LATEST_NUM: ${{ steps.check-version.outputs.latest_num }}
LATEST_YEAR: ${{ steps.check-version.outputs.latest_year }}
run: |
./scripts/update-sqlite-amalgamation.sh "$LATEST_NUM" "$LATEST_YEAR"
- name: Create Pull Request
if: success() && steps.check-version.outputs.current_num < steps.check-version.outputs.latest_num
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
src/jsc/bindings/sqlite/sqlite3.c
src/jsc/bindings/sqlite/sqlite3_local.h
commit-message: "deps: update sqlite to ${{ steps.check-version.outputs.latest }}"
title: "deps: update sqlite to ${{ steps.check-version.outputs.latest }}"
delete-branch: true
branch: deps/update-sqlite
body: |
## What does this PR do?
Updates SQLite to version ${{ steps.check-version.outputs.latest }}
Compare: https://sqlite.org/src/vdiff?from=${{ steps.check-version.outputs.current }}&to=${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-sqlite3.yml)
+88
View File
@@ -0,0 +1,88 @@
name: Update vendor
permissions:
contents: read
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
strategy:
matrix:
package:
- elysia
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup-bun
- name: Check version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
current=$(bun -p '(await Bun.file("test/vendor.json").json()).filter(v=>v.package===process.argv[1])[0].tag' ${{ matrix.package }})
repository=$(bun -p '(await Bun.file("test/vendor.json").json()).filter(v=>v.package===process.argv[1])[0].repository' ${{ matrix.package }} | cut -d'/' -f4,5)
if [ -z "$current" ]; then
echo "Error: Could not find COMMIT line in test/vendor.json"
exit 1
fi
echo "current=$current" >> $GITHUB_OUTPUT
echo "repository=$repository" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/${repository}/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
echo "latest=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
PACKAGE: ${{ matrix.package }}
LATEST_TAG: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
bun -e 'await Bun.write("test/vendor.json", JSON.stringify((await Bun.file("test/vendor.json").json()).map(v=>{if(v.package===process.argv[1])v.tag=process.argv[2];return v;}), null, 2) + "\n")' "$PACKAGE" "$LATEST_TAG"
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
test/vendor.json
commit-message: "deps: update ${{ matrix.package }} to ${{ steps.check-version.outputs.latest }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update ${{ matrix.package }} to ${{ steps.check-version.outputs.latest }}"
delete-branch: true
branch: deps/update-${{ matrix.package }}
body: |
## What does this PR do?
Updates ${{ matrix.package }} to version ${{ steps.check-version.outputs.latest }}
Compare: https://github.com/${{ steps.check-version.outputs.repository }}/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-vendor.yml)
+106
View File
@@ -0,0 +1,106 @@
name: Update zstd
permissions:
contents: read
on:
schedule:
- cron: "0 1 * * 0"
workflow_dispatch:
jobs:
check-update:
if: github.repository == 'oven-sh/bun'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check zstd version
id: check-version
run: |
set -euo pipefail
DEP_FILE=scripts/build/deps/zstd.ts
CURRENT_VERSION=$(sed -nE 's/^const ZSTD_COMMIT = "([0-9a-f]{40})";$/\1/p' "$DEP_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find ZSTD_COMMIT in $DEP_FILE"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in $DEP_FILE"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/facebook/zstd/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/facebook/zstd/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/facebook/zstd/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
env:
LATEST: ${{ steps.check-version.outputs.latest }}
run: |
set -euo pipefail
sed -i -E 's/^(const ZSTD_COMMIT = ")[0-9a-f]{40}(";)$/\1'"$LATEST"'\2/' scripts/build/deps/zstd.ts
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
scripts/build/deps/zstd.ts
commit-message: "deps: update zstd to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update zstd to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-zstd
body: |
## What does this PR do?
Updates zstd to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/facebook/zstd/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-zstd.yml)
+59
View File
@@ -0,0 +1,59 @@
name: VSCode Extension Publish
permissions:
contents: read
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 0.0.25) - Check the marketplace for the latest version"
required: true
type: string
jobs:
publish:
name: "Publish to VS Code Marketplace"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: "1.3.14"
- name: Install dependencies (root)
run: bun install
- name: Install dependencies
run: bun install
working-directory: packages/bun-vscode
- name: Set Version
env:
VERSION: ${{ inputs.version }}
run: bun pm version "$VERSION" --no-git-tag-version --allow-same-version
working-directory: packages/bun-vscode
- name: Build (inspector protocol)
run: bun install && bun run build
working-directory: packages/bun-inspector-protocol
- name: Build (vscode extension)
run: bun run build
working-directory: packages/bun-vscode
- name: Publish
if: success()
run: bunx vsce publish
env:
VSCE_PAT: ${{ secrets.VSCODE_EXTENSION }}
working-directory: packages/bun-vscode/extension
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bun-vscode-${{ github.event.inputs.version }}.vsix
path: packages/bun-vscode/extension/bun-vscode-${{ github.event.inputs.version }}.vsix
+220
View File
@@ -0,0 +1,220 @@
.rustup
.claude/settings.local.json
.cargo
Library/Caches
.direnv
.DS_Store
.env
.envrc
.eslintcache
.gdb_history
.idea
.next
.ninja_deps
.ninja_log
.npm
.npmrc
.npm.gz
.parcel-cache
.swcrc
.trace
.uuid
.vs
.vscode/clang*
.vscode/cpp*
.bake-debug
*.a
*.bc
*.big
*.blob
*.bun
*.crash
*.database
*.db
*.dmg
*.dSYM
*.generated.ts
*.jsb
*.lib
*.log
*.o
*.out.js
*.out.refresh.js
*.pdb
*.sqlite
*.swp
*.tmp
*.trace
*.wat
*.zip
**/.verdaccio-db.json
**/*.dir
**/*.pdb
**/*.sln*
**/*.vcxproj*
**/package-lock.json
/.cache
/.webkit-cache
/build-*/
/test/build/
/bun-webkit
/kcov-out
/test-report.json
/test-report.md
/test.js
/test.ts
/testdir
/build/
cmake/sources
build.ninja
bun-binary
bun-mimalloc
bun-nomimalloc
bun-singlehtreaded
bun-test-scratch
bun-zigld
cmake_install.cmake
CMakeCache.txt
CMakeFiles
cold-jsc-start
cold-jsc-start.d
compile_commands.json
cover
coverage
coverv
dist
esbuilddir
examples/lotta-modules/bun-nofscache
examples/lotta-modules/bun-old
examples/lotta-modules/bun-yday
failing-tests.txt
github
make-dev-stats.csv
misctools/fetch
misctools/machbench
misctools/sha
myscript.sh
node_modules
node_modules_*
out
out.*
outcss
outdir
outdir/
packages/*/*.wasm
packages/bun-*/*.o
packages/bun-*/bun
packages/bun-*/bun-profile
packages/bun-*/debug-bun
packages/bun-cli/bin/*
packages/bun-cli/postinstall.js
packages/debug-*
parceldist
pnpm-lock.yaml
profile.json
README.md.template
release/
scripts/env.local
sign.*.json
sign.json
src/bake/generated.ts
src/jsc/bindings-obj
src/bun.js/debug-bindings-obj
src/js/out/DebugPath.h
src/js/out/functions*
src/js/out/modules*
src/js/out/tmp
src/node-fallbacks/node_modules
src/node-fallbacks/out/*
src/runtime.version
test.txt
test/js/bun/glob/fixtures
test/node.js/upstream
tsconfig.tsbuildinfo
txt.js
x64
yarn.lock
test/node.js/upstream
scripts/env.local
*.generated.ts
src/bake/generated.ts
test/cli/install/registry/packages/publish-pkg-*
test/cli/install/registry/packages/@secret/publish-pkg-8
test/js/third_party/prisma/prisma/sqlite/dev.db-journal
tmp
codegen-for-zig-team.tar.gz
# Dependencies
/vendor
# Dependencies (before CMake)
# These can be removed in the far future
/src/bun.js/WebKit
/src/deps/boringssl
/src/deps/brotli
/src/deps/c*ares
/src/deps/libarchive
/src/deps/libdeflate
/src/deps/libuv
/src/deps/lol*html
/src/deps/ls*hpack
/src/deps/mimalloc
/src/deps/picohttpparser
/src/deps/tinycc
/src/deps/WebKit
/src/deps/zig
/src/deps/zlib
/src/deps/zstd
# Generated files
# Written at configure time from the discovered toolchain by
# scripts/build/cargo-config.ts — see scripts/build/CLAUDE.md.
.cargo/config.toml
.buildkite/ci.yml
*.sock
scratch*.{js,ts,tsx,cjs,mjs}
/a.js
scratch
*.bun-build
scripts/lldb-inline
test/integration/bun-types/fixture/bun.lock
.claude/scheduled_tasks.lock
.claude/worktrees
src/runtime/bake/generated.ts
/target/
# Heap snapshots, profiler output, and core dumps can contain secrets from
# process memory (env vars, tokens). Never commit them, compressed or not.
*.heapsnapshot
*.heapsnapshot.*
*.heapprofile
*.heapprofile.*
*.heaptimeline
*.heaptimeline.*
*.cpuprofile
*.cpuprofile.*
Heap-*.json
Heap-*.json.*
isolate-*.log
core
core.[0-9]*
# Legacy codegen outputs (no longer written, but old build trees still have
# them on disk; keep ignored so they don't show as untracked).
src/jsc/bindings/GeneratedJS2Native.zig
src/jsc/bindings/GeneratedBindings.zig
# Web Streams rewrite working notes (design docs, spec transcription, review logs).
# Kept locally for the ongoing work; not part of the source tree.
/specs/
/garbage-env
# compiled-bundler test outputs that land in cwd when tests run from the root
/entry
/entry.js.map
/nosourcemap_entry
# hawk analysis scaffolding (see tools/hawk/); generated per-run, never committed
src/bun_bin/hawk_root.rs
+2
View File
@@ -0,0 +1,2 @@
# To learn more about git's mailmap: https://ntietz.com/blog/git-mailmap-for-name-changes
chloe caruso <[email protected]> <[email protected]>
+41
View File
@@ -0,0 +1,41 @@
src/bun.js/WebKit
vendor
test/snapshots
test/bundler/transpiler/react-compiler-fixtures
src/**/*.generated.rs
test/js/deno
test/node.js
src/react-refresh.js
*.min.js
test/snippets
test/js/node/test
test/napi/node-napi-tests
bun.lock
# generated by scripts/update-test-durations.mjs
test/expected-durations.json
# vendored, generated bundle of github.com/jarred-sumner/xmac (see its header)
scripts/build/xmac.mjs
# the output codeblocks need to stay minified
docs/bundler/minifier.mdx
# generated working notes for the Rust port — dense markdown with inline code
# refs that prettier doesn't reformat to a fixed point (it keeps re-escaping
# `_`/`*` differently every pass), so autofix.ci ping-pongs commits forever.
# Not authored by hand; no value in normalizing it.
docs/.rust-rewrite-verified-claims.md
# Verbatim ports from Node.js v26.3.0 (lib/) — keep close to upstream
# (mirrors the oxlint.json ignore block for the same files)
src/js/node/repl.js
src/js/node/readline.js
src/js/node/readline.promises.js
src/js/internal/repl.js
src/js/internal/readline
src/js/internal/repl/history.js
src/js/internal/repl/utils.js
src/js/internal/repl/completion.js
src/js/internal/repl/await.js
src/js/internal/repl/acorn.js
src/js/internal/repl/acorn-walk.js
+30
View File
@@ -0,0 +1,30 @@
{
"arrowParens": "avoid",
"printWidth": 120,
"trailingComma": "all",
"useTabs": false,
"quoteProps": "preserve",
"overrides": [
{
"files": [".vscode/*.json"],
"options": {
"parser": "jsonc",
"quoteProps": "preserve",
"singleQuote": false,
"trailingComma": "all"
}
},
{
"files": ["*.md"],
"options": {
"printWidth": 80
}
},
{
"files": ["src/codegen/bindgenv2/**/*.ts", "*.bindv2.ts"],
"options": {
"printWidth": 100
}
}
]
}
+2
View File
@@ -0,0 +1,2 @@
[type.md]
extend-ignore-words-re = ["^ba"]
+120
View File
@@ -0,0 +1,120 @@
{
"configurations": [
{
"name": "Debug",
"forcedInclude": ["${workspaceFolder}/src/jsc/bindings/root.h"],
"compileCommands": "${workspaceFolder}/build/debug/compile_commands.json",
"includePath": [
"${workspaceFolder}/build/bun-webkit/include",
"${workspaceFolder}/build/debug/codegen",
"${workspaceFolder}/src/jsc/bindings/",
"${workspaceFolder}/src/jsc/bindings/webcore/",
"${workspaceFolder}/src/jsc/bindings/sqlite/",
"${workspaceFolder}/src/jsc/bindings/webcrypto/",
"${workspaceFolder}/src/jsc/modules/",
"${workspaceFolder}/src/js/builtins/",
"${workspaceFolder}/vendor/boringssl/include/",
"${workspaceFolder}/vendor",
"${workspaceFolder}/src/runtime/napi/*",
"${workspaceFolder}/packages/bun-usockets/src",
"${workspaceFolder}/packages/",
],
"browse": {
"path": [
"${workspaceFolder}/build/bun-webkit/include",
"${workspaceFolder}/src/jsc/bindings",
"${workspaceFolder}/src/runtime/napi/*",
"${workspaceFolder}/src/js/builtins/*",
"${workspaceFolder}/src/jsc/modules/*",
"${workspaceFolder}/vendor/*",
"${workspaceFolder}/vendor/boringssl/include/*",
"${workspaceFolder}/packages/bun-usockets/*",
"${workspaceFolder}/packages/bun-uws/*",
"${workspaceFolder}/src/runtime/napi/*",
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ".vscode/cppdb",
},
"defines": [
"STATICALLY_LINKED_WITH_JavaScriptCore=1",
"STATICALLY_LINKED_WITH_WTF=1",
"BUILDING_WITH_CMAKE=1",
"NOMINMAX",
"ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
"BUILDING_JSCONLY__",
"USE_FOUNDATION=1",
"ASSERT_ENABLED=1",
"DU_DISABLE_RENAMING=1",
],
"macFrameworkPath": [],
"compilerPath": "${workspaceFolder}/.vscode/clang++",
"cStandard": "c17",
"cppStandard": "c++20",
},
{
"name": "BunWithJSCDebug",
"forcedInclude": ["${workspaceFolder}/src/jsc/bindings/root.h"],
"includePath": [
"${workspaceFolder}/build/debug/codegen",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/ICU/Headers/",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/PrivateHeaders/",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/WTF/Headers",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/bmalloc/Headers/",
"${workspaceFolder}/src/jsc/bindings/",
"${workspaceFolder}/src/jsc/bindings/webcore/",
"${workspaceFolder}/src/jsc/bindings/sqlite/",
"${workspaceFolder}/src/jsc/bindings/webcrypto/",
"${workspaceFolder}/src/jsc/modules/",
"${workspaceFolder}/src/js/builtins/",
"${workspaceFolder}/src/js/out",
"${workspaceFolder}/vendor/boringssl/include/",
"${workspaceFolder}/vendor",
"${workspaceFolder}/src/runtime/napi/*",
"${workspaceFolder}/packages/bun-usockets/src",
"${workspaceFolder}/packages/",
],
"browse": {
"path": [
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/ICU/Headers/",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/PrivateHeaders/**",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/WTF/Headers/**",
"${workspaceFolder}/vendor/WebKit/WebKitBuild/Debug/bmalloc/Headers/**",
"${workspaceFolder}/src/jsc/bindings/*",
"${workspaceFolder}/src/jsc/bindings/*",
"${workspaceFolder}/src/runtime/napi/*",
"${workspaceFolder}/src/jsc/bindings/sqlite/",
"${workspaceFolder}/src/jsc/bindings/webcrypto/",
"${workspaceFolder}/src/jsc/bindings/webcore/",
"${workspaceFolder}/src/js/builtins/*",
"${workspaceFolder}/src/js/out/*",
"${workspaceFolder}/src/jsc/modules/*",
"${workspaceFolder}/vendor",
"${workspaceFolder}/vendor/boringssl/include/",
"${workspaceFolder}/packages/bun-usockets/",
"${workspaceFolder}/packages/bun-uws/",
"${workspaceFolder}/src/runtime/napi",
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ".vscode/cppdb_debug",
},
"defines": [
"STATICALLY_LINKED_WITH_JavaScriptCore=1",
"STATICALLY_LINKED_WITH_WTF=1",
"BUILDING_WITH_CMAKE=1",
"NOMINMAX",
"ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS=0",
"BUILDING_JSCONLY__",
"USE_FOUNDATION=1",
"ASSERT_ENABLED=1",
"DU_DISABLE_RENAMING=1",
],
"macFrameworkPath": [],
"compilerPath": "${workspaceFolder}/.vscode/clang++",
"cStandard": "c17",
"cppStandard": "c++20",
},
],
"version": 4,
}
+33
View File
@@ -0,0 +1,33 @@
{
"recommendations": [
// Rust
"rust-lang.rust-analyzer",
// C/C++
"clang.clangd",
"ms-vscode.cmake-tools",
"xaver.clang-format",
"vadimcn.vscode-lldb",
// JavaScript
"oven.bun-vscode",
"esbenp.prettier-vscode",
// TypeScript
"better-ts-errors.better-ts-errors",
"MylesMurphy.prettify-ts",
// Markdown
"bierner.markdown-preview-github-styles",
"bierner.markdown-emoji",
"bierner.emojisense",
"bierner.markdown-checkbox",
"bierner.jsdoc-markdown-highlighting",
// TOML
"tamasfe.even-better-toml",
// Other
"bierner.comment-tagged-templates",
],
}
Generated Vendored
+365
View File
@@ -0,0 +1,365 @@
{
// Notes:
// - BUN_GARBAGE_COLLECTOR_LEVEL=2 forces GC to run after every `expect()`, but is slower
// - BUN_DEBUG_QUIET_LOGS=1 disables the debug logs
// - FORCE_COLOR=1 forces colors in the terminal
// - "${workspaceFolder}/test" is the cwd for `bun test` so it matches CI, we should fix this later
// - "cppvsdbg" is used instead of "lldb" on Windows, because "lldb" is too slow
// - Seeing WebKit files requires `vendor/WebKit` to exist and have code from the right commit.
// Run `bun sync-webkit-source` to ensure that folder is at the right commit. If you haven't
// cloned it at all, that script will suggest how.
"version": "0.2.0",
"configurations": [
// bun test [file]
{
"type": "lldb",
"request": "launch",
"name": "bun test [file]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["test", "--timeout=3600000", "${file}"],
"cwd": "${workspaceFolder}",
"env": {
"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",
// "BUN_DESTRUCT_VM_ON_EXIT": "1",
// "ASAN_OPTIONS": "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1:abort_on_error=1",
// "LSAN_OPTIONS": "malloc_context_size=100:print_suppressions=1:suppressions=${workspaceFolder}/test/leaksan.supp",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
{
"type": "lldb",
"name": "Attach",
"request": "attach",
"pid": "${command:pickMyProcess}",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
// bun run [file]
{
"type": "lldb",
"request": "launch",
"name": "bun run [file]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["${file}"],
"cwd": "${workspaceFolder}",
"env": {
"FORCE_COLOR": "0",
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
// "BUN_JSC_validateExceptionChecks": "1",
// "BUN_JSC_dumpSimulatedThrows": "1",
// "BUN_JSC_unexpectedExceptionStackTraceLimit": "20",
// "BUN_DESTRUCT_VM_ON_EXIT": "1",
// "ASAN_OPTIONS": "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1:abort_on_error=1",
// "LSAN_OPTIONS": "malloc_context_size=100:print_suppressions=1:suppressions=${workspaceFolder}/test/leaksan.supp",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
// bun test [...]
{
"type": "lldb",
"request": "launch",
"name": "bun test [...]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["test", "--timeout=3600000", "${input:testName}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_DEBUG_jest": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
// bun exec [...]
{
"type": "lldb",
"request": "launch",
"name": "bun exec [...]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["exec", "${input:testName}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
// bun test [*]
{
"type": "lldb",
"request": "launch",
"name": "bun test [*]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["test"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
{
"type": "lldb",
"request": "launch",
"name": "bun install [folder]",
"program": "${workspaceFolder}/build/debug/bun-debug",
"args": ["install"],
"cwd": "${fileDirname}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
"console": "internalConsole",
"sourceMap": {
// macOS
"/Users/runner/work/_temp/webkit-release/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/Users/runner/work/_temp/webkit-release/WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
// linux
"/webkitbuild/vendor/WebKit": "${workspaceFolder}/vendor/WebKit",
"/webkitbuild/.WTF/Headers": "${workspaceFolder}/vendor/WebKit/Source/WTF",
},
},
// Windows: bun test [file]
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [file]",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["test", "--timeout=3600000", "${file}"],
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_DEBUG_jest",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "1",
},
],
},
// Windows: bun run [file]
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun run [file]",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["run", "${fileBasename}"],
"cwd": "${fileDirname}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_DEBUG_jest",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "2",
},
],
},
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun install",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["install"],
"cwd": "${fileDirname}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "0",
},
],
},
// Windows: bun test [...]
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [...]",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["test", "--timeout=3600000", "${input:testName}"],
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_DEBUG_jest",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "2",
},
],
},
// Windows: bun exec [...]
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun exec [...]",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["exec", "${input:testName}"],
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "2",
},
],
},
// Windows: bun test [*]
{
"type": "cppvsdbg",
"sourceFileMap": {
"D:\\a\\WebKit\\WebKit\\Source": "${workspaceFolder}\\src\\bun.js\\WebKit\\Source",
},
"request": "launch",
"name": "Windows: bun test [*]",
"program": "${workspaceFolder}/build/debug/bun-debug.exe",
"args": ["test"],
"cwd": "${workspaceFolder}",
"environment": [
{
"name": "BUN_DEBUG_QUIET_LOGS",
"value": "1",
},
{
"name": "BUN_GARBAGE_COLLECTOR_LEVEL",
"value": "2",
},
],
},
{
"type": "bun",
"name": "[JS] bun test [file]",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"runtimeArgs": ["test", "${file}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "bun",
"name": "[JS] bun run [file]",
"runtime": "${workspaceFolder}/build/debug/bun-debug",
"runtimeArgs": ["run", "${file}"],
"cwd": "${workspaceFolder}",
"env": {
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
},
},
{
"type": "midas-rr",
"request": "attach",
"name": "rr",
"trace": "Off",
"setupCommands": [
"handle SIGPWR nostop noprint pass",
"set substitute-path /webkitbuild/vendor/WebKit ${workspaceFolder}/vendor/WebKit",
"set substitute-path /webkitbuild/.WTF/Headers ${workspaceFolder}/vendor/WebKit/Source/WTF",
// uncomment if you like
// "set disassembly-flavor intel",
"set print asm-demangle",
],
},
],
"inputs": [
{
"id": "commandLine",
"type": "promptString",
"description": "Usage: bun [...]",
},
{
"id": "testName",
"type": "promptString",
"description": "Usage: bun test [...]",
},
],
}
+150
View File
@@ -0,0 +1,150 @@
{
// Editor
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
// Search
"search.quickOpen.includeSymbols": false,
"search.seedWithNearestWord": true,
"search.smartCase": true,
"search.exclude": {
"node_modules": true,
".git": true,
"vendor/*/**": true,
"test/node.js/upstream": true,
// This will fill up your whole search history.
"test/js/node/test/fixtures": true,
"test/js/node/test/common": true,
},
"search.followSymlinks": false,
"search.useIgnoreFiles": true,
// Git
"git.autoRepositoryDetection": "openEditors",
"git.ignoreSubmodules": true,
"git.ignoreLimitWarning": true,
// lldb
"lldb.launch.initCommands": ["command source ${workspaceFolder}/.lldbinit"],
"lldb.verboseLogging": false,
// C++
"cmake.configureOnOpen": false,
"C_Cpp.errorSquiggles": "enabled",
"[cpp]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "xaver.clang-format",
},
"[c]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "xaver.clang-format",
},
"[h]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "xaver.clang-format",
},
"clangd.arguments": ["--header-insertion=never"],
// JavaScript
"prettier.enable": true,
"prettier.configPath": ".prettierrc",
"eslint.workingDirectories": ["${workspaceFolder}/packages/bun-types"],
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"prettier.prettierPath": "./node_modules/prettier",
// TypeScript
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
// JSON
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
// Markdown
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.unicodeHighlight.ambiguousCharacters": true,
"editor.unicodeHighlight.invisibleCharacters": true,
"diffEditor.ignoreTrimWhitespace": false,
"editor.wordWrap": "on",
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off",
},
},
// TOML
"[toml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
// YAML
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
// Docker
"[dockerfile]": {
"editor.formatOnSave": false,
},
// Files
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/*.xcworkspacedata": true,
"**/*.xcscheme": true,
"**/*.xcodeproj": true,
"**/*.i": true,
},
"files.associations": {
"*.css": "tailwindcss",
"*.idl": "cpp",
"*.mdc": "markdown",
"array": "cpp",
"ios": "cpp",
"oxlint.json": "jsonc",
"bun.lock": "jsonc",
},
"C_Cpp.files.exclude": {
"**/.vscode": true,
"WebKit/JSTests": true,
"WebKit/Tools": true,
"WebKit/WebDriverTests": true,
"WebKit/WebKit.xcworkspace": true,
"WebKit/WebKitLibraries": true,
"WebKit/Websites": true,
"WebKit/resources": true,
"WebKit/LayoutTests": true,
"WebKit/ManualTests": true,
"WebKit/PerformanceTests": true,
"WebKit/WebKitLegacy": true,
"WebKit/WebCore": true,
"WebKit/WebDriver": true,
"WebKit/WebKitBuild": true,
"WebKit/WebInspectorUI": true,
},
"git.detectSubmodules": false,
"bun.test.customScript": "./build/debug/bun-debug test",
}
+40
View File
@@ -0,0 +1,40 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Bun",
"type": "shell",
"command": "bun run build",
"group": {
"kind": "build",
"isDefault": true,
},
"problemMatcher": [
{
"owner": "clang",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": [
{
"regexp": "^([^:]+):(\\d+):(\\d+):\\s+(warning|error|note|remark):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5,
},
{
"regexp": "^\\s*(.*)$",
"message": 1,
"loop": true,
},
],
},
],
"presentation": {
"reveal": "always",
"panel": "shared",
"clear": true,
},
},
],
}
Symlink
+1
View File
@@ -0,0 +1 @@
CLAUDE.md
+239
View File
@@ -0,0 +1,239 @@
This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed for speed, with a bundler, test runner, and Node.js-compatible package manager. It's written primarily in Rust with C++ for JavaScriptCore integration, powered by WebKit's JavaScriptCore engine.
## Building and Running Bun
### Build Commands
- **Build Bun**: `bun bd`
- Creates a debug build at `./build/debug/bun-debug`
- **CRITICAL**: do not set a timeout when running `bun bd`
- **Run tests with your debug build**: `bun bd test <test-file>`
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
- **Run any command with debug build**: `bun bd <command>`
- **Run with JavaScript exception scope verification**: `BUN_JSC_validateExceptionChecks=1
BUN_JSC_dumpSimulatedThrows=1 bun bd <command>`
Tip: Bun is already installed and in $PATH. The `bd` subcommand is a package.json script.
**All build scripts support build-then-exec.** Any `bun run build*` command (and `bun bd`) accepts trailing args which are passed to the built executable after building — you never invoke `./build/debug/bun-debug` directly.
```sh
bun bd test foo.test.ts # debug build + quiet debug logs
bun run build test foo.test.ts # debug build
bun run build:release -p 'Bun.version' # release build
bun run build:local run script.ts # debug build with local WebKit
```
When exec args are present, build output is suppressed unless the build fails — you see only the binary's output. Build flags (e.g. `--asan=off`) go before the exec args; see `scripts/build.ts` header for the full arg routing rules.
### Changes that don't require a build
Edits to **TypeScript type declarations** (`packages/bun-types/**/*.d.ts`) do not touch any compiled code, so `bun bd` is unnecessary. The types test just packs the `.d.ts` files and runs `tsc` against fixtures — it never executes your build. Run it directly with the system Bun (an explicit exception to the "never use `bun test` directly" rule):
```sh
bun test test/integration/bun-types/bun-types.test.ts
```
This is an explicit exception to the "never use `bun test` directly" rule. There are no native changes for a debug build to pick up, so don't wait on one.
## Testing
### Running Tests
- **Single test file**: `bun bd test test/js/bun/http/serve.test.ts`
- **Fuzzy match test file**: `bun bd test http/serve.test.ts`
- **With filter**: `bun bd test test/js/bun/http/serve.test.ts -t "should handle"`
### Test Organization
**Default: add your test to the existing test file for the code you're changing.** Do not create a new file. A fetch bug goes in `test/js/web/fetch/fetch.test.ts`, a `Bun.serve` bug goes in `test/js/bun/http/serve.test.ts`, and so on. Keeping tests next to related coverage is what makes them discoverable and prevents duplicated setup.
- `test/js/bun/` - Bun-specific API tests (http, crypto, ffi, shell, etc.)
- `test/js/node/` - Node.js compatibility tests
- `test/js/web/` - Web API tests (fetch, WebSocket, streams, etc.)
- `test/cli/` - CLI command tests (install, run, test, etc.)
- `test/bundler/` - Bundler and transpiler tests. Use `itBundled` helper.
- `test/integration/` - End-to-end integration tests
- `test/napi/` - N-API compatibility tests
- `test/v8/` - V8 C++ API compatibility tests
**Exception:** `test/regression/issue/${issueNumber}.test.ts` is reserved for bugs with a GitHub issue number **and** that are true regressions (worked in a previous release, then broke). If the behavior was never correct, it's not a regression — the test belongs in the existing file for that module. The issue number must be **REAL**, not a placeholder.
### Writing Tests
Tests use Bun's Jest-compatible test runner. For **single-file tests**, prefer spawning with `-e`; for **multi-file tests**, prefer `tempDir` and `Bun.spawn`:
```typescript
import { test, expect } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
const [stdout, stderr, exitCode] = await Promise.all([
test("(multi-file test) my feature", async () => {
using dir = tempDir("test-prefix", {
"index.js": `import { foo } from "./foo.ts"; foo();`,
"foo.ts": `export function foo() { console.log("foo"); }`,
});
// For a single-file test, use: cmd: [bunExe(), "-e", `console.log("foo")`] and omit cwd.
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
proc.stdout.text(),
proc.stderr.text(),
proc.exited,
]);
// Prefer snapshot tests over expect(stdout).toBe("hello\n");
expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"foo"`);
// Assert the exit code last. This gives you a more useful error message on test failure.
expect(exitCode).toBe(0);
});
```
- 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. These tests will never fail in CI.
- Use `tempDir` from `"harness"` to create a temporary directory. **Do not** use `tmpdirSync` or `fs.mkdtempSync` to create temporary directories.
- When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure.
- Keep tests fast: budget roughly 1s per test and 10s per file. Debug+ASAN builds run 10-100x slower than release, so a 1s local test can take a minute in CI. Use `test.concurrent` for independent subprocess-spawning tests.
- Never contact the public internet (registry.npmjs.org, github.com, CDNs). Use `VerdaccioRegistry` from `"harness"` for package installs and a local `Bun.serve({ port: 0 })` for HTTP.
- `setDefaultTimeout` is a ceiling, not a target. Leave the default and pass a per-test timeout only for the rare outlier; a 5-minute file default multiplies across retries when one test hangs.
- Leak tests branch their RSS threshold on `isASAN`/`isDebug` and keep the bound well below what the unfixed leak produces. An un-branched absolute delta flakes under ASAN quarantine and GC jitter.
- **CRITICAL**: Do not write flaky tests. Do not use `setTimeout` or `await sleep(N)` to wait for a condition; poll with a deadline or `await` the event itself. You are not testing the TIME PASSING, you are testing the CONDITION.
- **CRITICAL**: Verify your test fails with `USE_SYSTEM_BUN=1 bun test <file>` and passes with `bun bd test <file>`. Your test is NOT VALID if it passes with `USE_SYSTEM_BUN=1`.
## Code Architecture
### Language Structure
- **Rust code** (`src/**/*.rs`): Core runtime, JavaScript bindings, bundler, package manager. This is what compiles and ships.
- **C++ code** (`src/jsc/bindings/*.cpp`): JavaScriptCore bindings, Web APIs
- **TypeScript** (`src/js/`): Built-in JavaScript modules with special syntax (see JavaScript Modules section)
- **Generated code**: Many `.rs` and `.cpp` files are auto-generated from `.classes.ts` and other sources. The build regenerates them automatically when their inputs change.
### Core Source Organization
The Rust side is a Cargo workspace of ~200 crates rooted at `Cargo.toml`. The key ones:
- `src/bun_core/` - The `bun.*`-namespace foundation: strings/`String` (`string/`), formatting (`fmt.rs`), logging (`output.rs`), feature flags, env vars, allocator helpers
- `src/sys/` - Cross-platform syscall wrappers (`file.rs`, `dir.rs`, `fd.rs`, `Error.rs`, `tmp.rs`) — the `bun.sys` equivalent
- `src/collections/`, `src/threading/`, `src/paths/`, `src/semver/`, `src/sourcemap/` - shared utilities
- `src/bun_bin/` - Cargo entrypoint; produces `libbun_rust.a`, linked into the final binary
- `src/runtime/cli/` - CLI argument parsing and command dispatch
- `src/js_parser/`, `src/js_printer/` - JavaScript/TypeScript parsing and printing (each is its own crate; the lexer is `src/js_parser/lexer.rs`)
- `src/transpiler/` - Wrapper around the parser/printer with sourcemap support
- `src/resolver/` - Module resolution system
- `src/ast/` - AST node types and arena allocation
- `src/jsc/bindings/` - C++ JavaScriptCore bindings (generated classes from `.classes.ts` + manual bindings)
- `src/jsc/` - Rust-side JSC glue (`VirtualMachine.rs`, `web_worker.rs`, `event_loop.rs`, FFI imports)
- `src/runtime/api/` - Bun-specific JS-visible APIs (`BunObject.rs`, `JSBundler.rs`, `Glob`, `Archive`, …)
- `src/runtime/server/` - `Bun.serve` HTTP/WebSocket server
- `src/runtime/node/` - Node.js compatibility layer (fs, path, process, Buffer, …)
- `src/runtime/crypto/` - WebCrypto + `node:crypto` (`EVP.rs`, `HMAC.rs`, `CryptoHasher.rs`, …)
- `src/runtime/webcore/` - Web API implementations (`fetch.rs`, `streams.rs`, `Blob.rs`, `Response.rs`, `Request.rs`, …)
- `src/event_loop/` - Event loop and task management
- `src/bundler/` - JavaScript bundler (tree-shaking, CSS processing, HTML handling)
- `src/install/` - Package manager (`lockfile/`, `npm.rs` registry client, `lifecycle_script_runner.rs`)
- `src/shell/` - Cross-platform shell implementation
- `src/css/` - CSS parser and processor
- `src/http/` - HTTP client + `websocket_client/` (WebSocket, deflate)
- `src/sql/` - SQL database integrations (Postgres, MySQL, SQLite)
- `src/bake/` - Server-side rendering / dev server framework
#### Vendored Dependencies (`vendor/`)
Third-party C/C++ libraries are vendored locally and can be read from disk (not git submodules): boringssl (TLS/crypto), brotli, cares (async DNS), hdrhistogram, highway (SIMD), libarchive (tar/zip), libdeflate, libuv (Windows event loop), lolhtml (HTML rewriter), lshpack (HTTP/2 HPACK), lsqpack + lsquic (HTTP/3), mimalloc (allocator), nodejs (headers), picohttpparser, tinycc (FFI JIT, fork: oven-sh/tinycc), WebKit (JavaScriptCore), zlib (zlib-ng), zstd. Build configuration for these is in `scripts/build/deps/*.ts`.
### JavaScript Class Implementation (C++)
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`
2. Define properties using HashTableValue arrays
3. Add iso subspaces for classes with C++ fields
4. Cache structures in `ZigGlobalObject`
### Code Generation
Code generation happens automatically as part of the build process. The main scripts are:
- `src/codegen/generate-classes.ts` - Generates Rust & C++ bindings from `*.classes.ts` files
- `src/codegen/generate-jssink.ts` - Generates stream-related classes
- `src/codegen/bundle-modules.ts` - Bundles built-in modules like `node:fs`
- `src/codegen/bundle-functions.ts` - Bundles global functions like `ReadableStream`
In development, bundled JS modules can be reloaded without rebuilding native code by running `bun run build`.
## JavaScript Modules (`src/js/`)
Built-in JavaScript modules use special syntax and are organized as:
- `node/` - Node.js compatibility modules (`node:fs`, `node:path`, etc.)
- `bun/` - Bun-specific modules (`bun:ffi`, `bun:sqlite`, etc.)
- `thirdparty/` - NPM modules we replace (like `ws`)
- `internal/` - Internal modules not exposed to users
- `builtins/` - Core JavaScript builtins (streams, console, etc.)
## Landing PRs: What Bun Reviewers Catch
The code review rules — what blocks merges, distilled from ~2,500 merged PRs — live in `REVIEW.md`. Read it before writing code that makes a non-obvious choice.
Several situational sections live in `.claude/docs/landing-prs.md` — read the relevant one before the work it covers: **Node/Web compat** (touching `node:*` modules, Web APIs, or `src/runtime/node/`), **API design** (adding or changing user-facing API surface), **Performance** (optimizing, touching hot paths, or making perf claims), **Cross-platform** (platform-gated code, FFI/ABI, or platform-sensitive tests), **Dependencies & vendoring** (bumping deps or touching `vendor/`), **Docs, types, and comments** (docs, `.d.ts`, JSDoc), and **PR process** (opening or responding to a PR).
## 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. **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** - Prefer RAII (`Drop`) over manual cleanup. Arena edge case: values allocated in an arena (`Arena<T>`/`bumpalo`) do **not** run `Drop` on arena reset — types owning a heap allocation or refcount must be freed/deref'd explicitly first, mirroring the original Zig `deinit()` order.
9. **Cross-platform** - Run `bun run rust:check-all` to compile across all targets (linux/macos/windows × x64/aarch64) when making platform-specific changes. `#[cfg(...)]`-gated code is not type-checked unless the matching target is built.
10. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_<SCOPE>=1` to enable a specific `bun_core::output` scoped logger
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.
13. **If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code.**.
14. After every code comment you write, ask yourself, "Is this information the next Claude would spend multiple tool calls trying to understand?". If the answer isn't clearly yes, the code comment is noise - delete it.
**ONLY** push up changes after running `bun bd test <file>` and ensuring your tests pass.
## Debugging CI Failures
Requires the BuildKite CLI (`brew install buildkite/buildkite/bk`) and a read-scoped token in `BUILDKITE_API_TOKEN`. The repo's `.bk.yaml` sets the org/pipeline so `-p bun` is not needed.
```bash
bun run ci:errors # rendered test-failure output for this branch's latest build, [new] vs [also on main]
bun run ci:errors '#26173' # or a PR number / URL / branch / build number
bun run ci:status # one-screen progress summary (job counts, failed jobs, failing tests so far)
bun run ci:logs # save full logs for every failed job to ./tmp/ci-<build>/
bun run ci:find # just the build number, e.g. bk job log <job-uuid> -b $(bun run ci:find)
bun run ci:watch # watch the current branch's build until it finishes
```
For anything else, use `bk` directly — `bk build list`, `bk api`, `bk artifacts`, etc.
If output from these commands looks wrong (mis-parsed annotation HTML, a field BuildKite changed shape on), fix `scripts/find-build.ts` directly rather than working around it — it's a thin presenter over `bk`.
## Reading PR Feedback
`gh pr view --comments` silently omits review summaries and line-level review comments. For the complete picture — especially when responding to a review — use `bun run pr:comments`, which fetches issue comments, reviews, and line comments in one chronological, labelled listing.
```bash
bun run pr:comments # current branch's PR — resolved threads hidden
bun run pr:comments 28838 # by PR number; '#28838' and full URLs also work
bun run pr:comments --include-resolved # also show threads already marked resolved
# Machine-readable output for jq pipelines — one object per entry.
# Resolved threads and bot noise (robobun CI status, CodeRabbit summaries) are filtered out.
bun run pr:comments --json | jq '.[] | select(.user == "Jarred-Sumner")'
```
+375
View File
@@ -0,0 +1,375 @@
Configuring a development environment for Bun can take 10-30 minutes depending on your internet connection and computer speed. You will need ~10GB of free disk space for the repository and build artifacts.
If you are using Windows, please refer to [this guide](https://bun.com/docs/project/building-windows)
## Using Nix (Alternative)
A Nix flake is provided as an alternative to manual dependency installation:
```bash
nix develop
# or explicitly use the pure shell
# nix develop .#pure
export CMAKE_SYSTEM_PROCESSOR=$(uname -m)
bun bd
```
This provides all dependencies in an isolated, reproducible environment without requiring sudo.
## Install Dependencies (Manual)
Using your system's package manager, install Bun's dependencies:
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rustup-init ruby
```
```bash#Ubuntu/Debian
$ sudo apt install curl wget lsb-release software-properties-common cmake git golang libtool ninja-build pkg-config ruby-full xz-utils
```
```bash#Arch
$ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config python rustup sed unzip ruby
```
```bash#Fedora
$ sudo dnf install clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
```
```bash#openSUSE Tumbleweed
$ sudo zypper install go cmake ninja automake git icu rustup
```
{% /codetabs %}
Bun is written in Rust and requires a specific nightly toolchain (pinned in [`rust-toolchain.toml`](/rust-toolchain.toml)). Install Rust via [rustup](https://rustup.rs) rather than your distro's `rust`/`cargo` packages — the build scripts use rustup to automatically install and update the pinned nightly:
```bash
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Before starting, you will need to already have a release build of Bun installed, as we use our bundler to transpile and minify our code, as well as for code generation scripts.
{% codetabs %}
```bash#Native
$ curl -fsSL https://bun.com/install | bash
```
```bash#npm
$ npm install -g bun
```
```bash#Homebrew
$ brew tap oven-sh/bun
$ brew install bun
```
{% /codetabs %}
### Optional: Install `ccache`
ccache is used to cache compilation artifacts, significantly speeding up builds:
```bash
# For macOS
$ brew install ccache
# For Ubuntu/Debian
$ sudo apt install ccache
# For Arch
$ sudo pacman -S ccache
# For Fedora
$ sudo dnf install ccache
# For openSUSE
$ sudo zypper install ccache
```
Our build scripts will automatically detect and use `ccache` if available. You can check cache statistics with `ccache --show-stats`.
## Install LLVM
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install llvm@21
```
```bash#Ubuntu/Debian
$ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 all
```
```bash#Arch
$ sudo pacman -S llvm clang lld
```
```bash#Fedora
$ sudo dnf install llvm clang lld-devel
```
```bash#openSUSE Tumbleweed
$ sudo zypper install clang21 lld21 llvm21
```
{% /codetabs %}
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).
Make sure Clang/LLVM 21 is in your path:
```bash
$ which clang-21
```
If not, run this to manually add it:
{% codetabs group="os" %}
```bash#macOS (Homebrew)
# use fish_add_path if you're using fish
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
$ export PATH="$(brew --prefix llvm@21)/bin:$PATH"
```
```bash#Arch
# use fish_add_path if you're using fish
$ export PATH="$PATH:/usr/lib/llvm21/bin"
```
{% /codetabs %}
> ⚠️ Ubuntu distributions (<= 20.04) may require installation of the C++ standard library independently. See the [troubleshooting section](#span-file-not-found-on-ubuntu) for more information.
## Building Bun
After cloning the repository, run the following command to build. This may take a while as it will clone submodules and build dependencies.
```bash
$ bun run build
```
The binary will be located at `./build/debug/bun-debug`. It is recommended to add this to your `$PATH`. To verify the build worked, let's print the version number on the development build of Bun.
```bash
$ build/debug/bun-debug --version
x.y.z_debug
```
## VSCode
VSCode is the recommended IDE for working on Bun, as it has been configured. Once opening, you can run `Extensions: Show Recommended Extensions` to install the recommended extensions for Rust and C++. rust-analyzer will pick up the workspace `Cargo.toml` automatically; the pinned toolchain in `rust-toolchain.toml` is used for analysis so diagnostics match the build.
If you use a different editor, point rust-analyzer (or your editor's Rust plugin) at the repo root — the Cargo workspace and `rust-toolchain.toml` are discovered automatically.
We recommend adding `./build/debug` to your `$PATH` so that you can run `bun-debug` in your terminal:
```sh
$ bun-debug
```
## Running debug builds
The `bd` package.json script compiles and runs a debug build of Bun, only printing the output of the build process if it fails.
```sh
$ bun bd <args>
$ bun bd test foo.test.ts
$ bun bd ./foo.ts
```
A full debug build can take a few minutes when Rust or C++ has changed; cargo's incremental compilation makes subsequent Rust-only rebuilds much faster. If your development workflow is "change one line, save, rebuild", you will still spend too much time waiting for the link step. Instead:
- Batch up your changes
- Use `cargo check -p <crate>` (or `bun run rust:check` for the whole workspace) to type-check Rust changes without linking. `bun run watch` runs `cargo check` on every save.
- Ensure rust-analyzer is running for inline diagnostics (if you use VSCode and install the recommended extensions, this should just work)
- Prefer using the debugger ("CodeLLDB" in VSCode) to step through the code.
- Use debug logs. `BUN_DEBUG_<scope>=1` will enable debug logging for the corresponding `declare_scope!(<scope>, ...)` / `scoped_log!(<scope>, ...)` logs. You can also set `BUN_DEBUG_QUIET_LOGS=1` to disable all debug logging that isn't explicitly enabled. To dump debug logs into a file, `BUN_DEBUG=<path-to-file>.log`. Debug logs are aggressively removed in release builds.
- src/js/\*\*.ts changes are pretty much instant to rebuild. Single-crate Rust changes and C++ changes are incremental; only the final link is unavoidable.
## Code generation scripts
Several code generation scripts are used during Bun's build process. These are run automatically when changes are made to certain files.
In particular, these are:
- `./src/codegen/generate-jssink.ts` -- Generates `build/debug/codegen/JSSink.cpp`, `build/debug/codegen/JSSink.h` which implement various classes for interfacing with `ReadableStream`. This is internally how `FileSink`, `ArrayBufferSink`, `"type": "direct"` streams and other code related to streams works.
- `./src/codegen/generate-classes.ts` -- Generates Rust & C++ bindings for JavaScriptCore classes implemented in Rust. In `**/*.classes.ts` files, we define the interfaces for various classes, methods, prototypes, getters/setters etc which the code generator reads to generate boilerplate code implementing the JavaScript objects in C++ and wiring them up to Rust.
- `./src/codegen/cppbind.ts` -- Scans the C++ bindings for functions marked with an export attribute and generates automatic Rust FFI wrappers (`cpp.rs`) for them.
- `./src/codegen/bundle-modules.ts` -- Bundles built-in modules like `node:fs`, `bun:ffi` into files we can include in the final binary. In development, these can be reloaded without rebuilding native code (you still need to run `bun run build`, but it re-reads the transpiled files from disk afterwards). In release builds, these are embedded into the binary.
- `./src/codegen/bundle-functions.ts` -- Bundles globally-accessible functions implemented in JavaScript/TypeScript like `ReadableStream`, `WritableStream`, and a handful more. These are used similarly to the builtin modules, but the output more closely aligns with what WebKit/Safari does for Safari's built-in functions so that we can copy-paste the implementations from WebKit as a starting point.
## Modifying ESM modules
Certain modules like `node:fs`, `node:stream`, `bun:sqlite`, and `ws` are implemented in JavaScript. These live in `src/js/{node,bun,thirdparty}` files and are pre-bundled using Bun.
## Release build
To compile a release build of Bun, run:
```bash
$ bun run build:release
```
The binary will be located at `./build/release/bun` and `./build/release/bun-profile`.
### Download release build from pull requests
To save you time spent building a release build locally, we provide a way to run release builds from pull requests. This is useful for manually testing changes in a release build before they are merged.
To run a release build from a pull request, you can use the `bun-pr` npm package:
```sh
bunx bun-pr <pr-number>
bunx bun-pr <branch-name>
bunx bun-pr "https://github.com/oven-sh/bun/pull/1234566"
bunx bun-pr --asan <pr-number> # Linux x64 only
```
This will download the release build from the pull request and add it to `$PATH` as `bun-${pr-number}`. You can then run the build with `bun-${pr-number}`.
```sh
bun-1234566 --version
```
This works by downloading the release build from the GitHub Actions artifacts on the linked pull request. You may need the `gh` CLI installed to authenticate with GitHub.
### Viewing CI failures from the terminal
Bun's CI runs on BuildKite. Install the [BuildKite CLI](https://github.com/buildkite/cli) (`brew install buildkite/buildkite/bk`) and set `BUILDKITE_API_TOKEN` to a read-scoped [API token](https://buildkite.com/user/api-access-tokens). The repo includes a `.bk.yaml` so `bk` commands default to the `bun` pipeline.
```sh
bun run ci:status # progress summary for the current branch's latest build
bun run ci:errors # rendered test-failure output, tagged [new] vs [also on main]
bun run ci:logs # save full logs for each failed job to ./tmp/ci-<build>/
bun run ci:watch # watch until the build finishes
bun run ci:find # print the build number (compose with raw `bk`)
```
All of these accept a target: `#1234` (PR number), a PR URL, a branch name, or a build number. Without one they use the current git branch.
## AddressSanitizer
[AddressSanitizer](https://en.wikipedia.org/wiki/AddressSanitizer) helps find memory issues, and is enabled by default in debug builds of Bun on Linux and macOS. This covers the Rust code, the C++ bindings, and all dependencies. It makes the build take about 2x longer; if that's stopping you from being productive you can disable it with `bun run build:debug:noasan` (or pass `--asan=off` to `scripts/build.ts`), but generally we recommend batching your changes up between builds.
To build a release build with Address Sanitizer, run:
```bash
$ bun run build:asan
```
In CI, we run our test suite with at least one target that is built with Address Sanitizer.
## Building WebKit locally + Debug mode of JSC
WebKit is not cloned by default (to save time and disk space). To clone and build WebKit locally, run:
```bash
# Clone WebKit into ./vendor/WebKit
$ git clone https://github.com/oven-sh/WebKit vendor/WebKit
# Check out the version pinned in WEBKIT_VERSION in scripts/build/deps/webkit.ts
# (a commit sha or an autobuild-* release tag; this handles both)
$ bun sync-webkit-source
# Build bun with the local JSC build — this automatically configures and builds JSC
$ bun run build:local
```
`bun run build:local` handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC will incrementally rebuild if any WebKit sources changed. `ninja -Cbuild/debug-local` also works after the first build, and will build Bun+JSC.
The build output goes to `./build/debug-local` (instead of `./build/debug`), so you'll need to update a couple of places:
- The first line in [`src/js/builtins.d.ts`](/src/js/builtins.d.ts)
- The `CompilationDatabase` line in [`.clangd` config](/.clangd) should be `CompilationDatabase: build/debug-local`
- In [`.vscode/launch.json`](/.vscode/launch.json), many configurations use `./build/debug/`, change them as you see fit
Note that the WebKit folder, including build artifacts, is 8GB+ in size.
If you are using a JSC debug build and using VScode, make sure to run the `C/C++: Select a Configuration` command to configure intellisense to find the debug headers.
Note that if you make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change `WEBKIT_VERSION` in [`scripts/build/deps/webkit.ts`](/scripts/build/deps/webkit.ts) to point to your commit hash or release tag.
## Troubleshooting
### 'span' file not found on Ubuntu
> ⚠️ Please note that the instructions below are specific to issues occurring on Ubuntu. It is unlikely that the same issues will occur on other Linux distributions.
The Clang compiler typically uses the `libstdc++` C++ standard library by default. `libstdc++` is the default C++ Standard Library implementation provided by the GNU Compiler Collection (GCC). While Clang may link against the `libc++` library, this requires explicitly providing the `-stdlib` flag when running Clang.
Bun relies on C++20 features like `std::span`, which are not available in GCC versions lower than 11. GCC 10 doesn't have all of the C++20 features implemented. As a result, running `make setup` may fail with the following error:
```
fatal error: 'span' file not found
#include <span>
^~~~~~
```
The issue may manifest when initially running `bun setup` as Clang being unable to compile a simple program:
```
The C++ compiler
"/usr/bin/clang++-21"
is not able to compile a simple test program.
```
To fix the error, we need to update the GCC version to 11. To do this, we'll need to check if the latest version is available in the distribution's official repositories or use a third-party repository that provides GCC 11 packages. Here are general steps:
```bash
$ sudo apt update
$ sudo apt install gcc-11 g++-11
# If the above command fails with `Unable to locate package gcc-11` we need
# to add the APT repository
$ sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
# Now run `apt install` again
$ sudo apt install gcc-11 g++-11
```
Now, we need to set GCC 11 as the default compiler:
```bash
$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
$ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100
```
### libarchive
If you see an error on macOS when compiling `libarchive`, run:
```bash
$ brew install pkg-config
```
### macOS `library not found for -lSystem`
If you see this error when compiling, run:
```bash
$ xcode-select --install
```
### Cannot find `libatomic.a`
Bun defaults to linking `libatomic` statically, as not all systems have it. If you are building on a distro that does not have a static libatomic available, you can run the following command to enable dynamic linking:
```bash
$ bun run build -DUSE_STATIC_LIBATOMIC=OFF
```
The built version of Bun may not work on other systems if compiled this way.
## Using bun-debug
- Disable logging: `BUN_DEBUG_QUIET_LOGS=1 bun-debug ...` (to disable all debug logging)
- Enable logging for a specific scope: `BUN_DEBUG_EventLoop=1 bun-debug ...` (to enable `scoped_log!(EventLoop, ...)` output)
- Bun transpiles every file it runs, to see the actual executed source in a debug build find it in `/tmp/bun-debug-src/...path/to/file`, for example the transpiled version of `/home/bun/index.ts` would be in `/tmp/bun-debug-src/home/bun/index.ts`
Generated
+3726
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
1.3.14
+446
View File
@@ -0,0 +1,446 @@
<p align="center">
<a href="https://bun.com"><img src="https://github.com/user-attachments/assets/50282090-adfd-4ddb-9e27-c30753c6b161" alt="Logo" height=170></a>
</p>
<h1 align="center">Bun</h1>
<p align="center">
<a href="https://bun.com/discord" target="_blank"><img height=20 src="https://img.shields.io/discord/876711213126520882" /></a>
<img src="https://img.shields.io/github/stars/oven-sh/bun" alt="stars">
<a href="https://twitter.com/jarredsumner/status/1542824445810642946"><img src="https://img.shields.io/static/v1?label=speed&message=fast&color=success" alt="Bun speed" /></a>
</p>
<div align="center">
<a href="https://bun.com/docs">Documentation</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://bun.com/discord">Discord</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://github.com/oven-sh/bun/issues/new">Issues</a>
<span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
<a href="https://github.com/oven-sh/bun/issues/159">Roadmap</a>
<br />
</div>
### [Read the docs →](https://bun.com/docs)
## What is Bun?
Bun is an all-in-one toolkit for JavaScript and TypeScript apps. It ships as a single executable called `bun`.
At its core is the _Bun runtime_, a fast JavaScript runtime designed as **a drop-in replacement for Node.js**. It's written in Rust and powered by JavaScriptCore under the hood, dramatically reducing startup times and memory usage.
```bash
bun run index.tsx # TS and JSX supported out-of-the-box
```
The `bun` command-line tool also implements a test runner, script runner, and Node.js-compatible package manager. Instead of 1,000 node_modules for development, you only need `bun`. Bun's built-in tools are significantly faster than existing options and usable in existing Node.js projects with little to no changes.
```bash
bun test # run tests
bun run start # run the `start` script in `package.json`
bun install <pkg> # install a package
bunx cowsay 'Hello, world!' # execute a package
```
## Install
Bun supports Linux (x64 & arm64), macOS (x64 & Apple Silicon), and Windows (x64 & arm64).
> **Linux users** — Kernel version 5.6 or higher is strongly recommended, but the minimum is 5.1.
> **x64 users** — if you see "illegal instruction" or similar errors, check our [CPU requirements](https://bun.com/docs/installation#cpu-requirements-and-baseline-builds)
```sh
# with install script (recommended)
curl -fsSL https://bun.com/install | bash
# on windows
powershell -c "irm bun.sh/install.ps1 | iex"
# with npm
npm install -g bun
# with Homebrew
brew tap oven-sh/bun
brew install bun
# with Docker
docker pull oven/bun
docker run --rm --init --ulimit memlock=-1:-1 oven/bun
```
### Upgrade
To upgrade to the latest version of Bun, run:
```sh
bun upgrade
```
Bun automatically releases a canary build on every commit to `main`. To upgrade to the latest canary build, run:
```sh
bun upgrade --canary
```
[View canary build](https://github.com/oven-sh/bun/releases/tag/canary)
## Quick links
- Intro
- [What is Bun?](https://bun.com/docs/index)
- [Installation](https://bun.com/docs/installation)
- [Quickstart](https://bun.com/docs/quickstart)
- [TypeScript](https://bun.com/docs/typescript)
- [TypeScript 6](https://bun.com/docs/typescript-6)
- Templating
- [`bun init`](https://bun.com/docs/runtime/templating/init)
- [`bun create`](https://bun.com/docs/runtime/templating/create)
- Runtime
- [`bun run`](https://bun.com/docs/runtime/index)
- [File types (Loaders)](https://bun.com/docs/runtime/file-types)
- [JSX](https://bun.com/docs/runtime/jsx)
- [Environment variables](https://bun.com/docs/runtime/environment-variables)
- [Bun APIs](https://bun.com/docs/runtime/bun-apis)
- [Web APIs](https://bun.com/docs/runtime/web-apis)
- [Node.js compatibility](https://bun.com/docs/runtime/nodejs-compat)
- [Plugins](https://bun.com/docs/runtime/plugins)
- [Watch mode / Hot Reloading](https://bun.com/docs/runtime/watch-mode)
- [Module resolution](https://bun.com/docs/runtime/module-resolution)
- [Auto-install](https://bun.com/docs/runtime/auto-install)
- [bunfig.toml](https://bun.com/docs/runtime/bunfig)
- [Debugger](https://bun.com/docs/runtime/debugger)
- [REPL](https://bun.com/docs/runtime/repl)
- [$ Shell](https://bun.com/docs/runtime/shell)
- Package manager
- [`bun install`](https://bun.com/docs/pm/cli/install)
- [`bun add`](https://bun.com/docs/pm/cli/add)
- [`bun remove`](https://bun.com/docs/pm/cli/remove)
- [`bun update`](https://bun.com/docs/pm/cli/update)
- [`bun link`](https://bun.com/docs/pm/cli/link)
- [`bun pm`](https://bun.com/docs/pm/cli/pm)
- [`bun outdated`](https://bun.com/docs/pm/cli/outdated)
- [`bun publish`](https://bun.com/docs/pm/cli/publish)
- [`bun patch`](https://bun.com/docs/pm/cli/patch)
- [`bun why`](https://bun.com/docs/pm/cli/why)
- [`bun audit`](https://bun.com/docs/pm/cli/audit)
- [`bun info`](https://bun.com/docs/pm/cli/info)
- [Global cache](https://bun.com/docs/pm/global-cache)
- [Global store](https://bun.com/docs/pm/global-store)
- [Isolated installs](https://bun.com/docs/pm/isolated-installs)
- [Workspaces](https://bun.com/docs/pm/workspaces)
- [Catalogs](https://bun.com/docs/pm/catalogs)
- [Lifecycle scripts](https://bun.com/docs/pm/lifecycle)
- [Filter](https://bun.com/docs/pm/filter)
- [Lockfile](https://bun.com/docs/pm/lockfile)
- [Scopes and registries](https://bun.com/docs/pm/scopes-registries)
- [Overrides and resolutions](https://bun.com/docs/pm/overrides)
- [Security scanner API](https://bun.com/docs/pm/security-scanner-api)
- [`.npmrc`](https://bun.com/docs/pm/npmrc)
- Bundler
- [`Bun.build`](https://bun.com/docs/bundler/index)
- [Loaders](https://bun.com/docs/bundler/loaders)
- [Plugins](https://bun.com/docs/bundler/plugins)
- [Macros](https://bun.com/docs/bundler/macros)
- [vs esbuild](https://bun.com/docs/bundler/esbuild)
- [Single-file executable](https://bun.com/docs/bundler/executables)
- [CSS](https://bun.com/docs/bundler/css)
- [HTML & static sites](https://bun.com/docs/bundler/html-static)
- [Hot Module Replacement (HMR)](https://bun.com/docs/bundler/hot-reloading)
- [Full-stack with HTML imports](https://bun.com/docs/bundler/fullstack)
- [Standalone HTML](https://bun.com/docs/bundler/standalone-html)
- [Bytecode caching](https://bun.com/docs/bundler/bytecode)
- [Minifier](https://bun.com/docs/bundler/minifier)
- Test runner
- [`bun test`](https://bun.com/docs/test/index)
- [Writing tests](https://bun.com/docs/test/writing-tests)
- [Lifecycle hooks](https://bun.com/docs/test/lifecycle)
- [Mocks](https://bun.com/docs/test/mocks)
- [Snapshots](https://bun.com/docs/test/snapshots)
- [Dates and times](https://bun.com/docs/test/dates-times)
- [DOM testing](https://bun.com/docs/test/dom)
- [Code coverage](https://bun.com/docs/test/code-coverage)
- [Configuration](https://bun.com/docs/test/configuration)
- [Discovery](https://bun.com/docs/test/discovery)
- [Reporters](https://bun.com/docs/test/reporters)
- [Runtime Behavior](https://bun.com/docs/test/runtime-behavior)
- Package runner
- [`bunx`](https://bun.com/docs/pm/bunx)
- API
- [HTTP server (`Bun.serve`)](https://bun.com/docs/runtime/http/server)
- [HTTP routing](https://bun.com/docs/runtime/http/routing)
- [HTTP error handling](https://bun.com/docs/runtime/http/error-handling)
- [HTTP metrics](https://bun.com/docs/runtime/http/metrics)
- [WebSockets](https://bun.com/docs/runtime/http/websockets)
- [Workers](https://bun.com/docs/runtime/workers)
- [Binary data](https://bun.com/docs/runtime/binary-data)
- [Streams](https://bun.com/docs/runtime/streams)
- [File I/O (`Bun.file`)](https://bun.com/docs/runtime/file-io)
- [Archive (tar)](https://bun.com/docs/runtime/archive)
- [SQLite (`bun:sqlite`)](https://bun.com/docs/runtime/sqlite)
- [PostgreSQL (`Bun.sql`)](https://bun.com/docs/runtime/sql)
- [Redis (`Bun.redis`)](https://bun.com/docs/runtime/redis)
- [S3 Client (`Bun.s3`)](https://bun.com/docs/runtime/s3)
- [FileSystemRouter](https://bun.com/docs/runtime/file-system-router)
- [TCP sockets](https://bun.com/docs/runtime/networking/tcp)
- [UDP sockets](https://bun.com/docs/runtime/networking/udp)
- [Globals](https://bun.com/docs/runtime/globals)
- [Child processes (spawn)](https://bun.com/docs/runtime/child-process)
- [Cron (`Bun.cron`)](https://bun.com/docs/runtime/cron)
- [WebView](https://bun.com/docs/runtime/webview)
- [Transpiler (`Bun.Transpiler`)](https://bun.com/docs/runtime/transpiler)
- [Hashing](https://bun.com/docs/runtime/hashing)
- [Colors (`Bun.color`)](https://bun.com/docs/runtime/color)
- [Console](https://bun.com/docs/runtime/console)
- [FFI (`bun:ffi`)](https://bun.com/docs/runtime/ffi)
- [C Compiler (`bun:ffi` cc)](https://bun.com/docs/runtime/c-compiler)
- [HTMLRewriter](https://bun.com/docs/runtime/html-rewriter)
- [Cookies (`Bun.Cookie`)](https://bun.com/docs/runtime/cookies)
- [CSRF (`Bun.CSRF`)](https://bun.com/docs/runtime/csrf)
- [Secrets (`Bun.secrets`)](https://bun.com/docs/runtime/secrets)
- [YAML (`Bun.YAML`)](https://bun.com/docs/runtime/yaml)
- [TOML (`Bun.TOML`)](https://bun.com/docs/runtime/toml)
- [JSON5](https://bun.com/docs/runtime/json5)
- [JSONL](https://bun.com/docs/runtime/jsonl)
- [Markdown](https://bun.com/docs/runtime/markdown)
- [Image processing](https://bun.com/docs/runtime/image)
- [Utils](https://bun.com/docs/runtime/utils)
- [Node-API](https://bun.com/docs/runtime/node-api)
- [Glob (`Bun.Glob`)](https://bun.com/docs/runtime/glob)
- [Semver (`Bun.semver`)](https://bun.com/docs/runtime/semver)
- [DNS](https://bun.com/docs/runtime/networking/dns)
- [fetch API extensions](https://bun.com/docs/runtime/networking/fetch)
## Guides
- Deployment
- [Deploy to Vercel](https://bun.com/guides/deployment/vercel)
- [Deploy to Railway](https://bun.com/guides/deployment/railway)
- [Deploy to Render](https://bun.com/guides/deployment/render)
- [Deploy to AWS Lambda](https://bun.com/guides/deployment/aws-lambda)
- [Deploy to DigitalOcean](https://bun.com/guides/deployment/digital-ocean)
- [Deploy to Google Cloud Run](https://bun.com/guides/deployment/google-cloud-run)
- Binary
- [Convert a Blob to a string](https://bun.com/guides/binary/blob-to-string)
- [Convert a Buffer to a blob](https://bun.com/guides/binary/buffer-to-blob)
- [Convert a Blob to a DataView](https://bun.com/guides/binary/blob-to-dataview)
- [Convert a Buffer to a string](https://bun.com/guides/binary/buffer-to-string)
- [Convert a Blob to a ReadableStream](https://bun.com/guides/binary/blob-to-stream)
- [Convert a Blob to a Uint8Array](https://bun.com/guides/binary/blob-to-typedarray)
- [Convert a DataView to a string](https://bun.com/guides/binary/dataview-to-string)
- [Convert a Uint8Array to a Blob](https://bun.com/guides/binary/typedarray-to-blob)
- [Convert a Blob to an ArrayBuffer](https://bun.com/guides/binary/blob-to-arraybuffer)
- [Convert an ArrayBuffer to a Blob](https://bun.com/guides/binary/arraybuffer-to-blob)
- [Convert a Buffer to a Uint8Array](https://bun.com/guides/binary/buffer-to-typedarray)
- [Convert a Uint8Array to a Buffer](https://bun.com/guides/binary/typedarray-to-buffer)
- [Convert a Uint8Array to a string](https://bun.com/guides/binary/typedarray-to-string)
- [Convert a Buffer to an ArrayBuffer](https://bun.com/guides/binary/buffer-to-arraybuffer)
- [Convert an ArrayBuffer to a Buffer](https://bun.com/guides/binary/arraybuffer-to-buffer)
- [Convert an ArrayBuffer to a string](https://bun.com/guides/binary/arraybuffer-to-string)
- [Convert a Uint8Array to a DataView](https://bun.com/guides/binary/typedarray-to-dataview)
- [Convert a Buffer to a ReadableStream](https://bun.com/guides/binary/buffer-to-readablestream)
- [Convert a Uint8Array to an ArrayBuffer](https://bun.com/guides/binary/typedarray-to-arraybuffer)
- [Convert an ArrayBuffer to a Uint8Array](https://bun.com/guides/binary/arraybuffer-to-typedarray)
- [Convert an ArrayBuffer to an array of numbers](https://bun.com/guides/binary/arraybuffer-to-array)
- [Convert a Uint8Array to a ReadableStream](https://bun.com/guides/binary/typedarray-to-readablestream)
- Ecosystem
- [Use React and JSX](https://bun.com/guides/ecosystem/react)
- [Use Gel with Bun](https://bun.com/guides/ecosystem/gel)
- [Use Prisma with Bun](https://bun.com/guides/ecosystem/prisma)
- [Use Prisma Postgres with Bun](https://bun.com/guides/ecosystem/prisma-postgres)
- [Add Sentry to a Bun app](https://bun.com/guides/ecosystem/sentry)
- [Create a Discord bot](https://bun.com/guides/ecosystem/discordjs)
- [Run Bun as a daemon with PM2](https://bun.com/guides/ecosystem/pm2)
- [Use Drizzle ORM with Bun](https://bun.com/guides/ecosystem/drizzle)
- [Use Upstash Redis with Bun](https://bun.com/guides/ecosystem/upstash)
- [Build an app with Nuxt and Bun](https://bun.com/guides/ecosystem/nuxt)
- [Build an app with Qwik and Bun](https://bun.com/guides/ecosystem/qwik)
- [Build an app with Astro and Bun](https://bun.com/guides/ecosystem/astro)
- [Build an app with Remix and Bun](https://bun.com/guides/ecosystem/remix)
- [Build a frontend using Vite and Bun](https://bun.com/guides/ecosystem/vite)
- [Build an app with Next.js and Bun](https://bun.com/guides/ecosystem/nextjs)
- [Run Bun as a daemon with systemd](https://bun.com/guides/ecosystem/systemd)
- [Build an HTTP server using Hono and Bun](https://bun.com/guides/ecosystem/hono)
- [Build an app with SvelteKit and Bun](https://bun.com/guides/ecosystem/sveltekit)
- [Build an app with SolidStart and Bun](https://bun.com/guides/ecosystem/solidstart)
- [Build an app with TanStack Start and Bun](https://bun.com/guides/ecosystem/tanstack-start)
- [Build an HTTP server using Elysia and Bun](https://bun.com/guides/ecosystem/elysia)
- [Build an HTTP server using StricJS and Bun](https://bun.com/guides/ecosystem/stric)
- [Containerize a Bun application with Docker](https://bun.com/guides/ecosystem/docker)
- [Build an HTTP server using Express and Bun](https://bun.com/guides/ecosystem/express)
- [Use Neon Postgres through Drizzle ORM](https://bun.com/guides/ecosystem/neon-drizzle)
- [Server-side render (SSR) a React component](https://bun.com/guides/ecosystem/ssr-react)
- [Read and write data to MongoDB using Mongoose and Bun](https://bun.com/guides/ecosystem/mongoose)
- [Use Neon's Serverless Postgres with Bun](https://bun.com/guides/ecosystem/neon-serverless-postgres)
- HTMLRewriter
- [Extract links from a webpage using HTMLRewriter](https://bun.com/guides/html-rewriter/extract-links)
- [Extract social share images and Open Graph tags](https://bun.com/guides/html-rewriter/extract-social-meta)
- HTTP
- [Hot reload an HTTP server](https://bun.com/guides/http/hot)
- [Common HTTP server usage](https://bun.com/guides/http/server)
- [Write a simple HTTP server](https://bun.com/guides/http/simple)
- [Configure TLS on an HTTP server](https://bun.com/guides/http/tls)
- [Send an HTTP request using fetch](https://bun.com/guides/http/fetch)
- [Proxy HTTP requests using fetch()](https://bun.com/guides/http/proxy)
- [Start a cluster of HTTP servers](https://bun.com/guides/http/cluster)
- [Stream a file as an HTTP Response](https://bun.com/guides/http/stream-file)
- [fetch with unix domain sockets in Bun](https://bun.com/guides/http/fetch-unix)
- [Upload files via HTTP using FormData](https://bun.com/guides/http/file-uploads)
- [Streaming HTTP Server with Async Iterators](https://bun.com/guides/http/stream-iterator)
- [Streaming HTTP Server with Node.js Streams](https://bun.com/guides/http/stream-node-streams-in-bun)
- [Server-Sent Events (SSE) with Bun](https://bun.com/guides/http/sse)
- Install
- [Add a dependency](https://bun.com/guides/install/add)
- [Add a Git dependency](https://bun.com/guides/install/add-git)
- [Add a peer dependency](https://bun.com/guides/install/add-peer)
- [Add a trusted dependency](https://bun.com/guides/install/trusted)
- [Add a development dependency](https://bun.com/guides/install/add-dev)
- [Add a tarball dependency](https://bun.com/guides/install/add-tarball)
- [Add an optional dependency](https://bun.com/guides/install/add-optional)
- [Generate a yarn-compatible lockfile](https://bun.com/guides/install/yarnlock)
- [Configuring a monorepo using workspaces](https://bun.com/guides/install/workspaces)
- [Install a package under a different name](https://bun.com/guides/install/npm-alias)
- [Install dependencies with Bun in GitHub Actions](https://bun.com/guides/install/cicd)
- [Using bun install with Artifactory](https://bun.com/guides/install/jfrog-artifactory)
- [Configure git to diff Bun's lockb lockfile](https://bun.com/guides/install/git-diff-bun-lockfile)
- [Override the default npm registry for bun install](https://bun.com/guides/install/custom-registry)
- [Using bun install with an Azure Artifacts npm registry](https://bun.com/guides/install/azure-artifacts)
- [Migrate from npm install to bun install](https://bun.com/guides/install/from-npm-install-to-bun-install)
- [Configure a private registry for an organization scope with bun install](https://bun.com/guides/install/registry-scope)
- Process
- [Read from stdin](https://bun.com/guides/process/stdin)
- [Listen for CTRL+C](https://bun.com/guides/process/ctrl-c)
- [Spawn a child process](https://bun.com/guides/process/spawn)
- [Listen to OS signals](https://bun.com/guides/process/os-signals)
- [Parse command-line arguments](https://bun.com/guides/process/argv)
- [Read stderr from a child process](https://bun.com/guides/process/spawn-stderr)
- [Read stdout from a child process](https://bun.com/guides/process/spawn-stdout)
- [Get the process uptime in nanoseconds](https://bun.com/guides/process/nanoseconds)
- [Spawn a child process and communicate using IPC](https://bun.com/guides/process/ipc)
- Read file
- [Read a JSON file](https://bun.com/guides/read-file/json)
- [Check if a file exists](https://bun.com/guides/read-file/exists)
- [Read a file as a string](https://bun.com/guides/read-file/string)
- [Read a file to a Buffer](https://bun.com/guides/read-file/buffer)
- [Get the MIME type of a file](https://bun.com/guides/read-file/mime)
- [Watch a directory for changes](https://bun.com/guides/read-file/watch)
- [Read a file as a ReadableStream](https://bun.com/guides/read-file/stream)
- [Read a file to a Uint8Array](https://bun.com/guides/read-file/uint8array)
- [Read a file to an ArrayBuffer](https://bun.com/guides/read-file/arraybuffer)
- Runtime
- [Delete files](https://bun.com/guides/runtime/delete-file)
- [Run a Shell Command](https://bun.com/guides/runtime/shell)
- [Import a JSON file](https://bun.com/guides/runtime/import-json)
- [Import a TOML file](https://bun.com/guides/runtime/import-toml)
- [Import a YAML file](https://bun.com/guides/runtime/import-yaml)
- [Import a JSON5 file](https://bun.com/guides/runtime/import-json5)
- [Set a time zone in Bun](https://bun.com/guides/runtime/timezone)
- [Set environment variables](https://bun.com/guides/runtime/set-env)
- [Re-map import paths](https://bun.com/guides/runtime/tsconfig-paths)
- [Delete directories](https://bun.com/guides/runtime/delete-directory)
- [Read environment variables](https://bun.com/guides/runtime/read-env)
- [Import a HTML file as text](https://bun.com/guides/runtime/import-html)
- [Install and run Bun in GitHub Actions](https://bun.com/guides/runtime/cicd)
- [Debugging Bun with the web debugger](https://bun.com/guides/runtime/web-debugger)
- [Install TypeScript declarations for Bun](https://bun.com/guides/runtime/typescript)
- [Debugging Bun with the VS Code extension](https://bun.com/guides/runtime/vscode-debugger)
- [Inspect memory usage using V8 heap snapshots](https://bun.com/guides/runtime/heap-snapshot)
- [Define and replace static globals & constants](https://bun.com/guides/runtime/define-constant)
- [Build-time constants with --define](https://bun.com/guides/runtime/build-time-constants)
- [Codesign a single-file JavaScript executable on macOS](https://bun.com/guides/runtime/codesign-macos-executable)
- Streams
- [Convert a ReadableStream to JSON](https://bun.com/guides/streams/to-json)
- [Convert a ReadableStream to a Blob](https://bun.com/guides/streams/to-blob)
- [Convert a ReadableStream to a Buffer](https://bun.com/guides/streams/to-buffer)
- [Convert a ReadableStream to a string](https://bun.com/guides/streams/to-string)
- [Convert a ReadableStream to a Uint8Array](https://bun.com/guides/streams/to-typedarray)
- [Convert a ReadableStream to an array of chunks](https://bun.com/guides/streams/to-array)
- [Convert a Node.js Readable to JSON](https://bun.com/guides/streams/node-readable-to-json)
- [Convert a ReadableStream to an ArrayBuffer](https://bun.com/guides/streams/to-arraybuffer)
- [Convert a Node.js Readable to a Blob](https://bun.com/guides/streams/node-readable-to-blob)
- [Convert a Node.js Readable to a string](https://bun.com/guides/streams/node-readable-to-string)
- [Convert a Node.js Readable to an Uint8Array](https://bun.com/guides/streams/node-readable-to-uint8array)
- [Convert a Node.js Readable to an ArrayBuffer](https://bun.com/guides/streams/node-readable-to-arraybuffer)
- Test
- [Spy on methods in `bun test`](https://bun.com/guides/test/spy-on)
- [Bail early with the Bun test runner](https://bun.com/guides/test/bail)
- [Mock functions in `bun test`](https://bun.com/guides/test/mock-functions)
- [Run tests in watch mode with Bun](https://bun.com/guides/test/watch-mode)
- [Use snapshot testing in `bun test`](https://bun.com/guides/test/snapshot)
- [Skip tests with the Bun test runner](https://bun.com/guides/test/skip-tests)
- [Using Testing Library with Bun](https://bun.com/guides/test/testing-library)
- [Update snapshots in `bun test`](https://bun.com/guides/test/update-snapshots)
- [Run your tests with the Bun test runner](https://bun.com/guides/test/run-tests)
- [Set the system time in Bun's test runner](https://bun.com/guides/test/mock-clock)
- [Set a per-test timeout with the Bun test runner](https://bun.com/guides/test/timeout)
- [Migrate from Jest to Bun's test runner](https://bun.com/guides/test/migrate-from-jest)
- [Write browser DOM tests with Bun and happy-dom](https://bun.com/guides/test/happy-dom)
- [Mark a test as a "todo" with the Bun test runner](https://bun.com/guides/test/todo-tests)
- [Re-run tests multiple times with the Bun test runner](https://bun.com/guides/test/rerun-each)
- [Generate code coverage reports with the Bun test runner](https://bun.com/guides/test/coverage)
- [import, require, and test Svelte components with bun test](https://bun.com/guides/test/svelte-test)
- [Set a code coverage threshold with the Bun test runner](https://bun.com/guides/test/coverage-threshold)
- [Selectively run tests concurrently with glob patterns](https://bun.com/guides/test/concurrent-test-glob)
- Util
- [Generate a UUID](https://bun.com/guides/util/javascript-uuid)
- [Hash a password](https://bun.com/guides/util/hash-a-password)
- [Escape an HTML string](https://bun.com/guides/util/escape-html)
- [Get the current Bun version](https://bun.com/guides/util/version)
- [Upgrade Bun to the latest version](https://bun.com/guides/util/upgrade)
- [Encode and decode base64 strings](https://bun.com/guides/util/base64)
- [Compress and decompress data with gzip](https://bun.com/guides/util/gzip)
- [Sleep for a fixed number of milliseconds](https://bun.com/guides/util/sleep)
- [Detect when code is executed with Bun](https://bun.com/guides/util/detect-bun)
- [Check if two objects are deeply equal](https://bun.com/guides/util/deep-equals)
- [Compress and decompress data with DEFLATE](https://bun.com/guides/util/deflate)
- [Get the absolute path to the current entrypoint](https://bun.com/guides/util/main)
- [Get the directory of the current file](https://bun.com/guides/util/import-meta-dir)
- [Check if the current file is the entrypoint](https://bun.com/guides/util/entrypoint)
- [Get the file name of the current file](https://bun.com/guides/util/import-meta-file)
- [Convert a file URL to an absolute path](https://bun.com/guides/util/file-url-to-path)
- [Convert an absolute path to a file URL](https://bun.com/guides/util/path-to-file-url)
- [Get the absolute path of the current file](https://bun.com/guides/util/import-meta-path)
- [Get the path to an executable bin file](https://bun.com/guides/util/which-path-to-executable-bin)
- WebSocket
- [Build a publish-subscribe WebSocket server](https://bun.com/guides/websocket/pubsub)
- [Build a simple WebSocket server](https://bun.com/guides/websocket/simple)
- [Enable compression for WebSocket messages](https://bun.com/guides/websocket/compression)
- [Set per-socket contextual data on a WebSocket](https://bun.com/guides/websocket/context)
- Write file
- [Delete a file](https://bun.com/guides/write-file/unlink)
- [Write to stdout](https://bun.com/guides/write-file/stdout)
- [Write a file to stdout](https://bun.com/guides/write-file/cat)
- [Write a Blob to a file](https://bun.com/guides/write-file/blob)
- [Write a string to a file](https://bun.com/guides/write-file/basic)
- [Append content to a file](https://bun.com/guides/write-file/append)
- [Write a file incrementally](https://bun.com/guides/write-file/filesink)
- [Write a Response to a file](https://bun.com/guides/write-file/response)
- [Copy a file to another location](https://bun.com/guides/write-file/file-cp)
- [Write a ReadableStream to a file](https://bun.com/guides/write-file/stream)
## Contributing
Refer to the [Project > Contributing](https://bun.com/docs/project/contributing) guide to start contributing to Bun.
## License
Refer to the [Project > License](https://bun.com/docs/project/license) page for information about Bun's licensing.
+72
View File
@@ -0,0 +1,72 @@
# Landing PRs: What Bun Reviewers Catch
Distilled from the review history of ~2,500 merged PRs where review feedback led to fix commits; everything here has blocked merges. Before writing code that makes a non-obvious choice, pre-emptively ask "why this and not the alternative?" — if you can't answer, research until you can.
Several situational sections live in `.claude/docs/landing-prs.md` — read the relevant one before the work it covers: **Node/Web compat** (touching `node:*` modules, Web APIs, or `src/runtime/node/`), **API design** (adding or changing user-facing API surface), **Performance** (optimizing, touching hot paths, or making perf claims), **Cross-platform** (platform-gated code, FFI/ABI, or platform-sensitive tests), **Dependencies & vendoring** (bumping deps or touching `vendor/`), **Docs, types, and comments** (docs, `.d.ts`, JSDoc), and **PR process** (opening or responding to a PR).
## Tests reviewers reject
- **Await conditions, wire failures to reject.** Await the actual observable condition (promise resolved from the event handler, ack handshake, readiness line from child stdout). Wire EVERY failure event (`error`, `close`, `abort`, process exit) to reject the awaited promise — never throw inside event callbacks. Don't raise per-test timeouts to make a slow test pass; shrink the workload. Buffer raw socket/stdout chunks to the protocol's framing before asserting. For "X does not happen", poll a bounded window rather than sleep-then-check. A literal `sleep`/`setTimeout` of 50ms or more outside a bounded poll loop needs a comment naming why no observable signal exists.
- **Prove the test fails for the RIGHT reason.** Beyond `USE_SYSTEM_BUN=1`: trace the fixture through every earlier guard/threshold using real constants from source; check that OS fast paths (clonefile, sendfile), error-swallowing APIs (`existsSync`), and build-time fast paths can't satisfy the test without running your code; confirm env knobs the test sets are actually read by `src/`; assert that setup created the precondition. Hang-guard tests assert the process exited on its own (`signalCode === null`). Confirm deleting each load-bearing clause of your fix breaks at least one test — a test that passes both ways is worse than no test.
- **Every assertion must be able to fail, and assert the strongest invariant.** Hunt vacuous patterns: un-awaited `.rejects`/`.resolves`, expects inside catch blocks or callbacks that may never fire, async arrows passed to `toThrow()`, loops over possibly-empty collections, conditional assertions. Assert exact values on normalized output: specific error class/code/message (never bare `toThrow()`), `toBe` over `toContain`, actual bytes not lengths. Read snapshot contents before committing — a snapshot captured from buggy code certifies the bug. Never combine `--update` with a name filter.
- **Cover the variant matrix, not just the repro.** Every sibling entry point receiving the same fix (CLI flag AND JS API), both states of every flag, exact limit boundaries (at the limit succeeds, one past fails), every overload, ESM and CJS, alternate modes (`--compile`, `--bytecode`, watch), error paths, the negative contract (sibling files unmodified, callbacks NOT fired), adversarial inputs for anything parsing user data. Add new variants ALONGSIDE existing tests — never mutate an existing test's input to the new case.
- **Subprocess tests: drain pipes concurrently.** `Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited])` — an unread pipe fills the ~64KB OS buffer and deadlocks the child. assert a combined `{ stdout, stderr, exitCode }` object. In multi-stage tests, assert each stage's output in order.
- **Tests must be hermetic and leave nothing behind.** Never contact external network hosts or live registries — reproduce the condition with a local in-process server or the container harness. Tests that need a system binary (headless chrome, docker, node-gyp) `skipIf` when the dependency is unavailable; forked helper servers hard-deadline their `beforeAll` startup. Isolate process-global flags by running each case in a fresh subprocess. Release every resource via `using`/`await using` or try/finally registered BEFORE the assertions (cleanup after expectations leaks on the first failure and poisons later tests on persistent CI runners); no manual close alongside `using`; `server.close()` does not terminate live connections; restore mutated globals in finally.
- **Every behavioral change ships an automated test in the same PR.** "Verified manually", unnamed "existing tests", and benchmarks don't count, even for one-liners. Crash fixes need the crashing input as a spawned fixture; UAF/leak fixes need an ASan repro on the unfixed build or a leak regression test (`Bun.gc(true)` + `heapStats` objectTypeCounts; RSS thresholds branch on `isASAN`/`isDebug` with the bound well below the unfixed leak, and measure after a warmup window when signal is tight). Include every reproduction from the linked issue. Never add production code solely to make a test writable — use `bun:internal-for-testing` or externally observable behavior.
- **Never silently weaken, skip, or delete an existing test or safety net.** Every deletion needs a stated reason or replacement; every skip/todo needs a comment with the observed failure. When de-flaking, keep asserting the property the original assertion protected — branch per-platform rather than dropping precision. Never disable sanitizers or weaken CI verification to get green. When changing output/defaults/messages, grep the suite for assertions on the old behavior and update them in the same PR. Un-skip `.todo` tests your fix makes pass. Never edit a test to route around a runtime bug it exposed.
- **Copy harness conventions exactly.** Spread `bunEnv` when modifying it (`{...bunEnv, KEY: undefined}`). `Buffer.alloc(n, fill).toString()` instead of `"x".repeat(n)` (slow in debug JSC). `test.each` for matrices; `test.concurrent` for independent subprocess suites; `jest.fn()` over boolean flags; async spawns over `spawnSync`. Use harness skip mechanisms with a reason — a bare top-level `return` reports PASSED. Check `harness.ts` for platform helpers before writing your own. Keep tests fast (~1s per test; debug+ASAN runs 10-100x slower); a new file over ~10s on the default lane gets scrutinized for `test.concurrent` and staying serial needs a stated reason. A correct but slow test still gets changes-requested.
## Native code: memory safety (the most-blocked category)
- **Pair every acquisition with its release at the acquisition site.** Arm a Drop/RAII guard before any fallible call; disarm only after ownership provably transfers. New early returns or fallible calls → re-audit everything acquired above them. A struct gaining an owning field wires its release into the owner's Drop and ALL lifecycle exits (VM deinit, worker termination, process exit, transfer, close) in the same commit. Prefer validate-first-allocate-last. Free each element of a collection, not just the container. Check the SUCCESS path for leaks too.
- **Every allocation has exactly one named owner, released exactly once, with the allocator that allocated it.** Be able to answer "who frees this, when, on which paths, with which allocator" in one sentence — comment it when non-local, especially across FFI. Arenas, worker-local mimalloc heaps, and per-subsystem allocators are not interchangeable. Neutralize the source handle when ownership transfers; gate deallocation on an ownership indicator, never a content heuristic; never blanket-free a field that sometimes borrows.
- **Treat all size/index/length arithmetic on external data as adversarial.** Bounds-check the TOTAL bytes of a record before reading fields; re-establish bounds after every re-slice; validate header-derived counts before using them as loop bounds. Size output buffers for worst-case OUTPUT expansion, not input size. Widen before multiplying untrusted quantities; clamp kernel/peer-reported lengths to capacity. Debug assertions compile out — validation of untrusted input must survive release builds. Zero-init out-params and every slot a GC visitor or destructor can walk.
- **Exception checks after every call that can enter JS.** Every call that can throw or run user code (toString/toNumber, getIfPropertyExists, getIndex, coercions, callbacks) needs RETURN_IF_EXCEPTION under a ThrowScope (C++) or JSError propagation (Rust) before its result is used. Never call non-throwing accessors (asNumber, jsCast, getDirect) on user values without validating type first. Never clearException(). Throwing tail calls go through RELEASE_AND_RETURN; no check macros inside lambdas. Verify with `BUN_JSC_validateExceptionChecks=1` instead of adding suppression-list entries; for calls that provably cannot throw, use scope.assertNoException() with a comment.
- **Never let a pointer or slice outlive the memory it points into** (unsafe Rust, C++, FFI). Rejected shapes: slices of stack buffers; pointers into growable containers held across any call that can append; network/parser callback buffers stored without cloning (they are reused); `.data()` of a dead temporary; slices of small-string-optimized values; buffers handed to layers that store them past the call. If background threads reference stack state, every exit path must join them first.
- **Root or copy every JSValue held beyond the current call.** WriteBarrier members declared in `.classes.ts` and visited in visitChildrenImpl (same change); Strong/protect only for justified self-keepalive; MarkedArgumentBuffer for values accumulated across slow calls — never raw JSValues in malloc'd memory or std containers. A Strong ref does not prevent ArrayBuffer detach; pin() is not a GC root; hasPendingActivity uses a counter; zero-copy toSlice-style helpers return borrowed views — consume synchronously or clone. Prove GC-safety with a stress test (thousands of iterations + `Bun.gc(true)`). Don't add Strong refs or ensureStillAlive you can't justify — and don't silently delete existing ones.
- **Anything that can run user JS can synchronously free your state** — toString/valueOf, getters, Proxy traps, event emits, close(). Do all coercions first while holding no raw pointers; read mutable state (byteLength, typed-array vectors) once, after all observable side effects; re-validate liveness guards after every callback; copy-and-null stored one-shot callbacks before calling them; bracket entry points that can reach synchronous teardown with ref()/defer deref(); register state and listeners BEFORE the call that can trigger them; null member fields before calling close on a local copy.
- **Know the thread affinity of every line you touch.** JS-heap operations run only on the JS thread — marshal raw data and enqueue a task. Atomics for every shared counter (even metrics); a mutex only counts if EVERY accessor takes it; benign same-value races are still UB. Copy or `toThreadSafe` strings before another thread touches them; default to seq_cst and comment any weakened ordering. Cross-thread lifetime needs refcounts — a "finalized" boolean cannot prevent UAF. Never invoke callbacks while holding a non-recursive lock. Never back per-VM state with globals or thread-locals — workers share them.
- **Reference counts provably balanced on every terminal path** — success, error, cancellation, finalize. Map each ref to a named owner; take a ref only after a fallible enqueue succeeds; never use saturating arithmetic on counts; never add a ref just to silence ASAN — find the actual imbalance. The released ref may be the last one mid-callback; dropping the final reference while holding the object's own lock is UB.
## Correctness: the bug class, not the bug
- **Fix the whole class in the same PR** (same-class sites are ONE concern, not scope creep). Grep for every sibling site sharing the pattern: parallel switch arms, sync/async twins, fast/slow paths, POSIX/Windows branches, SSL/non-SSL variants, copy-pasted blocks, every caller of a changed helper. Prefer moving the guard into the shared helper. If a site is intentionally excluded, say so in the PR.
- **Enumerate the input space deliberately.** Empty input, lone `.`, delimiter-only, inputs that become empty after processing, CRLF, IPv6 literals, integer max, every accepted spelling of an option. Treat "empty", "zero", and "unset" as three distinct states — gate on presence, not truthiness. Use real parsers, never prefix-stripping or regex heuristics over user-controlled input. When tightening validation, enumerate every legitimate input class and prove each still passes.
- **Every line you add must be demonstrably live.** Trace any flag you gate on through every context that sets it; trace new state to an actual consumer — parsed-but-never-read is a red flag; check for an unconditional overwrite after a new conditional; manually exercise the failure path of any checker whose purpose is to fail. Delete defensive code only when you can show the condition cannot occur; when a new assertion trips, fix the violating call sites.
- **Verify semantics empirically, never from names or intuition.** Read the implementation of every helper, macro, and sentinel you rely on. For protocols, derive behavior from the spec and cite the upstream source line for every magic number. For ported code, the reference implementation (esbuild, Node) is the spec — diff control flow against it before "fixing" apparent bugs. For codecs, a self-round-trip proves nothing — validate against external known-answer vectors. Settle behavioral disputes by running the scenario.
- **Validate representation at every boundary.** Numbers from JS or the wire: handle NaN, ±Infinity, negatives, out-of-range before casting; compare in the wide type, cast last; coercing conversions (`toInt32(global)`, never `asInt32` on user values); 64-bit types for byte lengths. Strings: never run byte-level checks without branching on encoding (JSC 8-bit strings are Latin1, not UTF-8); never compare byte counts to code-unit counts across a conversion; WTF-8 helpers for lone surrogates (real Windows paths contain them); test with non-ASCII beyond emoji.
- **Treat every refactor as guilty until proven behavior-preserving.** Diff the old path's complete behavior — out-parameter writes, error-path side effects, condition polarity, defaults, unconditional operations becoming conditional. Test status flags with bitwise-AND, not equality. Audit every hit of bulk find-and-replace. Before deleting odd-looking code, git-blame why it was written — it is usually load-bearing. If neighboring code does something differently than you're about to, find out why.
- **One source of truth; update every consumer atomically.** When a fact lives in two places (mirrored tables, encode/decode pairs), derive one from the other. New enum variant or struct field → audit every switch on the discriminant, every constructor/clone site, every hasher/serialization pair. Signature changes and renames → grep the whole repo including cfg-gated code and generated-binding inputs; stale call sites compile fine and silently miss the new behavior.
- **Cache keys cover every input that shapes the output** (target OS/arch, config, registry of origin; for pooled connections, establishment-time TLS mode/SNI/credentials). A false hit is far worse than a false miss. Completion markers are written only after the last mutation. Any change to cached/serialized output bumps the format version constant. File-keyed caches include mtime/size or content hash.
- **It's never a conservative stack scanner bug** - JSC runs sanitizeStack at every interpreter entrance site. Never blame the conservative stack scanner. It's always a bug in YOUR code.
## Error handling
- **Never swallow a failure or signal success on one.** `catch {}`, catch-return-default, discarded I/O results, and unchecked syscall returns convert diagnosable errors into silent corruption. Exit nonzero after printing an error; never let a trailing cleanup command be the last statement in a pipeline. Operations the user explicitly requested fail the whole command on any failure — never warn-and-exit-zero; best-effort auxiliary steps (hints, optional deps) degrade quietly instead of aborting.
- **Error messages are reviewed word-for-word as code.** Name what failed and why: the specific resource (quoted path/URL), the violated constraint with the rejected value echoed back, the underlying cause (errno), a concrete remedy. User-facing API names, not internal field names. Repo voice: no "Please", recovery hints on a `note:` line, quoting via `bun.fmt.quote`, stderr not stdout. Don't wrap foreign error messages in `new Error()`. Never genericize a rich existing message during refactors.
- **User-reachable failures are recoverable errors, never panics.** Anything reachable from user input, syscalls, network bytes, file contents, CLI args, or env vars surfaces as a catchable error — a reachable `unreachable` is release-build UB and a panic on user input is a DoS. Route allocation failure through `bun_core::handle_oom`. Reserve loud panics for true internal invariants — where they're then required. New invariant checks on existing code default to debug-only unless continuing would corrupt memory.
- **Every error/abort/timeout path actively completes the operation.** Settle every pending promise slot (an unsettled promise pins objects and hangs callers forever). Invoke the done/completion callback on every path; send protocol cancels; clear timers; mirror the success path's release ordering. Set "in-progress" flags only after the fallible step succeeds; do all fallible work before irreversible buffer writes. Invoke user callbacks through `event_loop.runCallback` so microtasks drain and one throwing callback doesn't skip the rest.
- **Propagate the actual error through typed channels.** Widen the return type and `try` rather than catching locally with a default; typed errors, never magic sentinels. Route user-facing JS errors through the centralized ErrorCode machinery (`src/jsc/bindings/ErrorCode.ts`, `$ERR_*`) — never inline `new Error` with a hand-assigned `.code`. Pass the original error object through rather than stringifying early. Map only the specific expected errno (ENOENT) to the benign path; everything else stays loud. Place validation at the layer whose caller implements the recovery you intend.
## Code style & idioms reviewers enforce
- **In runtime native code, grep for the in-tree helper before hand-writing anything.** File I/O, paths, strings, hashing, formatting, validation, spawning, timers — use the most specific existing helper: `bun.sys`/FD syscall wrappers (never raw std fs/posix), bun_core strings/fmt/Output, shared ref-count helpers, WTF:: containers over std:: in C++ bindings. Being the only file touching a raw primitive is a red flag. New helpers go on the type that owns the concept; extend a maintained in-tree equivalent rather than forking. Verify the helper's actual semantics fit.
- **Match the exact file's local conventions:** the namespace aliases the neighboring lines actually use, import placement, canonical parameter names, the same error-path sequence as sibling exit sites, formatter output. Name things truthfully: booleans state the invariant positively; no numeric-suffix variants (create2); magic numbers become named constants derived from what they describe (`".tgz".len`, not 4).
- **Built-in JS modules (`src/js/`) are hot-path code in a hostile environment.** Tamper-resistance: $-prefixed intrinsics and primordial-safe calls (`$isJSArray`, `map.$get`, `$call`), globals captured at module load, `require`with the`node:` prefix, never route internal logic through user-overridable machinery (`Array.isArray`, never `instanceof Array`). Performance: heavy requires stay inside the branch that needs them (`x ??= require(...)`); named functions over inline closures; `Promise.$resolve`/withResolvers over `new Promise`executors;`createFIFO`over`Array#shift`queues; declare every instance field with a default in the class body; cache repeated property reads in locals;`process.platform === 'win32'` for platform checks (tree-shaken per platform).
- **Delete dead code in the same PR that makes it dead** (required scope — name the deletions in the description): superseded implementations, helpers whose last caller you rewired, fields nothing reads, parameters discarded in the body, guards a new validator makes redundant. Public items escape dead-code lints — grep for callers manually. Always delete an unmaintained dead features, never rename them. Do not add tests to check dead code stays dead. Do not keep empty files around. Do not stub empty files. Delete empty files. Delete dead code.
- **Simplest honest shape; deduplicate within your own diff.** Early returns over else-after-return; `if let`/`?` over null-check-then-unwrap; exhaustive match over equality chains. Don't condense working explicit code into clever one-liners, and don't ride file-wide standardization on a focused bugfix. The second time a multi-line block appears in your diff, extract a named helper and use it at EVERY parallel site. If your fix makes two functions byte-identical, delete one.
- **Only comment what the code cannot say.** One line. Never restate what the code does. Never narrate the change. Prefer links to GitHub issues.
- **Use the compiler to enforce safety.** Code comments do not enforce safety. SAFETY comments are required above use of `unsafe` and must be accurate.
## Architecture & layering
- **Fix bugs at the layer that owns the violated invariant, never where the symptom appears.** If a shared helper produces wrong output, fix the helper, not one call site; escaping/serialization lives in the output layer that sees every producer; a downstream null-check or isDead() probe on a possibly-freed object is papering over the defect. Prove the mechanism, don't correlate — "the crash goes away" is not a root cause, and a fix you can't explain hides an adjacent unhandled case. Before changing anything shared, enumerate every consumer; prefer scoping the change to your one caller via an explicit flag. Never change a Bun-native default to fix Node compatibility — that belongs in the node: compat layer.
- **One implementation, in the right place.** Never copy a helper or constant table between modules or between the read and write sides of a format — share or derive it. Parameterize the existing path rather than cloning a parallel branch; when your change supersedes a mechanism, delete the old path in the same PR. Place new code in the module that owns the feature, never god files (no new fields on ZigGlobalObject, no bindings in monolithic bindings.cpp). Substantial subsystems get their own globally-unique filename. No re-export shim files.
- **Store state on the object whose lifetime matches it.** Per-VM state goes on VirtualMachine/RareData, never process globals or thread-locals (workers share globals; pool threads are reused). Per-connection facts live on the socket, never a shared context. Reset per-operation state at the start of each use of a reusable object; update every lifecycle method (reset/init/drop/clone) when adding mutable state; prune bookkeeping keyed by recyclable identifiers (PIDs, fds) on every path that learns of death. Don't add fields mirroring recoverable information — compute from the source of truth at use.
- **Use the simplest mechanism the invariants allow.** No vtables when the implementation set is closed at compile time; no bit-packing or lock-free tricks when a stated invariant makes plain code correct; no speculative edge-case handling nobody filed an issue for. When a heuristic keeps sprouting counterexamples in review, redesign structurally instead of adding tie-breakers. If a maintainer doesn't understand your logic after one explanation, simplify rather than justify. New cross-cutting abstractions need maintainer agreement before appearing inside a feature PR.
## Security
- **Validate untrusted input BEFORE any processing, allocation, or side effect.** Verify integrity hashes before extraction; check bounds before base64/decompression allocates; enforce resource limits on bytes actually received, never only a client-declared header; clamp user-controllable limits including Infinity and negatives. Attack your own guard with degenerate inputs — empty values that short-circuit a check are bypasses (`.every()` is vacuously true on empty); tokens split across read boundaries must still validate. Any string from an archive or lockfile that becomes a path rejects empty, `.`, `..`, NUL, absolute paths, and both separators; lexical containment is defeated by symlinks — re-verify after realpath, prefer O_NOFOLLOW-style atomic flags over check-then-act. Reject embedded NULs in strings passed to C APIs. Never hand-roll security-sensitive parsing — use the hardened in-tree library and replicate the FULL verification path existing clients use.
- **Security checks fail closed and cover every path to the protected effect.** If a check's prerequisite is missing or its setup fails (null TLS handle, OOM), fail the operation — never fall back to a laxer default. Never carry credentials across an https→http downgrade. Key pools/caches on every parameter that influenced establishment; security flags on pooled sessions are monotonic — once tainted, always tainted. When adding a security gate, enumerate every route to the effect (h2/h3, streaming vs buffered, upgrade paths) and enforce through one shared predicate. Never remove a flag you don't understand in a TLS/crypto path.
- **Assume userland is hostile on security-relevant paths.** Prototype-pollution-safe own-property lookups for flags like rejectUnauthorized; merged option objects built with `{ __proto__: null, ... }`; security options read as strict booleans (never `!!`-coerced); never call user-overridable JS methods from native code or builtins — use engine intrinsics.
+11
View File
@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
## Reporting a Vulnerability
Report any discovered vulnerabilities to the Bun team by emailing `[email protected]`. Your report will acknowledged within 5 days, and a team member will be assigned as the primary handler. To the greatest extent possible, the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement, and may ask for additional information or guidance surrounding the reported issue.
+355
View File
@@ -0,0 +1,355 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "bun",
"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.5",
"mitata": "^0.1.14",
"oxlint": "1.70.0",
"prettier": "^3.6.2",
"prettier-plugin-organize-imports": "^4.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"source-map-js": "^1.2.1",
"typescript": "6.0.2",
},
},
"packages/@types/bun": {
"name": "@types/bun",
"version": "1.2.2",
"dependencies": {
"bun-types": "workspace:",
},
},
"packages/bun-types": {
"name": "bun-types",
"dependencies": {
"@types/node": "*",
},
},
},
"overrides": {
"@types/bun": "workspace:packages/@types/bun",
"@types/node": "25.0.0",
"bun-types": "workspace:packages/bun-types",
},
"packages": {
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
"@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
"@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
"@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
"@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
"@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
"@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
"@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
"@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
"@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
"@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
"@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
"@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
"@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
"@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
"@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
"@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
"@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
"@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
"@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
"@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
"@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
"@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"@lezer/common": ["@lezer/[email protected]", "", {}, "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ=="],
"@lezer/cpp": ["@lezer/[email protected]", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w=="],
"@lezer/highlight": ["@lezer/[email protected]", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
"@lezer/lr": ["@lezer/[email protected]", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA=="],
"@octokit/app": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/types": "^12.0.0", "@octokit/webhooks": "^12.0.4" } }, "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw=="],
"@octokit/auth-app": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-oauth-app": "^7.1.0", "@octokit/auth-oauth-user": "^4.1.0", "@octokit/request": "^8.3.1", "@octokit/request-error": "^5.1.0", "@octokit/types": "^13.1.0", "deprecation": "^2.3.1", "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", "universal-github-app-jwt": "^1.1.2", "universal-user-agent": "^6.0.0" } }, "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ=="],
"@octokit/auth-oauth-app": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-oauth-device": "^6.1.0", "@octokit/auth-oauth-user": "^4.1.0", "@octokit/request": "^8.3.1", "@octokit/types": "^13.0.0", "@types/btoa-lite": "^1.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA=="],
"@octokit/auth-oauth-device": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/oauth-methods": "^4.1.0", "@octokit/request": "^8.3.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw=="],
"@octokit/auth-oauth-user": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-oauth-device": "^6.1.0", "@octokit/oauth-methods": "^4.1.0", "@octokit/request": "^8.3.1", "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ=="],
"@octokit/auth-token": ["@octokit/[email protected]", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
"@octokit/auth-unauthenticated": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0" } }, "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg=="],
"@octokit/core": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
"@octokit/endpoint": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
"@octokit/graphql": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
"@octokit/oauth-app": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", "@octokit/auth-unauthenticated": "^5.0.0", "@octokit/core": "^5.0.0", "@octokit/oauth-authorization-url": "^6.0.2", "@octokit/oauth-methods": "^4.0.0", "@types/aws-lambda": "^8.10.83", "universal-user-agent": "^6.0.0" } }, "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g=="],
"@octokit/oauth-authorization-url": ["@octokit/[email protected]", "", {}, "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA=="],
"@octokit/oauth-methods": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/oauth-authorization-url": "^6.0.2", "@octokit/request": "^8.3.1", "@octokit/request-error": "^5.1.0", "@octokit/types": "^13.0.0", "btoa-lite": "^1.0.0" } }, "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw=="],
"@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@octokit/plugin-paginate-graphql": ["@octokit/[email protected]", "", { "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A=="],
"@octokit/plugin-paginate-rest": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^13.7.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^13.8.0" }, "peerDependencies": { "@octokit/core": "^5" } }, "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ=="],
"@octokit/plugin-retry": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^13.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig=="],
"@octokit/plugin-throttling": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^12.2.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "^5.0.0" } }, "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ=="],
"@octokit/request": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
"@octokit/request-error": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
"@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@octokit/webhooks": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/webhooks-methods": "^4.1.0", "@octokit/webhooks-types": "7.6.1", "aggregate-error": "^3.1.0" } }, "sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ=="],
"@octokit/webhooks-methods": ["@octokit/[email protected]", "", {}, "sha512-NGlEHZDseJTCj8TMMFehzwa9g7On4KJMPVHDSrHxCQumL6uSQR8wIkP/qesv52fXqV1BPf4pTxwtS31ldAt9Xg=="],
"@octokit/webhooks-types": ["@octokit/[email protected]", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw=="],
"@oxlint/binding-android-arm64": ["@oxlint/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w=="],
"@oxlint/binding-darwin-x64": ["@oxlint/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-1e2L7cFCvx9QDzq6NPP+0tABKb5z6nWHyddWTNKprEsjO9xNrAtPowuCGpjNXxkTdsMiZ4jc8YQ5SstZd4XK6g=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-Kwu/l/8GcYibCWA9m9N5pRXMIKVSsL/YbgpLzYkqDhWTiqdRfnNJ/+nqIKRKQiFbHWsdlHEhzMwruJK+qcEruA=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-tap04CsHYOl0nSAQJfPNIuBxqEPB2HnhQqwaOXLg1jnp2XfRo8Fa814dA4QC4zpvTWXCjAAaCY1W5LOORkEQuQ=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-hzJa/WgvtJpbBD9rgfy0qe+MjbxOXNUT0bfR1S6EQQzfTtBFA9xg5q8KSwRrQ2QfSS+TaP4j+4mVPQrfNc6UNg=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-xbsaNSNzVSnaJACCUYr1HQMyY/Q/Q1LkePmHG3UvZPvGCYGNxrsZp9OmtA6ick8xH47ltRRbRrPCM1YXYcyC+A=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-icAEsUI7JbW1TMRdEXV83mVAInhRVQYuuAlPpxdGwJ95chNdnCzjloRW8GglT0WvzOEZSio6fnYSk2DJ2Hv7LQ=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-FHMSWbVsPVs/f+Jcl04ws4JJ2wUnauyTzlpxWRG/lSO/8GpX08Fo2gQZqdA6CrRFI+zvkxl+N/KwJGWfUwYVZA=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-ptOlKwCz7n4AKs5VweMqG6DAg677FmKOK+vBkkL9DMNgFATIQ+upqUYBTOEwRQyRAx1ncGlPlXleV2hIcm3z4g=="],
"@sentry/types": ["@sentry/[email protected]", "", {}, "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q=="],
"@types/aws-lambda": ["@types/[email protected]", "", {}, "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg=="],
"@types/btoa-lite": ["@types/[email protected]", "", {}, "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg=="],
"@types/bun": ["@types/bun@workspace:packages/@types/bun"],
"@types/jsonwebtoken": ["@types/[email protected]", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
"@types/ms": ["@types/[email protected]", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew=="],
"aggregate-error": ["[email protected]", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
"before-after-hook": ["[email protected]", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
"bottleneck": ["[email protected]", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
"btoa-lite": ["[email protected]", "", {}, "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA=="],
"buffer-equal-constant-time": ["[email protected]", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"bun-tracestrings": ["bun-tracestrings@github:oven-sh/bun.report#912ca63", { "dependencies": { "@octokit/webhooks-methods": "^5.1.0", "@sentry/types": "^7.112.2", "@types/bun": "^1.2.6", "html-minifier": "^4.0.0", "lightningcss": "^1.24.1", "marked": "^12.0.1", "octokit": "^3.2.0", "prettier": "^3.2.5", "typescript": "^5.0.0" }, "bin": { "ci-remap-server": "./bin/ci-remap-server.ts" } }, "oven-sh-bun.report-912ca63"],
"bun-types": ["bun-types@workspace:packages/bun-types"],
"camel-case": ["[email protected]", "", { "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w=="],
"clean-css": ["[email protected]", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A=="],
"clean-stack": ["[email protected]", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
"commander": ["[email protected]", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"deprecation": ["[email protected]", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
"detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"ecdsa-sig-formatter": ["[email protected]", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
"he": ["[email protected]", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"html-minifier": ["[email protected]", "", { "dependencies": { "camel-case": "^3.0.0", "clean-css": "^4.2.1", "commander": "^2.19.0", "he": "^1.2.0", "param-case": "^2.1.1", "relateurl": "^0.2.7", "uglify-js": "^3.5.1" }, "bin": { "html-minifier": "./cli.js" } }, "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig=="],
"indent-string": ["[email protected]", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"js-tokens": ["[email protected]", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsonwebtoken": ["[email protected]", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
"jwa": ["[email protected]", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
"jws": ["[email protected]", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss-android-arm64": ["[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-darwin-arm64": ["[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-x64": ["[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-freebsd-x64": ["[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-linux-arm-gnueabihf": ["[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-musl": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-x64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-musl": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-win32-arm64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-x64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"lodash.includes": ["[email protected]", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["[email protected]", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
"lodash.isinteger": ["[email protected]", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["[email protected]", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.isplainobject": ["[email protected]", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.isstring": ["[email protected]", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.once": ["[email protected]", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"loose-envify": ["[email protected]", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lower-case": ["[email protected]", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="],
"lru-cache": ["@wolfy1339/[email protected]", "", {}, "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA=="],
"marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="],
"mitata": ["[email protected]", "", {}, "sha512-8kRs0l636eT4jj68PFXOR2D5xl4m56T478g16SzUPOYgkzQU+xaw62guAQxzBPm+SXb15GQi1cCpDxJfkr4CSA=="],
"ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"no-case": ["[email protected]", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="],
"octokit": ["[email protected]", "", { "dependencies": { "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-graphql": "^4.0.0", "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@octokit/request-error": "^5.0.0", "@octokit/types": "^13.0.0", "@octokit/webhooks": "^12.3.1" } }, "sha512-7Abo3nADdja8l/aglU6Y3lpnHSfv0tw7gFPiqzry/yCU+2gTAX7R1roJ8hJrxIK+S1j+7iqRJXtmuHJ/UDsBhQ=="],
"once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"oxlint": ["[email protected]", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.70.0", "@oxlint/binding-android-arm64": "1.70.0", "@oxlint/binding-darwin-arm64": "1.70.0", "@oxlint/binding-darwin-x64": "1.70.0", "@oxlint/binding-freebsd-x64": "1.70.0", "@oxlint/binding-linux-arm-gnueabihf": "1.70.0", "@oxlint/binding-linux-arm-musleabihf": "1.70.0", "@oxlint/binding-linux-arm64-gnu": "1.70.0", "@oxlint/binding-linux-arm64-musl": "1.70.0", "@oxlint/binding-linux-ppc64-gnu": "1.70.0", "@oxlint/binding-linux-riscv64-gnu": "1.70.0", "@oxlint/binding-linux-riscv64-musl": "1.70.0", "@oxlint/binding-linux-s390x-gnu": "1.70.0", "@oxlint/binding-linux-x64-gnu": "1.70.0", "@oxlint/binding-linux-x64-musl": "1.70.0", "@oxlint/binding-openharmony-arm64": "1.70.0", "@oxlint/binding-win32-arm64-msvc": "1.70.0", "@oxlint/binding-win32-ia32-msvc": "1.70.0", "@oxlint/binding-win32-x64-msvc": "1.70.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-D6JgHtzkhRwvEC+A0Nw5AEc5bk8x5i1pHzvZIEf/a0C4hOzmAACNGtkDGPyFaxxX3ZVGxCPeig3P3rMM8XU3/g=="],
"param-case": ["[email protected]", "", { "dependencies": { "no-case": "^2.2.0" } }, "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w=="],
"prettier": ["[email protected]", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"prettier-plugin-organize-imports": ["[email protected]", "", { "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", "vue-tsc": "^2.1.0 || 3" }, "optionalPeers": ["vue-tsc"] }, "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw=="],
"react": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-dom": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
"relateurl": ["[email protected]", "", {}, "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="],
"safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"scheduler": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
"uglify-js": ["[email protected]", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"undici-types": ["[email protected]", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"universal-github-app-jwt": ["[email protected]", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" } }, "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g=="],
"universal-user-agent": ["[email protected]", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
"upper-case": ["[email protected]", "", {}, "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA=="],
"wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"@octokit/app/@octokit/plugin-paginate-rest": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
"@octokit/app/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/auth-unauthenticated/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/plugin-throttling/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/webhooks/@octokit/webhooks-methods": ["@octokit/[email protected]", "", {}, "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ=="],
"bun-tracestrings/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"@octokit/app/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
"@octokit/auth-unauthenticated/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
"@octokit/plugin-throttling/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
}
}
+8
View File
@@ -0,0 +1,8 @@
# FIXME: move this back to test/js/node
# https://github.com/oven-sh/bun/issues/16289
[test]
preload = ["./test/js/node/harness.ts", "./test/preload.ts"]
[install]
# Node.js never auto-installs modules.
auto = "disable"
+20
View File
@@ -0,0 +1,20 @@
[test]
# Large monorepos (like Bun) may want to specify the test directory more specifically
# By default, `bun test` scans every single folder recursively which, if you
# have a gigantic submodule (like WebKit), requires lots of directory
# traversals
#
# Instead, we can only scan the test directory for Bun's runtime tests
root = "test"
preload = "./test/preload.ts"
[install]
linker = "isolated"
# The CI test runner points BUN_INSTALL_CACHE_DIR at a per-call tmpdir and
# deletes it between the root and test/ installs. With the global store
# enabled, `node_modules/.bun/<pkg>` is an absolute symlink into that
# now-deleted cache, so `test/`'s `file:../node_modules/react` dep dangles.
# The feature is exercised by test/cli/install/isolated-install.test.ts.
globalStore = false
minimumReleaseAge = 259200 # three days
minimumReleaseAgeExcludes = ["typescript"]
+100
View File
@@ -0,0 +1,100 @@
disallowed-methods = [
{ path = "std::fs::read", reason = "use bun_sys::file" },
{ path = "std::fs::write", reason = "use bun_sys::file" },
{ path = "std::fs::read_to_string", reason = "use bun_sys::file" },
{ path = "std::fs::remove_file", reason = "use bun_sys::file" },
{ path = "std::fs::remove_dir_all", reason = "use bun_sys::dir" },
{ path = "std::fs::create_dir", reason = "use bun_sys::dir" },
{ path = "std::fs::create_dir_all", reason = "use bun_sys::dir" },
{ path = "std::fs::metadata", reason = "use bun_sys::file" },
{ path = "std::fs::canonicalize", reason = "use bun_paths" },
{ path = "std::env::var", reason = "use bun_core::env_var" },
{ path = "std::env::var_os", reason = "use bun_core::env_var" },
{ path = "std::thread::spawn", reason = "use bun_threading::spawn_named or ThreadPool" },
{ path = "std::mem::zeroed", reason = "use MaybeUninit; zeroed is UB for most Bun types" },
{ path = "bun_core::output::pretty_fmt_rt", reason = "const format strings must use pretty_fmt!/write_pretty! (compile-time rewrite); the runtime walk is only for templates built at runtime — allow with justification" },
{ path = "bun_core::output::pretty_fmt_args", reason = "const format strings must use pretty_fmt!/write_pretty! (compile-time rewrite); the runtime walk is only for templates built at runtime — allow with justification" },
{ path = "bun_core::output::pretty_fmt_runtime", reason = "const format strings must use pretty_fmt!/write_pretty! (compile-time rewrite); the runtime walk is only for templates built at runtime — allow with justification" },
{ path = "bun_core::output::pretty", reason = "renders args then tag-walks the rendered bytes (allocates twice, mangles user-controlled '<'); use the pretty! macro — allow only for runtime-built payloads, with justification" },
{ path = "bun_core::output::prettyln", reason = "renders args then tag-walks the rendered bytes; use the prettyln! macro — allow only for runtime-built payloads, with justification" },
{ path = "bun_core::output::pretty_errorln", reason = "renders args then tag-walks the rendered bytes; use the pretty_errorln! macro — allow only for runtime-built payloads, with justification" },
{ path = "bun_core::output::warn", reason = "renders args then tag-walks the rendered bytes; use the warn! macro — allow only for runtime-built payloads, with justification" },
{ path = "bun_core::output::debug_warn", reason = "renders args then tag-walks the rendered bytes; use the debug_warn! macro — allow only for runtime-built payloads, with justification" },
{ path = "alloc::string::String::from_utf8", reason = "keep data as bytes; for display use bstr::BStr, for JS-visible strings use bun_core::String::clone_utf8" },
{ path = "alloc::string::String::from_utf8_lossy", reason = "silently corrupts non-UTF-8 bytes and allocates; keep data as bytes (bstr::BStr for Display)" },
# == byte/substring search must go through bun_core::strings (highway, runtime-dispatched SIMD) ==
# libcore's searchers are scalar or compile-time-gated SSE2/NEON (we build with
# -Ctarget-cpu=nehalem on x64); highway picks AVX2/AVX-512 at runtime. The
# element-generic forms (`<[u8]>::contains`, `iter().position(|b| ..)`) can't be
# expressed here and are covered by test/internal/source-lints/byte-search.test.ts.
{ path = "str::find", reason = "use bun_core::strings::index_of_char / index_of on .as_bytes() (highway SIMD)" },
{ path = "str::rfind", reason = "use bun_core::strings::last_index_of_char / last_index_of on .as_bytes() (highway SIMD)" },
{ path = "str::contains", reason = "use bun_core::strings::contains_char / contains on .as_bytes() (highway SIMD)" },
{ path = "str::split_once", reason = "use bun_core::strings::split_once_char / split_once on .as_bytes() (highway SIMD)" },
{ path = "str::rsplit_once", reason = "use bun_core::strings::rsplit_once_char / rsplit_once on .as_bytes() (highway SIMD)" },
{ path = "str::split", reason = "use bun_core::strings::split on .as_bytes() (highway SIMD)" },
{ path = "str::rsplit", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::splitn", reason = "use bun_core::strings::split / split_once on .as_bytes() (highway SIMD)" },
{ path = "str::rsplitn", reason = "use bun_core::strings::rsplit_once on .as_bytes() (highway SIMD)" },
{ path = "str::split_terminator", reason = "use bun_core::strings::split on .as_bytes() (highway SIMD)" },
{ path = "str::rsplit_terminator", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::split_inclusive", reason = "use bun_core::strings::index_of_char in a loop on .as_bytes() (highway SIMD)" },
{ path = "str::lines", reason = "use bun_core::strings::split(bytes, b\"\\n\") and strip a trailing b'\\r' per field (str::lines does) (highway SIMD)" },
{ path = "str::matches", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::rmatches", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::match_indices", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::rmatch_indices", reason = "use bun_core::strings on .as_bytes() (highway SIMD)" },
{ path = "str::replace", reason = "use bun_core::strings::replace_owned on .as_bytes() (highway SIMD)" },
{ path = "str::replacen", reason = "use bun_core::strings::replace_owned on .as_bytes() (highway SIMD)" },
{ path = "slice::windows", reason = "substring search must use bun_core::strings::index_of (highway memmem); for genuine sliding-window iteration, #[allow] with a reason" },
{ path = "memchr::memchr", reason = "use bun_core::strings::index_of_char (highway SIMD)" },
{ path = "memchr::memchr2", reason = "use bun_core::strings::index_of_any (highway SIMD)" },
{ path = "memchr::memchr3", reason = "use bun_core::strings::index_of_any (highway SIMD)" },
{ path = "memchr::memrchr", reason = "use bun_core::strings::last_index_of_char (highway SIMD)" },
{ path = "memchr::memchr_iter", reason = "use bun_core::strings::index_of_char in a loop (highway SIMD)" },
{ path = "memchr::memmem::find", reason = "use bun_core::strings::index_of (highway memmem)" },
{ path = "memchr::memmem::rfind", reason = "use bun_core::strings::last_index_of (highway memrmem)" },
{ path = "memchr::memmem::find_iter", reason = "use bun_core::strings::index_of in a loop (highway memmem)" },
{ path = "bstr::ByteSlice::find", reason = "use bun_core::strings::index_of (highway memmem)" },
{ path = "bstr::ByteSlice::rfind", reason = "use bun_core::strings::last_index_of (highway memrmem)" },
{ path = "bstr::ByteSlice::find_byte", reason = "use bun_core::strings::index_of_char (highway SIMD)" },
{ path = "bstr::ByteSlice::rfind_byte", reason = "use bun_core::strings::last_index_of_char (highway SIMD)" },
{ path = "bstr::ByteSlice::find_char", reason = "use bun_core::strings::index_of_char / index_of (highway SIMD)" },
{ path = "bstr::ByteSlice::rfind_char", reason = "use bun_core::strings::last_index_of_char / last_index_of (highway SIMD)" },
{ path = "bstr::ByteSlice::find_byteset", reason = "use bun_core::strings::index_of_any (highway SIMD)" },
{ path = "bstr::ByteSlice::contains_str", reason = "use bun_core::strings::contains (highway memmem)" },
{ path = "bstr::ByteSlice::find_iter", reason = "use bun_core::strings::index_of in a loop (highway memmem)" },
{ path = "bstr::ByteSlice::rfind_iter", reason = "use bun_core::strings::last_index_of in a loop (highway memrmem)" },
{ path = "bstr::ByteSlice::split_str", reason = "use bun_core::strings::split (highway memmem)" },
{ path = "bstr::ByteSlice::rsplit_str", reason = "use bun_core::strings (highway memrmem)" },
{ path = "bstr::ByteSlice::split_once_str", reason = "use bun_core::strings::split_once (highway memmem)" },
{ path = "bstr::ByteSlice::rsplit_once_str", reason = "use bun_core::strings::rsplit_once (highway memrmem)" },
{ path = "bstr::ByteSlice::replace", reason = "use bun_core::strings::replace_owned (highway memmem)" },
]
disallowed-types = [
{ path = "std::sync::Mutex", reason = "use bun_threading::Mutex" },
{ path = "std::sync::RwLock", reason = "use bun_threading::RwLock" },
{ path = "parking_lot::Mutex", reason = "use bun_threading::Mutex" },
{ path = "parking_lot::RwLock", reason = "use bun_threading::RwLock" },
{ path = "parking_lot::RawMutex", reason = "use bun_threading::Mutex" },
{ path = "std::collections::HashMap", reason = "use bun_collections (wyhash)" },
{ path = "std::collections::HashSet", reason = "use bun_collections (wyhash)" },
{ path = "std::fs::File", reason = "use bun_sys::File" },
{ path = "std::process::Command", reason = "use bun_core::util::spawn_sync_inherit (CLI helpers) or bun_spawn_sys (full control)" },
]
disallowed-macros = [
{ path = "std::println", reason = "use bun_core::output" },
{ path = "std::eprintln", reason = "use bun_core::output" },
{ path = "std::print", reason = "use bun_core::output" },
{ path = "std::eprint", reason = "use bun_core::output" },
{ path = "std::dbg", reason = "remove before commit" },
]
pass-by-value-size-limit = 64
stack-size-threshold = 131072
enum-variant-size-threshold = 128
too-large-for-stack = 4096
avoid-breaking-exported-api = false
accept-comment-above-attributes = true

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