initial commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Executable
+1724
File diff suppressed because it is too large
Load Diff
Executable
+71
@@ -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
|
||||
}
|
||||
}
|
||||
Executable
+409
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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 -->
|
||||
|
||||
---
|
||||
@@ -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 -->
|
||||
|
||||
---
|
||||
@@ -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 -->
|
||||
|
||||
---
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.").
|
||||
Executable
+62
@@ -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);
|
||||
Executable
+204
@@ -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);
|
||||
@@ -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`)
|
||||
@@ -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.
|
||||
@@ -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 21–N in the same bullet format, packed into chunks under 4800 chars each (~70 rows per chunk). Post chunks sequentially so they stay ordered.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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"'],
|
||||
});
|
||||
```
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
# https://EditorConfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
# Project
|
||||
/.github/CODEOWNERS @Jarred-Sumner
|
||||
|
||||
# Tests
|
||||
/test/expectations.txt @Jarred-Sumner
|
||||
|
||||
# Types
|
||||
*.d.ts @alii
|
||||
/packages/bun-types/ @alii
|
||||
@@ -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?
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directories:
|
||||
- /
|
||||
- /.github/actions/*
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
actions:
|
||||
patterns: ["*"]
|
||||
@@ -0,0 +1,3 @@
|
||||
### What does this PR do?
|
||||
|
||||
### How did you verify your code works?
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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']
|
||||
});
|
||||
@@ -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
|
||||
@@ -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).`);
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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)})'
|
||||
@@ -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
|
||||
@@ -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'
|
||||
});
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
@@ -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]>
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[type.md]
|
||||
extend-ignore-words-re = ["^ba"]
|
||||
Vendored
+120
@@ -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,
|
||||
}
|
||||
Vendored
+33
@@ -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",
|
||||
],
|
||||
}
|
||||
+365
@@ -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 [...]",
|
||||
},
|
||||
],
|
||||
}
|
||||
Vendored
+150
@@ -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",
|
||||
}
|
||||
Vendored
+40
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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> • </span>
|
||||
<a href="https://bun.com/discord">Discord</a>
|
||||
<span> • </span>
|
||||
<a href="https://github.com/oven-sh/bun/issues/new">Issues</a>
|
||||
<span> • </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.
|
||||
@@ -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
@@ -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.
|
||||
@@ -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=="],
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user