Merge branch 'main' into jarred/change-timers

This commit is contained in:
Jarred Sumner
2024-12-05 18:43:01 -08:00
2321 changed files with 83170 additions and 61972 deletions

View File

@@ -304,15 +304,23 @@ function getPipeline(options) {
* @param {Target} target
* @returns {Agent}
*/
const getZigAgent = target => {
const { abi, arch } = target;
// if (abi === "musl") {
// const instanceType = arch === "aarch64" ? "c8g.large" : "c7i.large";
// return getEmphemeralAgent("v2", target, instanceType);
// }
const getZigAgent = platform => {
const { arch } = platform;
const instanceType = arch === "aarch64" ? "c8g.2xlarge" : "c7i.2xlarge";
return {
queue: "build-zig",
robobun: true,
robobun2: true,
os: "linux",
arch,
distro: "debian",
release: "11",
"image-name": `linux-${arch}-debian-11-v5`, // v5 is not on main yet
"instance-type": instanceType,
};
// TODO: Temporarily disable due to configuration
// return {
// queue: "build-zig",
// };
};
/**
@@ -443,7 +451,7 @@ function getPipeline(options) {
label: `${getTargetLabel(platform)} - build-zig`,
depends_on: getDependsOn(platform),
agents: getZigAgent(platform),
retry: getRetry(1), // FIXME: Sometimes zig build hangs, so we need to retry once
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
env: getBuildEnv(platform),
command: `bun run build:ci --target bun-zig --toolchain ${toolchain}`,
@@ -664,16 +672,18 @@ async function main() {
}
let changedFiles;
// FIXME: Fix various bugs when calculating changed files
// false -> !isFork() && !isMainBranch()
if (false) {
let changedFilesBranch;
if (!isFork() && !isMainBranch()) {
console.log("Checking changed files...");
const baseRef = lastBuild?.commit_id || getTargetBranch() || getMainBranch();
const targetRef = getTargetBranch();
console.log(" - Target Ref:", targetRef);
const baseRef = lastBuild?.commit_id || targetRef || getMainBranch();
console.log(" - Base Ref:", baseRef);
const headRef = getCommit();
console.log(" - Head Ref:", headRef);
changedFiles = await getChangedFiles(undefined, baseRef, headRef);
changedFilesBranch = await getChangedFiles(undefined, targetRef, headRef);
if (changedFiles) {
if (changedFiles.length) {
changedFiles.forEach(filename => console.log(` - ${filename}`));
@@ -698,7 +708,7 @@ async function main() {
forceBuild = true;
}
for (const coref of [".buildkite/ci.mjs", "scripts/utils.mjs", "scripts/bootstrap.sh", "scripts/machine.mjs"]) {
if (changedFiles && changedFiles.includes(coref)) {
if (changedFilesBranch && changedFilesBranch.includes(coref)) {
console.log(" - Yes, because the list of changed files contains:", coref);
forceBuild = true;
ciFileChanged = true;

92
.github/workflows/update-cares.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Update c-ares
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check c-ares version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildCares.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildCares.cmake"
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 BuildCares.cmake"
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_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
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
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildCares.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildCares.cmake
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-${{ github.run_number }}
body: |
## What does this PR do?
Updates c-ares to version ${{ steps.check-version.outputs.tag }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-cares.yml)

92
.github/workflows/update-libarchive.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Update libarchive
on:
schedule:
- cron: "0 3 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check libarchive version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildLibArchive.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildLibArchive.cmake"
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 BuildLibArchive.cmake"
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_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
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
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildLibArchive.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildLibArchive.cmake
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-${{ github.run_number }}
body: |
## What does this PR do?
Updates libarchive to version ${{ steps.check-version.outputs.tag }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-libarchive.yml)

92
.github/workflows/update-libdeflate.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Update libdeflate
on:
schedule:
- cron: "0 2 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check libdeflate version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildLibDeflate.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildLibDeflate.cmake"
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 BuildLibDeflate.cmake"
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_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
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
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildLibDeflate.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildLibDeflate.cmake
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-${{ github.run_number }}
body: |
## What does this PR do?
Updates libdeflate to version ${{ steps.check-version.outputs.tag }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-libdeflate.yml)

92
.github/workflows/update-lolhtml.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Update lolhtml
on:
schedule:
- cron: "0 1 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check lolhtml version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildLolHtml.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildLolHtml.cmake"
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 BuildLolHtml.cmake"
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
LATEST_SHA=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
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
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildLolHtml.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildLolHtml.cmake
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-${{ github.run_number }}
body: |
## What does this PR do?
Updates lolhtml to version ${{ steps.check-version.outputs.tag }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-lolhtml.yml)

92
.github/workflows/update-lshpack.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Update lshpack
on:
schedule:
- cron: "0 5 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check lshpack version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildLshpack.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildLshpack.cmake"
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 BuildLshpack.cmake"
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
LATEST_SHA=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
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
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildLshpack.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildLshpack.cmake
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-${{ github.run_number }}
body: |
## What does this PR do?
Updates lshpack to version ${{ steps.check-version.outputs.tag }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-lshpack.yml)

109
.github/workflows/update-sqlite3.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
name: Update SQLite3
on:
schedule:
- cron: "0 6 * * 0" # Run weekly
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- 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/bun.js/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 / 1000) % 1000))
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
run: |
set -euo pipefail
TEMP_DIR=$(mktemp -d)
cd $TEMP_DIR
echo "Downloading from: https://sqlite.org/${{ steps.check-version.outputs.latest_year }}/sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip"
# Download and extract latest version
wget "https://sqlite.org/${{ steps.check-version.outputs.latest_year }}/sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip"
unzip "sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip"
cd "sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}"
# Add header comment and copy files
echo "// clang-format off" > $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3.c
cat sqlite3.c >> $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3.c
echo "// clang-format off" > $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3_local.h
cat sqlite3.h >> $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3_local.h
- name: Create Pull Request
if: success() && steps.check-version.outputs.current_num < steps.check-version.outputs.latest_num
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
src/bun.js/bindings/sqlite/sqlite3.c
src/bun.js/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-${{ steps.check-version.outputs.latest }}
body: |
## What does this PR do?
Updates SQLite to version ${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-sqlite3.yml)

3
.gitignore vendored
View File

@@ -175,4 +175,5 @@ test/js/third_party/prisma/prisma/sqlite/dev.db-journal
# Generated files
.buildkite/ci.yml
*.sock
*.sock
scratch*.{js,ts,tsx,cjs,mjs}

View File

@@ -5,6 +5,5 @@ test/js/deno
test/node.js
src/react-refresh.js
*.min.js
test/js/node/test/fixtures
test/js/node/test/common
test/snippets
test/js/node/test

5
.vscode/launch.json generated vendored
View File

@@ -224,8 +224,11 @@
"cwd": "${fileDirname}",
"env": {
"FORCE_COLOR": "1",
// "BUN_DEBUG_DEBUGGER": "1",
// "BUN_DEBUG_INTERNAL_DEBUGGER": "1",
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2",
// "BUN_INSPECT": "ws+unix:///var/folders/jk/8fzl9l5119598vsqrmphsw7m0000gn/T/tl15npi7qtf.sock?report=1",
},
"console": "internalConsole",
// Don't pause when the GC runs while the debugger is open.
@@ -1254,4 +1257,4 @@
"description": "Usage: bun test [...]",
},
],
}
}

91
.vscode/tasks.json vendored
View File

@@ -2,50 +2,57 @@
"version": "2.0.0",
"tasks": [
{
"type": "process",
"label": "Install Dependencies",
"command": "scripts/all-dependencies.sh",
"windows": {
"command": "scripts/all-dependencies.ps1",
},
"icon": {
"id": "arrow-down",
},
"options": {
"cwd": "${workspaceFolder}",
},
},
{
"type": "process",
"label": "Setup Environment",
"dependsOn": ["Install Dependencies"],
"command": "scripts/setup.sh",
"windows": {
"command": "scripts/setup.ps1",
},
"icon": {
"id": "check",
},
"options": {
"cwd": "${workspaceFolder}",
},
},
{
"type": "process",
"label": "Build Bun",
"dependsOn": ["Setup Environment"],
"command": "bun",
"args": ["run", "build"],
"icon": {
"id": "gear",
"type": "shell",
"command": "bun run build",
"group": {
"kind": "build",
"isDefault": true,
},
"options": {
"cwd": "${workspaceFolder}",
},
"isBuildCommand": true,
"runOptions": {
"instanceLimit": 1,
"reevaluateOnRerun": true,
"problemMatcher": [
{
"owner": "zig",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": [
{
"regexp": "^(.+?):(\\d+):(\\d+): (error|warning|note): (.+)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5,
},
{
"regexp": "^\\s+(.+)$",
"message": 1,
"loop": true,
},
],
},
{
"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,
},
},
],

View File

@@ -1,6 +1,6 @@
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](/docs/project/building-windows)
If you are using Windows, please refer to [this guide](/docs/project/building-windows.md)
{% details summary="For Ubuntu users" %}
TL;DR: Ubuntu 22.04 is suggested.

2
LATEST
View File

@@ -1 +1 @@
1.1.36
1.1.38

View File

@@ -414,6 +414,15 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
}
addInternalPackages(b, obj, opts);
obj.root_module.addImport("build_options", opts.buildOptionsModule(b));
const translate_plugin_api = b.addTranslateC(.{
.root_source_file = b.path("./packages/bun-native-bundler-plugin-api/bundler_plugin.h"),
.target = opts.target,
.optimize = opts.optimize,
.link_libc = true,
});
obj.root_module.addImport("bun-native-bundler-plugin-api", translate_plugin_api.createModule());
return obj;
}

View File

@@ -136,16 +136,6 @@ else()
set(WARNING WARNING)
endif()
if(LINUX)
if(EXISTS "/etc/alpine-release")
set(DEFAULT_ABI "musl")
else()
set(DEFAULT_ABI "gnu")
endif()
optionx(ABI "musl|gnu" "The ABI to use (e.g. musl, gnu)" DEFAULT ${DEFAULT_ABI})
endif()
# TODO: This causes flaky zig builds in CI, so temporarily disable it.
# if(CI)
# set(DEFAULT_VENDOR_PATH ${CACHE_PATH}/vendor)

View File

@@ -48,6 +48,16 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(LINUX)
if(EXISTS "/etc/alpine-release")
set(DEFAULT_ABI "musl")
else()
set(DEFAULT_ABI "gnu")
endif()
optionx(ABI "musl|gnu" "The ABI to use (e.g. musl, gnu)" DEFAULT ${DEFAULT_ABI})
endif()
if(ARCH STREQUAL "x64")
optionx(ENABLE_BASELINE BOOL "If baseline features should be used for older CPUs (e.g. disables AVX, AVX2)" DEFAULT OFF)
endif()

View File

@@ -1163,7 +1163,7 @@ if(NOT BUN_CPP_ONLY)
if(CI)
set(bunTriplet bun-${OS}-${ARCH})
if(ABI STREQUAL "musl")
if(LINUX AND ABI STREQUAL "musl")
set(bunTriplet ${bunTriplet}-musl)
endif()
if(ENABLE_BASELINE)

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
c-ares/c-ares
COMMIT
d1722e6e8acaf10eb73fa995798a9cd421d9f85e
41ee334af3e3d0027dca5e477855d0244936bd49
)
register_cmake_command(

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
ebiggers/libdeflate
COMMIT
dc76454a39e7e83b68c3704b6e3784654f8d5ac5
9d624d1d8ba82c690d6d6be1d0a961fc5a983ea4
)
register_cmake_command(

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
cloudflare/lol-html
COMMIT
8d4c273ded322193d017042d1f48df2766b0f88b
4f8becea13a0021c8b71abd2dcc5899384973b66
)
set(LOLHTML_CWD ${VENDOR_PATH}/lolhtml/c-api)

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
litespeedtech/ls-hpack
COMMIT
3d0f1fc1d6e66a642e7a98c55deb38aa986eb4b0
32e96f10593c7cb8553cd8c9c12721100ae9e924
)
if(WIN32)

View File

@@ -4,7 +4,7 @@ if(NOT ENABLE_LLVM)
return()
endif()
if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR ABI STREQUAL "musl")
if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR EXISTS "/etc/alpine-release")
set(DEFAULT_LLVM_VERSION "18.1.8")
else()
set(DEFAULT_LLVM_VERSION "16.0.6")

View File

@@ -63,7 +63,7 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(ABI STREQUAL "musl")
if(LINUX AND ABI STREQUAL "musl")
set(WEBKIT_SUFFIX "-musl")
endif()

View File

@@ -75,8 +75,10 @@ jobs:
name: build-app
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies # (assuming your project has dependencies)
run: bun install # You can use npm/yarn/pnpm instead if you prefer
- name: Run tests

View File

@@ -16,7 +16,7 @@ Set these variables in a `.env` file.
Bun reads the following files automatically (listed in order of increasing precedence).
- `.env`
- `.env.production` or `.env.development` (depending on value of `NODE_ENV`)
- `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
- `.env.local`
```txt#.env

View File

@@ -73,8 +73,7 @@ There are also image variants for different operating systems.
$ docker pull oven/bun:debian
$ docker pull oven/bun:slim
$ docker pull oven/bun:distroless
# alpine not recommended until #918 is fixed
# $ docker pull oven/bun:alpine
$ docker pull oven/bun:alpine
```
## Checking installation
@@ -190,14 +189,19 @@ For convenience, here are download links for the latest version:
- [`bun-linux-x64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip)
- [`bun-linux-x64-baseline.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-baseline.zip)
- [`bun-linux-x64-musl.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-musl.zip)
- [`bun-linux-x64-musl-baseline.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64-musl-baseline.zip)
- [`bun-windows-x64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-windows-x64.zip)
- [`bun-windows-x64-baseline.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-windows-x64-baseline.zip)
- [`bun-darwin-aarch64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-aarch64.zip)
- [`bun-linux-aarch64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64.zip)
- [`bun-linux-aarch64-musl.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-linux-aarch64-musl.zip)
- [`bun-darwin-x64.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-x64.zip)
- [`bun-darwin-x64-baseline.zip`](https://github.com/oven-sh/bun/releases/latest/download/bun-darwin-x64-baseline.zip)
The `baseline` binaries are built for older CPUs which may not support AVX2 instructions. If you run into an "Illegal Instruction" error when running Bun, try using the `baseline` binaries instead. Bun's install scripts automatically choose the correct binary for your system which helps avoid this issue. Baseline builds are slower than regular builds, so use them only if necessary.
The `musl` binaries are built for distributions that do not ship with the glibc libraries by default, instead relying on musl. The two most popular distros are Void Linux and Alpine Linux, with the latter is used heavily in Docker containers. If you encounter an error like the following: `bun: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29' not found (required by bun)`, try using the musl binary. Bun's install script automatically chooses the correct binary for your system.
The `baseline` binaries are built for older CPUs which may not support AVX2 instructions. If you run into an "Illegal Instruction" error when running Bun, try using the `baseline` binaries instead. Bun's install scripts automatically chooses the correct binary for your system which helps avoid this issue. Baseline builds are slower than regular builds, so use them only if necessary.
<!--
## Native

View File

@@ -355,7 +355,7 @@ Bun.build({
{% /callout %}
## Lifecycle callbacks
## Lifecycle hooks
Plugins can register callbacks to be run at various points in the lifecycle of a bundle:
@@ -363,6 +363,8 @@ Plugins can register callbacks to be run at various points in the lifecycle of a
- [`onResolve()`](#onresolve): Run before a module is resolved
- [`onLoad()`](#onload): Run before a module is loaded.
### Reference
A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions):
```ts
@@ -603,3 +605,98 @@ plugin({
```
Note that the `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback.
## Native plugins
{% callout %}
**NOTE** — This is an advanced and experiemental API recommended for plugin developers who are familiar with systems programming and the C ABI. Use with caution.
{% /callout %}
One of the reasons why Bun's bundler is so fast is that it is written in native code and leverages multi-threading to load and parse modules in parallel.
However, one limitation of plugins written in JavaScript is that JavaScript itself is single-threaded.
Native plugins are written as [NAPI](/docs/node-api) modules and can be run on multiple threads. This allows native plugins to run much faster than JavaScript plugins.
In addition, native plugins can skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript.
These are the following lifecycle hooks which are available to native plugins:
- [`onBeforeParse()`](#onbeforeparse): Called on any thread before a file is parsed by Bun's bundler.
### Creating a native plugin
Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions.
To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement.
#### Example: Rust with napi-rs
First initialize a napi project (see [here](https://napi.rs/docs/introduction/getting-started) for a more comprehensive guide).
Then install Bun's official safe plugin wrapper crate:
```bash
cargo add bun-native-plugin
```
Now you can export an `extern "C" fn` which is the implementation of your plugin:
```rust
#[no_mangle]
extern "C" fn on_before_parse_impl(
args: *const bun_native_plugin::sys::OnBeforeParseArguments,
result: *mut bun_native_plugin::sys::OnBeforeParseResult,
) {
let args = unsafe { &*args };
let result = unsafe { &mut *result };
let mut handle = match bun_native_plugin::OnBeforeParse::from_raw(args, result) {
Ok(handle) => handle,
Err(_) => {
return;
}
};
let source_code = match handle.input_source_code() {
Ok(source_code) => source_code,
Err(_) => {
handle.log_error("Fetching source code failed!");
return;
}
};
let loader = handle.output_loader();
handle.set_output_source_code(source_code.replace("foo", "bar"), loader);
```
Use napi-rs to compile the plugin to a `.node` file, then you can `require()` it from JS and use it:
```js
await Bun.build({
entrypoints: ["index.ts"],
setup(build) {
const myNativePlugin = require("./path/to/plugin.node");
build.onBeforeParse(
{ filter: /\.ts/ },
{ napiModule: myNativePlugin, symbol: "on_before_parse_impl" },
);
},
});
```
### `onBeforeParse`
```ts
onBeforeParse(
args: { filter: RegExp; namespace?: string },
callback: { napiModule: NapiModule; symbol: string; external?: unknown },
): void;
```
This lifecycle callback is run immediately before a file is parsed by Bun's bundler.
As input, it receives the file's contents and can optionally return new source code.
This callback can be called from any thread and so the napi module implementation must be thread-safe.

View File

@@ -1,67 +0,0 @@
## Troubleshooting
### Bun not running on an M1 (or Apple Silicon)
If you see a message like this
> [1] 28447 killed bun create next ./test
It most likely means youre running Buns x64 version on Apple Silicon. This happens if Bun is running via Rosetta. Rosetta is unable to emulate AVX2 instructions, which Bun indirectly uses.
The fix is to ensure you installed a version of Bun built for Apple Silicon.
### error: Unexpected
If you see an error like this:
![image](https://user-images.githubusercontent.com/709451/141210854-89434678-d21b-42f4-b65a-7df3b785f7b9.png)
It usually means the max number of open file descriptors is being explicitly set to a low number. By default, Bun requests the max number of file descriptors available (which on macOS, is something like 32,000). But, if you previously ran into ulimit issues with, e.g., Chokidar, someone on The Internet may have advised you to run `ulimit -n 8192`.
That advice unfortunately **lowers** the hard limit to `8192`. This can be a problem in large repositories or projects with lots of dependencies. Chokidar (and other watchers) dont seem to call `setrlimit`, which means theyre reliant on the (much lower) soft limit.
To fix this issue:
1. Remove any scripts that call `ulimit -n` and restart your shell.
2. Try again, and if the error still occurs, try setting `ulimit -n` to an absurdly high number, such as `ulimit -n 2147483646`
3. Try again, and if that still doesnt fix it, open an issue
### Unzip is required
Unzip is required to install Bun on Linux. You can use one of the following commands to install `unzip`:
#### Debian / Ubuntu / Mint
```sh
$ sudo apt install unzip
```
#### RedHat / CentOS / Fedora
```sh
$ sudo dnf install unzip
```
#### Arch / Manjaro
```sh
$ sudo pacman -S unzip
```
#### OpenSUSE
```sh
$ sudo zypper install unzip
```
### bun install is stuck
Please run `bun install --verbose 2> logs.txt` and send them to me in Bun's discord. If you're on Linux, it would also be helpful if you run `sudo perf trace bun install --silent` and attach the logs.
### Uninstalling
Bun's binary and install cache is located in `~/.bun` by default. To uninstall bun, delete this directory and edit your shell config (`.bashrc`, `.zshrc`, or similar) to remove `~/.bun/bin` from the `$PATH` variable.
```sh
$ rm -rf ~/.bun # make sure to remove ~/.bun/bin from $PATH
```

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.1.37",
"version": "1.1.39",
"workspaces": [
"./packages/bun-types"
],
@@ -30,8 +30,8 @@
"bun-types": "workspace:packages/bun-types"
},
"scripts": {
"build": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug",
"build:debug": "bun run build",
"build": "bun run build:debug",
"build:debug": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug",
"build:valgrind": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_BASELINE=ON -ENABLE_VALGRIND=ON -B build/debug-valgrind",
"build:release": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -B build/release",
"build:ci": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=ON -DCI=true -B build/release-ci --verbose --fresh",
@@ -39,8 +39,8 @@
"build:logs": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DENABLE_LOGS=ON -B build/release-logs",
"build:safe": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DZIG_OPTIMIZE=ReleaseSafe -B build/release-safe",
"build:smol": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -B build/release-smol",
"build:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DWEBKIT_LOCAL=ON -B build/debug",
"build:release:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DWEBKIT_LOCAL=ON -B build/release",
"build:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DWEBKIT_LOCAL=ON -B build/debug-local",
"build:release:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DWEBKIT_LOCAL=ON -B build/release-local",
"build:release:with_logs": "cmake . -DCMAKE_BUILD_TYPE=Release -DENABLE_LOGS=true -GNinja -Bbuild-release && ninja -Cbuild-release",
"build:debug-zig-release": "cmake . -DCMAKE_BUILD_TYPE=Release -DZIG_OPTIMIZE=Debug -GNinja -Bbuild-debug-zig-release && ninja -Cbuild-debug-zig-release",
"css-properties": "bun run src/css/properties/generate_properties.ts",

View File

@@ -0,0 +1,5 @@
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

202
packages/bun-build-mdx-rs/.gitignore vendored Normal file
View File

@@ -0,0 +1,202 @@
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# End of https://www.toptal.com/developers/gitignore/api/node
# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/macos
# Created by https://www.toptal.com/developers/gitignore/api/windows
# Edit at https://www.toptal.com/developers/gitignore?templates=windows
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/windows
#Added by cargo
/target
Cargo.lock
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
*.node
dist/
index.js
index.d.ts

View File

@@ -0,0 +1,13 @@
target
Cargo.lock
.cargo
.github
npm
.eslintrc
.prettierignore
rustfmt.toml
yarn.lock
*.node
.yarn
__test__
renovate.json

View File

@@ -0,0 +1,21 @@
[package]
edition = "2021"
name = "bun-mdx-rs"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.2", default-features = false, features = ["napi4"] }
napi-derive = "2.12.2"
mdxjs = "0.2.11"
bun-native-plugin = { path = "../bun-native-plugin-rs" }
[build-dependencies]
napi-build = "2.0.1"
[profile.release]
lto = true
strip = "symbols"

View File

@@ -0,0 +1,34 @@
# bun-build-mdx-rs
This is a proof of concept for using a third-party native addon in `Bun.build()`.
This uses `mdxjs-rs` to convert MDX to JSX.
TODO: **This needs to be built & published to npm.**
## Building locally:
```sh
cargo build --release
```
```js
import { build } from "bun";
import mdx from "./index.js";
// TODO: This needs to be prebuilt for the current platform
// Probably use a napi-rs template for this
import addon from "./target/release/libmdx_bun.dylib" with { type: "file" };
const results = await build({
entrypoints: ["./hello.jsx"],
plugins: [mdx({ addon })],
minify: true,
outdir: "./dist",
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
});
console.log(results);
```

View File

@@ -0,0 +1,7 @@
import test from 'ava'
import { sum } from '../index.js'
test('sum from native', (t) => {
t.is(sum(1, 2), 3)
})

View File

@@ -0,0 +1,5 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

View File

@@ -0,0 +1,6 @@
import page1 from "./page1.mdx";
import page2 from "./page2.mdx";
import page3 from "./page3.mdx";
import page4 from "./page4.mdx";
console.log(page1, page2, page3, page4);

View File

@@ -0,0 +1,11 @@
# Hello World
This is a sample MDX file that demonstrates various MDX features.
## Components
You can use JSX components directly in MDX:
<Button onClick={() => alert("Hello!")}>Click me</Button>
## Code Blocks

View File

@@ -0,0 +1,11 @@
# Hello World
This is a sample MDX file that demonstrates various MDX features.
## Components
You can use JSX components directly in MDX:
<Button onClick={() => alert("Hello!")}>Click me</Button>
## Code Blocks

View File

@@ -0,0 +1,11 @@
# Hello World
This is a sample MDX file that demonstrates various MDX features.
## Components
You can use JSX components directly in MDX:
<Button onClick={() => alert("Hello!")}>Click me</Button>
## Code Blocks

View File

@@ -0,0 +1,11 @@
# Hello World
This is a sample MDX file that demonstrates various MDX features.
## Components
You can use JSX components directly in MDX:
<Button onClick={() => alert("Hello!")}>Click me</Button>
## Code Blocks

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,18 @@
{
"name": "bun-mdx-rs-darwin-arm64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"main": "bun-mdx-rs.darwin-arm64.node",
"files": [
"bun-mdx-rs.darwin-arm64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-darwin-x64`
This is the **x86_64-apple-darwin** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,18 @@
{
"name": "bun-mdx-rs-darwin-x64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"x64"
],
"main": "bun-mdx-rs.darwin-x64.node",
"files": [
"bun-mdx-rs.darwin-x64.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-linux-arm64-gnu`
This is the **aarch64-unknown-linux-gnu** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,21 @@
{
"name": "bun-mdx-rs-linux-arm64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "bun-mdx-rs.linux-arm64-gnu.node",
"files": [
"bun-mdx-rs.linux-arm64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"glibc"
]
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-linux-arm64-musl`
This is the **aarch64-unknown-linux-musl** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,21 @@
{
"name": "bun-mdx-rs-linux-arm64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "bun-mdx-rs.linux-arm64-musl.node",
"files": [
"bun-mdx-rs.linux-arm64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"musl"
]
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-linux-x64-gnu`
This is the **x86_64-unknown-linux-gnu** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,21 @@
{
"name": "bun-mdx-rs-linux-x64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "bun-mdx-rs.linux-x64-gnu.node",
"files": [
"bun-mdx-rs.linux-x64-gnu.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"glibc"
]
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-linux-x64-musl`
This is the **x86_64-unknown-linux-musl** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,21 @@
{
"name": "bun-mdx-rs-linux-x64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "bun-mdx-rs.linux-x64-musl.node",
"files": [
"bun-mdx-rs.linux-x64-musl.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
},
"libc": [
"musl"
]
}

View File

@@ -0,0 +1,3 @@
# `bun-mdx-rs-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `bun-mdx-rs`

View File

@@ -0,0 +1,18 @@
{
"name": "bun-mdx-rs-win32-x64-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"x64"
],
"main": "bun-mdx-rs.win32-x64-msvc.node",
"files": [
"bun-mdx-rs.win32-x64-msvc.node"
],
"license": "MIT",
"engines": {
"node": ">= 10"
}
}

View File

@@ -0,0 +1,37 @@
{
"name": "bun-mdx-rs",
"version": "0.0.0",
"main": "index.js",
"types": "index.d.ts",
"napi": {
"name": "bun-mdx-rs",
"triples": {
"additional": [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-musl"
]
}
},
"license": "MIT",
"devDependencies": {
"@napi-rs/cli": "^2.18.4",
"ava": "^6.0.1"
},
"ava": {
"timeout": "3m"
},
"engines": {
"node": ">= 10"
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --platform --release",
"build:debug": "napi build --platform",
"prepublishOnly": "napi prepublish -t npm",
"test": "ava",
"universal": "napi universal",
"version": "napi version"
}
}

View File

@@ -0,0 +1,2 @@
tab_spaces = 2
edition = "2021"

View File

@@ -0,0 +1,55 @@
use bun_native_plugin::{define_bun_plugin, BunLoader, OnBeforeParse};
use mdxjs::{compile, Options as CompileOptions};
use napi_derive::napi;
#[macro_use]
extern crate napi;
define_bun_plugin!("bun-mdx-rs");
#[no_mangle]
pub extern "C" fn bun_mdx_rs(
args: *const bun_native_plugin::sys::OnBeforeParseArguments,
result: *mut bun_native_plugin::sys::OnBeforeParseResult,
) {
let args = unsafe { &*args };
let mut handle = match OnBeforeParse::from_raw(args, result) {
Ok(handle) => handle,
Err(_) => {
return;
}
};
let source_str = match handle.input_source_code() {
Ok(source_str) => source_str,
Err(_) => {
handle.log_error("Failed to fetch source code");
return;
}
};
let mut options = CompileOptions::gfm();
// Leave it as JSX for Bun to handle
options.jsx = true;
let path = match handle.path() {
Ok(path) => path,
Err(e) => {
handle.log_error(&format!("Failed to get path: {:?}", e));
return;
}
};
options.filepath = Some(path.to_string());
match compile(&source_str, &options) {
Ok(compiled) => {
handle.set_output_source_code(compiled, BunLoader::BUN_LOADER_JSX);
}
Err(_) => {
handle.log_error("Failed to compile MDX");
return;
}
}
}

View File

@@ -294,7 +294,7 @@ export abstract class BaseDebugAdapter<T extends Inspector = Inspector>
/**
* Gets the inspector url. This is deprecated and exists for compat.
* @deprecated You should get the inspector directly, and if it's a WebSocketInspector you can access `.url` direclty.
* @deprecated You should get the inspector directly (with .getInspector()), and if it's a WebSocketInspector you can access `.url` direclty.
*/
get url(): string {
// This code has been migrated from a time when the inspector was always a WebSocketInspector.
@@ -305,6 +305,10 @@ export abstract class BaseDebugAdapter<T extends Inspector = Inspector>
throw new Error("Inspector does not offer a URL");
}
public getInspector() {
return this.inspector;
}
abstract start(...args: unknown[]): Promise<boolean>;
/**
@@ -2064,7 +2068,7 @@ export class NodeSocketDebugAdapter extends BaseDebugAdapter<NodeSocketInspector
/**
* The default debug adapter. Connects via WebSocket
*/
export class DebugAdapter extends BaseDebugAdapter<WebSocketInspector> {
export class WebSocketDebugAdapter extends BaseDebugAdapter<WebSocketInspector> {
#process?: ChildProcess;
public constructor(url?: string | URL, untitledDocPath?: string, bunEvalPath?: string) {
@@ -2331,6 +2335,8 @@ export class DebugAdapter extends BaseDebugAdapter<WebSocketInspector> {
}
}
export const DebugAdapter = WebSocketDebugAdapter;
function stoppedReason(reason: JSC.Debugger.PausedEvent["reason"]): DAP.StoppedEvent["reason"] {
switch (reason) {
case "Breakpoint":

View File

@@ -35,7 +35,6 @@ export class NodeSocketInspector extends EventEmitter<InspectorEventMap> impleme
this.#pendingResponses = new Map();
this.#framer = new SocketFramer(socket, message => {
// console.log(message);
if (Array.isArray(message)) {
for (const m of message) {
this.#accept(m);

View File

@@ -1,7 +1,7 @@
import { EventEmitter } from "node:events";
import { WebSocket } from "ws";
import type { Inspector, InspectorEventMap } from "./index";
import type { JSC } from "../protocol";
import type { Inspector, InspectorEventMap } from "./index";
/**
* An inspector that communicates with a debugger over a WebSocket.
@@ -170,6 +170,7 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
#accept(message: string): void {
let data: JSC.Event | JSC.Response;
try {
data = JSON.parse(message);
} catch (cause) {

View File

@@ -0,0 +1,73 @@
#ifndef BUN_NATIVE_BUNDLER_PLUGIN_API_H
#define BUN_NATIVE_BUNDLER_PLUGIN_API_H
#include <stddef.h>
#include <stdint.h>
typedef enum {
BUN_LOADER_JSX = 0,
BUN_LOADER_JS = 1,
BUN_LOADER_TS = 2,
BUN_LOADER_TSX = 3,
BUN_LOADER_CSS = 4,
BUN_LOADER_FILE = 5,
BUN_LOADER_JSON = 6,
BUN_LOADER_TOML = 7,
BUN_LOADER_WASM = 8,
BUN_LOADER_NAPI = 9,
BUN_LOADER_BASE64 = 10,
BUN_LOADER_DATAURL = 11,
BUN_LOADER_TEXT = 12,
} BunLoader;
const BunLoader BUN_LOADER_MAX = BUN_LOADER_TEXT;
typedef struct BunLogOptions {
size_t __struct_size;
const uint8_t *message_ptr;
size_t message_len;
const uint8_t *path_ptr;
size_t path_len;
const uint8_t *source_line_text_ptr;
size_t source_line_text_len;
int8_t level;
int line;
int lineEnd;
int column;
int columnEnd;
} BunLogOptions;
typedef struct {
size_t __struct_size;
void *bun;
const uint8_t *path_ptr;
size_t path_len;
const uint8_t *namespace_ptr;
size_t namespace_len;
uint8_t default_loader;
void *external;
} OnBeforeParseArguments;
typedef struct OnBeforeParseResult {
size_t __struct_size;
uint8_t *source_ptr;
size_t source_len;
uint8_t loader;
int (*fetchSourceCode)(const OnBeforeParseArguments *args,
struct OnBeforeParseResult *result);
void *plugin_source_code_context;
void (*free_plugin_source_code_context)(void *ctx);
void (*log)(const OnBeforeParseArguments *args, BunLogOptions *options);
} OnBeforeParseResult;
typedef enum {
BUN_LOG_LEVEL_VERBOSE = 0,
BUN_LOG_LEVEL_DEBUG = 1,
BUN_LOG_LEVEL_INFO = 2,
BUN_LOG_LEVEL_WARN = 3,
BUN_LOG_LEVEL_ERROR = 4,
} BunLogLevel;
const BunLogLevel BUN_LOG_MAX = BUN_LOG_LEVEL_ERROR;
#endif // BUN_NATIVE_BUNDLER_PLUGIN_API_H

View File

@@ -0,0 +1 @@
target/

286
packages/bun-native-plugin-rs/Cargo.lock generated Normal file
View File

@@ -0,0 +1,286 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bun-native-plugin"
version = "0.1.0"
dependencies = [
"bindgen",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.166"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
[[package]]
name = "libloading"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -0,0 +1,7 @@
[package]
name = "bun-native-plugin"
version = "0.1.0"
edition = "2021"
[build-dependencies]
bindgen = "0.70.1"

View File

@@ -0,0 +1,248 @@
> ⚠️ Note: This is an advanced and experimental API recommended only for plugin developers who are familiar with systems proramming and the C ABI. Use with caution.
# Bun Native Plugins
This crate provides a Rustified wrapper over the Bun's native bundler plugin C API.
Some advantages to _native_ bundler plugins as opposed to regular ones implemented in JS:
- Native plugins take full advantage of Bun's parallelized bundler pipeline and run on multiple threads at the same time
- Unlike JS, native plugins don't need to do the UTF-8 <-> UTF-16 source code string conversions
What are native bundler plugins exactly? Precisely, they are NAPI modules which expose a C ABI function which implement a plugin lifecycle hook.
The currently supported lifecycle hooks are:
- `onBeforeParse` (called immediately before a file is parsed, allows you to modify the source code of the file)
## Getting started
Since native bundler plugins are NAPI modules, the easiest way to get started is to create a new [napi-rs](https://github.com/napi-rs/napi-rs) project:
```bash
bun add -g @napi-rs/cli
napi new
```
Then install this crate:
```bash
cargo add bun-native-plugin
```
Now, inside the `lib.rs` file, expose a C ABI function which has the same function signature as the plugin lifecycle hook that you want to implement.
For example, implementing `onBeforeParse`:
```rs
use bun_native_plugin::{define_bun_plugin, OnBeforeParse};
use napi_derive::napi;
/// Define with the name of the plugin
define_bun_plugin!("replace-foo-with-bar");
/// This is necessary for napi-rs to compile this into a proper NAPI module
#[napi]
pub fn register_bun_plugin() {}
/// Use `no_mangle` so that we can reference this symbol by name later
/// when registering this native plugin in JS.
///
/// Here we'll create a dummy plugin which replaces all occurences of
/// `foo` with `bar`
#[no_mangle]
pub extern "C" fn on_before_parse_plugin_impl(
args: *const bun_native_plugin::sys::OnBeforeParseArguments,
result: *mut bun_native_plugin::sys::OnBeforeParseResult,
) {
let args = unsafe { &*args };
// This returns a handle which is a safe wrapper over the raw
// C API.
let mut handle = OnBeforeParse::from_raw(args, result) {
Ok(handle) => handle,
Err(_) => {
// `OnBeforeParse::from_raw` handles error logging
// so it fine to return here.
return;
}
};
let input_source_code = match handle.input_source_code() {
Ok(source_str) => source_str,
Err(_) => {
// If we encounter an error, we must log it so that
// Bun knows this plugin failed.
handle.log_error("Failed to fetch source code!");
return;
}
};
let loader = handle.output_loader();
let output_source_code = source_str.replace("foo", "bar");
handle.set_output_source_code(output_source_code, loader);
}
```
Then compile this NAPI module. If you using napi-rs, the `package.json` should have a `build` script you can run:
```bash
bun run build
```
This will produce a `.node` file in the project directory.
With the compiled NAPI module, you can now register the plugin from JS:
```js
const result = await Bun.build({
entrypoints: ["index.ts"],
plugins: [
{
name: "replace-foo-with-bar",
setup(build) {
const napiModule = require("path/to/napi_module.node");
// Register the `onBeforeParse` hook to run on all `.ts` files.
// We tell it to use function we implemented inside of our `lib.rs` code.
build.onBeforeParse(
{ filter: /\.ts/ },
{ napiModule, symbol: "on_before_parse_plugin_impl" },
);
},
},
],
});
```
## Very important information
### Error handling and panics
It is highly recommended to avoid panicking as this will crash the runtime. Instead, you must handle errors and log them:
```rs
let input_source_code = match handle.input_source_code() {
Ok(source_str) => source_str,
Err(_) => {
// If we encounter an error, we must log it so that
// Bun knows this plugin failed.
handle.log_error("Failed to fetch source code!");
return;
}
};
```
### Passing state to and from JS: `External`
One way to communicate data from your plugin and JS and vice versa is through the NAPI's [External](https://napi.rs/docs/concepts/external) type.
An External in NAPI is like an opaque pointer to data that can be passed to and from JS. Inside your NAPI module, you can retrieve
the pointer and modify the data.
As an example that extends our getting started example above, let's say you wanted to count the number of `foo`'s that the native plugin encounters.
You would expose a NAPI module function which creates this state. Recall that state in native plugins must be threadsafe. This usually means
that your state must be `Sync`:
```rs
struct PluginState {
foo_count: std::sync::atomic::AtomicU32,
}
#[napi]
pub fn create_plugin_state() -> External<PluginState> {
let external = External::new(PluginState {
foo_count: 0,
});
external
}
#[napi]
pub fn get_foo_count(plugin_state: External<PluginState>) -> u32 {
let plugin_state: &PluginState = &plugin_state;
plugin_state.foo_count.load(std::sync::atomic::Ordering::Relaxed)
}
```
When you register your plugin from Javascript, you call the napi module function to create the external and then pass it:
```js
const napiModule = require("path/to/napi_module.node");
const pluginState = napiModule.createPluginState();
const result = await Bun.build({
entrypoints: ["index.ts"],
plugins: [
{
name: "replace-foo-with-bar",
setup(build) {
build.onBeforeParse(
{ filter: /\.ts/ },
{
napiModule,
symbol: "on_before_parse_plugin_impl",
// pass our NAPI external which contains our plugin state here
external: pluginState,
},
);
},
},
],
});
console.log("Total `foo`s encountered: ", pluginState.getFooCount());
```
Finally, from the native implementation of your plugin, you can extract the external:
```rs
pub extern "C" fn on_before_parse_plugin_impl(
args: *const bun_native_plugin::sys::OnBeforeParseArguments,
result: *mut bun_native_plugin::sys::OnBeforeParseResult,
) {
let args = unsafe { &*args };
let mut handle = OnBeforeParse::from_raw(args, result) {
Ok(handle) => handle,
Err(_) => {
// `OnBeforeParse::from_raw` handles error logging
// so it fine to return here.
return;
}
};
let plugin_state: &PluginState =
// This operation is only safe if you pass in an external when registering the plugin.
// If you don't, this could lead to a segfault or access of undefined memory.
match unsafe { handle.external().and_then(|state| state.ok_or(Error::Unknown)) } {
Ok(state) => state,
Err(_) => {
handle.log_error("Failed to get external!");
return;
}
};
// Fetch our source code again
let input_source_code = match handle.input_source_code() {
Ok(source_str) => source_str,
Err(_) => {
handle.log_error("Failed to fetch source code!");
return;
}
};
// Count the number of `foo`s and add it to our state
let foo_count = source_code.matches("foo").count() as u32;
plugin_state.foo_count.fetch_add(foo_count, std::sync::atomic::Ordering::Relaxed);
}
```
### Concurrency
Your `extern "C"` plugin function can be called _on any thread_ at _any time_ and _multiple times at once_.
Therefore, you must design any state management to be threadsafe

View File

@@ -0,0 +1,20 @@
use std::path::PathBuf;
fn main() {
println!("cargo:rustc-link-search=./headers");
let bindings = bindgen::Builder::default()
.header("wrapper.h")
// Add absolute path to headers directory
.clang_arg("-I./headers")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.rustified_enum("BunLogLevel")
.rustified_enum("BunLoader")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

View File

@@ -0,0 +1,6 @@
import { join } from "node:path";
const dirname = join(import.meta.dir, "../", "bun-native-bundler-plugin-api");
await Bun.$`rm -rf headers`;
await Bun.$`mkdir -p headers`;
await Bun.$`cp -R ${dirname} headers/bun-native-bundler-plugin-api`;

View File

@@ -0,0 +1,79 @@
#ifndef BUN_NATIVE_BUNDLER_PLUGIN_API_H
#define BUN_NATIVE_BUNDLER_PLUGIN_API_H
#include <stddef.h>
#include <stdint.h>
typedef enum {
BUN_LOADER_JSX = 0,
BUN_LOADER_JS = 1,
BUN_LOADER_TS = 2,
BUN_LOADER_TSX = 3,
BUN_LOADER_CSS = 4,
BUN_LOADER_FILE = 5,
BUN_LOADER_JSON = 6,
BUN_LOADER_TOML = 7,
BUN_LOADER_WASM = 8,
BUN_LOADER_NAPI = 9,
BUN_LOADER_BASE64 = 10,
BUN_LOADER_DATAURL = 11,
BUN_LOADER_TEXT = 12,
BUN_LOADER_BUNSH = 13,
BUN_LOADER_SQLITE = 14,
BUN_LOADER_SQLITE_EMBEDDED = 15
} BunLoader;
const BunLoader BUN_LOADER_MAX = BUN_LOADER_SQLITE_EMBEDDED;
typedef struct BunLogOptions {
size_t __struct_size;
const uint8_t* message_ptr;
size_t message_len;
const uint8_t* path_ptr;
size_t path_len;
const uint8_t* source_line_text_ptr;
size_t source_line_text_len;
int8_t level;
int line;
int lineEnd;
int column;
int columnEnd;
} BunLogOptions;
typedef struct {
size_t __struct_size;
void* bun;
const uint8_t* path_ptr;
size_t path_len;
const uint8_t* namespace_ptr;
size_t namespace_len;
uint8_t default_loader;
void *external;
} OnBeforeParseArguments;
typedef struct OnBeforeParseResult {
size_t __struct_size;
uint8_t* source_ptr;
size_t source_len;
uint8_t loader;
int (*fetchSourceCode)(
const OnBeforeParseArguments* args,
struct OnBeforeParseResult* result
);
void* plugin_source_code_context;
void (*free_plugin_source_code_context)(void* ctx);
void (*log)(const OnBeforeParseArguments* args, BunLogOptions* options);
} OnBeforeParseResult;
typedef enum {
BUN_LOG_LEVEL_VERBOSE = 0,
BUN_LOG_LEVEL_DEBUG = 1,
BUN_LOG_LEVEL_INFO = 2,
BUN_LOG_LEVEL_WARN = 3,
BUN_LOG_LEVEL_ERROR = 4,
} BunLogLevel;
const BunLogLevel BUN_LOG_MAX = BUN_LOG_LEVEL_ERROR;
#endif // BUN_NATIVE_BUNDLER_PLUGIN_API_H

View File

@@ -0,0 +1,627 @@
//! > ⚠️ Note: This is an advanced and experimental API recommended only for plugin developers who are familiar with systems proramming and the C ABI. Use with caution.
//!
//! # Bun Native Plugins
//!
//! This crate provides a Rustified wrapper over the Bun's native bundler plugin C API.
//!
//! Some advantages to _native_ bundler plugins as opposed to regular ones implemented in JS:
//!
//! - Native plugins take full advantage of Bun's parallelized bundler pipeline and run on multiple threads at the same time
//! - Unlike JS, native plugins don't need to do the UTF-8 <-> UTF-16 source code string conversions
//!
//! What are native bundler plugins exactly? Precisely, they are NAPI modules which expose a C ABI function which implement a plugin lifecycle hook.
//!
//! The currently supported lifecycle hooks are:
//!
//! - `onBeforeParse` (called immediately before a file is parsed, allows you to modify the source code of the file)
//!
//! ## Getting started
//!
//! Since native bundler plugins are NAPI modules, the easiest way to get started is to create a new [napi-rs](https://github.com/napi-rs/napi-rs) project:
//!
//! ```bash
//! bun add -g @napi-rs/cli
//! napi new
//! ```
//!
//! Then install this crate:
//!
//! ```bash
//! cargo add bun-native-plugin
//! ```
//!
//! Now, inside the `lib.rs` file, expose a C ABI function which has the same function signature as the plugin lifecycle hook that you want to implement.
//!
//! For example, implementing `onBeforeParse`:
//!
//! ```rust
//! use bun_native_plugin::{OnBeforeParse};
//!
//! /// This is necessary for napi-rs to compile this into a proper NAPI module
//! #[napi]
//! pub fn register_bun_plugin() {}
//!
//! /// Use `no_mangle` so that we can reference this symbol by name later
//! /// when registering this native plugin in JS.
//! ///
//! /// Here we'll create a dummy plugin which replaces all occurences of
//! /// `foo` with `bar`
//! #[no_mangle]
//! pub extern "C" fn on_before_parse_plugin_impl(
//! args: *const bun_native_plugin::sys::OnBeforeParseArguments,
//! result: *mut bun_native_plugin::sys::OnBeforeParseResult,
//! ) {
//! let args = unsafe { &*args };
//! let result = unsafe { &mut *result };
//!
//! // This returns a handle which is a safe wrapper over the raw
//! // C API.
//! let mut handle = OnBeforeParse::from_raw(args, result) {
//! Ok(handle) => handle,
//! Err(_) => {
//! // `OnBeforeParse::from_raw` handles error logging
//! // so it fine to return here.
//! return;
//! }
//! };
//!
//! let input_source_code = match handle.input_source_code() {
//! Ok(source_str) => source_str,
//! Err(_) => {
//! // If we encounter an error, we must log it so that
//! // Bun knows this plugin failed.
//! handle.log_error("Failed to fetch source code!");
//! return;
//! }
//! };
//!
//! let loader = handle.output_loader();
//! let output_source_code = source_str.replace("foo", "bar");
//! handle.set_output_source_code(output_source_code, loader);
//! }
//! ```
//!
//! Then compile this NAPI module. If you using napi-rs, the `package.json` should have a `build` script you can run:
//!
//! ```bash
//! bun run build
//! ```
//!
//! This will produce a `.node` file in the project directory.
//!
//! With the compiled NAPI module, you can now register the plugin from JS:
//!
//! ```js
//! const result = await Bun.build({
//! entrypoints: ["index.ts"],
//! plugins: [
//! {
//! name: "replace-foo-with-bar",
//! setup(build) {
//! const napiModule = require("path/to/napi_module.node");
//!
//! // Register the `onBeforeParse` hook to run on all `.ts` files.
//! // We tell it to use function we implemented inside of our `lib.rs` code.
//! build.onBeforeParse(
//! { filter: /\.ts/ },
//! { napiModule, symbol: "on_before_parse_plugin_impl" },
//! );
//! },
//! },
//! ],
//! });
//! ```
//!
//! ## Very important information
//!
//! ### Error handling and panics
//!
//! It is highly recommended to avoid panicking as this will crash the runtime. Instead, you must handle errors and log them:
//!
//! ```rust
//! let input_source_code = match handle.input_source_code() {
//! Ok(source_str) => source_str,
//! Err(_) => {
//! // If we encounter an error, we must log it so that
//! // Bun knows this plugin failed.
//! handle.log_error("Failed to fetch source code!");
//! return;
//! }
//! };
//! ```
//!
//! ### Passing state to and from JS: `External`
//!
//! One way to communicate data from your plugin and JS and vice versa is through the NAPI's [External](https://napi.rs/docs/concepts/external) type.
//!
//! An External in NAPI is like an opaque pointer to data that can be passed to and from JS. Inside your NAPI module, you can retrieve
//! the pointer and modify the data.
//!
//! As an example that extends our getting started example above, let's say you wanted to count the number of `foo`'s that the native plugin encounters.
//!
//! You would expose a NAPI module function which creates this state. Recall that state in native plugins must be threadsafe. This usually means
//! that your state must be `Sync`:
//!
//! ```rust
//! struct PluginState {
//! foo_count: std::sync::atomic::AtomicU32,
//! }
//!
//! #[napi]
//! pub fn create_plugin_state() -> External<PluginState> {
//! let external = External::new(PluginState {
//! foo_count: 0,
//! });
//!
//! external
//! }
//!
//!
//! #[napi]
//! pub fn get_foo_count(plugin_state: External<PluginState>) -> u32 {
//! let plugin_state: &PluginState = &plugin_state;
//! plugin_state.foo_count.load(std::sync::atomic::Ordering::Relaxed)
//! }
//! ```
//!
//! When you register your plugin from Javascript, you call the napi module function to create the external and then pass it:
//!
//! ```js
//! const napiModule = require("path/to/napi_module.node");
//! const pluginState = napiModule.createPluginState();
//!
//! const result = await Bun.build({
//! entrypoints: ["index.ts"],
//! plugins: [
//! {
//! name: "replace-foo-with-bar",
//! setup(build) {
//! build.onBeforeParse(
//! { filter: /\.ts/ },
//! {
//! napiModule,
//! symbol: "on_before_parse_plugin_impl",
//! // pass our NAPI external which contains our plugin state here
//! external: pluginState,
//! },
//! );
//! },
//! },
//! ],
//! });
//!
//! console.log("Total `foo`s encountered: ", pluginState.getFooCount());
//! ```
//!
//! Finally, from the native implementation of your plugin, you can extract the external:
//!
//! ```rust
//! pub extern "C" fn on_before_parse_plugin_impl(
//! args: *const bun_native_plugin::sys::OnBeforeParseArguments,
//! result: *mut bun_native_plugin::sys::OnBeforeParseResult,
//! ) {
//! let args = unsafe { &*args };
//! let result = unsafe { &mut *result };
//!
//! let mut handle = OnBeforeParse::from_raw(args, result) {
//! Ok(handle) => handle,
//! Err(_) => {
//! // `OnBeforeParse::from_raw` handles error logging
//! // so it fine to return here.
//! return;
//! }
//! };
//!
//! let plugin_state: &PluginState =
//! // This operation is only safe if you pass in an external when registering the plugin.
//! // If you don't, this could lead to a segfault or access of undefined memory.
//! match unsafe { handle.external().and_then(|state| state.ok_or(Error::Unknown)) } {
//! Ok(state) => state,
//! Err(_) => {
//! handle.log_error("Failed to get external!");
//! return;
//! }
//! };
//!
//!
//! // Fetch our source code again
//! let input_source_code = match handle.input_source_code() {
//! Ok(source_str) => source_str,
//! Err(_) => {
//! handle.log_error("Failed to fetch source code!");
//! return;
//! }
//! };
//!
//! // Count the number of `foo`s and add it to our state
//! let foo_count = source_code.matches("foo").count() as u32;
//! plugin_state.foo_count.fetch_add(foo_count, std::sync::atomic::Ordering::Relaxed);
//! }
//! ```
//!
//! ### Concurrency
//!
//! Your `extern "C"` plugin function can be called _on any thread_ at _any time_ and _multiple times at once_.
//!
//! Therefore, you must design any state management to be threadsafe
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#[repr(transparent)]
pub struct BunPluginName(*const c_char);
impl BunPluginName {
pub const fn new(ptr: *const c_char) -> Self {
Self(ptr)
}
}
#[macro_export]
macro_rules! define_bun_plugin {
($name:expr) => {
pub static BUN_PLUGIN_NAME_STRING: &str = $name;
#[no_mangle]
pub static BUN_PLUGIN_NAME: bun_native_plugin::BunPluginName =
bun_native_plugin::BunPluginName::new(BUN_PLUGIN_NAME_STRING.as_ptr() as *const _);
#[napi]
fn bun_plugin_register() {}
};
}
unsafe impl Sync for BunPluginName {}
use std::{
any::TypeId,
borrow::Cow,
cell::UnsafeCell,
ffi::{c_char, c_void},
str::Utf8Error,
};
pub mod sys {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[repr(C)]
pub struct TaggedObject<T> {
type_id: TypeId,
pub(crate) object: Option<T>,
}
struct SourceCodeContext {
source_ptr: *mut u8,
source_len: usize,
source_cap: usize,
}
extern "C" fn free_plugin_source_code_context(ctx: *mut c_void) {
// SAFETY: The ctx pointer is a pointer to the `SourceCodeContext` struct we allocated.
unsafe {
drop(Box::from_raw(ctx as *mut SourceCodeContext));
}
}
impl Drop for SourceCodeContext {
fn drop(&mut self) {
if !self.source_ptr.is_null() {
// SAFETY: These fields come from a `String` that we allocated.
unsafe {
drop(String::from_raw_parts(
self.source_ptr,
self.source_len,
self.source_cap,
));
}
}
}
}
pub type BunLogLevel = sys::BunLogLevel;
pub type BunLoader = sys::BunLoader;
fn get_from_raw_str<'a>(ptr: *const u8, len: usize) -> Result<Cow<'a, str>> {
let slice: &'a [u8] = unsafe { std::slice::from_raw_parts(ptr, len) };
// Windows allows invalid UTF-16 strings in the filesystem. These get converted to WTF-8 in Zig.
// Meaning the string may contain invalid UTF-8, we'll have to use the safe checked version.
#[cfg(target_os = "windows")]
{
std::str::from_utf8(slice)
.map(Into::into)
.or_else(|_| Ok(String::from_utf8_lossy(slice)))
}
#[cfg(not(target_os = "windows"))]
{
// SAFETY: The source code comes from Zig, which uses UTF-8, so this should be safe.
std::str::from_utf8(slice)
.map(Into::into)
.or_else(|_| Ok(String::from_utf8_lossy(slice)))
}
}
#[derive(Debug, Clone)]
pub enum Error {
Utf8(Utf8Error),
IncompatiblePluginVersion,
ExternalTypeMismatch,
Unknown,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<Utf8Error> for Error {
fn from(value: Utf8Error) -> Self {
Self::Utf8(value)
}
}
/// A safe handle for the arguments + result struct for the
/// `OnBeforeParse` bundler lifecycle hook.
///
/// This struct acts as a safe wrapper around the raw C API structs
/// (`sys::OnBeforeParseArguments`/`sys::OnBeforeParseResult`) needed to
/// implement the `OnBeforeParse` bundler lifecycle hook.
///
/// To initialize this struct, see the `from_raw` method.
pub struct OnBeforeParse<'a> {
args_raw: &'a sys::OnBeforeParseArguments,
result_raw: *mut sys::OnBeforeParseResult,
compilation_context: *mut SourceCodeContext,
}
impl<'a> OnBeforeParse<'a> {
/// Initialize this struct from references to their raw counterparts.
///
/// This function will do a versioning check to ensure that the plugin
/// is compatible with the current version of Bun. If the plugin is not
/// compatible, it will log an error and return an error result.
///
/// # Example
/// ```rust
/// extern "C" fn on_before_parse_impl(args: *const sys::OnBeforeParseArguments, result: *mut sys::OnBeforeParseResult) {
/// let args = unsafe { &*args };
/// let result = unsafe { &mut *result };
/// let handle = match OnBeforeParse::from_raw(args, result) {
/// Ok(handle) => handle,
/// Err(()) => return,
/// };
/// }
/// ```
pub fn from_raw(
args: &'a sys::OnBeforeParseArguments,
result: *mut sys::OnBeforeParseResult,
) -> Result<Self> {
if args.__struct_size < std::mem::size_of::<sys::OnBeforeParseArguments>()
|| unsafe { (*result).__struct_size } < std::mem::size_of::<sys::OnBeforeParseResult>()
{
let message = "This plugin is not compatible with the current version of Bun.";
let mut log_options = sys::BunLogOptions {
__struct_size: std::mem::size_of::<sys::BunLogOptions>(),
message_ptr: message.as_ptr(),
message_len: message.len(),
path_ptr: args.path_ptr,
path_len: args.path_len,
source_line_text_ptr: std::ptr::null(),
source_line_text_len: 0,
level: BunLogLevel::BUN_LOG_LEVEL_ERROR as i8,
line: 0,
lineEnd: 0,
column: 0,
columnEnd: 0,
};
// SAFETY: The `log` function pointer is guaranteed to be valid by the Bun runtime.
unsafe {
((*result).log.unwrap())(args, &mut log_options);
}
return Err(Error::IncompatiblePluginVersion);
}
Ok(Self {
args_raw: args,
result_raw: result,
compilation_context: std::ptr::null_mut() as *mut _,
})
}
pub fn path(&self) -> Result<Cow<'_, str>> {
get_from_raw_str(self.args_raw.path_ptr, self.args_raw.path_len)
}
pub fn namespace(&self) -> Result<Cow<'_, str>> {
get_from_raw_str(self.args_raw.namespace_ptr, self.args_raw.namespace_len)
}
/// Get the external object from the `OnBeforeParse` arguments.
///
/// The external object is set by the plugin definition inside of JS:
/// ```js
/// await Bun.build({
/// plugins: [
/// {
/// name: "my-plugin",
/// setup(builder) {
/// const native_plugin = require("./native_plugin.node");
/// const external = native_plugin.createExternal();
/// builder.external({ napiModule: native_plugin, symbol: 'onBeforeParse', external });
/// },
/// },
/// ],
/// });
/// ```
///
/// The external object must be created from NAPI for this function to be safe!
///
/// This function will return an error if the external object is not a
/// valid tagged object for the given type.
///
/// This function will return `Ok(None)` if there is no external object
/// set.
///
/// # Example
/// The code to create the external from napi-rs:
/// ```rs
/// #[no_mangle]
/// #[napi]
/// pub fn create_my_external() -> External<MyStruct> {
/// let external = External::new(MyStruct::new());
///
/// external
/// }
/// ```
///
/// The code to extract the external:
/// ```rust
/// let external = match handle.external::<MyStruct>() {
/// Ok(Some(external)) => external,
/// _ => {
/// handle.log_error("Could not get external object.");
/// return;
/// },
/// };
/// ```
pub unsafe fn external<T: 'static + Sync>(&self) -> Result<Option<&'static T>> {
if self.args_raw.external.is_null() {
return Ok(None);
}
let external: *mut TaggedObject<T> = self.args_raw.external as *mut TaggedObject<T>;
unsafe {
if (*external).type_id != TypeId::of::<T>() {
return Err(Error::ExternalTypeMismatch);
}
Ok((*external).object.as_ref())
}
}
/// The same as [`crate::bun_native_plugin::OnBeforeParse::external`], but returns a mutable reference.
///
/// This is unsafe as you must ensure that no other invocation of the plugin
/// simultaneously holds a mutable reference to the external.
pub unsafe fn external_mut<T: 'static + Sync>(&mut self) -> Result<Option<&mut T>> {
if self.args_raw.external.is_null() {
return Ok(None);
}
let external: *mut TaggedObject<T> = self.args_raw.external as *mut TaggedObject<T>;
unsafe {
if (*external).type_id != TypeId::of::<T>() {
return Err(Error::ExternalTypeMismatch);
}
Ok((*external).object.as_mut())
}
}
/// Get the input source code for the current file.
///
/// On Windows, this function may return an `Err(Error::Utf8(...))` if the
/// source code contains invalid UTF-8.
pub fn input_source_code(&self) -> Result<Cow<'_, str>> {
let fetch_result = unsafe {
((*self.result_raw).fetchSourceCode.unwrap())(self.args_raw, self.result_raw)
};
if fetch_result != 0 {
Err(Error::Unknown)
} else {
// SAFETY: We don't hand out mutable references to `result_raw` so dereferencing here is safe.
unsafe {
get_from_raw_str((*self.result_raw).source_ptr, (*self.result_raw).source_len)
}
}
}
/// Set the output source code for the current file.
pub fn set_output_source_code(&mut self, source: String, loader: BunLoader) {
let source_cap = source.capacity();
let source = source.leak();
let source_ptr = source.as_mut_ptr();
let source_len = source.len();
if self.compilation_context.is_null() {
self.compilation_context = Box::into_raw(Box::new(SourceCodeContext {
source_ptr,
source_len,
source_cap,
}));
// SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
unsafe {
(*self.result_raw).plugin_source_code_context =
self.compilation_context as *mut c_void;
(*self.result_raw).free_plugin_source_code_context =
Some(free_plugin_source_code_context);
}
} else {
unsafe {
// SAFETY: If we're here we know that `compilation_context` is not null.
let context = &mut *self.compilation_context;
drop(String::from_raw_parts(
context.source_ptr,
context.source_len,
context.source_cap,
));
context.source_ptr = source_ptr;
context.source_len = source_len;
context.source_cap = source_cap;
}
}
// SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
unsafe {
(*self.result_raw).loader = loader as u8;
(*self.result_raw).source_ptr = source_ptr;
(*self.result_raw).source_len = source_len;
}
}
/// Set the output loader for the current file.
pub fn set_output_loader(&self, loader: BunLogLevel) {
// SAFETY: We don't hand out mutable references to `result_raw` so dereferencing it is safe.
unsafe {
(*self.result_raw).loader = loader as u8;
}
}
/// Get the output loader for the current file.
pub fn output_loader(&self) -> BunLoader {
unsafe { std::mem::transmute((*self.result_raw).loader as u32) }
}
/// Log an error message.
pub fn log_error(&self, message: &str) {
self.log(message, BunLogLevel::BUN_LOG_LEVEL_ERROR)
}
/// Log a message with the given level.
pub fn log(&self, message: &str, level: BunLogLevel) {
let mut log_options = sys::BunLogOptions {
__struct_size: std::mem::size_of::<sys::BunLogOptions>(),
message_ptr: message.as_ptr(),
message_len: message.len(),
path_ptr: self.args_raw.path_ptr,
path_len: self.args_raw.path_len,
source_line_text_ptr: std::ptr::null(),
source_line_text_len: 0,
level: level as i8,
line: 0,
lineEnd: 0,
column: 0,
columnEnd: 0,
};
unsafe {
((*self.result_raw).log.unwrap())(self.args_raw, &mut log_options);
}
}
}

View File

@@ -0,0 +1 @@
#include <bun-native-bundler-plugin-api/bundler_plugin.h>

Binary file not shown.

View File

@@ -97,6 +97,8 @@ export async function getBuild(): Promise<number> {
}
export async function getSemver(tag?: string, build?: number): Promise<string> {
const { tag_name: latest_tag_name } = await getRelease();
const version = latest_tag_name.replace("bun-v", "");
const { tag_name } = await getRelease(tag);
if (tag_name !== "canary") {
return tag_name.replace("bun-v", "");
@@ -106,7 +108,7 @@ export async function getSemver(tag?: string, build?: number): Promise<string> {
}
const sha = await getSha(tag_name, "short");
const date = new Date().toISOString().split("T")[0].replace(/-/g, "");
return `${Bun.version}-canary.${date}.${build}+${sha}`;
return `${version}-canary.${date}.${build}+${sha}`;
}
export function formatTag(tag: string): string {

View File

@@ -3,7 +3,7 @@ import { spawn } from "../spawn";
import { chmod, join, rename, rm, tmp, write } from "../fs";
import { unzipSync } from "zlib";
import type { Platform } from "../platform";
import { os, arch, supportedPlatforms } from "../platform";
import { os, arch, abi, supportedPlatforms } from "../platform";
import { debug, error } from "../console";
declare const version: string;
@@ -12,7 +12,7 @@ declare const owner: string;
export async function importBun(): Promise<string> {
if (!supportedPlatforms.length) {
throw new Error(`Unsupported platform: ${os} ${arch}`);
throw new Error(`Unsupported platform: ${os} ${arch} ${abi || ""}`);
}
for (const platform of supportedPlatforms) {
try {
@@ -121,7 +121,8 @@ async function downloadBun(platform: Platform, dst: string): Promise<void> {
}
export function optimizeBun(path: string): void {
const installScript = os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash";
const installScript =
os === "win32" ? 'powershell -c "irm bun.sh/install.ps1 | iex"' : "curl -fsSL https://bun.sh/install | bash";
try {
rename(path, join(__dirname, "bin", "bun.exe"));
return;

View File

@@ -1,5 +1,5 @@
import { spawn } from "./spawn";
import { read } from "./fs";
import { exists, read } from "./fs";
import { debug } from "./console";
export const os = process.platform;
@@ -10,9 +10,12 @@ export const avx2 =
arch === "x64" &&
((os === "linux" && isLinuxAVX2()) || (os === "darwin" && isDarwinAVX2()) || (os === "win32" && isWindowsAVX2()));
export const abi = os === "linux" && isLinuxMusl() ? "musl" : undefined;
export type Platform = {
os: string;
arch: string;
abi?: "musl";
avx2?: boolean;
bin: string;
exe: string;
@@ -57,6 +60,28 @@ export const platforms: Platform[] = [
bin: "bun-linux-x64-baseline",
exe: "bin/bun",
},
{
os: "linux",
arch: "aarch64",
abi: "musl",
bin: "bun-linux-aarch64-musl",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
abi: "musl",
avx2: true,
bin: "bun-linux-x64-musl",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
abi: "musl",
bin: "bun-linux-x64-musl-baseline",
exe: "bin/bun",
},
{
os: "win32",
arch: "x64",
@@ -73,9 +98,24 @@ export const platforms: Platform[] = [
];
export const supportedPlatforms: Platform[] = platforms
.filter(platform => platform.os === os && platform.arch === arch && (!platform.avx2 || avx2))
.filter(
platform =>
platform.os === os &&
platform.arch === arch &&
(!platform.avx2 || avx2) &&
(!platform.abi || abi === platform.abi),
)
.sort((a, b) => (a.avx2 === b.avx2 ? 0 : a.avx2 ? -1 : 1));
function isLinuxMusl(): boolean {
try {
return exists("/etc/alpine-release");
} catch (error) {
debug("isLinuxMusl failed", error);
return false;
}
}
function isLinuxAVX2(): boolean {
try {
return read("/proc/cpuinfo").includes("avx2");

View File

@@ -1,2 +1,4 @@
node_modules/
dist/
dist/
docs/
*.tgz

View File

@@ -14,6 +14,7 @@
* This module aliases `globalThis.Bun`.
*/
declare module "bun" {
import type { FFIFunctionCallableSymbol } from "bun:ffi";
import type { Encoding as CryptoEncoding } from "crypto";
import type { CipherNameAndProtocol, EphemeralKeyInfo, PeerCertificate } from "tls";
interface Env {
@@ -3873,7 +3874,6 @@ declare module "bun" {
* The default loader for this file extension
*/
loader: Loader;
/**
* Defer the execution of this callback until all other modules have been parsed.
*
@@ -3882,7 +3882,7 @@ declare module "bun" {
defer: () => Promise<void>;
}
type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject | undefined;
type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject | undefined | void;
type OnLoadCallback = (args: OnLoadArgs) => OnLoadResult | Promise<OnLoadResult>;
type OnStartCallback = () => void | Promise<void>;
@@ -3899,6 +3899,10 @@ declare module "bun" {
* The namespace of the importer.
*/
namespace: string;
/**
* The directory to perform file-based resolutions in.
*/
resolveDir: string;
/**
* The kind of import this resolve is for.
*/
@@ -3928,7 +3932,30 @@ declare module "bun" {
args: OnResolveArgs,
) => OnResolveResult | Promise<OnResolveResult | undefined | null> | undefined | null;
type FFIFunctionCallable = Function & {
// Making a nominally typed function so that the user must get it from dlopen
readonly __ffi_function_callable: typeof FFIFunctionCallableSymbol;
};
interface PluginBuilder {
/**
* Register a callback which will be invoked when bundling starts.
* @example
* ```ts
* Bun.plugin({
* setup(builder) {
* builder.onStart(() => {
* console.log("bundle just started!!")
* });
* },
* });
* ```
*/
onStart(callback: OnStartCallback): void;
onBeforeParse(
constraints: PluginConstraints,
callback: { napiModule: unknown; symbol: string; external?: unknown | undefined },
): void;
/**
* Register a callback to load imports with a specific import specifier
* @param constraints The constraints to apply the plugin to
@@ -3961,20 +3988,6 @@ declare module "bun" {
* ```
*/
onResolve(constraints: PluginConstraints, callback: OnResolveCallback): void;
/**
* Register a callback which will be invoked when bundling starts.
* @example
* ```ts
* Bun.plugin({
* setup(builder) {
* builder.onStart(() => {
* console.log("bundle just started!!")
* });
* },
* });
* ```
*/
onStart(callback: OnStartCallback): void;
/**
* The config object passed to `Bun.build` as is. Can be mutated.
*/

View File

@@ -566,17 +566,21 @@ declare module "bun:ffi" {
type ToFFIType<T extends FFITypeOrString> = T extends FFIType ? T : T extends string ? FFITypeStringToType[T] : never;
const FFIFunctionCallableSymbol: unique symbol;
type ConvertFns<Fns extends Symbols> = {
[K in keyof Fns]: (
...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[]
? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] }
: // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
[unknown] extends [Fns[K]["args"]]
? []
: never
) => [unknown] extends [Fns[K]["returns"]] // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
? undefined
: FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
[K in keyof Fns]: {
(
...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[]
? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] }
: // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
[unknown] extends [Fns[K]["args"]]
? []
: never
): [unknown] extends [Fns[K]["returns"]] // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
? undefined
: FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
__ffi_function_callable: typeof FFIFunctionCallableSymbol;
};
};
/**

View File

@@ -3,14 +3,16 @@
"license": "MIT",
"main": "",
"types": "index.d.ts",
"description": "Type definitions for Bun, an incredibly fast JavaScript runtime",
"description": "Type definitions and documentation for Bun, an incredibly fast JavaScript runtime",
"repository": {
"type": "git",
"url": "https://github.com/oven-sh/bun",
"directory": "packages/bun-types"
},
"files": [
"*.d.ts"
"*.d.ts",
"docs/**/*.md",
"docs/*.md"
],
"homepage": "https://bun.sh",
"dependencies": {
@@ -25,7 +27,8 @@
},
"scripts": {
"prebuild": "echo $(pwd)",
"build": "bun scripts/build.ts && bun run fmt",
"copy-docs": "rm -rf docs && cp -r ../../docs/ ./docs",
"build": "bun run copy-docs && bun scripts/build.ts && bun run fmt",
"test": "tsc",
"fmt": "echo $(which biome) && biome format --write ."
},

View File

@@ -623,18 +623,34 @@ inline __attribute__((always_inline)) LIBUS_SOCKET_DESCRIPTOR bsd_bind_listen_fd
setsockopt(listenFd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &optval2, sizeof(optval2));
#endif
} else {
#if defined(SO_REUSEPORT)
int optval2 = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, (void *) &optval2, sizeof(optval2));
#endif
#if defined(SO_REUSEPORT)
if((options & LIBUS_LISTEN_REUSE_PORT)) {
int optval2 = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, (void *) &optval2, sizeof(optval2));
}
#endif
}
#if defined(SO_REUSEADDR)
#ifndef _WIN32
// Unlike on Unix, here we don't set SO_REUSEADDR, because it doesn't just
// allow binding to addresses that are in use by sockets in TIME_WAIT, it
// effectively allows 'stealing' a port which is in use by another application.
// See libuv issue #1360.
int optval3 = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *) &optval3, sizeof(optval3));
#endif
#endif
#ifdef IPV6_V6ONLY
// TODO: revise support to match node.js
// if (listenAddr->ai_family == AF_INET6) {
// int disabled = (options & LIBUS_SOCKET_IPV6_ONLY) != 0;
// setsockopt(listenFd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &disabled, sizeof(disabled));
// }
int disabled = 0;
setsockopt(listenFd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &disabled, sizeof(disabled));
#endif

View File

@@ -1858,9 +1858,6 @@ ssl_wrapped_context_on_close(struct us_internal_ssl_socket_t *s, int code,
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->events.on_close) {
wrapped_context->events.on_close((struct us_socket_t *)s, code, reason);
}
// writting here can cause the context to not be writable anymore but its the
// user responsability to check for that
@@ -1868,6 +1865,10 @@ ssl_wrapped_context_on_close(struct us_internal_ssl_socket_t *s, int code,
wrapped_context->old_events.on_close((struct us_socket_t *)s, code, reason);
}
if (wrapped_context->events.on_close) {
wrapped_context->events.on_close((struct us_socket_t *)s, code, reason);
}
us_socket_context_unref(0, wrapped_context->tcp_context);
return s;
}
@@ -1880,9 +1881,6 @@ ssl_wrapped_context_on_writable(struct us_internal_ssl_socket_t *s) {
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->events.on_writable) {
wrapped_context->events.on_writable((struct us_socket_t *)s);
}
// writting here can cause the context to not be writable anymore but its the
// user responsability to check for that
@@ -1890,6 +1888,10 @@ ssl_wrapped_context_on_writable(struct us_internal_ssl_socket_t *s) {
wrapped_context->old_events.on_writable((struct us_socket_t *)s);
}
if (wrapped_context->events.on_writable) {
wrapped_context->events.on_writable((struct us_socket_t *)s);
}
return s;
}
@@ -1916,14 +1918,14 @@ ssl_wrapped_context_on_timeout(struct us_internal_ssl_socket_t *s) {
struct us_wrapped_socket_context_t *wrapped_context =
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->old_events.on_timeout) {
wrapped_context->old_events.on_timeout((struct us_socket_t *)s);
}
if (wrapped_context->events.on_timeout) {
wrapped_context->events.on_timeout((struct us_socket_t *)s);
}
if (wrapped_context->old_events.on_timeout) {
wrapped_context->old_events.on_timeout((struct us_socket_t *)s);
}
return s;
}
@@ -1935,15 +1937,14 @@ ssl_wrapped_context_on_long_timeout(struct us_internal_ssl_socket_t *s) {
struct us_wrapped_socket_context_t *wrapped_context =
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->old_events.on_long_timeout) {
wrapped_context->old_events.on_long_timeout((struct us_socket_t *)s);
}
if (wrapped_context->events.on_long_timeout) {
wrapped_context->events.on_long_timeout((struct us_socket_t *)s);
}
if (wrapped_context->old_events.on_long_timeout) {
wrapped_context->old_events.on_long_timeout((struct us_socket_t *)s);
}
return s;
}
@@ -1954,14 +1955,13 @@ ssl_wrapped_context_on_end(struct us_internal_ssl_socket_t *s) {
struct us_wrapped_socket_context_t *wrapped_context =
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->events.on_end) {
wrapped_context->events.on_end((struct us_socket_t *)s);
}
if (wrapped_context->old_events.on_end) {
wrapped_context->old_events.on_end((struct us_socket_t *)s);
}
if (wrapped_context->events.on_end) {
wrapped_context->events.on_end((struct us_socket_t *)s);
}
return s;
}
@@ -1973,13 +1973,13 @@ ssl_wrapped_on_connect_error(struct us_internal_ssl_socket_t *s, int code) {
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->old_events.on_connect_error) {
wrapped_context->old_events.on_connect_error((struct us_connecting_socket_t *)s, code);
}
if (wrapped_context->events.on_connect_error) {
wrapped_context->events.on_connect_error((struct us_connecting_socket_t *)s, code);
}
if (wrapped_context->old_events.on_connect_error) {
wrapped_context->old_events.on_connect_error((struct us_connecting_socket_t *)s, code);
}
return s;
}
@@ -1990,14 +1990,14 @@ ssl_wrapped_on_socket_connect_error(struct us_internal_ssl_socket_t *s, int code
struct us_wrapped_socket_context_t *wrapped_context =
(struct us_wrapped_socket_context_t *)us_internal_ssl_socket_context_ext(
context);
if (wrapped_context->old_events.on_connecting_socket_error) {
wrapped_context->old_events.on_connecting_socket_error((struct us_socket_t *)s, code);
}
if (wrapped_context->events.on_connecting_socket_error) {
wrapped_context->events.on_connecting_socket_error((struct us_socket_t *)s, code);
}
if (wrapped_context->old_events.on_connecting_socket_error) {
wrapped_context->old_events.on_connecting_socket_error((struct us_socket_t *)s, code);
}
return s;
}
@@ -2153,6 +2153,8 @@ us_socket_context_on_socket_connect_error(
socket->ssl_read_wants_write = 0;
socket->fatal_error = 0;
socket->handshake_state = HANDSHAKE_PENDING;
// always resume the socket
us_socket_resume(1, &socket->s);
return socket;
}

View File

@@ -96,6 +96,10 @@ enum {
LIBUS_LISTEN_EXCLUSIVE_PORT = 1,
/* Allow socket to keep writing after readable side closes */
LIBUS_SOCKET_ALLOW_HALF_OPEN = 2,
/* Setting reusePort allows multiple sockets on the same host to bind to the same port. Incoming connections are distributed by the operating system to listening sockets. This option is available only on some platforms, such as Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+*/
LIBUS_LISTEN_REUSE_PORT = 4,
/* etting ipv6Only will disable dual-stack support, i.e., binding to host :: won't make 0.0.0.0 be bound.*/
LIBUS_SOCKET_IPV6_ONLY = 8,
};
/* Library types publicly available */

View File

@@ -22,6 +22,7 @@
#include <sys/ioctl.h>
#endif
/* The loop has 2 fallthrough polls */
void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct us_loop_t *loop),
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop)) {

View File

@@ -1,6 +1,6 @@
{
"name": "bun-vscode",
"version": "0.0.15",
"version": "0.0.22",
"author": "oven",
"repository": {
"type": "git",
@@ -21,7 +21,6 @@
"activationEvents": [
"onStartupFinished"
],
"browser": "dist/web-extension.js",
"bugs": {
"url": "https://github.com/oven-sh/bun/issues"
},
@@ -46,6 +45,18 @@
"scope": "window",
"default": null
},
"bun.diagnosticsSocket.enabled": {
"type": "boolean",
"description": "If Bun extension should communicate with Bun over a socket to show errors in editor.",
"scope": "window",
"default": true
},
"bun.bunlockb.enabled": {
"type": "boolean",
"description": "If visual lockfile viewer (`bun.lockb`) should be enabled ",
"scope": "window",
"default": true
},
"bun.debugTerminal.enabled": {
"type": "boolean",
"description": "If Bun should be added to the JavaScript Debug Terminal.",
@@ -321,7 +332,7 @@
]
},
"description": "The Visual Studio Code extension for Bun.",
"displayName": "Bun for Visual Studio Code",
"displayName": "Bun",
"engines": {
"vscode": "^1.60.0"
},
@@ -358,4 +369,4 @@
"../bun-debug-adapter-protocol",
"../bun-inspector-protocol"
]
}
}

View File

@@ -53,3 +53,7 @@ export function activate(context: vscode.ExtensionContext) {
// Only register for text editors
context.subscriptions.push(vscode.commands.registerTextEditorCommand("extension.bun.runUnsavedCode", runUnsavedCode));
}
export function getConfig<T>(path: string, scope?: vscode.ConfigurationScope) {
return vscode.workspace.getConfiguration("bun", scope).get<T>(path);
}

View File

@@ -4,12 +4,13 @@ import { join } from "node:path";
import * as vscode from "vscode";
import {
type DAP,
DebugAdapter,
getAvailablePort,
getRandomId,
TCPSocketSignal,
UnixSignal,
WebSocketDebugAdapter,
} from "../../../bun-debug-adapter-protocol";
import { getConfig } from "../extension";
export const DEBUG_CONFIGURATION: vscode.DebugConfiguration = {
type: "bun",
@@ -120,6 +121,7 @@ async function injectDebugTerminal(terminal: vscode.Terminal): Promise<void> {
...env,
"BUN_INSPECT": `${adapter.url}?${query}`,
"BUN_INSPECT_NOTIFY": signal.url,
BUN_INSPECT_CONNECT_TO: "",
},
});
@@ -239,7 +241,7 @@ class FileDebugSession extends DebugSession {
// If these classes are moved/published, we should make sure
// we remove these non-null assertions so consumers of
// this lib are not running into these hard
adapter!: DebugAdapter;
adapter!: WebSocketDebugAdapter;
sessionId?: string;
untitledDocPath?: string;
bunEvalPath?: string;
@@ -263,7 +265,7 @@ class FileDebugSession extends DebugSession {
: `ws+unix://${tmpdir()}/${uniqueId}.sock`;
const { untitledDocPath, bunEvalPath } = this;
this.adapter = new DebugAdapter(url, untitledDocPath, bunEvalPath);
this.adapter = new WebSocketDebugAdapter(url, untitledDocPath, bunEvalPath);
if (untitledDocPath) {
this.adapter.on("Adapter.response", (response: DebugProtocolResponse) => {
@@ -351,6 +353,7 @@ class TerminalDebugSession extends FileDebugSession {
env: {
"BUN_INSPECT": `${this.adapter.url}?wait=1`,
"BUN_INSPECT_NOTIFY": this.signal.url,
BUN_INSPECT_CONNECT_TO: "",
},
isTransient: true,
iconPath: new vscode.ThemeIcon("debug-console"),
@@ -370,10 +373,6 @@ function getRuntime(scope?: vscode.ConfigurationScope): string {
return "bun";
}
function getConfig<T>(path: string, scope?: vscode.ConfigurationScope) {
return vscode.workspace.getConfiguration("bun", scope).get<T>(path);
}
export async function runUnsavedCode() {
const editor = vscode.window.activeTextEditor;
if (!editor || !editor.document.isUntitled) return;

View File

@@ -1,6 +1,7 @@
import * as fs from "node:fs/promises";
import { Socket } from "node:net";
import * as os from "node:os";
import { inspect } from "node:util";
import * as vscode from "vscode";
import {
getAvailablePort,
@@ -10,6 +11,7 @@ import {
} from "../../../../bun-debug-adapter-protocol";
import type { JSC } from "../../../../bun-inspector-protocol";
import { typedGlobalState } from "../../global-state";
import { getConfig } from "../../extension";
const output = vscode.window.createOutputChannel("Bun - Diagnostics");
@@ -76,7 +78,7 @@ class BunDiagnosticsManager {
private static async getOrRecreateSignal(context: vscode.ExtensionContext) {
const globalState = typedGlobalState(context.globalState);
const existing = globalState.get("BUN_INSPECT_NOTIFY");
const existing = globalState.get("BUN_INSPECT_CONNECT_TO");
const isWin = os.platform() === "win32";
@@ -100,7 +102,7 @@ class BunDiagnosticsManager {
if (isWin) {
const port = await getAvailablePort();
await globalState.update("BUN_INSPECT_NOTIFY", {
await globalState.update("BUN_INSPECT_CONNECT_TO", {
type: "tcp",
port,
});
@@ -111,7 +113,7 @@ class BunDiagnosticsManager {
} else {
const signal = new UnixSignal();
await globalState.update("BUN_INSPECT_NOTIFY", {
await globalState.update("BUN_INSPECT_CONNECT_TO", {
type: "unix",
url: signal.url,
});
@@ -122,11 +124,21 @@ class BunDiagnosticsManager {
}
}
// private static getOrCreateOldVersionInspectURL = createGlobalStateGenerationFn(
// "DIAGNOSTICS_BUN_INSPECT",
// async () => {
// const url =
// process.platform === "win32"
// ? `ws://127.0.0.1:${await getAvailablePort()}/${getRandomId()}`
// : `ws+unix://${os.tmpdir()}/${getRandomId()}.sock`;
// return url;
// },
// );
public static async initialize(context: vscode.ExtensionContext) {
const signal = await BunDiagnosticsManager.getOrRecreateSignal(context);
await signal.ready;
return new BunDiagnosticsManager(context, signal);
}
@@ -146,6 +158,10 @@ class BunDiagnosticsManager {
output.appendLine(`Received inspector event: ${e.method}`);
});
debugAdapter.on("Inspector.error", e => {
output.appendLine(inspect(e, true, null));
});
debugAdapter.on("LifecycleReporter.error", event => this.handleLifecycleError(event));
const ok = await debugAdapter.start();
@@ -159,7 +175,7 @@ class BunDiagnosticsManager {
debugAdapter.initialize({
adapterID: "bun-vsc-terminal-debug-adapter",
enableControlFlowProfiler: true,
enableControlFlowProfiler: false,
enableLifecycleAgentReporter: true,
sendImmediatePreventExit: false,
enableDebugger: false, // Performance overhead when debugger is enabled
@@ -203,8 +219,6 @@ class BunDiagnosticsManager {
const [line = null, col = null] = event.lineColumns.slice(i * 2, i * 2 + 2);
output.appendLine(`Adding related information for ${url} at ${line}:${col}`);
if (line === null || col === null) {
return [];
}
@@ -252,10 +266,14 @@ const description = new vscode.MarkdownString(
);
export async function registerDiagnosticsSocket(context: vscode.ExtensionContext) {
context.environmentVariableCollection.clear();
context.environmentVariableCollection.description = description;
if (!getConfig("diagnosticsSocket.enabled")) return;
const manager = await BunDiagnosticsManager.initialize(context);
context.environmentVariableCollection.replace("BUN_INSPECT_NOTIFY", manager.signalUrl);
context.environmentVariableCollection.replace("BUN_INSPECT_CONNECT_TO", manager.signalUrl);
context.subscriptions.push(manager);
}

View File

@@ -1,6 +1,7 @@
import { spawn } from "node:child_process";
import * as vscode from "vscode";
import { styleLockfile } from "./lockfile.style";
import { getConfig } from "../../extension";
export type BunLockfile = vscode.CustomDocument & {
readonly preview: string;
@@ -36,6 +37,11 @@ export class BunLockfileEditorProvider implements vscode.CustomReadonlyEditorPro
}
function renderLockfile({ webview }: vscode.WebviewPanel, preview: string, extensionUri: vscode.Uri): void {
if (!getConfig("bunlockb.enabled")) {
webview.html = "<code>bun.bunlockb</code> config option is disabled."
return
}
const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, "assets", "vscode.css"));
const lockfileContent = styleLockfile(preview);

View File

@@ -4,6 +4,7 @@
import * as vscode from "vscode";
import { debugCommand } from "../debug";
import { BunTask } from "./tasks";
import { getConfig } from "../../extension";
/**
* Parses tasks defined in the package.json.

View File

@@ -1,7 +1,9 @@
import { ExtensionContext } from "vscode";
export const GLOBAL_STATE_VERSION = 1;
export type GlobalStateTypes = {
BUN_INSPECT_NOTIFY:
BUN_INSPECT_CONNECT_TO:
| {
type: "tcp";
port: number;
@@ -10,8 +12,16 @@ export type GlobalStateTypes = {
type: "unix";
url: string;
};
DIAGNOSTICS_BUN_INSPECT: string;
};
export async function clearGlobalState(gs: ExtensionContext["globalState"]) {
const tgs = typedGlobalState(gs);
await Promise.all(tgs.keys().map(key => tgs.update(key, undefined as never)));
}
export function typedGlobalState(state: ExtensionContext["globalState"]) {
return state as {
get<K extends keyof GlobalStateTypes>(key: K): GlobalStateTypes[K] | undefined;
@@ -37,4 +47,19 @@ export function typedGlobalState(state: ExtensionContext["globalState"]) {
};
}
export function createGlobalStateGenerationFn<T extends keyof GlobalStateTypes>(
key: T,
resolve: () => Promise<GlobalStateTypes[T]>,
) {
return async (gs: ExtensionContext["globalState"]) => {
const value = (gs as TypedGlobalState).get(key);
if (value) return value;
const next = await resolve();
await (gs as TypedGlobalState).update(key, next);
return next;
};
}
export type TypedGlobalState = ReturnType<typeof typedGlobalState>;

View File

@@ -83,6 +83,7 @@ async function doBuildkiteAgent(action) {
}
`;
writeFile(servicePath, service, { mode: 0o755 });
writeFile(`/etc/conf.d/buildkite-agent`, `rc_ulimit="-n 262144"`);
await spawnSafe(["rc-update", "add", "buildkite-agent", "default"], { stdio: "inherit", privileged: true });
}
@@ -93,7 +94,7 @@ async function doBuildkiteAgent(action) {
Description=Buildkite Agent
After=syslog.target
After=network-online.target
[Service]
Type=simple
User=${username}
@@ -105,7 +106,7 @@ async function doBuildkiteAgent(action) {
[Journal]
Storage=persistent
StateDirectory=${escape(agentLogPath)}
[Install]
WantedBy=multi-user.target
`;

View File

@@ -124,6 +124,22 @@ append_to_file() {
done
}
append_to_file_sudo() {
file="$1"
content="$2"
if ! [ -f "$file" ]; then
execute_sudo mkdir -p "$(dirname "$file")"
execute_sudo touch "$file"
fi
echo "$content" | while read -r line; do
if ! grep -q "$line" "$file"; then
echo "$line" | execute_sudo tee "$file" > /dev/null
fi
done
}
append_to_profile() {
content="$1"
profiles=".profile .zprofile .bash_profile .bashrc .zshrc"
@@ -309,7 +325,7 @@ check_package_manager() {
pm="brew"
;;
linux)
if [ -f "$(which apt-get)" ]; then
if [ -f "$(which apt)" ]; then
pm="apt"
elif [ -f "$(which dnf)" ]; then
pm="dnf"
@@ -371,10 +387,7 @@ check_user() {
package_manager() {
case "$pm" in
apt)
while ! sudo -n apt-get update -y; do
sleep 1
done
execute_sudo apt-get "$@"
execute_sudo apt "$@"
;;
dnf)
case "$distro" in
@@ -864,7 +877,7 @@ create_buildkite_user() {
execute_sudo mkdir -p "$path"
execute_sudo chown -R "$user:$group" "$path"
done
files="/var/run/buildkite-agent/buildkite-agent.pid"
for file in $files; do
execute_sudo touch "$file"
@@ -966,6 +979,11 @@ install_chrome_dependencies() {
esac
}
raise_file_descriptor_limit() {
append_to_file_sudo /etc/security/limits.conf '* soft nofile 262144'
append_to_file_sudo /etc/security/limits.conf '* hard nofile 262144'
}
main() {
check_features "$@"
check_operating_system
@@ -976,6 +994,7 @@ main() {
install_common_software
install_build_essentials
install_chrome_dependencies
raise_file_descriptor_limit # XXX: temporary
}
main "$@"

20
scripts/check-node.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
i=0
j=0
for x in $(git ls-files test/js/node/test/parallel --exclude-standard --others | grep test-$1)
do
i=$((i+1))
echo ./$x
if timeout 2 $PWD/build/debug/bun-debug ./$x
then
j=$((j+1))
git add ./$x
fi
echo
echo
done
echo $i tests tested
echo $j tests passed

View File

@@ -708,12 +708,6 @@ function getCloudInit(cloudInit) {
// https://cloudinit.readthedocs.io/en/stable/
return `#cloud-config
package_update: true
packages:
- curl
- ca-certificates
- openssh-server
write_files:
- path: /etc/ssh/sshd_config
content: |

View File

@@ -28,9 +28,11 @@ import {
getEnv,
getFileUrl,
getWindowsExitReason,
isArm64,
isBuildkite,
isCI,
isGithubAction,
isMacOS,
isWindows,
printEnvironment,
startGroup,
@@ -206,6 +208,31 @@ async function runTests() {
if (results.every(({ ok }) => ok)) {
for (const testPath of tests) {
const title = relative(cwd, join(testsPath, testPath)).replace(/\\/g, "/");
if (title.startsWith("test/js/node/test/parallel/")) {
await runTest(title, async () => {
const { ok, error, stdout } = await spawnBun(execPath, {
cwd: cwd,
args: [title],
timeout: spawnTimeout,
env: {
FORCE_COLOR: "0",
},
stdout: chunk => pipeTestStdout(process.stdout, chunk),
stderr: chunk => pipeTestStdout(process.stderr, chunk),
});
return {
testPath: title,
ok,
status: ok ? "pass" : "fail",
error,
errors: [],
tests: [],
stdout,
stdoutPreview: "",
};
});
continue;
}
await runTest(title, async () => spawnBunTest(execPath, join("test", testPath)));
}
}
@@ -495,11 +522,11 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) {
stderr,
});
} finally {
try {
rmSync(tmpdirPath, { recursive: true, force: true });
} catch (error) {
console.warn(error);
}
// try {
// rmSync(tmpdirPath, { recursive: true, force: true });
// } catch (error) {
// console.warn(error);
// }
}
}
@@ -770,7 +797,7 @@ function isJavaScriptTest(path) {
* @returns {boolean}
*/
function isTest(path) {
if (path.replaceAll(sep, "/").includes("/test-cluster-") && path.endsWith(".js")) return true;
if (path.startsWith("js/node/test/parallel/") && isMacOS && isArm64) return true;
if (path.replaceAll(sep, "/").startsWith("js/node/cluster/test-") && path.endsWith(".ts")) return true;
return isTestStrict(path);
}

View File

@@ -23,6 +23,8 @@ export const isMacOS = process.platform === "darwin";
export const isLinux = process.platform === "linux";
export const isPosix = isMacOS || isLinux;
export const isArm64 = process.arch === "arm64";
/**
* @param {string} name
* @param {boolean} [required]
@@ -1118,17 +1120,6 @@ export function getFileUrl(filename, line) {
}
const filePath = (cwd ? relative(cwd, filename) : filename).replace(/\\/g, "/");
const pullRequest = getPullRequest();
if (pullRequest) {
const fileMd5 = createHash("sha256").update(filePath).digest("hex");
const url = new URL(`pull/${pullRequest}/files#diff-${fileMd5}`, `${baseUrl}/`);
if (typeof line !== "undefined") {
return new URL(`R${line}`, url);
}
return url;
}
const commit = getCommit(cwd);
const url = new URL(`blob/${commit}/${filePath}`, `${baseUrl}/`).toString();
if (typeof line !== "undefined") {
@@ -2135,6 +2126,11 @@ export function printEnvironment() {
console.log("Working Directory:", process.cwd());
console.log("Temporary Directory:", tmpdir());
});
if (isPosix) {
startGroup("ulimit -a", () => {
spawnSync(["ulimit", "-a"], { stdio: ["ignore", "inherit", "inherit"] });
});
}
if (isCI) {
startGroup("Environment", () => {

View File

@@ -130,7 +130,20 @@ pub fn raiseIgnoringPanicHandler(sig: bun.SignalCode) noreturn {
Output.flush();
Output.Source.Stdio.restore();
// clear segfault handler
bun.crash_handler.resetSegfaultHandler();
// clear signal handler
if (bun.Environment.os != .windows) {
var sa: std.c.Sigaction = .{
.handler = .{ .handler = std.posix.SIG.DFL },
.mask = std.posix.empty_sigset,
.flags = std.posix.SA.RESETHAND,
};
_ = std.c.sigaction(@intFromEnum(sig), &sa, null);
}
// kill self
_ = std.c.raise(@intFromEnum(sig));
std.c.abort();
}

View File

@@ -498,7 +498,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
}
pub fn getOrPut(self: *Self, denormalized_key: []const u8) !Result {
const key = if (comptime remove_trailing_slashes) std.mem.trimRight(u8, denormalized_key, "/") else denormalized_key;
const key = if (comptime remove_trailing_slashes) std.mem.trimRight(u8, denormalized_key, std.fs.path.sep_str) else denormalized_key;
const _key = bun.hash(key);
self.mutex.lock();
@@ -526,7 +526,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
}
pub fn get(self: *Self, denormalized_key: []const u8) ?*ValueType {
const key = if (comptime remove_trailing_slashes) std.mem.trimRight(u8, denormalized_key, "/") else denormalized_key;
const key = if (comptime remove_trailing_slashes) std.mem.trimRight(u8, denormalized_key, std.fs.path.sep_str) else denormalized_key;
const _key = bun.hash(key);
self.mutex.lock();
defer self.mutex.unlock();
@@ -588,7 +588,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
defer self.mutex.unlock();
const key = if (comptime remove_trailing_slashes)
std.mem.trimRight(u8, denormalized_key, "/")
std.mem.trimRight(u8, denormalized_key, std.fs.path.sep_str)
else
denormalized_key;

View File

@@ -99,8 +99,8 @@ pub const Features = struct {
pub var https_server: usize = 0;
/// Set right before JSC::initialize is called
pub var jsc: usize = 0;
/// Set when kit.DevServer is initialized
pub var kit_dev: usize = 0;
/// Set when bake.DevServer is initialized
pub var dev_server: usize = 0;
pub var lifecycle_scripts: usize = 0;
pub var loaders: usize = 0;
pub var lockfile_migration_from_package_lock: usize = 0;

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