Compare commits

..

8 Commits

Author SHA1 Message Date
Alistair Smith
d53e32df89 smol change 2025-08-22 16:39:58 -07:00
autofix-ci[bot]
b0adecb4bb [autofix.ci] apply automated fixes 2025-08-22 23:38:57 +00:00
Claude Bot
724599ce81 Implement async support for onEnd plugin callbacks
- Make runOnEndPlugins async to properly handle promise-returning callbacks
- Add serial execution with await for async onEnd callbacks
- Update TypeScript interface to reflect async capability
- Add comprehensive test for async onEnd callback behavior
- Document current limitation: build completion is not delayed for async callbacks
- Fix missing expect() assertion in bundler plugin test
- All onEnd functionality now works correctly with proper error handling
2025-08-22 23:36:52 +00:00
Claude Bot
ed5168086d Fix missing assertion in onEnd plugin test
Replace console.log with proper expect() assertion to verify onEnd callback was called
2025-08-22 23:05:48 +00:00
autofix-ci[bot]
7ae25106c3 [autofix.ci] apply automated fixes 2025-08-22 22:48:45 +00:00
Claude Bot
805a90dbcc Implement build.onEnd plugin hook functionality
- Add onEnd callback registration and execution to BundlerPlugin
- Implement C++ bridge in Bun__runOnEndPlugins to call JavaScript callbacks
- Store onEnd callbacks per plugin instance and execute on build completion
- Convert build logs to esbuild-compatible errors/warnings format
- Add comprehensive tests for onEnd validation and execution
- Support multiple plugins with onEnd callbacks
2025-08-22 22:47:08 +00:00
autofix-ci[bot]
62d651420a [autofix.ci] apply automated fixes 2025-08-22 20:26:08 +00:00
Claude Bot
108eb6efce Start implementing build.onEnd hook
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 20:24:28 +00:00
914 changed files with 15668 additions and 48233 deletions

View File

@@ -434,17 +434,11 @@ function getBuildEnv(target, options) {
* @param {PipelineOptions} options
* @returns {string}
*/
function getBuildCommand(target, options, label) {
function getBuildCommand(target, options) {
const { profile } = target;
const buildProfile = profile || "release";
if (target.os === "windows" && label === "build-bun") {
// Only sign release builds, not canary builds (DigiCert charges per signature)
const enableSigning = !options.canary ? " -DENABLE_WINDOWS_CODESIGNING=ON" : "";
return `bun run build:${buildProfile}${enableSigning}`;
}
return `bun run build:${buildProfile}`;
const label = profile || "release";
return `bun run build:${label}`;
}
/**
@@ -540,7 +534,7 @@ function getLinkBunStep(platform, options) {
BUN_LINK_ONLY: "ON",
...getBuildEnv(platform, options),
},
command: `${getBuildCommand(platform, options, "build-bun")} --target bun`,
command: `${getBuildCommand(platform, options)} --target bun`,
};
}

View File

@@ -1,464 +0,0 @@
# Windows Code Signing Script for Bun
# Uses DigiCert KeyLocker for Authenticode signing
# Native PowerShell implementation - no path translation issues
param(
[Parameter(Mandatory=$true)]
[string]$BunProfileExe,
[Parameter(Mandatory=$true)]
[string]$BunExe
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
# Logging functions
function Log-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Cyan
}
function Log-Success {
param([string]$Message)
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
}
function Log-Error {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
function Log-Debug {
param([string]$Message)
if ($env:DEBUG -eq "true" -or $env:DEBUG -eq "1") {
Write-Host "[DEBUG] $Message" -ForegroundColor Gray
}
}
# Load Visual Studio environment if not already loaded
function Ensure-VSEnvironment {
if ($null -eq $env:VSINSTALLDIR) {
Log-Info "Loading Visual Studio environment..."
$vswhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
if (!(Test-Path $vswhere)) {
throw "Command not found: vswhere (did you install Visual Studio?)"
}
$vsDir = & $vswhere -prerelease -latest -property installationPath
if ($null -eq $vsDir) {
$vsDir = Get-ChildItem -Path "C:\Program Files\Microsoft Visual Studio\2022" -Directory -ErrorAction SilentlyContinue
if ($null -eq $vsDir) {
throw "Visual Studio directory not found."
}
$vsDir = $vsDir.FullName
}
Push-Location $vsDir
try {
$vsShell = Join-Path -Path $vsDir -ChildPath "Common7\Tools\Launch-VsDevShell.ps1"
. $vsShell -Arch amd64 -HostArch amd64
} finally {
Pop-Location
}
Log-Success "Visual Studio environment loaded"
}
if ($env:VSCMD_ARG_TGT_ARCH -eq "x86") {
throw "Visual Studio environment is targeting 32 bit, but only 64 bit is supported."
}
}
# Check for required environment variables
function Check-Environment {
Log-Info "Checking environment variables..."
$required = @{
"SM_API_KEY" = $env:SM_API_KEY
"SM_CLIENT_CERT_PASSWORD" = $env:SM_CLIENT_CERT_PASSWORD
"SM_KEYPAIR_ALIAS" = $env:SM_KEYPAIR_ALIAS
"SM_HOST" = $env:SM_HOST
"SM_CLIENT_CERT_FILE" = $env:SM_CLIENT_CERT_FILE
}
$missing = @()
foreach ($key in $required.Keys) {
if ([string]::IsNullOrEmpty($required[$key])) {
$missing += $key
} else {
Log-Debug "$key is set (length: $($required[$key].Length))"
}
}
if ($missing.Count -gt 0) {
throw "Missing required environment variables: $($missing -join ', ')"
}
Log-Success "All required environment variables are present"
}
# Setup certificate file
function Setup-Certificate {
Log-Info "Setting up certificate..."
# Always try to decode as base64 first
# If it fails, then treat as file path
try {
Log-Info "Attempting to decode certificate as base64..."
Log-Debug "Input string length: $($env:SM_CLIENT_CERT_FILE.Length) characters"
$tempCertPath = Join-Path $env:TEMP "digicert_cert_$(Get-Random).p12"
# Try to decode as base64
$certBytes = [System.Convert]::FromBase64String($env:SM_CLIENT_CERT_FILE)
[System.IO.File]::WriteAllBytes($tempCertPath, $certBytes)
# Validate the decoded certificate size
$fileSize = (Get-Item $tempCertPath).Length
if ($fileSize -lt 100) {
throw "Decoded certificate too small: $fileSize bytes (expected >100 bytes)"
}
# Update environment to point to file
$env:SM_CLIENT_CERT_FILE = $tempCertPath
Log-Success "Certificate decoded and written to: $tempCertPath"
Log-Debug "Decoded certificate file size: $fileSize bytes"
# Register cleanup
$global:TEMP_CERT_PATH = $tempCertPath
} catch {
# If base64 decode fails, check if it's a file path
Log-Info "Base64 decode failed, checking if it's a file path..."
Log-Debug "Decode error: $_"
if (Test-Path $env:SM_CLIENT_CERT_FILE) {
$fileSize = (Get-Item $env:SM_CLIENT_CERT_FILE).Length
# Validate file size
if ($fileSize -lt 100) {
throw "Certificate file too small: $fileSize bytes at $env:SM_CLIENT_CERT_FILE (possibly corrupted)"
}
Log-Info "Using certificate file: $env:SM_CLIENT_CERT_FILE"
Log-Debug "Certificate file size: $fileSize bytes"
} else {
throw "SM_CLIENT_CERT_FILE is neither valid base64 nor an existing file: $env:SM_CLIENT_CERT_FILE"
}
}
}
# Install DigiCert KeyLocker tools
function Install-KeyLocker {
Log-Info "Setting up DigiCert KeyLocker tools..."
# Define our controlled installation directory
$installDir = "C:\BuildTools\DigiCert"
$smctlPath = Join-Path $installDir "smctl.exe"
# Check if already installed in our controlled location
if (Test-Path $smctlPath) {
Log-Success "KeyLocker tools already installed at: $smctlPath"
# Add to PATH if not already there
if ($env:PATH -notlike "*$installDir*") {
$env:PATH = "$installDir;$env:PATH"
Log-Info "Added to PATH: $installDir"
}
return $smctlPath
}
Log-Info "Installing KeyLocker tools to: $installDir"
# Create the installation directory if it doesn't exist
if (!(Test-Path $installDir)) {
Log-Info "Creating installation directory: $installDir"
try {
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
Log-Success "Created directory: $installDir"
} catch {
throw "Failed to create directory $installDir : $_"
}
}
# Download MSI installer
$msiUrl = "https://bun-ci-assets.bun.sh/Keylockertools-windows-x64.msi"
$msiPath = Join-Path $env:TEMP "Keylockertools-windows-x64.msi"
Log-Info "Downloading MSI from: $msiUrl"
Log-Info "Downloading to: $msiPath"
try {
# Remove existing MSI if present
if (Test-Path $msiPath) {
Remove-Item $msiPath -Force
Log-Debug "Removed existing MSI file"
}
# Download with progress tracking
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($msiUrl, $msiPath)
if (!(Test-Path $msiPath)) {
throw "MSI download failed - file not found"
}
$fileSize = (Get-Item $msiPath).Length
Log-Success "MSI downloaded successfully (size: $fileSize bytes)"
} catch {
throw "Failed to download MSI: $_"
}
# Install MSI
Log-Info "Installing MSI..."
Log-Debug "MSI path: $msiPath"
Log-Debug "File exists: $(Test-Path $msiPath)"
Log-Debug "File size: $((Get-Item $msiPath).Length) bytes"
# Check if running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
Log-Info "Running as administrator: $isAdmin"
# Install MSI silently to our controlled directory
$arguments = @(
"/i", "`"$msiPath`"",
"/quiet",
"/norestart",
"TARGETDIR=`"$installDir`"",
"INSTALLDIR=`"$installDir`"",
"ACCEPT_EULA=1",
"ADDLOCAL=ALL"
)
Log-Debug "Running: msiexec.exe $($arguments -join ' ')"
Log-Info "Installing to: $installDir"
$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $arguments -Wait -PassThru -NoNewWindow
if ($process.ExitCode -ne 0) {
Log-Error "MSI installation failed with exit code: $($process.ExitCode)"
# Try to get error details from event log
try {
$events = Get-WinEvent -LogName "Application" -MaxEvents 10 |
Where-Object { $_.ProviderName -eq "MsiInstaller" -and $_.TimeCreated -gt (Get-Date).AddMinutes(-1) }
foreach ($event in $events) {
Log-Debug "MSI Event: $($event.Message)"
}
} catch {
Log-Debug "Could not retrieve MSI installation events"
}
throw "MSI installation failed with exit code: $($process.ExitCode)"
}
Log-Success "MSI installation completed"
# Wait for installation to complete
Start-Sleep -Seconds 2
# Verify smctl.exe exists in our controlled location
if (Test-Path $smctlPath) {
Log-Success "KeyLocker tools installed successfully at: $smctlPath"
# Add to PATH
$env:PATH = "$installDir;$env:PATH"
Log-Info "Added to PATH: $installDir"
return $smctlPath
}
# If not in our expected location, check if it installed somewhere in the directory
$found = Get-ChildItem -Path $installDir -Filter "smctl.exe" -Recurse -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($found) {
Log-Success "Found smctl.exe at: $($found.FullName)"
$smctlDir = $found.DirectoryName
$env:PATH = "$smctlDir;$env:PATH"
return $found.FullName
}
throw "KeyLocker tools installation succeeded but smctl.exe not found in $installDir"
}
# Configure KeyLocker
function Configure-KeyLocker {
param([string]$SmctlPath)
Log-Info "Configuring KeyLocker..."
# Verify smctl is accessible
try {
$version = & $SmctlPath --version 2>&1
Log-Debug "smctl version: $version"
} catch {
throw "Failed to run smctl: $_"
}
# Configure KeyLocker credentials and environment
Log-Info "Configuring KeyLocker credentials..."
try {
# Save credentials (API key and password)
Log-Info "Saving credentials to OS store..."
$saveOutput = & $SmctlPath credentials save $env:SM_API_KEY $env:SM_CLIENT_CERT_PASSWORD 2>&1 | Out-String
Log-Debug "Credentials save output: $saveOutput"
if ($saveOutput -like "*Credentials saved*") {
Log-Success "Credentials saved successfully"
}
# Set environment variables for smctl
Log-Info "Setting KeyLocker environment variables..."
$env:SM_HOST = $env:SM_HOST # Already set, but ensure it's available
$env:SM_API_KEY = $env:SM_API_KEY # Already set
$env:SM_CLIENT_CERT_FILE = $env:SM_CLIENT_CERT_FILE # Path to decoded cert file
Log-Debug "SM_HOST: $env:SM_HOST"
Log-Debug "SM_CLIENT_CERT_FILE: $env:SM_CLIENT_CERT_FILE"
# Run health check
Log-Info "Running KeyLocker health check..."
$healthOutput = & $SmctlPath healthcheck 2>&1 | Out-String
Log-Debug "Health check output: $healthOutput"
if ($healthOutput -like "*Healthy*" -or $healthOutput -like "*SUCCESS*" -or $LASTEXITCODE -eq 0) {
Log-Success "KeyLocker health check passed"
} else {
Log-Error "Health check failed: $healthOutput"
# Don't throw here, sometimes healthcheck is flaky but signing still works
}
# Sync certificates to Windows certificate store
Log-Info "Syncing certificates to Windows store..."
$syncOutput = & $SmctlPath windows certsync 2>&1 | Out-String
Log-Debug "Certificate sync output: $syncOutput"
if ($syncOutput -like "*success*" -or $syncOutput -like "*synced*" -or $LASTEXITCODE -eq 0) {
Log-Success "Certificates synced to Windows store"
} else {
Log-Info "Certificate sync output: $syncOutput"
}
} catch {
throw "Failed to configure KeyLocker: $_"
}
}
# Sign an executable
function Sign-Executable {
param(
[string]$ExePath,
[string]$SmctlPath
)
if (!(Test-Path $ExePath)) {
throw "Executable not found: $ExePath"
}
$fileName = Split-Path $ExePath -Leaf
Log-Info "Signing $fileName..."
Log-Debug "Full path: $ExePath"
Log-Debug "File size: $((Get-Item $ExePath).Length) bytes"
# Check if already signed
$existingSig = Get-AuthenticodeSignature $ExePath
if ($existingSig.Status -eq "Valid") {
Log-Info "$fileName is already signed by: $($existingSig.SignerCertificate.Subject)"
Log-Info "Skipping re-signing"
return
}
# Sign the executable using smctl
try {
# smctl sign command with keypair-alias
$signArgs = @(
"sign",
"--keypair-alias", $env:SM_KEYPAIR_ALIAS,
"--input", $ExePath,
"--verbose"
)
Log-Debug "Running: $SmctlPath $($signArgs -join ' ')"
$signOutput = & $SmctlPath $signArgs 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
Log-Error "Signing output: $signOutput"
throw "Signing failed with exit code: $LASTEXITCODE"
}
Log-Debug "Signing output: $signOutput"
Log-Success "Signing command completed"
} catch {
throw "Failed to sign $fileName : $_"
}
# Verify signature
$newSig = Get-AuthenticodeSignature $ExePath
if ($newSig.Status -eq "Valid") {
Log-Success "$fileName signed successfully"
Log-Info "Signed by: $($newSig.SignerCertificate.Subject)"
Log-Info "Thumbprint: $($newSig.SignerCertificate.Thumbprint)"
Log-Info "Valid from: $($newSig.SignerCertificate.NotBefore) to $($newSig.SignerCertificate.NotAfter)"
} else {
throw "$fileName signature verification failed: $($newSig.Status) - $($newSig.StatusMessage)"
}
}
# Cleanup function
function Cleanup {
if ($global:TEMP_CERT_PATH -and (Test-Path $global:TEMP_CERT_PATH)) {
try {
Remove-Item $global:TEMP_CERT_PATH -Force
Log-Info "Cleaned up temporary certificate"
} catch {
Log-Error "Failed to cleanup temporary certificate: $_"
}
}
}
# Main execution
try {
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Windows Code Signing for Bun" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Ensure we're in a VS environment
Ensure-VSEnvironment
# Check environment variables
Check-Environment
# Setup certificate
Setup-Certificate
# Install and configure KeyLocker
$smctlPath = Install-KeyLocker
Configure-KeyLocker -SmctlPath $smctlPath
# Sign both executables
Sign-Executable -ExePath $BunProfileExe -SmctlPath $smctlPath
Sign-Executable -ExePath $BunExe -SmctlPath $smctlPath
Write-Host "========================================" -ForegroundColor Green
Write-Host " Code signing completed successfully!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
exit 0
} catch {
Log-Error "Code signing failed: $_"
exit 1
} finally {
Cleanup
}

View File

@@ -6,7 +6,7 @@ on:
jobs:
auto-label:
if: github.event.pull_request.user.login == 'robobun' || contains(github.event.pull_request.body, '🤖 Generated with')
if: github.event.pull_request.user.login == 'robobun'
runs-on: ubuntu-latest
permissions:
contents: read
@@ -21,4 +21,4 @@ jobs:
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['claude']
});
});

View File

@@ -8,8 +8,10 @@ on:
workflow_dispatch:
pull_request:
merge_group:
push:
branches: ["main"]
env:
BUN_VERSION: "1.2.20"
BUN_VERSION: "1.2.11"
LLVM_VERSION: "19.1.7"
LLVM_VERSION_MAJOR: "19"
@@ -35,14 +37,13 @@ jobs:
- name: Setup Dependencies
run: |
bun install
bun scripts/glob-sources.mjs
- name: Format Code
run: |
# Start prettier in background with prefixed output
echo "::group::Prettier"
(bun run prettier 2>&1 | sed 's/^/[prettier] /' || echo "[prettier] Failed with exit code $?") &
PRETTIER_PID=$!
# Start clang-format installation and formatting in background with prefixed output
echo "::group::Clang-format"
(
@@ -55,13 +56,13 @@ jobs:
LLVM_VERSION_MAJOR=${{ env.LLVM_VERSION_MAJOR }} ./scripts/run-clang-format.sh format 2>&1 | sed 's/^/[clang-format] /'
) &
CLANG_PID=$!
# Setup Zig in temp directory and run zig fmt in background with prefixed output
echo "::group::Zig fmt"
(
ZIG_TEMP=$(mktemp -d)
echo "[zig] Downloading Zig (musl build)..."
wget -q -O "$ZIG_TEMP/zig.zip" https://github.com/oven-sh/zig/releases/download/autobuild-e0b7c318f318196c5f81fdf3423816a7b5bb3112/bootstrap-x86_64-linux-musl.zip
wget -q -O "$ZIG_TEMP/zig.zip" https://github.com/oven-sh/zig/releases/download/autobuild-d1a4e0b0ddc75f37c6a090b97eef0cbb6335556e/bootstrap-x86_64-linux-musl.zip
unzip -q -d "$ZIG_TEMP" "$ZIG_TEMP/zig.zip"
export PATH="$ZIG_TEMP/bootstrap-x86_64-linux-musl:$PATH"
echo "[zig] Running zig fmt..."
@@ -71,39 +72,38 @@ jobs:
rm -rf "$ZIG_TEMP"
) &
ZIG_PID=$!
# Wait for all formatting tasks to complete
echo ""
echo "Running formatters in parallel..."
FAILED=0
if ! wait $PRETTIER_PID; then
echo "::error::Prettier failed"
FAILED=1
fi
echo "::endgroup::"
if ! wait $CLANG_PID; then
echo "::error::Clang-format failed"
FAILED=1
fi
echo "::endgroup::"
if ! wait $ZIG_PID; then
echo "::error::Zig fmt failed"
FAILED=1
fi
echo "::endgroup::"
# Exit with error if any formatter failed
if [ $FAILED -eq 1 ]; then
echo "::error::One or more formatters failed"
exit 1
fi
echo "✅ All formatters completed successfully"
- name: Ban Words
run: |
bun ./test/internal/ban-words.test.ts
git rm -f cmake/sources/*.txt || true
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27

41
.github/workflows/glob-sources.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Glob Sources
permissions:
contents: write
on:
workflow_call:
workflow_dispatch:
pull_request:
env:
BUN_VERSION: "1.2.11"
jobs:
glob-sources:
name: Glob Sources
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global core.autocrlf true
git config --global core.ignorecase true
git config --global core.precomposeUnicode true
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Setup Dependencies
run: |
bun install
- name: Glob sources
run: bun scripts/glob-sources.mjs
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "`bun scripts/glob-sources.mjs`"

View File

@@ -5,8 +5,6 @@ env:
on:
issues:
types: [labeled]
pull_request_target:
types: [labeled, opened, reopened, synchronize, unlabeled]
jobs:
# on-bug:
@@ -45,46 +43,9 @@ jobs:
# token: ${{ secrets.GITHUB_TOKEN }}
# issue-number: ${{ github.event.issue.number }}
# labels: ${{ steps.add-labels.outputs.labels }}
on-slop:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'slop')
permissions:
issues: write
pull-requests: write
contents: write
steps:
- name: Update PR title and body for slop and close
uses: actions/github-script@v7
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
title: 'ai slop',
body: 'This PR has been marked as AI slop and the description has been updated to avoid confusion or misleading reviewers.\n\nMany AI PRs are fine, but sometimes they submit a PR too early, fail to test if the problem is real, fail to reproduce the problem, or fail to test that the problem is fixed. If you think this PR is not AI slop, please leave a comment.',
state: 'closed'
});
// Delete the branch if it's from a fork or if it's not a protected branch
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${pr.data.head.ref}`
});
} catch (error) {
console.log('Could not delete branch:', error.message);
}
on-labeled:
runs-on: ubuntu-latest
if: github.event_name == 'issues' && (github.event.label.name == 'crash' || github.event.label.name == 'needs repro')
if: github.event.label.name == 'crash' || github.event.label.name == 'needs repro'
permissions:
issues: write
steps:

5
.gitignore vendored
View File

@@ -186,7 +186,4 @@ scratch*.{js,ts,tsx,cjs,mjs}
*.bun-build
scripts/lldb-inline
# We regenerate these in all the build scripts
cmake/sources/*.txt
scripts/lldb-inline

View File

@@ -43,11 +43,16 @@ Tests use Bun's Jest-compatible test runner with proper test fixtures:
```typescript
import { test, expect } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
import {
bunEnv,
bunExe,
normalizeBunSnapshot,
tempDirWithFiles,
} from "harness";
test("my feature", async () => {
// Create temp directory with test files
using dir = tempDir("test-prefix", {
const dir = tempDirWithFiles("test-prefix", {
"index.js": `console.log("hello");`,
});
@@ -55,7 +60,7 @@ test("my feature", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
cwd: dir,
stderr: "pipe",
});

View File

@@ -223,8 +223,8 @@ $ git clone https://github.com/oven-sh/WebKit vendor/WebKit
$ git -C vendor/WebKit checkout <commit_hash>
# Make a debug build of JSC. This will output build artifacts in ./vendor/WebKit/WebKitBuild/Debug
# Optionally, you can use `bun run jsc:build` for a release build
$ bun run jsc:build:debug && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# Optionally, you can use `make jsc` for a release build
$ make jsc-debug && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# After an initial run of `make jsc-debug`, you can rebuild JSC with:
$ cmake --build vendor/WebKit/WebKitBuild/Debug --target jsc && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h

2
LATEST
View File

@@ -1 +1 @@
1.2.21
1.2.20

View File

@@ -1,116 +0,0 @@
// Benchmark for object fast path optimization in postMessage with Workers
import { bench, run } from "mitata";
import { Worker } from "node:worker_threads";
const extraProperties = {
a: "a!",
b: "b!",
"second": "c!",
bool: true,
nully: null,
undef: undefined,
int: 0,
double: 1.234,
falsy: false,
};
const objects = {
small: { property: "Hello world", ...extraProperties },
medium: {
property: Buffer.alloc("Hello World!!!".length * 1024, "Hello World!!!").toString(),
...extraProperties,
},
large: {
property: Buffer.alloc("Hello World!!!".length * 1024 * 256, "Hello World!!!").toString(),
...extraProperties,
},
};
let worker;
let receivedCount = new Int32Array(new SharedArrayBuffer(4));
let sentCount = 0;
function createWorker() {
const workerCode = `
import { parentPort, workerData } from "node:worker_threads";
let int = workerData;
parentPort?.on("message", data => {
switch (data.property.length) {
case ${objects.small.property.length}:
case ${objects.medium.property.length}:
case ${objects.large.property.length}: {
if (
data.a === "a!" &&
data.b === "b!" &&
data.second === "c!" &&
data.bool === true &&
data.nully === null &&
data.undef === undefined &&
data.int === 0 &&
data.double === 1.234 &&
data.falsy === false) {
Atomics.add(int, 0, 1);
break;
}
}
default: {
throw new Error("Invalid data object: " + JSON.stringify(data));
}
}
});
`;
worker = new Worker(workerCode, { eval: true, workerData: receivedCount });
worker.on("message", confirmationId => {});
worker.on("error", error => {
console.error("Worker error:", error);
});
}
// Initialize worker before running benchmarks
createWorker();
function fmt(int) {
if (int < 1000) {
return `${int} chars`;
}
if (int < 100000) {
return `${(int / 1024) | 0} KB`;
}
return `${(int / 1024 / 1024) | 0} MB`;
}
// Benchmark postMessage with pure strings (uses fast path)
bench("postMessage({ prop: " + fmt(objects.small.property.length) + " string, ...9 more props })", async () => {
sentCount++;
worker.postMessage(objects.small);
});
bench("postMessage({ prop: " + fmt(objects.medium.property.length) + " string, ...9 more props })", async () => {
sentCount++;
worker.postMessage(objects.medium);
});
bench("postMessage({ prop: " + fmt(objects.large.property.length) + " string, ...9 more props })", async () => {
sentCount++;
worker.postMessage(objects.large);
});
await run();
await new Promise(resolve => setTimeout(resolve, 5000));
if (receivedCount[0] !== sentCount) {
throw new Error("Expected " + receivedCount[0] + " to equal " + sentCount);
}
// Cleanup worker
worker?.terminate();

Binary file not shown.

View File

@@ -1,58 +0,0 @@
const isBun = typeof globalThis?.Bun?.sql !== "undefined";
let conn;
let sql;
import * as mariadb from "mariadb";
import * as mysql2 from "mysql2/promise";
let useMYSQL2 = false;
if (process.argv.includes("--mysql2")) {
useMYSQL2 = true;
}
if (isBun) {
sql = new Bun.SQL({
adapter: "mysql",
database: "test",
username: "root",
});
} else {
const pool = (useMYSQL2 ? mysql2 : mariadb).createPool({
// Add your MariaDB connection details here
user: "root",
database: "test",
});
conn = await pool.getConnection();
}
if (isBun) {
// Initialize the benchmark table (equivalent to initFct)
await sql`DROP TABLE IF EXISTS test100`;
await sql`CREATE TABLE test100 (i1 int,i2 int,i3 int,i4 int,i5 int,i6 int,i7 int,i8 int,i9 int,i10 int,i11 int,i12 int,i13 int,i14 int,i15 int,i16 int,i17 int,i18 int,i19 int,i20 int,i21 int,i22 int,i23 int,i24 int,i25 int,i26 int,i27 int,i28 int,i29 int,i30 int,i31 int,i32 int,i33 int,i34 int,i35 int,i36 int,i37 int,i38 int,i39 int,i40 int,i41 int,i42 int,i43 int,i44 int,i45 int,i46 int,i47 int,i48 int,i49 int,i50 int,i51 int,i52 int,i53 int,i54 int,i55 int,i56 int,i57 int,i58 int,i59 int,i60 int,i61 int,i62 int,i63 int,i64 int,i65 int,i66 int,i67 int,i68 int,i69 int,i70 int,i71 int,i72 int,i73 int,i74 int,i75 int,i76 int,i77 int,i78 int,i79 int,i80 int,i81 int,i82 int,i83 int,i84 int,i85 int,i86 int,i87 int,i88 int,i89 int,i90 int,i91 int,i92 int,i93 int,i94 int,i95 int,i96 int,i97 int,i98 int,i99 int,i100 int)`;
await sql`INSERT INTO test100 value (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100)`;
} else {
// Initialize the benchmark table (equivalent to initFct)
await conn.query("DROP TABLE IF EXISTS test100");
await conn.query(
"CREATE TABLE test100 (i1 int,i2 int,i3 int,i4 int,i5 int,i6 int,i7 int,i8 int,i9 int,i10 int,i11 int,i12 int,i13 int,i14 int,i15 int,i16 int,i17 int,i18 int,i19 int,i20 int,i21 int,i22 int,i23 int,i24 int,i25 int,i26 int,i27 int,i28 int,i29 int,i30 int,i31 int,i32 int,i33 int,i34 int,i35 int,i36 int,i37 int,i38 int,i39 int,i40 int,i41 int,i42 int,i43 int,i44 int,i45 int,i46 int,i47 int,i48 int,i49 int,i50 int,i51 int,i52 int,i53 int,i54 int,i55 int,i56 int,i57 int,i58 int,i59 int,i60 int,i61 int,i62 int,i63 int,i64 int,i65 int,i66 int,i67 int,i68 int,i69 int,i70 int,i71 int,i72 int,i73 int,i74 int,i75 int,i76 int,i77 int,i78 int,i79 int,i80 int,i81 int,i82 int,i83 int,i84 int,i85 int,i86 int,i87 int,i88 int,i89 int,i90 int,i91 int,i92 int,i93 int,i94 int,i95 int,i96 int,i97 int,i98 int,i99 int,i100 int)",
);
await conn.query(
"INSERT INTO test100 value (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100)",
);
}
// Run the benchmark (equivalent to benchFct)
const type = isBun ? "Bun.SQL" : useMYSQL2 ? "mysql2" : "mariadb";
console.time(type);
let promises = [];
for (let i = 0; i < 100_000; i++) {
if (isBun) {
promises.push(sql`select * FROM test100`);
} else {
promises.push(conn.query("select * FROM test100"));
}
}
await Promise.all(promises);
console.timeEnd(type);
// Clean up connection
if (!isBun && conn.release) {
conn.release();
}

View File

@@ -9,8 +9,6 @@
"typescript": "^5.0.0"
},
"dependencies": {
"mariadb": "^3.4.5",
"mysql2": "^3.14.3",
"postgres": "^3.4.7"
}
}

View File

@@ -12,9 +12,6 @@ const scenarios = [
{ alg: "sha1", digest: "base64" },
{ alg: "sha256", digest: "hex" },
{ alg: "sha256", digest: "base64" },
{ alg: "blake2b512", digest: "hex" },
{ alg: "sha512-224", digest: "hex" },
{ alg: "sha512-256", digest: "hex" },
];
for (const { alg, digest } of scenarios) {
@@ -26,10 +23,6 @@ for (const { alg, digest } of scenarios) {
bench(`${alg}-${digest} (Bun.CryptoHasher)`, () => {
new Bun.CryptoHasher(alg).update(data).digest(digest);
});
bench(`${alg}-${digest} (Bun.CryptoHasher.hash)`, () => {
return Bun.CryptoHasher.hash(alg, data, digest);
});
}
}

View File

@@ -1,19 +0,0 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "yaml-benchmark",
"dependencies": {
"js-yaml": "^4.1.0",
"yaml": "^2.8.1",
},
},
},
"packages": {
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
}
}

View File

@@ -1,8 +0,0 @@
{
"name": "yaml-benchmark",
"version": "1.0.0",
"dependencies": {
"js-yaml": "^4.1.0",
"yaml": "^2.8.1"
}
}

View File

@@ -1,368 +0,0 @@
import { bench, group, run } from "../runner.mjs";
import jsYaml from "js-yaml";
import yaml from "yaml";
// Small YAML document
const smallYaml = `
name: John Doe
age: 30
email: john@example.com
active: true
`;
// Medium YAML document with nested structures
const mediumYaml = `
company: Acme Corp
employees:
- name: John Doe
age: 30
position: Developer
skills:
- JavaScript
- TypeScript
- Node.js
- name: Jane Smith
age: 28
position: Designer
skills:
- Figma
- Photoshop
- Illustrator
- name: Bob Johnson
age: 35
position: Manager
skills:
- Leadership
- Communication
- Planning
settings:
database:
host: localhost
port: 5432
name: mydb
cache:
enabled: true
ttl: 3600
`;
// Large YAML document with complex structures
const largeYaml = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
env:
- name: ENV_VAR_1
value: "value1"
- name: ENV_VAR_2
value: "value2"
volumeMounts:
- name: config
mountPath: /etc/nginx
resources:
limits:
cpu: "1"
memory: "1Gi"
requests:
cpu: "0.5"
memory: "512Mi"
volumes:
- name: config
configMap:
name: nginx-config
items:
- key: nginx.conf
path: nginx.conf
- key: mime.types
path: mime.types
nodeSelector:
disktype: ssd
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key2"
operator: "Exists"
effect: "NoExecute"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/e2e-az-name
operator: In
values:
- e2e-az1
- e2e-az2
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: kubernetes.io/hostname
`;
// YAML with anchors and references
const yamlWithAnchors = `
defaults: &defaults
adapter: postgresql
host: localhost
port: 5432
development:
<<: *defaults
database: dev_db
test:
<<: *defaults
database: test_db
production:
<<: *defaults
database: prod_db
host: prod.example.com
`;
// Array of items
const arrayYaml = `
- id: 1
name: Item 1
price: 10.99
tags: [electronics, gadgets]
- id: 2
name: Item 2
price: 25.50
tags: [books, education]
- id: 3
name: Item 3
price: 5.00
tags: [food, snacks]
- id: 4
name: Item 4
price: 100.00
tags: [electronics, computers]
- id: 5
name: Item 5
price: 15.75
tags: [clothing, accessories]
`;
// Multiline strings
const multilineYaml = `
description: |
This is a multiline string
that preserves line breaks
and indentation.
It can contain multiple paragraphs
and special characters: !@#$%^&*()
folded: >
This is a folded string
where line breaks are converted
to spaces unless there are
empty lines like above.
plain: This is a plain string
quoted: "This is a quoted string with \\"escapes\\""
literal: 'This is a literal string with ''quotes'''
`;
// Numbers and special values
const numbersYaml = `
integer: 42
negative: -17
float: 3.14159
scientific: 1.23e-4
infinity: .inf
negativeInfinity: -.inf
notANumber: .nan
octal: 0o755
hex: 0xFF
binary: 0b1010
`;
// Dates and timestamps
const datesYaml = `
date: 2024-01-15
datetime: 2024-01-15T10:30:00Z
timestamp: 2024-01-15 10:30:00.123456789 -05:00
canonical: 2024-01-15T10:30:00.123456789Z
`;
// Parse benchmarks
group("parse small YAML", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(smallYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(smallYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(smallYaml);
});
});
group("parse medium YAML", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(mediumYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(mediumYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(mediumYaml);
});
});
group("parse large YAML", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(largeYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(largeYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(largeYaml);
});
});
group("parse YAML with anchors", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(yamlWithAnchors);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(yamlWithAnchors);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(yamlWithAnchors);
});
});
group("parse YAML array", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(arrayYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(arrayYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(arrayYaml);
});
});
group("parse YAML with multiline strings", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(multilineYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(multilineYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(multilineYaml);
});
});
group("parse YAML with numbers", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(numbersYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(numbersYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(numbersYaml);
});
});
group("parse YAML with dates", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.parse", () => {
globalThis.result = Bun.YAML.parse(datesYaml);
});
}
bench("js-yaml.load", () => {
globalThis.result = jsYaml.load(datesYaml);
});
bench("yaml.parse", () => {
globalThis.result = yaml.parse(datesYaml);
});
});
// // Stringify benchmarks
// const smallObjJs = jsYaml.load(smallYaml);
// const mediumObjJs = jsYaml.load(mediumYaml);
// const largeObjJs = jsYaml.load(largeYaml);
// group("stringify small object", () => {
// bench("js-yaml.dump", () => {
// globalThis.result = jsYaml.dump(smallObjJs);
// });
// });
// group("stringify medium object", () => {
// bench("js-yaml.dump", () => {
// globalThis.result = jsYaml.dump(mediumObjJs);
// });
// });
// group("stringify large object", () => {
// bench("js-yaml.dump", () => {
// globalThis.result = jsYaml.dump(largeObjJs);
// });
// });
await run();

View File

@@ -1,407 +0,0 @@
import { bench, group, run } from "../runner.mjs";
import jsYaml from "js-yaml";
import yaml from "yaml";
// Small object
const smallObject = {
name: "John Doe",
age: 30,
email: "john@example.com",
active: true,
};
// Medium object with nested structures
const mediumObject = {
company: "Acme Corp",
employees: [
{
name: "John Doe",
age: 30,
position: "Developer",
skills: ["JavaScript", "TypeScript", "Node.js"],
},
{
name: "Jane Smith",
age: 28,
position: "Designer",
skills: ["Figma", "Photoshop", "Illustrator"],
},
{
name: "Bob Johnson",
age: 35,
position: "Manager",
skills: ["Leadership", "Communication", "Planning"],
},
],
settings: {
database: {
host: "localhost",
port: 5432,
name: "mydb",
},
cache: {
enabled: true,
ttl: 3600,
},
},
};
// Large object with complex structures
const largeObject = {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "nginx-deployment",
labels: {
app: "nginx",
},
},
spec: {
replicas: 3,
selector: {
matchLabels: {
app: "nginx",
},
},
template: {
metadata: {
labels: {
app: "nginx",
},
},
spec: {
containers: [
{
name: "nginx",
image: "nginx:1.14.2",
ports: [
{
containerPort: 80,
},
],
env: [
{
name: "ENV_VAR_1",
value: "value1",
},
{
name: "ENV_VAR_2",
value: "value2",
},
],
volumeMounts: [
{
name: "config",
mountPath: "/etc/nginx",
},
],
resources: {
limits: {
cpu: "1",
memory: "1Gi",
},
requests: {
cpu: "0.5",
memory: "512Mi",
},
},
},
],
volumes: [
{
name: "config",
configMap: {
name: "nginx-config",
items: [
{
key: "nginx.conf",
path: "nginx.conf",
},
{
key: "mime.types",
path: "mime.types",
},
],
},
},
],
nodeSelector: {
disktype: "ssd",
},
tolerations: [
{
key: "key1",
operator: "Equal",
value: "value1",
effect: "NoSchedule",
},
{
key: "key2",
operator: "Exists",
effect: "NoExecute",
},
],
affinity: {
nodeAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: {
nodeSelectorTerms: [
{
matchExpressions: [
{
key: "kubernetes.io/e2e-az-name",
operator: "In",
values: ["e2e-az1", "e2e-az2"],
},
],
},
],
},
},
podAntiAffinity: {
preferredDuringSchedulingIgnoredDuringExecution: [
{
weight: 100,
podAffinityTerm: {
labelSelector: {
matchExpressions: [
{
key: "app",
operator: "In",
values: ["web-store"],
},
],
},
topologyKey: "kubernetes.io/hostname",
},
},
],
},
},
},
},
},
};
// Object with anchors and references (after resolution)
const objectWithAnchors = {
defaults: {
adapter: "postgresql",
host: "localhost",
port: 5432,
},
development: {
adapter: "postgresql",
host: "localhost",
port: 5432,
database: "dev_db",
},
test: {
adapter: "postgresql",
host: "localhost",
port: 5432,
database: "test_db",
},
production: {
adapter: "postgresql",
host: "prod.example.com",
port: 5432,
database: "prod_db",
},
};
// Array of items
const arrayObject = [
{
id: 1,
name: "Item 1",
price: 10.99,
tags: ["electronics", "gadgets"],
},
{
id: 2,
name: "Item 2",
price: 25.5,
tags: ["books", "education"],
},
{
id: 3,
name: "Item 3",
price: 5.0,
tags: ["food", "snacks"],
},
{
id: 4,
name: "Item 4",
price: 100.0,
tags: ["electronics", "computers"],
},
{
id: 5,
name: "Item 5",
price: 15.75,
tags: ["clothing", "accessories"],
},
];
// Multiline strings
const multilineObject = {
description:
"This is a multiline string\nthat preserves line breaks\nand indentation.\n\nIt can contain multiple paragraphs\nand special characters: !@#$%^&*()\n",
folded: "This is a folded string where line breaks are converted to spaces unless there are\nempty lines like above.",
plain: "This is a plain string",
quoted: 'This is a quoted string with "escapes"',
literal: "This is a literal string with 'quotes'",
};
// Numbers and special values
const numbersObject = {
integer: 42,
negative: -17,
float: 3.14159,
scientific: 0.000123,
infinity: Infinity,
negativeInfinity: -Infinity,
notANumber: NaN,
octal: 493, // 0o755
hex: 255, // 0xFF
binary: 10, // 0b1010
};
// Dates and timestamps
const datesObject = {
date: new Date("2024-01-15"),
datetime: new Date("2024-01-15T10:30:00Z"),
timestamp: new Date("2024-01-15T15:30:00.123456789Z"), // Adjusted for UTC-5
canonical: new Date("2024-01-15T10:30:00.123456789Z"),
};
// Stringify benchmarks
group("stringify small object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(smallObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(smallObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(smallObject);
});
});
group("stringify medium object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(mediumObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(mediumObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(mediumObject);
});
});
group("stringify large object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(largeObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(largeObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(largeObject);
});
});
group("stringify object with anchors", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(objectWithAnchors);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(objectWithAnchors);
});
bench("yaml.stringify", () => {
return yaml.stringify(objectWithAnchors);
});
});
group("stringify array", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(arrayObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(arrayObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(arrayObject);
});
});
group("stringify object with multiline strings", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(multilineObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(multilineObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(multilineObject);
});
});
group("stringify object with numbers", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(numbersObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(numbersObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(numbersObject);
});
});
group("stringify object with dates", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(datesObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(datesObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(datesObject);
});
});
await run();

View File

@@ -40,8 +40,8 @@
},
},
"overrides": {
"@types/bun": "workspace:packages/@types/bun",
"bun-types": "workspace:packages/bun-types",
"@types/bun": "workspace:packages/@types/bun",
},
"packages": {
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],

View File

@@ -57,23 +57,6 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
# Windows Code Signing Option
if(WIN32)
optionx(ENABLE_WINDOWS_CODESIGNING BOOL "Enable Windows code signing with DigiCert KeyLocker" DEFAULT OFF)
if(ENABLE_WINDOWS_CODESIGNING)
message(STATUS "Windows code signing: ENABLED")
# Check for required environment variables
if(NOT DEFINED ENV{SM_API_KEY})
message(WARNING "SM_API_KEY not set - code signing may fail")
endif()
if(NOT DEFINED ENV{SM_CLIENT_CERT_FILE})
message(WARNING "SM_CLIENT_CERT_FILE not set - code signing may fail")
endif()
endif()
endif()
if(LINUX)
if(EXISTS "/etc/alpine-release")
set(DEFAULT_ABI "musl")

View File

@@ -0,0 +1,22 @@
src/bake/bake.d.ts
src/bake/bake.private.d.ts
src/bake/bun-framework-react/index.ts
src/bake/client/css-reloader.ts
src/bake/client/data-view.ts
src/bake/client/error-serialization.ts
src/bake/client/inspect.ts
src/bake/client/JavaScriptSyntaxHighlighter.css
src/bake/client/JavaScriptSyntaxHighlighter.ts
src/bake/client/overlay.css
src/bake/client/overlay.ts
src/bake/client/stack-trace.ts
src/bake/client/websocket.ts
src/bake/debug.ts
src/bake/DevServer.bind.ts
src/bake/enums.ts
src/bake/hmr-module.ts
src/bake/hmr-runtime-client.ts
src/bake/hmr-runtime-error.ts
src/bake/hmr-runtime-server.ts
src/bake/server/stack-trace-stub.ts
src/bake/shared.ts

View File

@@ -0,0 +1,7 @@
src/bake.bind.ts
src/bake/DevServer.bind.ts
src/bun.js/api/BunObject.bind.ts
src/bun.js/bindgen_test.bind.ts
src/bun.js/bindings/NodeModuleModule.bind.ts
src/bun.js/node/node_os.bind.ts
src/fmt.bind.ts

View File

@@ -0,0 +1,12 @@
packages/bun-error/bun-error.css
packages/bun-error/img/close.png
packages/bun-error/img/error.png
packages/bun-error/img/powered-by.png
packages/bun-error/img/powered-by.webp
packages/bun-error/index.tsx
packages/bun-error/markdown.ts
packages/bun-error/package.json
packages/bun-error/runtime-error.ts
packages/bun-error/sourcemap.ts
packages/bun-error/stack-trace-parser.ts
packages/bun-error/tsconfig.json

View File

@@ -0,0 +1,15 @@
packages/bun-usockets/src/bsd.c
packages/bun-usockets/src/context.c
packages/bun-usockets/src/crypto/openssl.c
packages/bun-usockets/src/eventing/epoll_kqueue.c
packages/bun-usockets/src/eventing/libuv.c
packages/bun-usockets/src/loop.c
packages/bun-usockets/src/quic.c
packages/bun-usockets/src/socket.c
packages/bun-usockets/src/udp.c
src/asan-config.c
src/bun.js/bindings/node/http/llhttp/api.c
src/bun.js/bindings/node/http/llhttp/http.c
src/bun.js/bindings/node/http/llhttp/llhttp.c
src/bun.js/bindings/uv-posix-polyfills.c
src/bun.js/bindings/uv-posix-stubs.c

View File

@@ -87,7 +87,6 @@ src/bun.js/bindings/JSNodePerformanceHooksHistogramConstructor.cpp
src/bun.js/bindings/JSNodePerformanceHooksHistogramPrototype.cpp
src/bun.js/bindings/JSPropertyIterator.cpp
src/bun.js/bindings/JSS3File.cpp
src/bun.js/bindings/JSSecrets.cpp
src/bun.js/bindings/JSSocketAddressDTO.cpp
src/bun.js/bindings/JSStringDecoder.cpp
src/bun.js/bindings/JSWrappingFunction.cpp
@@ -95,7 +94,6 @@ src/bun.js/bindings/JSX509Certificate.cpp
src/bun.js/bindings/JSX509CertificateConstructor.cpp
src/bun.js/bindings/JSX509CertificatePrototype.cpp
src/bun.js/bindings/linux_perf_tracing.cpp
src/bun.js/bindings/MarkedArgumentBufferBinding.cpp
src/bun.js/bindings/MarkingConstraint.cpp
src/bun.js/bindings/ModuleLoader.cpp
src/bun.js/bindings/napi_external.cpp
@@ -190,15 +188,11 @@ src/bun.js/bindings/ProcessIdentifier.cpp
src/bun.js/bindings/RegularExpression.cpp
src/bun.js/bindings/S3Error.cpp
src/bun.js/bindings/ScriptExecutionContext.cpp
src/bun.js/bindings/SecretsDarwin.cpp
src/bun.js/bindings/SecretsLinux.cpp
src/bun.js/bindings/SecretsWindows.cpp
src/bun.js/bindings/Serialization.cpp
src/bun.js/bindings/ServerRouteList.cpp
src/bun.js/bindings/spawn.cpp
src/bun.js/bindings/SQLClient.cpp
src/bun.js/bindings/sqlite/JSSQLStatement.cpp
src/bun.js/bindings/StringBuilderBinding.cpp
src/bun.js/bindings/stripANSI.cpp
src/bun.js/bindings/Strong.cpp
src/bun.js/bindings/TextCodec.cpp

View File

@@ -0,0 +1,21 @@
src/codegen/bake-codegen.ts
src/codegen/bindgen-lib-internal.ts
src/codegen/bindgen-lib.ts
src/codegen/bindgen.ts
src/codegen/buildTypeFlag.ts
src/codegen/builtin-parser.ts
src/codegen/bundle-functions.ts
src/codegen/bundle-modules.ts
src/codegen/class-definitions.ts
src/codegen/client-js.ts
src/codegen/cppbind.ts
src/codegen/create-hash-table.ts
src/codegen/generate-classes.ts
src/codegen/generate-compact-string-table.ts
src/codegen/generate-js2native.ts
src/codegen/generate-jssink.ts
src/codegen/generate-node-errors.ts
src/codegen/helpers.ts
src/codegen/internal-module-registry-scanner.ts
src/codegen/replacements.ts
src/codegen/shared-types.ts

View File

@@ -0,0 +1,171 @@
src/js/builtins.d.ts
src/js/builtins/Bake.ts
src/js/builtins/BundlerPlugin.ts
src/js/builtins/ByteLengthQueuingStrategy.ts
src/js/builtins/CommonJS.ts
src/js/builtins/ConsoleObject.ts
src/js/builtins/CountQueuingStrategy.ts
src/js/builtins/Glob.ts
src/js/builtins/ImportMetaObject.ts
src/js/builtins/Ipc.ts
src/js/builtins/JSBufferConstructor.ts
src/js/builtins/JSBufferPrototype.ts
src/js/builtins/NodeModuleObject.ts
src/js/builtins/Peek.ts
src/js/builtins/ProcessObjectInternals.ts
src/js/builtins/ReadableByteStreamController.ts
src/js/builtins/ReadableByteStreamInternals.ts
src/js/builtins/ReadableStream.ts
src/js/builtins/ReadableStreamBYOBReader.ts
src/js/builtins/ReadableStreamBYOBRequest.ts
src/js/builtins/ReadableStreamDefaultController.ts
src/js/builtins/ReadableStreamDefaultReader.ts
src/js/builtins/ReadableStreamInternals.ts
src/js/builtins/shell.ts
src/js/builtins/StreamInternals.ts
src/js/builtins/TextDecoderStream.ts
src/js/builtins/TextEncoderStream.ts
src/js/builtins/TransformStream.ts
src/js/builtins/TransformStreamDefaultController.ts
src/js/builtins/TransformStreamInternals.ts
src/js/builtins/UtilInspect.ts
src/js/builtins/WasmStreaming.ts
src/js/builtins/WritableStreamDefaultController.ts
src/js/builtins/WritableStreamDefaultWriter.ts
src/js/builtins/WritableStreamInternals.ts
src/js/bun/ffi.ts
src/js/bun/sql.ts
src/js/bun/sqlite.ts
src/js/internal-for-testing.ts
src/js/internal/abort_listener.ts
src/js/internal/assert/assertion_error.ts
src/js/internal/assert/calltracker.ts
src/js/internal/assert/myers_diff.ts
src/js/internal/assert/utils.ts
src/js/internal/buffer.ts
src/js/internal/cluster/child.ts
src/js/internal/cluster/isPrimary.ts
src/js/internal/cluster/primary.ts
src/js/internal/cluster/RoundRobinHandle.ts
src/js/internal/cluster/Worker.ts
src/js/internal/crypto/x509.ts
src/js/internal/debugger.ts
src/js/internal/errors.ts
src/js/internal/fifo.ts
src/js/internal/fixed_queue.ts
src/js/internal/freelist.ts
src/js/internal/fs/cp-sync.ts
src/js/internal/fs/cp.ts
src/js/internal/fs/glob.ts
src/js/internal/fs/streams.ts
src/js/internal/html.ts
src/js/internal/http.ts
src/js/internal/http/FakeSocket.ts
src/js/internal/linkedlist.ts
src/js/internal/primordials.js
src/js/internal/promisify.ts
src/js/internal/shared.ts
src/js/internal/sql/errors.ts
src/js/internal/sql/postgres.ts
src/js/internal/sql/query.ts
src/js/internal/sql/shared.ts
src/js/internal/sql/sqlite.ts
src/js/internal/sql/utils.ts
src/js/internal/stream.promises.ts
src/js/internal/stream.ts
src/js/internal/streams/add-abort-signal.ts
src/js/internal/streams/compose.ts
src/js/internal/streams/destroy.ts
src/js/internal/streams/duplex.ts
src/js/internal/streams/duplexify.ts
src/js/internal/streams/duplexpair.ts
src/js/internal/streams/end-of-stream.ts
src/js/internal/streams/from.ts
src/js/internal/streams/lazy_transform.ts
src/js/internal/streams/legacy.ts
src/js/internal/streams/native-readable.ts
src/js/internal/streams/operators.ts
src/js/internal/streams/passthrough.ts
src/js/internal/streams/pipeline.ts
src/js/internal/streams/readable.ts
src/js/internal/streams/state.ts
src/js/internal/streams/transform.ts
src/js/internal/streams/utils.ts
src/js/internal/streams/writable.ts
src/js/internal/timers.ts
src/js/internal/tls.ts
src/js/internal/tty.ts
src/js/internal/url.ts
src/js/internal/util/colors.ts
src/js/internal/util/inspect.d.ts
src/js/internal/util/inspect.js
src/js/internal/util/mime.ts
src/js/internal/validators.ts
src/js/internal/webstreams_adapters.ts
src/js/node/_http_agent.ts
src/js/node/_http_client.ts
src/js/node/_http_common.ts
src/js/node/_http_incoming.ts
src/js/node/_http_outgoing.ts
src/js/node/_http_server.ts
src/js/node/_stream_duplex.ts
src/js/node/_stream_passthrough.ts
src/js/node/_stream_readable.ts
src/js/node/_stream_transform.ts
src/js/node/_stream_wrap.ts
src/js/node/_stream_writable.ts
src/js/node/_tls_common.ts
src/js/node/assert.strict.ts
src/js/node/assert.ts
src/js/node/async_hooks.ts
src/js/node/child_process.ts
src/js/node/cluster.ts
src/js/node/console.ts
src/js/node/crypto.ts
src/js/node/dgram.ts
src/js/node/diagnostics_channel.ts
src/js/node/dns.promises.ts
src/js/node/dns.ts
src/js/node/domain.ts
src/js/node/events.ts
src/js/node/fs.promises.ts
src/js/node/fs.ts
src/js/node/http.ts
src/js/node/http2.ts
src/js/node/https.ts
src/js/node/inspector.ts
src/js/node/net.ts
src/js/node/os.ts
src/js/node/path.posix.ts
src/js/node/path.ts
src/js/node/path.win32.ts
src/js/node/perf_hooks.ts
src/js/node/punycode.ts
src/js/node/querystring.ts
src/js/node/readline.promises.ts
src/js/node/readline.ts
src/js/node/repl.ts
src/js/node/stream.consumers.ts
src/js/node/stream.promises.ts
src/js/node/stream.ts
src/js/node/stream.web.ts
src/js/node/test.ts
src/js/node/timers.promises.ts
src/js/node/timers.ts
src/js/node/tls.ts
src/js/node/trace_events.ts
src/js/node/tty.ts
src/js/node/url.ts
src/js/node/util.ts
src/js/node/v8.ts
src/js/node/vm.ts
src/js/node/wasi.ts
src/js/node/worker_threads.ts
src/js/node/zlib.ts
src/js/private.d.ts
src/js/thirdparty/isomorphic-fetch.ts
src/js/thirdparty/node-fetch.ts
src/js/thirdparty/undici.js
src/js/thirdparty/vercel_fetch.js
src/js/thirdparty/ws.js
src/js/wasi-runner.js

View File

@@ -0,0 +1,24 @@
src/node-fallbacks/assert.js
src/node-fallbacks/buffer.js
src/node-fallbacks/console.js
src/node-fallbacks/constants.js
src/node-fallbacks/crypto.js
src/node-fallbacks/domain.js
src/node-fallbacks/events.js
src/node-fallbacks/http.js
src/node-fallbacks/https.js
src/node-fallbacks/net.js
src/node-fallbacks/os.js
src/node-fallbacks/path.js
src/node-fallbacks/process.js
src/node-fallbacks/punycode.js
src/node-fallbacks/querystring.js
src/node-fallbacks/stream.js
src/node-fallbacks/string_decoder.js
src/node-fallbacks/sys.js
src/node-fallbacks/timers.js
src/node-fallbacks/timers.promises.js
src/node-fallbacks/tty.js
src/node-fallbacks/url.js
src/node-fallbacks/util.js
src/node-fallbacks/zlib.js

View File

@@ -0,0 +1,25 @@
src/bun.js/api/BunObject.classes.ts
src/bun.js/api/crypto.classes.ts
src/bun.js/api/ffi.classes.ts
src/bun.js/api/filesystem_router.classes.ts
src/bun.js/api/Glob.classes.ts
src/bun.js/api/h2.classes.ts
src/bun.js/api/html_rewriter.classes.ts
src/bun.js/api/JSBundler.classes.ts
src/bun.js/api/postgres.classes.ts
src/bun.js/api/ResumableSink.classes.ts
src/bun.js/api/S3Client.classes.ts
src/bun.js/api/S3Stat.classes.ts
src/bun.js/api/server.classes.ts
src/bun.js/api/Shell.classes.ts
src/bun.js/api/ShellArgs.classes.ts
src/bun.js/api/sockets.classes.ts
src/bun.js/api/sourcemap.classes.ts
src/bun.js/api/streams.classes.ts
src/bun.js/api/valkey.classes.ts
src/bun.js/api/zlib.classes.ts
src/bun.js/node/node.classes.ts
src/bun.js/resolve_message.classes.ts
src/bun.js/test/jest.classes.ts
src/bun.js/webcore/encoding.classes.ts
src/bun.js/webcore/response.classes.ts

1017
cmake/sources/ZigSources.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1205,7 +1205,6 @@ if(NOT BUN_CPP_ONLY)
endif()
if(bunStrip)
# First, strip bun-profile.exe to create bun.exe
register_command(
TARGET
${bun}
@@ -1226,48 +1225,6 @@ if(NOT BUN_CPP_ONLY)
OUTPUTS
${BUILD_PATH}/${bunStripExe}
)
# Then sign both executables on Windows
if(WIN32 AND ENABLE_WINDOWS_CODESIGNING)
set(SIGN_SCRIPT "${CMAKE_SOURCE_DIR}/.buildkite/scripts/sign-windows.ps1")
# Verify signing script exists
if(NOT EXISTS "${SIGN_SCRIPT}")
message(FATAL_ERROR "Windows signing script not found: ${SIGN_SCRIPT}")
endif()
# Use PowerShell for Windows code signing (native Windows, no path issues)
find_program(POWERSHELL_EXECUTABLE
NAMES pwsh.exe powershell.exe
PATHS
"C:/Program Files/PowerShell/7"
"C:/Program Files (x86)/PowerShell/7"
"C:/Windows/System32/WindowsPowerShell/v1.0"
DOC "Path to PowerShell executable"
)
if(NOT POWERSHELL_EXECUTABLE)
set(POWERSHELL_EXECUTABLE "powershell.exe")
endif()
message(STATUS "Using PowerShell executable: ${POWERSHELL_EXECUTABLE}")
# Sign both bun-profile.exe and bun.exe after stripping
register_command(
TARGET
${bun}
TARGET_PHASE
POST_BUILD
COMMENT
"Code signing bun-profile.exe and bun.exe with DigiCert KeyLocker"
COMMAND
"${POWERSHELL_EXECUTABLE}" "-NoProfile" "-ExecutionPolicy" "Bypass" "-File" "${SIGN_SCRIPT}" "-BunProfileExe" "${BUILD_PATH}/${bunExe}" "-BunExe" "${BUILD_PATH}/${bunStripExe}"
CWD
${CMAKE_SOURCE_DIR}
SOURCES
${BUILD_PATH}/${bunStripExe}
)
endif()
endif()
# somehow on some Linux systems we need to disable ASLR for ASAN-instrumented binaries to run

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION f474428677de1fafaf13bb3b9a050fe3504dda25)
set(WEBKIT_VERSION 53385bda2d2270223ac66f7b021a4aec3dd6df75)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)

View File

@@ -20,7 +20,7 @@ else()
unsupported(CMAKE_SYSTEM_NAME)
endif()
set(ZIG_COMMIT "e0b7c318f318196c5f81fdf3423816a7b5bb3112")
set(ZIG_COMMIT "edc6229b1fafb1701a25fb4e17114cc756991546")
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
if(CMAKE_BUILD_TYPE STREQUAL "Release")

View File

@@ -1,319 +0,0 @@
Store and retrieve sensitive credentials securely using the operating system's native credential storage APIs.
**Experimental:** This API is new and experimental. It may change in the future.
```typescript
import { secrets } from "bun";
const githubToken = await secrets.get({
service: "my-cli-tool",
name: "github-token",
});
if (!githubToken) {
const response = await fetch("https://api.github.com/name", {
headers: { "Authorization": `token ${githubToken}` },
});
console.log("Please enter your GitHub token");
} else {
await secrets.set({
service: "my-cli-tool",
name: "github-token",
value: prompt("Please enter your GitHub token"),
});
console.log("GitHub token stored");
}
```
## Overview
`Bun.secrets` provides a cross-platform API for managing sensitive credentials that CLI tools and development applications typically store in plaintext files like `~/.npmrc`, `~/.aws/credentials`, or `.env` files. It uses:
- **macOS**: Keychain Services
- **Linux**: libsecret (GNOME Keyring, KWallet, etc.)
- **Windows**: Windows Credential Manager
All operations are asynchronous and non-blocking, running on Bun's threadpool.
Note: in the future, we may add an additional `provider` option to make this better for production deployment secrets, but today this API is mostly useful for local development tools.
## API
### `Bun.secrets.get(options)`
Retrieve a stored credential.
```typescript
import { secrets } from "bun";
const password = await Bun.secrets.get({
service: "my-app",
name: "alice@example.com",
});
// Returns: string | null
// Or if you prefer without an object
const password = await Bun.secrets.get("my-app", "alice@example.com");
```
**Parameters:**
- `options.service` (string, required) - The service or application name
- `options.name` (string, required) - The username or account identifier
**Returns:**
- `Promise<string | null>` - The stored password, or `null` if not found
### `Bun.secrets.set(options, value)`
Store or update a credential.
```typescript
import { secrets } from "bun";
await secrets.set({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});
```
**Parameters:**
- `options.service` (string, required) - The service or application name
- `options.name` (string, required) - The username or account identifier
- `value` (string, required) - The password or secret to store
**Notes:**
- If a credential already exists for the given service/name combination, it will be replaced
- The stored value is encrypted by the operating system
### `Bun.secrets.delete(options)`
Delete a stored credential.
```typescript
const deleted = await Bun.secrets.delete({
service: "my-app",
name: "alice@example.com",
value: "super-secret-password",
});
// Returns: boolean
```
**Parameters:**
- `options.service` (string, required) - The service or application name
- `options.name` (string, required) - The username or account identifier
**Returns:**
- `Promise<boolean>` - `true` if a credential was deleted, `false` if not found
## Examples
### Storing CLI Tool Credentials
```javascript
// Store GitHub CLI token (instead of ~/.config/gh/hosts.yml)
await Bun.secrets.set({
service: "my-app.com",
name: "github-token",
value: "ghp_xxxxxxxxxxxxxxxxxxxx",
});
// Or if you prefer without an object
await Bun.secrets.set("my-app.com", "github-token", "ghp_xxxxxxxxxxxxxxxxxxxx");
// Store npm registry token (instead of ~/.npmrc)
await Bun.secrets.set({
service: "npm-registry",
name: "https://registry.npmjs.org",
value: "npm_xxxxxxxxxxxxxxxxxxxx",
});
// Retrieve for API calls
const token = await Bun.secrets.get({
service: "gh-cli",
name: "github.com",
});
if (token) {
const response = await fetch("https://api.github.com/name", {
headers: {
"Authorization": `token ${token}`,
},
});
}
```
### Migrating from Plaintext Config Files
```javascript
// Instead of storing in ~/.aws/credentials
await Bun.secrets.set({
service: "aws-cli",
name: "AWS_SECRET_ACCESS_KEY",
value: process.env.AWS_SECRET_ACCESS_KEY,
});
// Instead of .env files with sensitive data
await Bun.secrets.set({
service: "my-app",
name: "api-key",
value: "sk_live_xxxxxxxxxxxxxxxxxxxx",
});
// Load at runtime
const apiKey =
(await Bun.secrets.get({
service: "my-app",
name: "api-key",
})) || process.env.API_KEY; // Fallback for CI/production
```
### Error Handling
```javascript
try {
await Bun.secrets.set({
service: "my-app",
name: "alice",
value: "password123",
});
} catch (error) {
console.error("Failed to store credential:", error.message);
}
// Check if a credential exists
const password = await Bun.secrets.get({
service: "my-app",
name: "alice",
});
if (password === null) {
console.log("No credential found");
}
```
### Updating Credentials
```javascript
// Initial password
await Bun.secrets.set({
service: "email-server",
name: "admin@example.com",
value: "old-password",
});
// Update to new password
await Bun.secrets.set({
service: "email-server",
name: "admin@example.com",
value: "new-password",
});
// The old password is replaced
```
## Platform Behavior
### macOS (Keychain)
- Credentials are stored in the name's login keychain
- The keychain may prompt for access permission on first use
- Credentials persist across system restarts
- Accessible by the name who stored them
### Linux (libsecret)
- Requires a secret service daemon (GNOME Keyring, KWallet, etc.)
- Credentials are stored in the default collection
- May prompt for unlock if the keyring is locked
- The secret service must be running
### Windows (Credential Manager)
- Credentials are stored in Windows Credential Manager
- Visible in Control Panel → Credential Manager → Windows Credentials
- Persist with `CRED_PERSIST_ENTERPRISE` flag so it's scoped per user
- Encrypted using Windows Data Protection API
## Security Considerations
1. **Encryption**: Credentials are encrypted by the operating system's credential manager
2. **Access Control**: Only the name who stored the credential can retrieve it
3. **No Plain Text**: Passwords are never stored in plain text
4. **Memory Safety**: Bun zeros out password memory after use
5. **Process Isolation**: Credentials are isolated per name account
## Limitations
- Maximum password length varies by platform (typically 2048-4096 bytes)
- Service and name names should be reasonable lengths (< 256 characters)
- Some special characters may need escaping depending on the platform
- Requires appropriate system services:
- Linux: Secret service daemon must be running
- macOS: Keychain Access must be available
- Windows: Credential Manager service must be enabled
## Comparison with Environment Variables
Unlike environment variables, `Bun.secrets`:
- ✅ Encrypts credentials at rest (thanks to the operating system)
- ✅ Avoids exposing secrets in process memory dumps (memory is zeroed after its no longer needed)
- ✅ Survives application restarts
- ✅ Can be updated without restarting the application
- ✅ Provides name-level access control
- ❌ Requires OS credential service
- ❌ Not very useful for deployment secrets (use environment variables in production)
## Best Practices
1. **Use descriptive service names**: Match the tool or application name
If you're building a CLI for external use, you probably should use a UTI (Uniform Type Identifier) for the service name.
```javascript
// Good - matches the actual tool
{ service: "com.docker.hub", name: "username" }
{ service: "com.vercel.cli", name: "team-name" }
// Avoid - too generic
{ service: "api", name: "key" }
```
2. **Credentials-only**: Don't store application configuration in this API
This API is slow, you probably still need to use a config file for some things.
3. **Use for local development tools**:
- ✅ CLI tools (gh, npm, docker, kubectl)
- ✅ Local development servers
- ✅ Personal API keys for testing
- ❌ Production servers (use proper secret management)
## TypeScript
```typescript
namespace Bun {
interface SecretsOptions {
service: string;
name: string;
}
interface Secrets {
get(options: SecretsOptions): Promise<string | null>;
set(options: SecretsOptions, value: string): Promise<void>;
delete(options: SecretsOptions): Promise<boolean>;
}
const secrets: Secrets;
}
```
## See Also
- [Environment Variables](./env.md) - For deployment configuration
- [Bun.password](./password.md) - For password hashing and verification

View File

@@ -1,4 +1,4 @@
Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite. The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports both PostgreSQL and SQLite. The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
```ts
import { sql, SQL } from "bun";
@@ -10,16 +10,9 @@ const users = await sql`
LIMIT ${10}
`;
// With MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
SELECT * FROM users
WHERE active = ${true}
`;
// With SQLite
// With a a SQLite db
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
const results = await sqlite`
SELECT * FROM users
WHERE active = ${1}
`;
@@ -59,7 +52,7 @@ Bun.SQL provides a unified API for multiple database systems:
PostgreSQL is used when:
- The connection string doesn't match SQLite or MySQL patterns (it's the fallback adapter)
- The connection string doesn't match SQLite patterns (it's the fallback adapter)
- The connection string explicitly uses `postgres://` or `postgresql://` protocols
- No connection string is provided and environment variables point to PostgreSQL
@@ -73,82 +66,9 @@ const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;
```
### MySQL
MySQL support is built into Bun.SQL, providing the same tagged template literal interface with full compatibility for MySQL 5.7+ and MySQL 8.0+:
```ts
import { SQL } from "bun";
// MySQL connection
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2 protocol also works
// Using options object
const mysql3 = new SQL({
adapter: "mysql",
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
});
// Works with parameters - automatically uses prepared statements
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions work the same as PostgreSQL
await mysql.begin(async tx => {
await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});
// Bulk inserts
const newUsers = [
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;
```
{% details summary="MySQL Connection String Formats" %}
MySQL accepts various URL formats for connection strings:
```ts
// Standard mysql:// protocol
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // Default port 3306
// mysql2:// protocol (compatibility with mysql2 npm package)
new SQL("mysql2://user:pass@localhost:3306/database");
// With query parameters
new SQL("mysql://user:pass@localhost/db?ssl=true");
// Unix socket connection
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
```
{% /details %}
{% details summary="MySQL-Specific Features" %}
MySQL databases support:
- **Prepared statements**: Automatically created for parameterized queries with statement caching
- **Binary protocol**: For better performance with prepared statements and accurate type handling
- **Multiple result sets**: Support for stored procedures returning multiple result sets
- **Authentication plugins**: Support for mysql_native_password, caching_sha2_password (MySQL 8.0 default), and sha256_password
- **SSL/TLS connections**: Configurable SSL modes similar to PostgreSQL
- **Connection attributes**: Client information sent to server for monitoring
- **Query pipelining**: Execute multiple prepared statements without waiting for responses
{% /details %}
### SQLite
SQLite support is built into Bun.SQL, providing the same tagged template literal interface:
SQLite support is now built into Bun.SQL, providing the same tagged template literal interface as PostgreSQL:
```ts
import { SQL } from "bun";
@@ -170,7 +90,8 @@ const db2 = new SQL({
const db3 = new SQL("myapp.db", { adapter: "sqlite" });
```
{% details summary="SQLite Connection String Formats" %}
<details>
<summary>SQLite Connection String Formats</summary>
SQLite accepts various URL formats for connection strings:
@@ -201,9 +122,10 @@ new SQL("sqlite://data.db?mode=rwc"); // Read-write-create mode (default)
**Note:** Simple filenames without a protocol (like `"myapp.db"`) require explicitly specifying `{ adapter: "sqlite" }` to avoid ambiguity with PostgreSQL.
{% /details %}
</details>
{% details summary="SQLite-Specific Options" %}
<details>
<summary>SQLite-Specific Options</summary>
SQLite databases support additional configuration options:
@@ -229,7 +151,7 @@ Query parameters in the URL are parsed to set these options:
- `?mode=rw``readonly: false, create: false`
- `?mode=rwc``readonly: false, create: true` (default)
{% /details %}
</details>
### Inserting data
@@ -442,24 +364,7 @@ await query;
### Automatic Database Detection
When using `Bun.sql()` without arguments or `new SQL()` with a connection string, the adapter is automatically detected based on the URL format:
#### MySQL Auto-Detection
MySQL is automatically selected when the connection string matches these patterns:
- `mysql://...` - MySQL protocol URLs
- `mysql2://...` - MySQL2 protocol URLs (compatibility alias)
```ts
// These all use MySQL automatically (no adapter needed)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");
// Works with DATABASE_URL environment variable
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js
```
When using `Bun.sql()` without arguments or `new SQL()` with a connection string, the adapter is automatically detected based on the URL format. SQLite becomes the default adapter in these cases:
#### SQLite Auto-Detection
@@ -485,42 +390,17 @@ DATABASE_URL="file://./data/app.db" bun run app.js
#### PostgreSQL Auto-Detection
PostgreSQL is the default for connection strings that don't match MySQL or SQLite patterns:
PostgreSQL is the default for all other connection strings:
```bash
# PostgreSQL is detected for these patterns
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js
# Or any URL that doesn't match MySQL or SQLite patterns
# Or any URL that doesn't match SQLite patterns
DATABASE_URL="localhost:5432/mydb" bun run app.js
```
### MySQL Environment Variables
MySQL connections can be configured via environment variables:
```bash
# Primary connection URL (checked first)
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"
# Alternative: DATABASE_URL with MySQL protocol
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"
```
If no connection URL is provided, MySQL checks these individual parameters:
| Environment Variable | Default Value | Description |
| ------------------------ | ------------- | -------------------------------- |
| `MYSQL_HOST` | `localhost` | Database host |
| `MYSQL_PORT` | `3306` | Database port |
| `MYSQL_USER` | `root` | Database user |
| `MYSQL_PASSWORD` | (empty) | Database password |
| `MYSQL_DATABASE` | `mysql` | Database name |
| `MYSQL_URL` | (empty) | Primary connection URL for MySQL |
| `TLS_MYSQL_DATABASE_URL` | (empty) | SSL/TLS-enabled connection URL |
### PostgreSQL Environment Variables
The following environment variables can be used to define the PostgreSQL connection:
@@ -578,54 +458,6 @@ The `--sql-preconnect` flag will automatically establish a PostgreSQL connection
You can configure your database connection manually by passing options to the SQL constructor. Options vary depending on the database adapter:
### MySQL Options
```ts
import { SQL } from "bun";
const db = new SQL({
// Required for MySQL when using options object
adapter: "mysql",
// Connection details
hostname: "localhost",
port: 3306,
database: "myapp",
username: "dbuser",
password: "secretpass",
// Unix socket connection (alternative to hostname/port)
// socket: "/var/run/mysqld/mysqld.sock",
// Connection pool settings
max: 20, // Maximum connections in pool (default: 10)
idleTimeout: 30, // Close idle connections after 30s
maxLifetime: 0, // Connection lifetime in seconds (0 = forever)
connectionTimeout: 30, // Timeout when establishing new connections
// SSL/TLS options
ssl: "prefer", // or "disable", "require", "verify-ca", "verify-full"
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// },
// Callbacks
onconnect: client => {
console.log("Connected to MySQL");
},
onclose: (client, err) => {
if (err) {
console.error("MySQL connection error:", err);
} else {
console.log("MySQL connection closed");
}
},
});
```
### PostgreSQL Options
```ts
@@ -700,14 +532,15 @@ const db = new SQL({
});
```
{% details summary="SQLite Connection Notes" %}
<details>
<summary>SQLite Connection Notes</summary>
- **Connection Pooling**: SQLite doesn't use connection pooling as it's a file-based database. Each `SQL` instance represents a single connection.
- **Transactions**: SQLite supports nested transactions through savepoints, similar to PostgreSQL.
- **Concurrent Access**: SQLite handles concurrent access through file locking. Use WAL mode for better concurrency.
- **Memory Databases**: Using `:memory:` creates a temporary database that exists only for the connection lifetime.
{% /details %}
</details>
## Dynamic passwords
@@ -1005,8 +838,6 @@ try {
}
```
{% details summary="PostgreSQL-Specific Error Codes" %}
### PostgreSQL Connection Errors
| Connection Errors | Description |
@@ -1072,13 +903,12 @@ try {
| `ERR_POSTGRES_UNSAFE_TRANSACTION` | Unsafe transaction operation detected |
| `ERR_POSTGRES_INVALID_TRANSACTION_STATE` | Invalid transaction state |
{% /details %}
### SQLite-Specific Errors
SQLite errors provide error codes and numbers that correspond to SQLite's standard error codes:
{% details summary="Common SQLite Error Codes" %}
<details>
<summary>Common SQLite Error Codes</summary>
| Error Code | errno | Description |
| ------------------- | ----- | ---------------------------------------------------- |
@@ -1115,7 +945,7 @@ try {
}
```
{% /details %}
</details>
## Numbers and BigInt
@@ -1149,106 +979,11 @@ console.log(typeof x, x); // "bigint" 9223372036854777n
There's still some things we haven't finished yet.
- Connection preloading via `--db-preconnect` Bun CLI flag
- MySQL support: [we're working on it](https://github.com/oven-sh/bun/pull/15274)
- Column name transforms (e.g. `snake_case` to `camelCase`). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit's `WTF::String`.
- Column type transforms
## Database-Specific Features
#### Authentication Methods
MySQL supports multiple authentication plugins that are automatically negotiated:
- **`mysql_native_password`** - Traditional MySQL authentication, widely compatible
- **`caching_sha2_password`** - Default in MySQL 8.0+, more secure with RSA key exchange
- **`sha256_password`** - SHA-256 based authentication
The client automatically handles authentication plugin switching when requested by the server, including secure password exchange over non-SSL connections.
#### Prepared Statements & Performance
MySQL uses server-side prepared statements for all parameterized queries:
```ts
// This automatically creates a prepared statement on the server
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;
// Prepared statements are cached and reused for identical queries
for (const id of userIds) {
// Same prepared statement is reused
await mysql`SELECT * FROM users WHERE id = ${id}`;
}
// Query pipelining - multiple statements sent without waiting
const [users, orders, products] = await Promise.all([
mysql`SELECT * FROM users WHERE active = ${true}`,
mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);
```
#### Multiple Result Sets
MySQL can return multiple result sets from multi-statement queries:
```ts
const mysql = new SQL("mysql://user:pass@localhost/mydb");
// Multi-statement queries with simple() method
const multiResults = await mysql`
SELECT * FROM users WHERE id = 1;
SELECT * FROM orders WHERE user_id = 1;
`.simple();
```
#### Character Sets & Collations
Bun.SQL automatically uses `utf8mb4` character set for MySQL connections, ensuring full Unicode support including emojis. This is the recommended character set for modern MySQL applications.
#### Connection Attributes
Bun automatically sends client information to MySQL for better monitoring:
```ts
// These attributes are sent automatically:
// _client_name: "Bun"
// _client_version: <bun version>
// You can see these in MySQL's performance_schema.session_connect_attrs
```
#### Type Handling
MySQL types are automatically converted to JavaScript types:
| MySQL Type | JavaScript Type | Notes |
| --------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
| INT, TINYINT, MEDIUMINT | number | Within safe integer range |
| BIGINT | string, number or BigInt | If the value fits in i32/u32 size will be number otherwise string or BigInt Based on `bigint` option |
| DECIMAL, NUMERIC | string | To preserve precision |
| FLOAT, DOUBLE | number | |
| DATE | Date | JavaScript Date object |
| DATETIME, TIMESTAMP | Date | With timezone handling |
| TIME | number | Total of microseconds |
| YEAR | number | |
| CHAR, VARCHAR, VARSTRING, STRING | string | |
| TINY TEXT, MEDIUM TEXT, TEXT, LONG TEXT | string | |
| TINY BLOB, MEDIUM BLOB, BLOG, LONG BLOB | string | BLOB Types are alias for TEXT types |
| JSON | object/array | Automatically parsed |
| BIT(1) | boolean | BIT(1) in MySQL |
| GEOMETRY | string | Geometry data |
#### Differences from PostgreSQL
While the API is unified, there are some behavioral differences:
1. **Parameter placeholders**: MySQL uses `?` internally but Bun converts `$1, $2` style automatically
2. **RETURNING clause**: MySQL doesn't support RETURNING; use `result.lastInsertRowid` or a separate SELECT
3. **Array types**: MySQL doesn't have native array types like PostgreSQL
### MySQL-Specific Features
We haven't implemented `LOAD DATA INFILE` support yet
### PostgreSQL-Specific Features
### Postgres-specific features
We haven't implemented these yet:
@@ -1263,89 +998,13 @@ We also haven't implemented some of the more uncommon features like:
- Point & PostGIS types
- All the multi-dimensional integer array types (only a couple of the types are supported)
## Common Patterns & Best Practices
### Working with MySQL Result Sets
```ts
// Getting insert ID after INSERT
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL's LAST_INSERT_ID()
// Handling affected rows
const updated =
await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Number of rows updated
// Using MySQL-specific functions
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;
```
### MySQL Error Handling
```ts
try {
await mysql`INSERT INTO users (email) VALUES (${"duplicate@email.com"})`;
} catch (error) {
if (error.code === "ER_DUP_ENTRY") {
console.log("Duplicate entry detected");
} else if (error.code === "ER_ACCESS_DENIED_ERROR") {
console.log("Access denied");
} else if (error.code === "ER_BAD_DB_ERROR") {
console.log("Database does not exist");
}
// MySQL error codes are compatible with mysql/mysql2 packages
}
```
### Performance Tips for MySQL
1. **Use connection pooling**: Set appropriate `max` pool size based on your workload
2. **Enable prepared statements**: They're enabled by default and improve performance
3. **Use transactions for bulk operations**: Group related queries in transactions
4. **Index properly**: MySQL relies heavily on indexes for query performance
5. **Use `utf8mb4` charset**: It's set by default and handles all Unicode characters
## Frequently Asked Questions
> Why is this `Bun.sql` and not `Bun.postgres`?
The plan was to add more database drivers in the future. Now with MySQL support added, this unified API supports PostgreSQL, MySQL, and SQLite.
The plan is to add more database drivers in the future.
> How do I know which database adapter is being used?
The adapter is automatically detected from the connection string:
- URLs starting with `mysql://` or `mysql2://` use MySQL
- URLs matching SQLite patterns (`:memory:`, `sqlite://`, `file://`) use SQLite
- Everything else defaults to PostgreSQL
> Are MySQL stored procedures supported?
Yes, stored procedures are fully supported including OUT parameters and multiple result sets:
```ts
// Call stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Get OUT parameter
const outParam = await mysql`SELECT @total_orders as total`;
```
> Can I use MySQL-specific SQL syntax?
Yes, you can use any MySQL-specific syntax:
```ts
// MySQL-specific syntax works fine
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
```
## Why not just use an existing library?
> Why not just use an existing library?
npm packages like postgres.js, pg, and node-postgres can be used in Bun too. They're great options.

View File

@@ -122,59 +122,6 @@ Messages are automatically enqueued until the worker is ready, so there is no ne
To send messages, use [`worker.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) and [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This leverages the [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
### Performance optimizations
Bun includes optimized fast paths for `postMessage` to dramatically improve performance for common data types:
**String fast path** - When posting pure string values, Bun bypasses the structured clone algorithm entirely, achieving significant performance gains with no serialization overhead.
**Simple object fast path** - For plain objects containing only primitive values (strings, numbers, booleans, null, undefined), Bun uses an optimized serialization path that stores properties directly without full structured cloning.
The simple object fast path activates when the object:
- Is a plain object with no prototype chain modifications
- Contains only enumerable, configurable data properties
- Has no indexed properties or getter/setter methods
- All property values are primitives or strings
With these fast paths, Bun's `postMessage` performs **2-241x faster** because the message length no longer has a meaningful impact on performance.
**Bun (with fast paths):**
```
postMessage({ prop: 11 chars string, ...9 more props }) - 648ns
postMessage({ prop: 14 KB string, ...9 more props }) - 719ns
postMessage({ prop: 3 MB string, ...9 more props }) - 1.26µs
```
**Node.js v24.6.0 (for comparison):**
```
postMessage({ prop: 11 chars string, ...9 more props }) - 1.19µs
postMessage({ prop: 14 KB string, ...9 more props }) - 2.69µs
postMessage({ prop: 3 MB string, ...9 more props }) - 304µs
```
```js
// String fast path - optimized
postMessage("Hello, worker!");
// Simple object fast path - optimized
postMessage({
message: "Hello",
count: 42,
enabled: true,
data: null,
});
// Complex objects still work but use standard structured clone
postMessage({
nested: { deep: { object: true } },
date: new Date(),
buffer: new ArrayBuffer(8),
});
```
```js
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
postMessage({ hello: "world" });

View File

@@ -1,459 +0,0 @@
In Bun, YAML is a first-class citizen alongside JSON and TOML.
Bun provides built-in support for YAML files through both runtime APIs and bundler integration. You can
- Parse YAML strings with `Bun.YAML.parse`
- import & require YAML files as modules at runtime (including hot reloading & watch mode support)
- import & require YAML files in frontend apps via bun's bundler
## Conformance
Bun's YAML parser currently passes over 90% of the official YAML test suite. While we're actively working on reaching 100% conformance, the current implementation covers the vast majority of real-world use cases. The parser is written in Zig for optimal performance and is continuously being improved.
## Runtime API
### `Bun.YAML.parse()`
Parse a YAML string into a JavaScript object.
```ts
import { YAML } from "bun";
const text = `
name: John Doe
age: 30
email: john@example.com
hobbies:
- reading
- coding
- hiking
`;
const data = YAML.parse(text);
console.log(data);
// {
// name: "John Doe",
// age: 30,
// email: "john@example.com",
// hobbies: ["reading", "coding", "hiking"]
// }
```
#### Multi-document YAML
When parsing YAML with multiple documents (separated by `---`), `Bun.YAML.parse()` returns an array:
```ts
const multiDoc = `
---
name: Document 1
---
name: Document 2
---
name: Document 3
`;
const docs = Bun.YAML.parse(multiDoc);
console.log(docs);
// [
// { name: "Document 1" },
// { name: "Document 2" },
// { name: "Document 3" }
// ]
```
#### Supported YAML Features
Bun's YAML parser supports the full YAML 1.2 specification, including:
- **Scalars**: strings, numbers, booleans, null values
- **Collections**: sequences (arrays) and mappings (objects)
- **Anchors and Aliases**: reusable nodes with `&` and `*`
- **Tags**: type hints like `!!str`, `!!int`, `!!float`, `!!bool`, `!!null`
- **Multi-line strings**: literal (`|`) and folded (`>`) scalars
- **Comments**: using `#`
- **Directives**: `%YAML` and `%TAG`
```ts
const yaml = `
# Employee record
employee: &emp
name: Jane Smith
department: Engineering
skills:
- JavaScript
- TypeScript
- React
manager: *emp # Reference to employee
config: !!str 123 # Explicit string type
description: |
This is a multi-line
literal string that preserves
line breaks and spacing.
summary: >
This is a folded string
that joins lines with spaces
unless there are blank lines.
`;
const data = Bun.YAML.parse(yaml);
```
#### Error Handling
`Bun.YAML.parse()` throws a `SyntaxError` if the YAML is invalid:
```ts
try {
Bun.YAML.parse("invalid: yaml: content:");
} catch (error) {
console.error("Failed to parse YAML:", error.message);
}
```
## Module Import
### ES Modules
You can import YAML files directly as ES modules. The YAML content is parsed and made available as both default and named exports:
```yaml#config.yaml
database:
host: localhost
port: 5432
name: myapp
redis:
host: localhost
port: 6379
features:
auth: true
rateLimit: true
analytics: false
```
#### Default Import
```ts#app.ts
import config from "./config.yaml";
console.log(config.database.host); // "localhost"
console.log(config.redis.port); // 6379
```
#### Named Imports
You can destructure top-level YAML properties as named imports:
```ts
import { database, redis, features } from "./config.yaml";
console.log(database.host); // "localhost"
console.log(redis.port); // 6379
console.log(features.auth); // true
```
Or combine both:
```ts
import config, { database, features } from "./config.yaml";
// Use the full config object
console.log(config);
// Or use specific parts
if (features.rateLimit) {
setupRateLimiting(database);
}
```
### CommonJS
YAML files can also be required in CommonJS:
```js
const config = require("./config.yaml");
console.log(config.database.name); // "myapp"
// Destructuring also works
const { database, redis } = require("./config.yaml");
console.log(database.port); // 5432
```
## Hot Reloading with YAML
One of the most powerful features of Bun's YAML support is hot reloading. When you run your application with `bun --hot`, changes to YAML files are automatically detected and reloaded without closing connections
### Configuration Hot Reloading
```yaml#config.yaml
server:
port: 3000
host: localhost
features:
debug: true
verbose: false
```
```ts#server.ts
import { server, features } from "./config.yaml";
console.log(`Starting server on ${server.host}:${server.port}`);
if (features.debug) {
console.log("Debug mode enabled");
}
// Your server code here
Bun.serve({
port: server.port,
hostname: server.host,
fetch(req) {
if (features.verbose) {
console.log(`${req.method} ${req.url}`);
}
return new Response("Hello World");
},
});
```
Run with hot reloading:
```bash
bun --hot server.ts
```
Now when you modify `config.yaml`, the changes are immediately reflected in your running application. This is perfect for:
- Adjusting configuration during development
- Testing different settings without restarts
- Live debugging with configuration changes
- Feature flag toggling
## Configuration Management
### Environment-Based Configuration
YAML excels at managing configuration across different environments:
```yaml#config.yaml
defaults: &defaults
timeout: 5000
retries: 3
cache:
enabled: true
ttl: 3600
development:
<<: *defaults
api:
url: http://localhost:4000
key: dev_key_12345
logging:
level: debug
pretty: true
staging:
<<: *defaults
api:
url: https://staging-api.example.com
key: ${STAGING_API_KEY}
logging:
level: info
pretty: false
production:
<<: *defaults
api:
url: https://api.example.com
key: ${PROD_API_KEY}
cache:
enabled: true
ttl: 86400
logging:
level: error
pretty: false
```
```ts#app.ts
import configs from "./config.yaml";
const env = process.env.NODE_ENV || "development";
const config = configs[env];
// Environment variables in YAML values can be interpolated
function interpolateEnvVars(obj: any): any {
if (typeof obj === "string") {
return obj.replace(/\${(\w+)}/g, (_, key) => process.env[key] || "");
}
if (typeof obj === "object") {
for (const key in obj) {
obj[key] = interpolateEnvVars(obj[key]);
}
}
return obj;
}
export default interpolateEnvVars(config);
```
### Feature Flags Configuration
```yaml#features.yaml
features:
newDashboard:
enabled: true
rolloutPercentage: 50
allowedUsers:
- admin@example.com
- beta@example.com
experimentalAPI:
enabled: false
endpoints:
- /api/v2/experimental
- /api/v2/beta
darkMode:
enabled: true
default: auto # auto, light, dark
```
```ts#feature-flags.ts
import { features } from "./features.yaml";
export function isFeatureEnabled(
featureName: string,
userEmail?: string,
): boolean {
const feature = features[featureName];
if (!feature?.enabled) {
return false;
}
// Check rollout percentage
if (feature.rolloutPercentage < 100) {
const hash = hashCode(userEmail || "anonymous");
if (hash % 100 >= feature.rolloutPercentage) {
return false;
}
}
// Check allowed users
if (feature.allowedUsers && userEmail) {
return feature.allowedUsers.includes(userEmail);
}
return true;
}
// Use with hot reloading to toggle features in real-time
if (isFeatureEnabled("newDashboard", user.email)) {
renderNewDashboard();
} else {
renderLegacyDashboard();
}
```
### Database Configuration
```yaml#database.yaml
connections:
primary:
type: postgres
host: ${DB_HOST:-localhost}
port: ${DB_PORT:-5432}
database: ${DB_NAME:-myapp}
username: ${DB_USER:-postgres}
password: ${DB_PASS}
pool:
min: 2
max: 10
idleTimeout: 30000
cache:
type: redis
host: ${REDIS_HOST:-localhost}
port: ${REDIS_PORT:-6379}
password: ${REDIS_PASS}
db: 0
analytics:
type: clickhouse
host: ${ANALYTICS_HOST:-localhost}
port: 8123
database: analytics
migrations:
autoRun: ${AUTO_MIGRATE:-false}
directory: ./migrations
seeds:
enabled: ${SEED_DB:-false}
directory: ./seeds
```
```ts#db.ts
import { connections, migrations } from "./database.yaml";
import { createConnection } from "./database-driver";
// Parse environment variables with defaults
function parseConfig(config: any) {
return JSON.parse(
JSON.stringify(config).replace(
/\${([^:-]+)(?::([^}]+))?}/g,
(_, key, defaultValue) => process.env[key] || defaultValue || "",
),
);
}
const dbConfig = parseConfig(connections);
export const db = await createConnection(dbConfig.primary);
export const cache = await createConnection(dbConfig.cache);
export const analytics = await createConnection(dbConfig.analytics);
// Auto-run migrations if configured
if (parseConfig(migrations).autoRun === "true") {
await runMigrations(db, migrations.directory);
}
```
### Bundler Integration
When you import YAML files in your application and bundle it with Bun, the YAML is parsed at build time and included as a JavaScript module:
```bash
bun build app.ts --outdir=dist
```
This means:
- Zero runtime YAML parsing overhead in production
- Smaller bundle sizes
- Tree-shaking support for unused configuration (named imports)
### Dynamic Imports
YAML files can be dynamically imported, useful for loading configuration on demand:
```ts#Load configuration based on environment
const env = process.env.NODE_ENV || "development";
const config = await import(`./configs/${env}.yaml`);
// Load user-specific settings
async function loadUserSettings(userId: string) {
try {
const settings = await import(`./users/${userId}/settings.yaml`);
return settings.default;
} catch {
return await import("./users/default-settings.yaml");
}
}
```

View File

@@ -408,119 +408,16 @@ $ bun build --compile --asset-naming="[name].[ext]" ./index.ts
To trim down the size of the executable a little, pass `--minify` to `bun build --compile`. This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller.
## Using Bun.build() API
You can also generate standalone executables using the `Bun.build()` JavaScript API. This is useful when you need programmatic control over the build process.
### Basic usage
```js
await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
compile: {
target: "bun-windows-x64",
outfile: "myapp.exe",
},
});
```
### Windows metadata with Bun.build()
When targeting Windows, you can specify metadata through the `windows` object:
```js
await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
compile: {
target: "bun-windows-x64",
outfile: "myapp.exe",
windows: {
title: "My Application",
publisher: "My Company Inc",
version: "1.2.3.4",
description: "A powerful application built with Bun",
copyright: "© 2024 My Company Inc",
hideConsole: false, // Set to true for GUI applications
icon: "./icon.ico", // Path to icon file
},
},
});
```
### Cross-compilation with Bun.build()
You can cross-compile for different platforms:
```js
// Build for multiple platforms
const platforms = [
{ target: "bun-windows-x64", outfile: "app-windows.exe" },
{ target: "bun-linux-x64", outfile: "app-linux" },
{ target: "bun-darwin-arm64", outfile: "app-macos" },
];
for (const platform of platforms) {
await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
compile: platform,
});
}
```
## Windows-specific flags
When compiling a standalone executable for Windows, there are several platform-specific options that can be used to customize the generated `.exe` file:
When compiling a standalone executable on Windows, there are two platform-specific options that can be used to customize metadata on the generated `.exe` file:
### Visual customization
- `--windows-icon=path/to/icon.ico` - Set the executable file icon
- `--windows-hide-console` - Disable the background terminal window (useful for GUI applications)
### Metadata customization
You can embed version information and other metadata into your Windows executable:
- `--windows-title <STR>` - Set the product name (appears in file properties)
- `--windows-publisher <STR>` - Set the company name
- `--windows-version <STR>` - Set the version number (e.g. "1.2.3.4")
- `--windows-description <STR>` - Set the file description
- `--windows-copyright <STR>` - Set the copyright information
#### Example with all metadata flags:
```sh
bun build --compile ./app.ts \
--outfile myapp.exe \
--windows-title "My Application" \
--windows-publisher "My Company Inc" \
--windows-version "1.2.3.4" \
--windows-description "A powerful application built with Bun" \
--windows-copyright "© 2024 My Company Inc"
```
This metadata will be visible in Windows Explorer when viewing the file properties:
1. Right-click the executable in Windows Explorer
2. Select "Properties"
3. Go to the "Details" tab
#### Version string format
The `--windows-version` flag accepts version strings in the following formats:
- `"1"` - Will be normalized to "1.0.0.0"
- `"1.2"` - Will be normalized to "1.2.0.0"
- `"1.2.3"` - Will be normalized to "1.2.3.0"
- `"1.2.3.4"` - Full version format
Each version component must be a number between 0 and 65535.
- `--windows-icon=path/to/icon.ico` to customize the executable file icon.
- `--windows-hide-console` to disable the background terminal, which can be used for applications that do not need a TTY.
{% callout %}
These flags currently cannot be used when cross-compiling because they depend on Windows APIs. They are only available when building on Windows itself.
These flags currently cannot be used when cross-compiling because they depend on Windows APIs.
{% /callout %}

View File

@@ -1,4 +1,4 @@
Bun's fast native bundler can be used via the `bun build` CLI command or the `Bun.build()` JavaScript API.
Bun's fast native bundler is now in beta. It can be used via the `bun build` CLI command or the `Bun.build()` JavaScript API.
{% codetabs group="a" %}
@@ -1259,33 +1259,6 @@ $ bun build ./index.tsx --outdir ./out --drop=console --drop=debugger --drop=any
{% /codetabs %}
### `throw`
Controls error handling behavior when the build fails. When set to `true` (default), the returned promise rejects with an `AggregateError`. When set to `false`, the promise resolves with a `BuildOutput` object where `success` is `false`.
```ts#JavaScript
// Default behavior: throws on error
try {
await Bun.build({
entrypoints: ['./index.tsx'],
throw: true, // default
});
} catch (error) {
// Handle AggregateError
console.error("Build failed:", error);
}
// Alternative: handle errors via success property
const result = await Bun.build({
entrypoints: ['./index.tsx'],
throw: false,
});
if (!result.success) {
console.error("Build failed with errors:", result.logs);
}
```
## Outputs
The `Bun.build` function returns a `Promise<BuildOutput>`, defined as:
@@ -1596,7 +1569,8 @@ interface BuildConfig {
* When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
* When set to `false`, the `success` property of the returned object will be `false` when a build failure happens.
*
* This defaults to `true`.
* This defaults to `false` in Bun 1.1 and will change to `true` in Bun 1.2
* as most usage of `Bun.build` forgets to check for errors.
*/
throw?: boolean;
}

View File

@@ -1,6 +1,6 @@
The Bun bundler implements a set of default loaders out of the box. As a rule of thumb, the bundler and the runtime both support the same set of file types out of the box.
`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.toml` `.json` `.yaml` `.yml` `.txt` `.wasm` `.node` `.html`
`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.toml` `.json` `.txt` `.wasm` `.node` `.html`
Bun uses the file extension to determine which built-in _loader_ should be used to parse the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building [plugins](https://bun.com/docs/bundler/plugins) that extend Bun with custom loaders.
@@ -121,55 +121,6 @@ export default {
{% /codetabs %}
### `yaml`
**YAML loader**. Default for `.yaml` and `.yml`.
YAML files can be directly imported. Bun will parse them with its fast native YAML parser.
```ts
import config from "./config.yaml";
config.database.host; // => "localhost"
// via import attribute:
// import myCustomYAML from './my.config' with {type: "yaml"};
```
During bundling, the parsed YAML is inlined into the bundle as a JavaScript object.
```ts
var config = {
database: {
host: "localhost",
port: 5432,
},
// ...other fields
};
config.database.host;
```
If a `.yaml` or `.yml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
{% codetabs %}
```yaml#Input
name: John Doe
age: 35
email: johndoe@example.com
```
```js#Output
export default {
name: "John Doe",
age: 35,
email: "johndoe@example.com"
}
```
{% /codetabs %}
For more details on YAML support including the runtime API `Bun.YAML.parse()`, see the [YAML API documentation](/docs/api/yaml).
### `text`
**Text loader**. Default for `.txt`.

View File

@@ -9,7 +9,6 @@ Plugins can register callbacks to be run at various points in the lifecycle of a
- [`onStart()`](#onstart): Run once the bundler has started a bundle
- [`onResolve()`](#onresolve): Run before a module is resolved
- [`onLoad()`](#onload): Run before a module is loaded.
- [`onEnd()`](#onend): Run after the bundle has completed
- [`onBeforeParse()`](#onbeforeparse): Run zero-copy native addons in the parser thread before a file is parsed.
### Reference
@@ -19,7 +18,6 @@ A rough overview of the types (please refer to Bun's `bun.d.ts` for the full typ
```ts
type PluginBuilder = {
onStart(callback: () => void): void;
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
onResolve: (
args: { filter: RegExp; namespace?: string },
callback: (args: { path: string; importer: string }) => {
@@ -287,53 +285,6 @@ plugin({
Note that the `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback.
### `onEnd`
```ts
onEnd(callback: (result: BuildOutput) => void | Promise<void>): void;
```
Registers a callback to be run when the bundler completes a bundle (whether successful or not).
The callback receives the `BuildOutput` object containing:
- `success`: boolean indicating if the build succeeded
- `outputs`: array of generated build artifacts
- `logs`: array of build messages (warnings, errors, etc.)
This is useful for post-processing, cleanup, notifications, or custom error handling.
```ts
await Bun.build({
entrypoints: ["./index.ts"],
outdir: "./out",
plugins: [
{
name: "onEnd example",
setup(build) {
build.onEnd(result => {
if (result.success) {
console.log(
`✅ Build succeeded with ${result.outputs.length} outputs`,
);
} else {
console.error(`❌ Build failed with ${result.logs.length} errors`);
}
});
},
},
],
});
```
The `onEnd` callbacks are called:
- **Before** the build promise resolves or rejects
- **After** all bundling is complete
- **In the order** they were registered
Multiple plugins can register `onEnd` callbacks, and they will all be called sequentially. If an `onEnd` callback returns a promise, the build will wait for it to resolve before continuing.
## Native plugins
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.

View File

@@ -245,8 +245,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
---
- `--jsx-side-effects`
- `--jsx-side-effects`
- Controls whether JSX expressions are marked as `/* @__PURE__ */` for dead code elimination. Default is `false` (JSX marked as pure).
- n/a
- JSX is always assumed to be side-effect-free
---
@@ -617,7 +617,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
- `jsxSideEffects`
- `jsxSideEffects`
- Controls whether JSX expressions are marked as pure for dead code elimination
- Not supported in JS API, configure in `tsconfig.json`
---

View File

@@ -230,15 +230,16 @@ $ bun install --backend copyfile
**`symlink`** is typically only used for `file:` dependencies (and eventually `link:`) internally. To prevent infinite loops, it skips symlinking the `node_modules` folder.
If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass `--preserve-symlinks` to `node` or `bun`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).
If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).
```bash
$ rm -rf node_modules
$ bun install --backend symlink
$ bun --preserve-symlinks ./my-file.js
$ node --preserve-symlinks ./my-file.js # https://nodejs.org/api/cli.html#--preserve-symlinks
```
Bun's runtime does not currently expose an equivalent of `--preserve-symlinks`, though the code for it does exist.
## npm registry metadata
bun uses a binary format for caching NPM registry responses. This loads much faster than JSON and tends to be smaller on disk.

View File

@@ -8,14 +8,6 @@ The `bun` CLI contains a Node.js-compatible package manager designed to be a dra
{% /callout %}
{% callout %}
**💾 Disk efficient** — Bun install stores all packages in a global cache (`~/.bun/install/cache/`) and creates hardlinks (Linux) or copy-on-write clones (macOS) to `node_modules`. This means duplicate packages across projects point to the same underlying data, taking up virtually no extra disk space.
For more details, see [Package manager > Global cache](https://bun.com/docs/install/cache).
{% /callout %}
{% details summary="For Linux users" %}
The recommended minimum Linux Kernel version is 5.6. If you're on Linux kernel 5.1 - 5.5, `bun install` will work, but HTTP requests will be slow due to a lack of support for io_uring's `connect()` operation.
@@ -215,12 +207,6 @@ Isolated installs create a central package store in `node_modules/.bun/` with sy
For complete documentation on isolated installs, refer to [Package manager > Isolated installs](https://bun.com/docs/install/isolated).
## Disk efficiency
Bun uses a global cache at `~/.bun/install/cache/` to minimize disk usage. Packages are stored once and linked to `node_modules` using hardlinks (Linux/Windows) or copy-on-write (macOS), so duplicate packages across projects don't consume additional disk space.
For complete documentation refer to [Package manager > Global cache](https://bun.com/docs/install/cache).
## Configuration
The default behavior of `bun install` can be configured in `bunfig.toml`. The default values are shown below.

View File

@@ -1,4 +0,0 @@
{
"name": "Deployment",
"description": "A collection of guides for deploying Bun to providers"
}

View File

@@ -1,157 +0,0 @@
---
name: Deploy a Bun application on Railway
description: Deploy Bun applications to Railway with this step-by-step guide covering CLI and dashboard methods, optional PostgreSQL setup, and automatic SSL configuration.
---
Railway is an infrastructure platform where you can provision infrastructure, develop with that infrastructure locally, and then deploy to the cloud. It enables instant deployments from GitHub with zero configuration, automatic SSL, and built-in database provisioning.
This guide walks through deploying a Bun application with a PostgreSQL database (optional), which is exactly what the template below provides.
You can either follow this guide step-by-step or simply deploy the pre-configured template with one click:
{% raw %}
<a href="https://railway.com/deploy/bun-react-postgres?referralCode=Bun&utm_medium=integration&utm_source=template&utm_campaign=bun" target="_blank">
<img src="https://railway.com/button.svg" alt="Deploy on Railway" />
</a>
{% /raw %}
---
**Prerequisites**:
- A Bun application ready for deployment
- A [Railway account](https://railway.app/)
- Railway CLI (for CLI deployment method)
- A GitHub account (for Dashboard deployment method)
---
## Method 1: Deploy via CLI
---
#### Step 1
Ensure sure you have the Railway CLI installed.
```bash
bun install -g @railway/cli
```
---
#### Step 2
Log into your Railway account.
```bash
railway login
```
---
#### Step 3
After successfully authenticating, initialize a new project.
```bash
# Initialize project
bun-react-postgres$ railway init
```
---
#### Step 4
After initializing the project, add a new database and service.
> **Note:** Step 4 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 5.
```bash
# Add PostgreSQL database. Make sure to add this first!
bun-react-postgres$ railway add --database postgres
# Add your application service.
bun-react-postgres$ railway add --service bun-react-db --variables DATABASE_URL=\${{Postgres.DATABASE_URL}}
```
---
#### Step 5
After the services have been created and connected, deploy the application to Railway. By default, services are only accessible within Railway's private network. To make your app publicly accessible, you need to generate a public domain.
```bash
# Deploy your application
bun-nextjs-starter$ railway up
# Generate public domain
bun-nextjs-starter$ railway domain
```
---
## Method 2: Deploy via Dashboard
---
#### Step 1
Create a new project
1. Go to [Railway Dashboard](http://railway.com/dashboard?utm_medium=integration&utm_source=docs&utm_campaign=bun)
2. Click **"+ New"** → **"GitHub repo"**
3. Choose your repository
---
#### Step 2
Add a PostgreSQL database, and connect this database to the service
> **Note:** Step 2 is only necessary if your application uses a database. If you don't need PostgreSQL, skip to Step 3.
1. Click **"+ New"** → **"Database"** → **"Add PostgreSQL"**
2. After the database has been created, select your service (not the database)
3. Go to **"Variables"** tab
4. Click **"+ New Variable"** → **"Add Reference"**
5. Select `DATABASE_URL` from postgres
---
#### Step 3
Generate a public domain
1. Select your service
2. Go to **"Settings"** tab
3. Under **"Networking"**, click **"Generate Domain"**
---
Your app is now live! Railway auto-deploys on every GitHub push.
---
## Configuration (Optional)
---
By default, Railway uses [Nixpacks](https://docs.railway.com/guides/build-configuration#nixpacks-options) to automatically detect and build your Bun application with zero configuration.
However, using the [Railpack](https://docs.railway.com/guides/build-configuration#railpack) application builder provides better Bun support, and will always support the latest version of Bun. The pre-configured templates use Railpack by default.
To enable Railpack in a custom project, add the following to your `railway.json`:
```json
{
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "RAILPACK"
}
}
```
For more build configuration settings, check out the [Railway documentation](https://docs.railway.com/guides/build-configuration).

View File

@@ -1,76 +0,0 @@
---
name: Import a YAML file
---
Bun natively supports `.yaml` and `.yml` imports.
```yaml#config.yaml
database:
host: localhost
port: 5432
name: myapp
server:
port: 3000
timeout: 30
features:
auth: true
rateLimit: true
```
---
Import the file like any other source file.
```ts
import config from "./config.yaml";
config.database.host; // => "localhost"
config.server.port; // => 3000
config.features.auth; // => true
```
---
You can also use named imports to destructure top-level properties:
```ts
import { database, server, features } from "./config.yaml";
console.log(database.name); // => "myapp"
console.log(server.timeout); // => 30
console.log(features.rateLimit); // => true
```
---
Bun also supports [Import Attributes](https://github.com/tc39/proposal-import-attributes) syntax:
```ts
import config from "./config.yaml" with { type: "yaml" };
config.database.port; // => 5432
```
---
For parsing YAML strings at runtime, use `Bun.YAML.parse()`:
```ts
const yamlString = `
name: John Doe
age: 30
hobbies:
- reading
- coding
`;
const data = Bun.YAML.parse(yamlString);
console.log(data.name); // => "John Doe"
console.log(data.hobbies); // => ["reading", "coding"]
```
---
See [Docs > API > YAML](https://bun.com/docs/api/yaml) for complete documentation on YAML support in Bun.

View File

@@ -17,7 +17,7 @@ Bun reads the following files automatically (listed in order of increasing prece
- `.env`
- `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
- `.env.local` (not loaded when `NODE_ENV=test`)
- `.env.local`
```txt#.env
FOO=hello

View File

@@ -35,7 +35,7 @@ Add this directive to _just one file_ in your project, such as:
- Any single `.ts` file that TypeScript includes in your compilation
```ts
/// <reference types="bun-types/test-globals" />
/// <reference types="bun/test-globals" />
```
---

View File

@@ -48,12 +48,12 @@ This behavior is configurable with the `--backend` flag, which is respected by a
- **`copyfile`**: The fallback used when any of the above fail. It is the slowest option. On macOS, it uses `fcopyfile()`; on Linux it uses `copy_file_range()`.
- **`symlink`**: Currently used only `file:` (and eventually `link:`) dependencies. To prevent infinite loops, it skips symlinking the `node_modules` folder.
If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own `node_modules` folder or you pass `--preserve-symlinks` to `node` or `bun`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).
If you install with `--backend=symlink`, Node.js won't resolve node_modules of dependencies unless each dependency has its own `node_modules` folder or you pass `--preserve-symlinks` to `node`. See [Node.js documentation on `--preserve-symlinks`](https://nodejs.org/api/cli.html#--preserve-symlinks).
```bash
$ bun install --backend symlink
$ node --preserve-symlinks ./foo.js
$ bun --preserve-symlinks ./foo.js
```
Bun's runtime does not currently expose an equivalent of `--preserve-symlinks`.
{% /details %}

View File

@@ -1,81 +0,0 @@
Bun's package manager can scan packages for security vulnerabilities before installation, helping protect your applications from supply chain attacks and known vulnerabilities.
## Quick Start
Configure a security scanner in your `bunfig.toml`:
```toml
[install.security]
scanner = "@acme/bun-security-scanner"
```
When configured, Bun will:
- Scan all packages before installation
- Display security warnings and advisories
- Cancel installation if critical vulnerabilities are found
- Automatically disable auto-install for security
## How It Works
Security scanners analyze packages during `bun install`, `bun add`, and other package operations. They can detect:
- Known security vulnerabilities (CVEs)
- Malicious packages
- License compliance issues
- ...and more!
### Security Levels
Scanners report issues at two severity levels:
- **`fatal`** - Installation stops immediately, exits with non-zero code
- **`warn`** - In interactive terminals, prompts to continue; in CI, exits immediately
## Using Pre-built Scanners
Many security companies publish Bun security scanners as npm packages that you can install and use immediately.
### Installing a Scanner
Install a security scanner from npm:
```bash
$ bun add -d @acme/bun-security-scanner
```
> **Note:** Consult your security scanner's documentation for their specific package name and installation instructions. Most scanners will be installed with `bun add`.
### Configuring the Scanner
After installation, configure it in your `bunfig.toml`:
```toml
[install.security]
scanner = "@acme/bun-security-scanner"
```
### Enterprise Configuration
Some enterprise scanners might support authentication and/or configuration through environment variables:
```bash
# This might go in ~/.bashrc, for example
export SECURITY_API_KEY="your-api-key"
# The scanner will now use these credentials automatically
bun install
```
Consult your security scanner's documentation to learn which environment variables to set and if any additional configuration is required.
### Authoring your own scanner
For a complete example with tests and CI setup, see the official template:
[github.com/oven-sh/security-scanner-template](https://github.com/oven-sh/security-scanner-template)
## Related
- [Configuration (bunfig.toml)](/docs/runtime/bunfig#install-security-scanner)
- [Package Manager](/docs/install)
- [Security Scanner Template](https://github.com/oven-sh/security-scanner-template)

View File

@@ -219,9 +219,6 @@ export default {
page("install/npmrc", ".npmrc support", {
description: "Bun supports loading some configuration options from .npmrc",
}),
page("install/security-scanner-api", "Security Scanner API", {
description: "Scan your project for vulnerabilities with Bun's security scanner API.",
}),
// page("install/utilities", "Utilities", {
// description: "Use `bun pm` to introspect your global module cache or project dependency tree.",
// }),
@@ -386,9 +383,6 @@ export default {
page("api/spawn", "Child processes", {
description: `Spawn sync and async child processes with easily configurable input and output streams.`,
}), // "`Bun.spawn`"),
page("api/yaml", "YAML", {
description: `Bun.YAML.parse(string) lets you parse YAML files in JavaScript`,
}), // "`Bun.spawn`"),
page("api/html-rewriter", "HTMLRewriter", {
description: `Parse and transform HTML with Bun's native HTMLRewriter API, inspired by Cloudflare Workers.`,
}), // "`HTMLRewriter`"),

View File

@@ -195,12 +195,12 @@ Click the link in the right column to jump to the associated documentation.
---
- Parsing & Formatting
- [`Bun.semver`](https://bun.com/docs/api/semver), `Bun.TOML.parse`, [`Bun.YAML.parse`](https://bun.com/docs/api/yaml), [`Bun.color`](https://bun.com/docs/api/color)
- [`Bun.semver`](https://bun.com/docs/api/semver), `Bun.TOML.parse`, [`Bun.color`](https://bun.com/docs/api/color)
---
- Low-level / Internals
- `Bun.mmap`, `Bun.gc`, `Bun.generateHeapSnapshot`, [`bun:jsc`](https://bun.com/reference/bun/jsc)
- `Bun.mmap`, `Bun.gc`, `Bun.generateHeapSnapshot`, [`bun:jsc`](https://bun.com/docs/api/bun-jsc)
---

View File

@@ -94,7 +94,6 @@ Bun supports the following loaders:
- `file`
- `json`
- `toml`
- `yaml`
- `wasm`
- `napi`
- `base64`
@@ -497,32 +496,6 @@ Whether to generate a non-Bun lockfile alongside `bun.lock`. (A `bun.lock` will
print = "yarn"
```
### `install.security.scanner`
Configure a security scanner to scan packages for vulnerabilities before installation.
First, install a security scanner from npm:
```bash
$ bun add -d @acme/bun-security-scanner
```
Then configure it in your `bunfig.toml`:
```toml
[install.security]
scanner = "@acme/bun-security-scanner"
```
When a security scanner is configured:
- Auto-install is automatically disabled for security
- Packages are scanned before installation
- Installation is cancelled if fatal issues are found
- Security warnings are displayed during installation
Learn more about [using and writing security scanners](/docs/install/security).
### `install.linker`
Configure the default linker strategy. Default `"hoisted"`.

View File

@@ -8,10 +8,6 @@ Bun reads the following files automatically (listed in order of increasing prece
- `.env.production`, `.env.development`, `.env.test` (depending on value of `NODE_ENV`)
- `.env.local`
{% callout %}
**Note:** When `NODE_ENV=test`, `.env.local` is **not** loaded. This ensures consistent test environments across different executions by preventing local overrides during testing. This behavior matches popular frameworks like [Next.js](https://nextjs.org/docs/pages/guides/environment-variables#test-environment-variables) and [Create React App](https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used).
{% /callout %}
```txt#.env
FOO=hello
BAR=world

View File

@@ -92,18 +92,15 @@ every file before execution. Its transpiler can directly run TypeScript and JSX
## JSX
## JSON, TOML, and YAML
## JSON and TOML
Source files can import `*.json`, `*.toml`, or `*.yaml` files to load their contents as plain JavaScript objects.
Source files can import a `*.json` or `*.toml` file to load its contents as a plain old JavaScript object.
```ts
import pkg from "./package.json";
import bunfig from "./bunfig.toml";
import config from "./config.yaml";
```
See the [YAML API documentation](/docs/api/yaml) for more details on YAML support.
## WASI
{% callout %}

View File

@@ -246,65 +246,6 @@ The module from which the component factory function (`createElement`, `jsx`, `j
{% /table %}
### `jsxSideEffects`
By default, Bun marks JSX expressions as `/* @__PURE__ */` so they can be removed during bundling if they are unused (known as "dead code elimination" or "tree shaking"). Set `jsxSideEffects` to `true` to prevent this behavior.
{% table %}
- Compiler options
- Transpiled output
---
- ```jsonc
{
"jsx": "react",
// jsxSideEffects is false by default
}
```
- ```tsx
// JSX expressions are marked as pure
/* @__PURE__ */ React.createElement("div", null, "Hello");
```
---
- ```jsonc
{
"jsx": "react",
"jsxSideEffects": true,
}
```
- ```tsx
// JSX expressions are not marked as pure
React.createElement("div", null, "Hello");
```
---
- ```jsonc
{
"jsx": "react-jsx",
"jsxSideEffects": true,
}
```
- ```tsx
// Automatic runtime also respects jsxSideEffects
jsx("div", { children: "Hello" });
```
{% /table %}
This option is also available as a CLI flag:
```bash
$ bun build --jsx-side-effects
```
### JSX pragma
All of these values can be set on a per-file basis using _pragmas_. A pragma is a special comment that sets a compiler option in a particular file.

View File

@@ -52,18 +52,15 @@ Hello world!
{% /codetabs %}
## JSON, TOML, and YAML
## JSON and TOML
JSON, TOML, and YAML files can be directly imported from a source file. The contents will be loaded and returned as a JavaScript object.
JSON and TOML files can be directly imported from a source file. The contents will be loaded and returned as a JavaScript object.
```ts
import pkg from "./package.json";
import data from "./data.toml";
import config from "./config.yaml";
```
For more details on YAML support, see the [YAML API documentation](/docs/api/yaml).
## WASI
{% callout %}

View File

@@ -12,8 +12,6 @@ test("NODE_ENV is set to test", () => {
});
```
When `NODE_ENV` is set to `"test"`, Bun will not load `.env.local` files. This ensures consistent test environments across different executions by preventing local overrides during testing. Instead, use `.env.test` for test-specific environment variables, which should be committed to your repository for consistency across all developers and CI environments.
#### `$TZ` environment variable
By default, all `bun test` runs use UTC (`Etc/UTC`) as the time zone unless overridden by the `TZ` environment variable. This ensures consistent date and time behavior across different development environments.

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.2.22",
"version": "1.2.21",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"
@@ -32,7 +32,7 @@
"watch-windows": "bun run zig build check-windows --watch -fincremental --prominent-compile-errors --global-cache-dir build/debug/zig-check-cache --zig-lib-dir vendor/zig/lib",
"bd:v": "(bun run --silent build:debug &> /tmp/bun.debug.build.log || (cat /tmp/bun.debug.build.log && rm -rf /tmp/bun.debug.build.log && exit 1)) && rm -f /tmp/bun.debug.build.log && ./build/debug/bun-debug",
"bd": "BUN_DEBUG_QUIET_LOGS=1 bun --silent bd:v",
"build:debug": "export COMSPEC=\"C:\\Windows\\System32\\cmd.exe\" && bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug --log-level=NOTICE",
"build:debug": "export COMSPEC=\"C:\\Windows\\System32\\cmd.exe\" && bun scripts/glob-sources.mjs > /dev/null && bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug --log-level=NOTICE",
"build:debug:asan": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON -B build/debug-asan --log-level=NOTICE",
"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",

View File

@@ -18,11 +18,9 @@ typedef enum {
BUN_LOADER_BASE64 = 10,
BUN_LOADER_DATAURL = 11,
BUN_LOADER_TEXT = 12,
BUN_LOADER_HTML = 17,
BUN_LOADER_YAML = 18,
} BunLoader;
const BunLoader BUN_LOADER_MAX = BUN_LOADER_YAML;
const BunLoader BUN_LOADER_MAX = BUN_LOADER_TEXT;
typedef struct BunLogOptions {
size_t __struct_size;

View File

@@ -619,65 +619,6 @@ declare module "bun" {
export function parse(input: string): object;
}
/**
* YAML related APIs
*/
namespace YAML {
/**
* Parse a YAML string into a JavaScript value
*
* @category Utilities
*
* @param input The YAML string to parse
* @returns A JavaScript value
*
* @example
* ```ts
* import { YAML } from "bun";
*
* console.log(YAML.parse("123")) // 123
* console.log(YAML.parse("123")) // null
* console.log(YAML.parse("false")) // false
* console.log(YAML.parse("abc")) // "abc"
* console.log(YAML.parse("- abc")) // [ "abc" ]
* console.log(YAML.parse("abc: def")) // { "abc": "def" }
* ```
*/
export function parse(input: string): unknown;
/**
* Convert a JavaScript value into a YAML string. Strings are double quoted if they contain keywords, non-printable or
* escaped characters, or if a YAML parser would parse them as numbers. Anchors and aliases are inferred from objects, allowing cycles.
*
* @category Utilities
*
* @param input The JavaScript value to stringify.
* @param replacer Currently not supported.
* @param space A number for how many spaces each level of indentation gets, or a string used as indentation. The number is clamped between 0 and 10, and the first 10 characters of the string are used.
* @returns A string containing the YAML document.
*
* @example
* ```ts
* import { YAML } from "bun";
*
* const input = {
* abc: "def"
* };
* console.log(YAML.stringify(input));
* // # output
* // abc: def
*
* const cycle = {};
* cycle.obj = cycle;
* console.log(YAML.stringify(cycle));
* // # output
* // &root
* // obj:
* // *root
*/
export function stringify(input: unknown, replacer?: undefined | null, space?: string | number): string;
}
/**
* Synchronously resolve a `moduleId` as though it were imported from `parent`
*
@@ -1687,7 +1628,7 @@ declare module "bun" {
kind: ImportKind;
}
namespace Build {
namespace _BunBuildInterface {
type Architecture = "x64" | "arm64";
type Libc = "glibc" | "musl";
type SIMD = "baseline" | "modern";
@@ -1700,21 +1641,15 @@ declare module "bun" {
| `bun-windows-x64-${SIMD}`
| `bun-linux-x64-${SIMD}-${Libc}`;
}
/**
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
*/
interface BuildConfigBase {
/**
* List of entrypoints, usually file paths
*/
entrypoints: string[];
entrypoints: string[]; // list of file path
/**
* @default "browser"
*/
target?: Target; // default: "browser"
/**
* Output module format. Top-level await is only supported for `"esm"`.
*
@@ -1878,10 +1813,9 @@ declare module "bun" {
drop?: string[];
/**
* - When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
* - When set to `false`, returns a {@link BuildOutput} with `{success: false}`
*
* @default true
* When set to `true`, the returned promise rejects with an AggregateError when a build failure happens.
* When set to `false`, the `success` property of the returned object will be `false` when a build failure happens.
* This defaults to `true`.
*/
throw?: boolean;
@@ -1902,7 +1836,7 @@ declare module "bun" {
}
interface CompileBuildOptions {
target?: Bun.Build.Target;
target?: _BunBuildInterface.Target;
execArgv?: string[];
executablePath?: string;
outfile?: string;
@@ -1910,10 +1844,6 @@ declare module "bun" {
hideConsole?: boolean;
icon?: string;
title?: string;
publisher?: string;
version?: string;
description?: string;
copyright?: string;
};
}
@@ -1944,29 +1874,13 @@ declare module "bun" {
* });
* ```
*/
compile: boolean | Bun.Build.Target | CompileBuildOptions;
/**
* Splitting is not currently supported with `.compile`
*/
splitting?: never;
}
interface NormalBuildConfig extends BuildConfigBase {
/**
* Enable code splitting
*
* This does not currently work with {@link CompileBuildConfig.compile `compile`}
*
* @default true
*/
splitting?: boolean;
compile: boolean | _BunBuildInterface.Target | CompileBuildOptions;
}
/**
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
*/
type BuildConfig = CompileBuildConfig | NormalBuildConfig;
type BuildConfig = BuildConfigBase | CompileBuildConfig;
/**
* Hash and verify passwords using argon2 or bcrypt
@@ -2206,287 +2120,6 @@ declare module "bun" {
): string;
};
/**
* Securely store and retrieve sensitive credentials using the operating system's native credential storage.
*
* Uses platform-specific secure storage:
* - **macOS**: Keychain Services
* - **Linux**: libsecret (GNOME Keyring, KWallet, etc.)
* - **Windows**: Windows Credential Manager
*
* @category Security
*
* @example
* ```ts
* import { secrets } from "bun";
*
* // Store a credential
* await secrets.set({
* service: "my-cli-tool",
* name: "github-token",
* value: "ghp_xxxxxxxxxxxxxxxxxxxx"
* });
*
* // Retrieve a credential
* const token = await secrets.get({
* service: "my-cli-tool",
* name: "github-token"
* });
*
* if (token) {
* console.log("Token found:", token);
* } else {
* console.log("Token not found");
* }
*
* // Delete a credential
* const deleted = await secrets.delete({
* service: "my-cli-tool",
* name: "github-token"
* });
* console.log("Deleted:", deleted); // true if deleted, false if not found
* ```
*
* @example
* ```ts
* // Replace plaintext config files
* import { secrets } from "bun";
*
* // Instead of storing in ~/.npmrc
* await secrets.set({
* service: "npm-registry",
* name: "https://registry.npmjs.org",
* value: "npm_xxxxxxxxxxxxxxxxxxxx"
* });
*
* // Instead of storing in ~/.aws/credentials
* await secrets.set({
* service: "aws-cli",
* name: "default",
* value: process.env.AWS_SECRET_ACCESS_KEY
* });
*
* // Load at runtime with fallback
* const apiKey = await secrets.get({
* service: "my-app",
* name: "api-key"
* }) || process.env.API_KEY;
* ```
*/
const secrets: {
/**
* Retrieve a stored credential from the operating system's secure storage.
*
* @param options - The service and name identifying the credential
* @returns The stored credential value, or null if not found
*
* @example
* ```ts
* const password = await Bun.secrets.get({
* service: "my-database",
* name: "admin"
* });
*
* if (password) {
* await connectToDatabase(password);
* }
* ```
*
* @example
* ```ts
* // Check multiple possible locations
* const token =
* await Bun.secrets.get({ service: "github", name: "token" }) ||
* await Bun.secrets.get({ service: "gh-cli", name: "github.com" }) ||
* process.env.GITHUB_TOKEN;
* ```
*/
get(options: {
/**
* The service or application name.
*
* Use a unique identifier for your application to avoid conflicts.
* Consider using reverse domain notation for production apps (e.g., "com.example.myapp").
*/
service: string;
/**
* The account name, username, or resource identifier.
*
* This identifies the specific credential within the service.
* Common patterns include usernames, email addresses, or resource URLs.
*/
name: string;
}): Promise<string | null>;
/**
* Store or update a credential in the operating system's secure storage.
*
* If a credential already exists for the given service/name combination, it will be replaced.
* The credential is encrypted by the operating system and only accessible to the current user.
*
* @param options - The service and name identifying the credential
* @param value - The secret value to store (e.g., password, API key, token)
*
* @example
* ```ts
* // Store an API key
* await Bun.secrets.set({
* service: "openai-api",
* name: "production",
* value: "sk-proj-xxxxxxxxxxxxxxxxxxxx"
* });
* ```
*
* @example
* ```ts
* // Update an existing credential
* const newPassword = generateSecurePassword();
* await Bun.secrets.set({
* service: "email-server",
* name: "admin@example.com",
* value: newPassword
* });
* ```
*
* @example
* ```ts
* // Store credentials from environment variables
* if (process.env.DATABASE_PASSWORD) {
* await Bun.secrets.set({
* service: "postgres",
* name: "production",
* value: process.env.DATABASE_PASSWORD
* });
* delete process.env.DATABASE_PASSWORD; // Remove from memory
* }
* ```
*
* @example
* ```ts
* // Delete a credential using empty string (equivalent to delete())
* await Bun.secrets.set({
* service: "my-service",
* name: "api-key",
* value: "" // Empty string deletes the credential
* });
* ```
*
* @example
* ```ts
* // Store credential with unrestricted access for CI environments
* await Bun.secrets.set({
* service: "github-actions",
* name: "deploy-token",
* value: process.env.DEPLOY_TOKEN,
* allowUnrestrictedAccess: true // Allows access without user interaction on macOS
* });
* ```
*/
set(options: {
/**
* The service or application name.
*
* Use a unique identifier for your application to avoid conflicts.
* Consider using reverse domain notation for production apps (e.g., "com.example.myapp").
*/
service: string;
/**
* The account name, username, or resource identifier.
*
* This identifies the specific credential within the service.
* Common patterns include usernames, email addresses, or resource URLs.
*/
name: string;
/**
* The secret value to store.
*
* This should be a sensitive credential like a password, API key, or token.
* The value is encrypted by the operating system before storage.
*
* Note: To delete a credential, use the delete() method or pass an empty string.
* An empty string value will delete the credential if it exists.
*/
value: string;
/**
* Allow unrestricted access to stored credentials on macOS.
*
* When true, allows all applications to access this keychain item without user interaction.
* This is useful for CI environments but reduces security.
*
* @default false
* @platform macOS - Only affects macOS keychain behavior. Ignored on other platforms.
*/
allowUnrestrictedAccess?: boolean;
}): Promise<void>;
/**
* Delete a stored credential from the operating system's secure storage.
*
* @param options - The service and name identifying the credential
* @returns true if a credential was deleted, false if not found
*
* @example
* ```ts
* // Delete a single credential
* const deleted = await Bun.secrets.delete({
* service: "my-app",
* name: "api-key"
* });
*
* if (deleted) {
* console.log("Credential removed successfully");
* } else {
* console.log("Credential was not found");
* }
* ```
*
* @example
* ```ts
* // Clean up multiple credentials
* const services = ["github", "npm", "docker"];
* for (const service of services) {
* await Bun.secrets.delete({
* service,
* name: "token"
* });
* }
* ```
*
* @example
* ```ts
* // Clean up on uninstall
* if (process.argv.includes("--uninstall")) {
* const deleted = await Bun.secrets.delete({
* service: "my-cli-tool",
* name: "config"
* });
* process.exit(deleted ? 0 : 1);
* }
* ```
*/
delete(options: {
/**
* The service or application name.
*
* Use a unique identifier for your application to avoid conflicts.
* Consider using reverse domain notation for production apps (e.g., "com.example.myapp").
*/
service: string;
/**
* The account name, username, or resource identifier.
*
* This identifies the specific credential within the service.
* Common patterns include usernames, email addresses, or resource URLs.
*/
name: string;
}): Promise<boolean>;
};
/**
* A build artifact represents a file that was generated by the bundler @see {@link Bun.build}
*
@@ -3846,11 +3479,6 @@ declare module "bun" {
* @category HTTP & Networking
*/
interface Server extends Disposable {
/*
* Closes all connections connected to this server which are not sending a request or waiting for a response. Does not close the listen socket.
*/
closeIdleConnections(): void;
/**
* Stop listening to prevent new connections from being accepted.
*
@@ -5571,12 +5199,7 @@ declare module "bun" {
type OnLoadResult = OnLoadResultSourceCode | OnLoadResultObject | undefined | void;
type OnLoadCallback = (args: OnLoadArgs) => OnLoadResult | Promise<OnLoadResult>;
type OnStartCallback = () => void | Promise<void>;
type OnEndCallback = (result: BuildOutput) => void | Promise<void>;
type OnBeforeParseCallback = {
napiModule: unknown;
symbol: string;
external?: unknown | undefined;
};
type OnEndCallback = () => void | Promise<void>;
interface OnResolveArgs {
/**
@@ -5654,26 +5277,15 @@ declare module "bun" {
* @returns `this` for method chaining
*/
onStart(callback: OnStartCallback): this;
/**
* Register a callback which will be invoked when bundling ends. This is
* called after all modules have been bundled and the build is complete.
*
* @example
* ```ts
* const plugin: Bun.BunPlugin = {
* name: "my-plugin",
* setup(builder) {
* builder.onEnd((result) => {
* console.log("bundle just finished!!", result);
* });
* },
* };
* ```
*
* @returns `this` for method chaining
*/
onEnd(callback: OnEndCallback): this;
onBeforeParse(constraints: PluginConstraints, callback: OnBeforeParseCallback): this;
onBeforeParse(
constraints: PluginConstraints,
callback: {
napiModule: unknown;
symbol: string;
external?: unknown | undefined;
},
): this;
/**
* Register a callback to load imports with a specific import specifier
* @param constraints The constraints to apply the plugin to

View File

@@ -191,9 +191,7 @@ declare module "bun" {
* };
* ```
*/
export type SSGPage<Params extends SSGParamsLike = SSGParamsLike> = import("react").ComponentType<
SSGPageProps<Params>
>;
export type SSGPage<Params extends SSGParamsLike = SSGParamsLike> = React.ComponentType<SSGPageProps<Params>>;
/**
* getStaticPaths is Bun's implementation of SSG (Static Site Generation) path determination.

View File

@@ -8,16 +8,6 @@ declare module "*.toml" {
export = contents;
}
declare module "*.yaml" {
var contents: any;
export = contents;
}
declare module "*.yml" {
var contents: any;
export = contents;
}
declare module "*.jsonc" {
var contents: any;
export = contents;

View File

@@ -219,39 +219,44 @@ declare module "bun:ffi" {
/**
* int64 is a 64-bit signed integer
*
* This is not implemented yet!
*/
int64_t = 7,
/**
* i64 is a 64-bit signed integer
*
* This is not implemented yet!
*/
i64 = 7,
/**
* 64-bit unsigned integer
*
* This is not implemented yet!
*/
uint64_t = 8,
/**
* 64-bit unsigned integer
*
* This is not implemented yet!
*/
u64 = 8,
/**
* IEEE-754 double precision float
* Doubles are not supported yet!
*/
double = 9,
/**
* Alias of {@link FFIType.double}
* Doubles are not supported yet!
*/
f64 = 9,
/**
* IEEE-754 single precision float
* Floats are not supported yet!
*/
float = 10,
/**
* Alias of {@link FFIType.float}
* Floats are not supported yet!
*/
f32 = 10,

View File

@@ -1564,12 +1564,6 @@ declare var AbortController: Bun.__internal.UseLibDomIfAvailable<
}
>;
interface AbortSignal extends EventTarget {
readonly aborted: boolean;
onabort: ((this: AbortSignal, ev: Event) => any) | null;
readonly reason: any;
throwIfAborted(): void;
}
declare var AbortSignal: Bun.__internal.UseLibDomIfAvailable<
"AbortSignal",
{
@@ -1954,21 +1948,3 @@ declare namespace fetch {
): void;
}
//#endregion
interface RegExpConstructor {
/**
* Escapes any potential regex syntax characters in a string, and returns a
* new string that can be safely used as a literal pattern for the RegExp()
* constructor.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape)
*
* @example
* ```ts
* const re = new RegExp(RegExp.escape("foo.bar"));
* re.test("foo.bar"); // true
* re.test("foo!bar"); // false
* ```
*/
escape(string: string): string;
}

View File

@@ -22,7 +22,6 @@
/// <reference path="./shell.d.ts" />
/// <reference path="./experimental.d.ts" />
/// <reference path="./sql.d.ts" />
/// <reference path="./security.d.ts" />
/// <reference path="./bun.ns.d.ts" />

View File

@@ -174,96 +174,6 @@ declare global {
UV_ENODATA: number;
UV_EUNATCH: number;
};
binding(m: "http_parser"): {
methods: [
"DELETE",
"GET",
"HEAD",
"POST",
"PUT",
"CONNECT",
"OPTIONS",
"TRACE",
"COPY",
"LOCK",
"MKCOL",
"MOVE",
"PROPFIND",
"PROPPATCH",
"SEARCH",
"UNLOCK",
"BIND",
"REBIND",
"UNBIND",
"ACL",
"REPORT",
"MKACTIVITY",
"CHECKOUT",
"MERGE",
"M - SEARCH",
"NOTIFY",
"SUBSCRIBE",
"UNSUBSCRIBE",
"PATCH",
"PURGE",
"MKCALENDAR",
"LINK",
"UNLINK",
"SOURCE",
"QUERY",
];
allMethods: [
"DELETE",
"GET",
"HEAD",
"POST",
"PUT",
"CONNECT",
"OPTIONS",
"TRACE",
"COPY",
"LOCK",
"MKCOL",
"MOVE",
"PROPFIND",
"PROPPATCH",
"SEARCH",
"UNLOCK",
"BIND",
"REBIND",
"UNBIND",
"ACL",
"REPORT",
"MKACTIVITY",
"CHECKOUT",
"MERGE",
"M - SEARCH",
"NOTIFY",
"SUBSCRIBE",
"UNSUBSCRIBE",
"PATCH",
"PURGE",
"MKCALENDAR",
"LINK",
"UNLINK",
"SOURCE",
"PRI",
"DESCRIBE",
"ANNOUNCE",
"SETUP",
"PLAY",
"PAUSE",
"TEARDOWN",
"GET_PARAMETER",
"SET_PARAMETER",
"REDIRECT",
"RECORD",
"FLUSH",
"QUERY",
];
HTTPParser: unknown;
ConnectionsList: unknown;
};
binding(m: string): object;
}

View File

@@ -1,101 +0,0 @@
declare module "bun" {
/**
* `bun install` security related declarations
*/
export namespace Security {
export interface Package {
/**
* The name of the package
*/
name: string;
/**
* The resolved version to be installed that matches the requested range.
*
* This is the exact version string, **not** a range.
*/
version: string;
/**
* The URL of the tgz of this package that Bun will download
*/
tarball: string;
/**
* The range that was requested by the command
*
* This could be a tag like `beta` or a semver range like `>=4.0.0`
*/
requestedRange: string;
}
/**
* Advisory represents the result of a security scan result of a package
*/
export interface Advisory {
/**
* Level represents the degree of danger for a security advisory
*
* Bun behaves differently depending on the values returned from the
* {@link Scanner.scan `scan()`} hook:
*
* > In any case, Bun *always* pretty prints *all* the advisories,
* > but...
* >
* > → if any **fatal**, Bun will immediately cancel the installation
* > and quit with a non-zero exit code
* >
* > → else if any **warn**, Bun will either ask the user if they'd like
* > to continue with the install if in a TTY environment, or
* > immediately exit if not.
*/
level: "fatal" | "warn";
/**
* The name of the package attempting to be installed.
*/
package: string;
/**
* If available, this is a url linking to a CVE or report online so
* users can learn more about the advisory.
*/
url: string | null;
/**
* If available, this is a brief description of the advisory that Bun
* will print to the user.
*/
description: string | null;
}
export interface Scanner {
/**
* This is the version of the scanner implementation. It may change in
* future versions, so we will use this version to discriminate between
* such versions. It's entirely possible this API changes in the future
* so much that version 1 would no longer be supported.
*
* The version is required because third-party scanner package versions
* are inherently unrelated to Bun versions
*/
version: "1";
/**
* Perform an advisory check when a user ran `bun add <package>
* [...packages]` or other related/similar commands.
*
* If this function throws an error, Bun will immediately stop the
* install process and print the error to the user.
*
* @param info An object containing an array of packages to be added.
* The package array will contain all proposed dependencies, including
* transitive ones. More simply, that means it will include dependencies
* of the packages the user wants to add.
*
* @returns A list of advisories.
*/
scan: (info: { packages: Package[] }) => Promise<Advisory[]>;
}
}
}

View File

@@ -211,7 +211,7 @@ declare module "bun" {
* try {
* const result = await $`exit 1`;
* } catch (error) {
* if (error instanceof $.ShellError) {
* if (error instanceof ShellError) {
* console.log(error.exitCode); // 1
* }
* }

View File

@@ -82,13 +82,6 @@ declare module "bun" {
);
}
class MySQLError extends SQLError {
public readonly code: string;
public readonly errno: number | undefined;
public readonly sqlState: string | undefined;
constructor(message: string, options: { code: string; errno: number | undefined; sqlState: string | undefined });
}
class SQLiteError extends SQLError {
public readonly code: string;
public readonly errno: number;
@@ -135,7 +128,7 @@ declare module "bun" {
onclose?: ((err: Error | null) => void) | undefined;
}
interface PostgresOrMySQLOptions {
interface PostgresOptions {
/**
* Connection URL (can be string or URL object)
*/
@@ -203,7 +196,7 @@ declare module "bun" {
* Database adapter/driver to use
* @default "postgres"
*/
adapter?: "postgres" | "mysql" | "mariadb";
adapter?: "postgres";
/**
* Maximum time in seconds to wait for connection to become available
@@ -272,11 +265,14 @@ declare module "bun" {
*/
ssl?: TLSOptions | boolean | undefined;
/**
* Unix domain socket path for connection
* @default undefined
*/
path?: string | undefined;
// `.path` is currently unsupported in Bun, the implementation is
// incomplete.
//
// /**
// * Unix domain socket path for connection
// * @default ""
// */
// path?: string | undefined;
/**
* Callback executed when a connection attempt completes
@@ -336,7 +332,7 @@ declare module "bun" {
* };
* ```
*/
type Options = SQLiteOptions | PostgresOrMySQLOptions;
type Options = SQLiteOptions | PostgresOptions;
/**
* Represents a SQL query that can be executed, with additional control

View File

@@ -3,7 +3,7 @@
// This file gets loaded by developers including the following triple slash directive:
//
// ```ts
// /// <reference types="bun-types/test-globals" />
// /// <reference types="bun/test-globals" />
// ```
declare var test: typeof import("bun:test").test;
@@ -19,6 +19,3 @@ declare var setDefaultTimeout: typeof import("bun:test").setDefaultTimeout;
declare var mock: typeof import("bun:test").mock;
declare var spyOn: typeof import("bun:test").spyOn;
declare var jest: typeof import("bun:test").jest;
declare var xit: typeof import("bun:test").xit;
declare var xtest: typeof import("bun:test").xtest;
declare var xdescribe: typeof import("bun:test").xdescribe;

View File

@@ -152,41 +152,11 @@ declare module "bun:test" {
type SpiedSetter<T> = JestMock.SpiedSetter<T>;
}
/**
* Create a spy on an object property or method
*/
export function spyOn<T extends object, K extends keyof T>(
obj: T,
methodOrPropertyValue: K,
): Mock<Extract<T[K], (...args: any[]) => any>>;
/**
* Vitest-compatible mocking utilities
* Provides Vitest-style mocking API for easier migration from Vitest to Bun
*/
export const vi: {
/**
* Create a mock function
*/
fn: typeof jest.fn;
/**
* Create a spy on an object property or method
*/
spyOn: typeof spyOn;
/**
* Mock a module
*/
module: typeof mock.module;
/**
* Restore all mocks to their original implementation
*/
restoreAllMocks: typeof jest.restoreAllMocks;
/**
* Clear all mock state (calls, results, etc.) without restoring original implementation
*/
clearAllMocks: typeof jest.clearAllMocks;
};
interface FunctionLike {
readonly name: string;
}
@@ -292,15 +262,6 @@ declare module "bun:test" {
* @param fn the function that defines the tests
*/
export const describe: Describe;
/**
* Skips a group of related tests.
*
* This is equivalent to calling `describe.skip()`.
*
* @param label the label for the tests
* @param fn the function that defines the tests
*/
export const xdescribe: Describe;
/**
* Runs a function, once, before all the tests.
*
@@ -554,17 +515,7 @@ declare module "bun:test" {
* @param fn the test function
*/
export const test: Test;
export { test as it, xtest as xit };
/**
* Skips a test.
*
* This is equivalent to calling `test.skip()`.
*
* @param label the label for the test
* @param fn the test function
*/
export const xtest: Test;
export { test as it };
/**
* Asserts that a value matches some criteria.

View File

@@ -153,7 +153,7 @@ void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_sock
}
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
void us_internal_socket_context_link_listen_socket(struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
struct us_socket_t* s = &ls->s;
s->context = context;
s->next = (struct us_socket_t *) context->head_listen_sockets;
@@ -162,7 +162,7 @@ void us_internal_socket_context_link_listen_socket(int ssl, struct us_socket_con
context->head_listen_sockets->s.prev = s;
}
context->head_listen_sockets = ls;
us_socket_context_ref(ssl, context);
us_socket_context_ref(0, context);
}
void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket_context_t *context, struct us_connecting_socket_t *c) {
@@ -179,7 +179,7 @@ void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s) {
void us_internal_socket_context_link_socket(struct us_socket_context_t *context, struct us_socket_t *s) {
s->context = context;
s->next = context->head_sockets;
s->prev = 0;
@@ -187,7 +187,7 @@ void us_internal_socket_context_link_socket(int ssl, struct us_socket_context_t
context->head_sockets->prev = s;
}
context->head_sockets = s;
us_socket_context_ref(ssl, context);
us_socket_context_ref(0, context);
us_internal_enable_sweep_timer(context->loop);
}
@@ -388,7 +388,7 @@ struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_co
s->flags.is_ipc = 0;
s->next = 0;
s->flags.allow_half_open = (options & LIBUS_SOCKET_ALLOW_HALF_OPEN);
us_internal_socket_context_link_listen_socket(ssl, context, ls);
us_internal_socket_context_link_listen_socket(context, ls);
ls->socket_ext_size = socket_ext_size;
@@ -423,7 +423,7 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock
s->flags.is_paused = 0;
s->flags.is_ipc = 0;
s->next = 0;
us_internal_socket_context_link_listen_socket(ssl, context, ls);
us_internal_socket_context_link_listen_socket(context, ls);
ls->socket_ext_size = socket_ext_size;
@@ -456,7 +456,7 @@ struct us_socket_t* us_socket_context_connect_resolved_dns(struct us_socket_cont
socket->connect_state = NULL;
socket->connect_next = NULL;
us_internal_socket_context_link_socket(0, context, socket);
us_internal_socket_context_link_socket(context, socket);
return socket;
}
@@ -584,7 +584,7 @@ int start_connections(struct us_connecting_socket_t *c, int count) {
flags->is_paused = 0;
flags->is_ipc = 0;
/* Link it into context so that timeout fires properly */
us_internal_socket_context_link_socket(0, context, s);
us_internal_socket_context_link_socket(context, s);
// TODO check this, specifically how it interacts with the SSL code
// does this work when we create multiple sockets at once? will we need multiple SSL contexts?
@@ -762,7 +762,7 @@ struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_con
connect_socket->flags.is_ipc = 0;
connect_socket->connect_state = NULL;
connect_socket->connect_next = NULL;
us_internal_socket_context_link_socket(ssl, context, connect_socket);
us_internal_socket_context_link_socket(context, connect_socket);
return connect_socket;
}
@@ -804,9 +804,12 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
}
struct us_connecting_socket_t *c = s->connect_state;
struct us_socket_t *new_s = s;
if (ext_size != -1) {
struct us_poll_t *pool_ref = &s->p;
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + ext_size);
if (c) {
c->connecting_head = new_s;
@@ -828,7 +831,7 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
/* We manually ref/unref context to handle context life cycle with low-priority queue */
us_socket_context_ref(ssl, context);
} else {
us_internal_socket_context_link_socket(ssl, context, new_s);
us_internal_socket_context_link_socket(context, new_s);
}
/* We can safely unref the old context here with can potentially be freed */
us_socket_context_unref(ssl, old_context);

View File

@@ -150,12 +150,16 @@ void us_internal_init_loop_ssl_data(us_loop_r loop);
void us_internal_free_loop_ssl_data(us_loop_r loop);
/* Socket context related */
void us_internal_socket_context_link_socket(int ssl, us_socket_context_r context, us_socket_r s);
void us_internal_socket_context_unlink_socket(int ssl, us_socket_context_r context, us_socket_r s);
void us_internal_socket_context_link_socket(us_socket_context_r context,
us_socket_r s);
void us_internal_socket_context_unlink_socket(int ssl,
us_socket_context_r context, us_socket_r s);
void us_internal_socket_after_resolve(struct us_connecting_socket_t *s);
void us_internal_socket_after_open(us_socket_r s, int error);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_close(us_internal_ssl_socket_r s, int code, void *reason);
struct us_internal_ssl_socket_t *
us_internal_ssl_socket_close(us_internal_ssl_socket_r s, int code,
void *reason);
int us_internal_handle_dns_results(us_loop_r loop);
@@ -267,7 +271,7 @@ struct us_listen_socket_t {
};
/* Listen sockets are keps in their own list */
void us_internal_socket_context_link_listen_socket(int ssl,
void us_internal_socket_context_link_listen_socket(
us_socket_context_r context, struct us_listen_socket_t *s);
void us_internal_socket_context_unlink_listen_socket(int ssl,
us_socket_context_r context, struct us_listen_socket_t *s);
@@ -284,7 +288,8 @@ struct us_socket_context_t {
struct us_socket_t *iterator;
struct us_socket_context_t *prev, *next;
struct us_socket_t *(*on_open)(struct us_socket_t *, int is_client, char *ip, int ip_length);
struct us_socket_t *(*on_open)(struct us_socket_t *, int is_client, char *ip,
int ip_length);
struct us_socket_t *(*on_data)(struct us_socket_t *, char *data, int length);
struct us_socket_t *(*on_fd)(struct us_socket_t *, int fd);
struct us_socket_t *(*on_writable)(struct us_socket_t *);
@@ -296,6 +301,7 @@ struct us_socket_context_t {
struct us_connecting_socket_t *(*on_connect_error)(struct us_connecting_socket_t *, int code);
struct us_socket_t *(*on_socket_connect_error)(struct us_socket_t *, int code);
int (*is_low_prio)(struct us_socket_t *);
};
/* Internal SSL interface */

View File

@@ -40,6 +40,7 @@ void us_internal_enable_sweep_timer(struct us_loop_t *loop) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
Bun__internal_ensureDateHeaderTimerIsEnabled(loop);
}
}
void us_internal_disable_sweep_timer(struct us_loop_t *loop) {
@@ -182,7 +183,7 @@ void us_internal_handle_low_priority_sockets(struct us_loop_t *loop) {
if (s->next) s->next->prev = 0;
s->next = 0;
us_internal_socket_context_link_socket(0, s->context, s);
us_internal_socket_context_link_socket(s->context, s);
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
s->flags.low_prio_state = 2;
@@ -339,7 +340,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
/* We always use nodelay */
bsd_socket_nodelay(client_fd, 1);
us_internal_socket_context_link_socket(0, listen_socket->s.context, s);
us_internal_socket_context_link_socket(listen_socket->s.context, s);
listen_socket->s.context->on_open(s, 0, bsd_addr_get_ip(&addr), bsd_addr_get_ip_length(&addr));
@@ -363,7 +364,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
/* Note: if we failed a write as a socket of one loop then adopted
* to another loop, this will be wrong. Absurd case though */
loop->data.last_write_failed = 0;
s = s->context->on_writable(s);
if (!s || us_socket_is_closed(0, s)) {

View File

@@ -329,7 +329,7 @@ struct us_socket_t *us_socket_from_fd(struct us_socket_context_t *ctx, int socke
bsd_socket_nodelay(fd, 1);
apple_no_sigpipe(fd);
bsd_set_nonblocking(fd);
us_internal_socket_context_link_socket(0, ctx, s);
us_internal_socket_context_link_socket(ctx, s);
return s;
#endif

View File

@@ -298,22 +298,6 @@ public:
return std::move(*this);
}
/** Closes all connections connected to this server which are not sending a request or waiting for a response. Does not close the listen socket. */
TemplatedApp &&closeIdle() {
auto context = (struct us_socket_context_t *)this->httpContext;
struct us_socket_t *s = context->head_sockets;
while (s) {
HttpResponseData<SSL> *httpResponseData = HttpResponse<SSL>::getHttpResponseDataS(s);
httpResponseData->shouldCloseOnceIdle = true;
struct us_socket_t *next = s->next;
if (httpResponseData->isIdle) {
us_socket_close(SSL, s, LIBUS_SOCKET_CLOSE_CODE_CLEAN_SHUTDOWN, 0);
}
s = next;
}
return std::move(*this);
}
template <typename UserData>
TemplatedApp &&ws(std::string_view pattern, WebSocketBehavior<UserData> &&behavior) {
/* Don't compile if alignment rules cannot be satisfied */

View File

@@ -386,9 +386,6 @@ public:
/* We do not need to care for buffering here, write does that */
return {0, true};
}
if (length == 0) {
return {written, failed};
}
}
/* We should only return with new writes, not things written to cork already */

View File

@@ -137,6 +137,10 @@ private:
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext());
}
static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) {
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s));
}
/* Init the HttpContext by registering libusockets event handlers */
HttpContext<SSL> *init() {
@@ -243,7 +247,6 @@ private:
/* Mark that we are inside the parser now */
httpContextData->flags.isParsingHttp = true;
httpResponseData->isIdle = false;
// clients need to know the cursor after http parse, not servers!
// how far did we read then? we need to know to continue with websocket parsing data? or?
@@ -395,7 +398,6 @@ private:
/* Timeout on uncork failure */
auto [written, failed] = ((AsyncSocket<SSL> *) returnedData)->uncork();
if (written > 0 || failed) {
httpResponseData->isIdle = true;
/* All Http sockets timeout by this, and this behavior match the one in HttpResponse::cork */
((HttpResponse<SSL> *) s)->resetTimeout();
}
@@ -640,10 +642,6 @@ public:
}, priority);
}
static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) {
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s));
}
/* Listen to port using this HttpContext */
us_listen_socket_t *listen(const char *host, int port, int options) {
int error = 0;

View File

@@ -63,6 +63,7 @@ private:
OnSocketClosedCallback onSocketClosed = nullptr;
OnClientErrorCallback onClientError = nullptr;
HttpFlags flags;
uint64_t maxHeaderSize = 0; // 0 means no limit
// TODO: SNI
@@ -72,8 +73,10 @@ private:
filterHandlers.clear();
}
public:
HttpFlags flags;
public:
bool isAuthorized() const {
return flags.isAuthorized;
}
};
}

View File

@@ -50,11 +50,6 @@ public:
HttpResponseData<SSL> *getHttpResponseData() {
return (HttpResponseData<SSL> *) Super::getAsyncSocketData();
}
static HttpResponseData<SSL> *getHttpResponseDataS(us_socket_t *s) {
return (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
}
void setTimeout(uint8_t seconds) {
auto* data = getHttpResponseData();
data->idleTimeout = seconds;
@@ -137,7 +132,7 @@ public:
/* Terminating 0 chunk */
Super::write("0\r\n\r\n", 5);
httpResponseData->markDone(this);
httpResponseData->markDone();
/* We need to check if we should close this socket here now */
if (!Super::isCorked()) {
@@ -203,7 +198,7 @@ public:
/* Remove onAborted function if we reach the end */
if (httpResponseData->offset == totalSize) {
httpResponseData->markDone(this);
httpResponseData->markDone();
/* We need to check if we should close this socket here now */
if (!Super::isCorked()) {

View File

@@ -22,15 +22,11 @@
#include "HttpParser.h"
#include "AsyncSocketData.h"
#include "ProxyParser.h"
#include "HttpContext.h"
#include "MoveOnlyFunction.h"
namespace uWS {
template <bool SSL>
struct HttpContext;
template <bool SSL>
struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
template <bool> friend struct HttpResponse;
@@ -42,7 +38,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
using OnDataCallback = void (*)(uWS::HttpResponse<SSL>* response, const char* chunk, size_t chunk_length, bool, void*);
/* When we are done with a response we mark it like so */
void markDone(uWS::HttpResponse<SSL> *uwsRes) {
void markDone() {
onAborted = nullptr;
/* Also remove onWritable so that we do not emit when draining behind the scenes. */
onWritable = nullptr;
@@ -54,9 +50,6 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
/* We are done with this request */
this->state &= ~HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
HttpResponseData<SSL> *httpResponseData = uwsRes->getHttpResponseData();
httpResponseData->isIdle = true;
}
/* Caller of onWritable. It is possible onWritable calls markDone so we need to borrow it. */
@@ -108,8 +101,6 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
uint8_t state = 0;
uint8_t idleTimeout = 10; // default HTTP_TIMEOUT 10 seconds
bool fromAncientRequest = false;
bool isIdle = true;
bool shouldCloseOnceIdle = false;
#ifdef UWS_WITH_PROXY

View File

@@ -1,21 +1,17 @@
#!/usr/bin/env node
import { spawn as nodeSpawn } from "node:child_process";
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
import { basename, join, relative, resolve } from "node:path";
import {
formatAnnotationToHtml,
getSecret,
isCI,
isWindows,
parseAnnotations,
printEnvironment,
reportAnnotationToBuildKite,
startGroup,
} from "./utils.mjs";
if (globalThis.Bun) {
await import("./glob-sources.mjs");
}
// https://cmake.org/cmake/help/latest/manual/cmake.1.html#generate-a-project-buildsystem
const generateFlags = [
["-S", "string", "path to source directory"],
@@ -218,47 +214,14 @@ function parseOptions(args, flags = []) {
async function spawn(command, args, options, label) {
const effectiveArgs = args.filter(Boolean);
const description = [command, ...effectiveArgs].map(arg => (arg.includes(" ") ? JSON.stringify(arg) : arg)).join(" ");
let env = options?.env;
console.log("$", description);
label ??= basename(command);
const pipe = process.env.CI === "true";
if (isBuildkite()) {
if (process.env.BUN_LINK_ONLY && isWindows) {
env ||= options?.env || { ...process.env };
// Pass signing secrets directly to the build process
// The PowerShell signing script will handle certificate decoding
env.SM_CLIENT_CERT_PASSWORD = getSecret("SM_CLIENT_CERT_PASSWORD", {
redact: true,
required: true,
});
env.SM_CLIENT_CERT_FILE = getSecret("SM_CLIENT_CERT_FILE", {
redact: true,
required: true,
});
env.SM_API_KEY = getSecret("SM_API_KEY", {
redact: true,
required: true,
});
env.SM_KEYPAIR_ALIAS = getSecret("SM_KEYPAIR_ALIAS", {
redact: true,
required: true,
});
env.SM_HOST = getSecret("SM_HOST", {
redact: true,
required: true,
});
}
}
const subprocess = nodeSpawn(command, effectiveArgs, {
stdio: pipe ? "pipe" : "inherit",
...options,
env,
});
let killedManually = false;

View File

@@ -1,107 +0,0 @@
#!/usr/bin/env bun
import { readFileSync } from "fs";
function parseLogFile(filename) {
const testDetails = new Map(); // Track individual attempts and total for each test
let currentTest = null;
let startTime = null;
// Pattern to match test group start: --- [90m[N/TOTAL][0m test/path
// Note: there are escape sequences before _bk
const startPattern = /_bk;t=(\d+).*?--- .*?\[90m\[(\d+)\/(\d+)\].*?\[0m (.+)/;
const content = readFileSync(filename, "utf-8");
const lines = content.split("\n");
for (const line of lines) {
const match = line.match(startPattern);
if (match) {
// If we have a previous test, calculate its duration
if (currentTest && startTime) {
const endTime = parseInt(match[1]);
const duration = endTime - startTime;
// Extract attempt info - match the actual ANSI pattern
const attemptMatch = currentTest.match(/\s+\x1b\[90m\[attempt #(\d+)\]\x1b\[0m$/);
const cleanName = currentTest.replace(/\s+\x1b\[90m\[attempt #\d+\]\x1b\[0m$/, "").trim();
const attemptNum = attemptMatch ? parseInt(attemptMatch[1]) : 1;
if (!testDetails.has(cleanName)) {
testDetails.set(cleanName, { total: 0, attempts: [] });
}
const testInfo = testDetails.get(cleanName);
testInfo.total += duration;
testInfo.attempts.push({ attempt: attemptNum, duration });
}
// Start new test
startTime = parseInt(match[1]);
currentTest = match[4].trim();
}
}
// Convert to array and sort by total duration
const testGroups = Array.from(testDetails.entries())
.map(([name, info]) => ({
name,
totalDuration: info.total,
attempts: info.attempts.sort((a, b) => a.attempt - b.attempt),
}))
.sort((a, b) => b.totalDuration - a.totalDuration);
return testGroups;
}
function formatAttempts(attempts) {
if (attempts.length <= 1) return "";
const attemptStrings = attempts.map(
({ attempt, duration }) => `${(duration / 1000).toFixed(1)}s attempt #${attempt}`,
);
return ` [${attemptStrings.join(", ")}]`;
}
if (process.argv.length !== 3) {
console.log("Usage: bun parse_test_logs.js <log_file>");
process.exit(1);
}
const filename = process.argv[2];
const testGroups = parseLogFile(filename);
const totalTime = testGroups.reduce((sum, group) => sum + group.totalDuration, 0) / 1000;
const avgTime = testGroups.length > 0 ? totalTime / testGroups.length : 0;
console.log(
`## Slowest Tests Analysis - ${testGroups.length} tests (${totalTime.toFixed(1)}s total, ${avgTime.toFixed(2)}s avg)`,
);
console.log("");
// Top 10 summary
console.log("**Top 10 slowest tests:**");
for (let i = 0; i < Math.min(10, testGroups.length); i++) {
const { name, totalDuration, attempts } = testGroups[i];
const durationSec = totalDuration / 1000;
const testName = name.replace("test/", "").replace(".test.ts", "").replace(".test.js", "");
const attemptInfo = formatAttempts(attempts);
console.log(`- **${durationSec.toFixed(1)}s** ${testName}${attemptInfo}`);
}
console.log("");
// Filter tests > 1 second
const slowTests = testGroups.filter(test => test.totalDuration > 1000);
console.log("```");
console.log(`All tests > 1s (${slowTests.length} tests):`);
for (let i = 0; i < slowTests.length; i++) {
const { name, totalDuration, attempts } = slowTests[i];
const durationSec = totalDuration / 1000;
const attemptInfo = formatAttempts(attempts);
console.log(`${(i + 1).toString().padStart(3)}. ${durationSec.toFixed(2).padStart(7)}s ${name}${attemptInfo}`);
}
console.log("```");

View File

@@ -40,25 +40,7 @@ if ($args.Count -gt 0) {
$commandArgs = @($args[1..($args.Count - 1)] | % {$_})
}
# Don't print the full command as it may contain sensitive information like certificates
# Just show the command name and basic info
$displayArgs = @()
foreach ($arg in $commandArgs) {
if ($arg -match "^-") {
# Include flags
$displayArgs += $arg
} elseif ($arg -match "\.(mjs|js|ts|cmake|zig|cpp|c|h|exe)$") {
# Include file names
$displayArgs += $arg
} elseif ($arg.Length -gt 100) {
# Truncate long arguments (likely certificates or encoded data)
$displayArgs += "[REDACTED]"
} else {
$displayArgs += $arg
}
}
Write-Host "$ $command $displayArgs"
Write-Host "$ $command $commandArgs"
& $command $commandArgs
exit $LASTEXITCODE
}

View File

@@ -41,7 +41,7 @@ fn createImportRecord(this: *HTMLScanner, input_path: []const u8, kind: ImportKi
const record = ImportRecord{
.path = fs.Path.init(try this.allocator.dupeZ(u8, path_to_use)),
.kind = kind,
.range = .none,
.range = logger.Range.None,
};
try this.import_records.push(this.allocator, record);
@@ -56,9 +56,9 @@ pub fn onWriteHTML(_: *HTMLScanner, bytes: []const u8) void {
pub fn onHTMLParseError(this: *HTMLScanner, message: []const u8) void {
this.log.addError(
this.source,
.none,
logger.Loc.Empty,
message,
) catch |err| bun.handleOom(err);
) catch bun.outOfMemory();
}
pub fn onTag(this: *HTMLScanner, _: *lol.Element, path: []const u8, url_attribute: []const u8, kind: ImportKind) void {
@@ -222,7 +222,7 @@ pub fn HTMLProcessor(
var builder = lol.HTMLRewriter.Builder.init();
defer builder.deinit();
var selectors: bun.BoundedArray(*lol.HTMLSelector, tag_handlers.len + if (visit_document_tags) 3 else 0) = .{};
var selectors: std.BoundedArray(*lol.HTMLSelector, tag_handlers.len + if (visit_document_tags) 3 else 0) = .{};
defer for (selectors.slice()) |selector| {
selector.deinit();
};

View File

@@ -44,20 +44,11 @@ pub const StandaloneModuleGraph = struct {
};
}
pub fn isBunStandaloneFilePathCanonicalized(str: []const u8) bool {
pub fn isBunStandaloneFilePath(str: []const u8) bool {
return bun.strings.hasPrefixComptime(str, base_path) or
(Environment.isWindows and bun.strings.hasPrefixComptime(str, base_public_path));
}
pub fn isBunStandaloneFilePath(str: []const u8) bool {
if (Environment.isWindows) {
// On Windows, remove NT path prefixes before checking
const canonicalized = strings.withoutNTPrefix(u8, str);
return isBunStandaloneFilePathCanonicalized(canonicalized);
}
return isBunStandaloneFilePathCanonicalized(str);
}
pub fn entryPoint(this: *const StandaloneModuleGraph) *File {
return &this.files.values()[this.entry_point_id];
}
@@ -257,7 +248,7 @@ pub const StandaloneModuleGraph = struct {
};
const source_files = serialized.sourceFileNames();
const slices = bun.handleOom(bun.default_allocator.alloc(?[]u8, source_files.len * 2));
const slices = bun.default_allocator.alloc(?[]u8, source_files.len * 2) catch bun.outOfMemory();
const file_names: [][]const u8 = @ptrCast(slices[0..source_files.len]);
const decompressed_contents_slice = slices[source_files.len..][0..source_files.len];
@@ -501,7 +492,9 @@ pub const StandaloneModuleGraph = struct {
const page_size = std.heap.page_size_max;
pub const InjectOptions = bun.options.WindowsOptions;
pub const InjectOptions = struct {
windows_hide_console: bool = false,
};
pub const CompileResult = union(enum) {
success: void,
@@ -522,7 +515,7 @@ pub const StandaloneModuleGraph = struct {
var buf: bun.PathBuffer = undefined;
var zname: [:0]const u8 = bun.span(bun.fs.FileSystem.instance.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get temporary file name: {s}", .{@errorName(err)});
return bun.invalid_fd;
Global.exit(1);
});
const cleanup = struct {
@@ -552,7 +545,7 @@ pub const StandaloneModuleGraph = struct {
bun.copyFile(in, out).unwrap() catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
return bun.invalid_fd;
Global.exit(1);
};
const file = bun.sys.openFileAtWindows(
bun.invalid_fd,
@@ -564,7 +557,7 @@ pub const StandaloneModuleGraph = struct {
},
).unwrap() catch |e| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open temporary file to copy bun into\n{}", .{e});
return bun.invalid_fd;
Global.exit(1);
};
break :brk file;
@@ -607,7 +600,7 @@ pub const StandaloneModuleGraph = struct {
std.fs.path.sep_str,
zname,
&.{0},
}) catch |e| bun.handleOom(e);
}) catch bun.outOfMemory();
zname = zname_z[0..zname_z.len -| 1 :0];
continue;
}
@@ -618,8 +611,7 @@ pub const StandaloneModuleGraph = struct {
}
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open temporary file to copy bun into\n{}", .{err});
// No fd to cleanup yet, just return error
return bun.invalid_fd;
Global.exit(1);
}
},
}
@@ -641,7 +633,7 @@ pub const StandaloneModuleGraph = struct {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to open bun executable to copy from as read-only\n{}", .{err});
cleanup(zname, fd);
return bun.invalid_fd;
Global.exit(1);
},
}
}
@@ -653,7 +645,7 @@ pub const StandaloneModuleGraph = struct {
bun.copyFile(self_fd, fd).unwrap() catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to copy bun executable into temporary file: {s}", .{@errorName(err)});
cleanup(zname, fd);
return bun.invalid_fd;
Global.exit(1);
};
break :brk fd;
@@ -665,18 +657,18 @@ pub const StandaloneModuleGraph = struct {
if (input_result.err) |err| {
Output.prettyErrorln("Error reading standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
}
var macho_file = bun.macho.MachoFile.init(bun.default_allocator, input_result.bytes.items, bytes.len) catch |err| {
Output.prettyErrorln("Error initializing standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
defer macho_file.deinit();
macho_file.writeSection(bytes) catch |err| {
Output.prettyErrorln("Error writing standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
input_result.bytes.deinit();
@@ -684,7 +676,7 @@ pub const StandaloneModuleGraph = struct {
.err => |err| {
Output.prettyErrorln("Error seeking to start of temporary file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
},
else => {},
}
@@ -692,19 +684,19 @@ pub const StandaloneModuleGraph = struct {
var file = bun.sys.File{ .handle = cloned_executable_fd };
const writer = file.writer();
const BufferedWriter = std.io.BufferedWriter(512 * 1024, @TypeOf(writer));
var buffered_writer = bun.handleOom(bun.default_allocator.create(BufferedWriter));
var buffered_writer = bun.default_allocator.create(BufferedWriter) catch bun.outOfMemory();
buffered_writer.* = .{
.unbuffered_writer = writer,
};
macho_file.buildAndSign(buffered_writer.writer()) catch |err| {
Output.prettyErrorln("Error writing standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
buffered_writer.flush() catch |err| {
Output.prettyErrorln("Error flushing standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
if (comptime !Environment.isWindows) {
_ = bun.c.fchmod(cloned_executable_fd.native(), 0o777);
@@ -716,18 +708,18 @@ pub const StandaloneModuleGraph = struct {
if (input_result.err) |err| {
Output.prettyErrorln("Error reading standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
}
var pe_file = bun.pe.PEFile.init(bun.default_allocator, input_result.bytes.items) catch |err| {
Output.prettyErrorln("Error initializing PE file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
defer pe_file.deinit();
pe_file.addBunSection(bytes) catch |err| {
Output.prettyErrorln("Error adding Bun section to PE file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
input_result.bytes.deinit();
@@ -735,7 +727,7 @@ pub const StandaloneModuleGraph = struct {
.err => |err| {
Output.prettyErrorln("Error seeking to start of temporary file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
},
else => {},
}
@@ -745,7 +737,7 @@ pub const StandaloneModuleGraph = struct {
pe_file.write(writer) catch |err| {
Output.prettyErrorln("Error writing PE file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
// Set executable permissions when running on POSIX hosts, even for Windows targets
if (comptime !Environment.isWindows) {
@@ -759,7 +751,7 @@ pub const StandaloneModuleGraph = struct {
total_byte_count = bytes.len + 8 + (Syscall.setFileOffsetToEndWindows(cloned_executable_fd).unwrap() catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to seek to end of temporary file\n{}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
});
} else {
const seek_position = @as(u64, @intCast(brk: {
@@ -768,7 +760,7 @@ pub const StandaloneModuleGraph = struct {
.err => |err| {
Output.prettyErrorln("{}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
},
};
@@ -795,7 +787,7 @@ pub const StandaloneModuleGraph = struct {
},
);
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
},
else => {},
}
@@ -808,7 +800,8 @@ pub const StandaloneModuleGraph = struct {
.err => |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
},
}
}
@@ -823,42 +816,12 @@ pub const StandaloneModuleGraph = struct {
},
}
if (Environment.isWindows and inject_options.hide_console) {
if (Environment.isWindows and inject_options.windows_hide_console) {
bun.windows.editWin32BinarySubsystem(.{ .handle = cloned_executable_fd }, .windows_gui) catch |err| {
Output.err(err, "failed to disable console on executable", .{});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
};
}
// Set Windows icon and/or metadata if any options are provided (single operation)
if (Environment.isWindows and (inject_options.icon != null or
inject_options.title != null or
inject_options.publisher != null or
inject_options.version != null or
inject_options.description != null or
inject_options.copyright != null))
{
var zname_buf: bun.OSPathBuffer = undefined;
const zname_w = bun.strings.toWPathNormalized(&zname_buf, zname) catch |err| {
Output.err(err, "failed to resolve executable path", .{});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
};
// Single call to set all Windows metadata at once
bun.windows.rescle.setWindowsMetadata(
zname_w.ptr,
inject_options.icon,
inject_options.title,
inject_options.publisher,
inject_options.version,
inject_options.description,
inject_options.copyright,
) catch |err| {
Output.err(err, "failed to set Windows metadata on executable", .{});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
Global.exit(1);
};
}
@@ -909,7 +872,7 @@ pub const StandaloneModuleGraph = struct {
Output.errGeneric("Failed to download {}: {s}", .{ target.*, @errorName(err) });
},
}
return error.DownloadFailed;
Global.exit(1);
};
}
@@ -925,7 +888,8 @@ pub const StandaloneModuleGraph = struct {
outfile: []const u8,
env: *bun.DotEnv.Loader,
output_format: bun.options.Format,
windows_options: bun.options.WindowsOptions,
windows_hide_console: bool,
windows_icon: ?[]const u8,
compile_exec_argv: []const u8,
self_exe_path: ?[]const u8,
) !CompileResult {
@@ -938,7 +902,7 @@ pub const StandaloneModuleGraph = struct {
var free_self_exe = false;
const self_exe = if (self_exe_path) |path| brk: {
free_self_exe = true;
break :brk bun.handleOom(allocator.dupeZ(u8, path));
break :brk allocator.dupeZ(u8, path) catch bun.outOfMemory();
} else if (target.isDefault())
bun.selfExePath() catch |err| {
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to get self executable path: {s}", .{@errorName(err)}) catch "failed to get self executable path");
@@ -967,7 +931,7 @@ pub const StandaloneModuleGraph = struct {
}
free_self_exe = true;
break :blk bun.handleOom(allocator.dupeZ(u8, dest_z));
break :blk allocator.dupeZ(u8, dest_z) catch bun.outOfMemory();
};
defer if (free_self_exe) {
@@ -977,7 +941,7 @@ pub const StandaloneModuleGraph = struct {
var fd = inject(
bytes,
self_exe,
windows_options,
.{ .windows_hide_console = windows_hide_console },
target,
);
defer if (fd != bun.invalid_fd) fd.close();
@@ -989,73 +953,32 @@ pub const StandaloneModuleGraph = struct {
}
if (Environment.isWindows) {
// Get the current path of the temp file
var temp_buf: bun.PathBuffer = undefined;
const temp_path = bun.getFdPath(fd, &temp_buf) catch |err| {
return CompileResult.fail(std.fmt.allocPrint(allocator, "Failed to get temp file path: {s}", .{@errorName(err)}) catch "Failed to get temp file path");
var outfile_buf: bun.OSPathBuffer = undefined;
const outfile_slice = brk: {
const outfile_w = bun.strings.toWPathNormalized(&outfile_buf, std.fs.path.basenameWindows(outfile));
bun.assert(outfile_w.ptr == &outfile_buf);
const outfile_buf_u16 = bun.reinterpretSlice(u16, &outfile_buf);
outfile_buf_u16[outfile_w.len] = 0;
break :brk outfile_buf_u16[0..outfile_w.len :0];
};
// Build the absolute destination path
// On Windows, we need an absolute path for MoveFileExW
// Get the current working directory and join with outfile
var cwd_buf: bun.PathBuffer = undefined;
const cwd_path = bun.getcwd(&cwd_buf) catch |err| {
return CompileResult.fail(std.fmt.allocPrint(allocator, "Failed to get current directory: {s}", .{@errorName(err)}) catch "Failed to get current directory");
bun.windows.moveOpenedFileAtLoose(fd, .fromStdDir(root_dir), outfile_slice, true).unwrap() catch |err| {
_ = bun.windows.deleteOpenedFile(fd);
if (err == error.EISDIR) {
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
} else {
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to move executable to result path: {s}", .{@errorName(err)}) catch "failed to move executable");
}
};
const dest_path = if (std.fs.path.isAbsolute(outfile))
outfile
else
bun.path.joinAbsString(cwd_path, &[_][]const u8{outfile}, .auto);
// Convert paths to Windows UTF-16
var temp_buf_w: bun.OSPathBuffer = undefined;
var dest_buf_w: bun.OSPathBuffer = undefined;
const temp_w = bun.strings.toWPathNormalized(&temp_buf_w, temp_path);
const dest_w = bun.strings.toWPathNormalized(&dest_buf_w, dest_path);
// Ensure null termination
const temp_buf_u16 = bun.reinterpretSlice(u16, &temp_buf_w);
const dest_buf_u16 = bun.reinterpretSlice(u16, &dest_buf_w);
temp_buf_u16[temp_w.len] = 0;
dest_buf_u16[dest_w.len] = 0;
// Close the file handle before moving (Windows requires this)
fd.close();
fd = bun.invalid_fd;
// Move the file using MoveFileExW
if (bun.windows.kernel32.MoveFileExW(temp_buf_u16[0..temp_w.len :0].ptr, dest_buf_u16[0..dest_w.len :0].ptr, bun.windows.MOVEFILE_COPY_ALLOWED | bun.windows.MOVEFILE_REPLACE_EXISTING | bun.windows.MOVEFILE_WRITE_THROUGH) == bun.windows.FALSE) {
const err = bun.windows.Win32Error.get();
if (err.toSystemErrno()) |sys_err| {
if (sys_err == .EISDIR) {
return CompileResult.fail(std.fmt.allocPrint(allocator, "{s} is a directory. Please choose a different --outfile or delete the directory", .{outfile}) catch "outfile is a directory");
} else {
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to move executable to {s}: {s}", .{ dest_path, @tagName(sys_err) }) catch "failed to move executable");
}
} else {
return CompileResult.fail(std.fmt.allocPrint(allocator, "failed to move executable to {s}", .{dest_path}) catch "failed to move executable");
}
}
// Set Windows icon and/or metadata using unified function
if (windows_options.icon != null or
windows_options.title != null or
windows_options.publisher != null or
windows_options.version != null or
windows_options.description != null or
windows_options.copyright != null)
{
// The file has been moved to dest_path
bun.windows.rescle.setWindowsMetadata(
dest_buf_u16[0..dest_w.len :0].ptr,
windows_options.icon,
windows_options.title,
windows_options.publisher,
windows_options.version,
windows_options.description,
windows_options.copyright,
) catch |err| {
return CompileResult.fail(std.fmt.allocPrint(allocator, "Failed to set Windows metadata: {s}", .{@errorName(err)}) catch "Failed to set Windows metadata");
if (windows_icon) |icon_utf8| {
var icon_buf: bun.OSPathBuffer = undefined;
const icon = bun.strings.toWPathNormalized(&icon_buf, icon_utf8);
bun.windows.rescle.setIcon(outfile_slice, icon) catch |err| {
Output.debug("Warning: Failed to set Windows icon for executable: {s}", .{@errorName(err)});
};
}
return .success;
@@ -1378,7 +1301,7 @@ pub const StandaloneModuleGraph = struct {
const compressed_file = compressed_codes[@intCast(index)].slice(this.map.bytes);
const size = bun.zstd.getDecompressedSize(compressed_file);
const bytes = bun.handleOom(bun.default_allocator.alloc(u8, size));
const bytes = bun.default_allocator.alloc(u8, size) catch bun.outOfMemory();
const result = bun.zstd.decompress(bytes, compressed_file);
if (result == .err) {
@@ -1498,6 +1421,7 @@ const w = std.os.windows;
const bun = @import("bun");
const Environment = bun.Environment;
const Global = bun.Global;
const Output = bun.Output;
const SourceMap = bun.sourcemap;
const StringPointer = bun.StringPointer;

View File

@@ -322,7 +322,7 @@ fn appendFileAssumeCapacity(
const watchlist_id = this.watchlist.len;
const file_path_: string = if (comptime clone_file_path)
bun.asByteSlice(bun.handleOom(this.allocator.dupeZ(u8, file_path)))
bun.asByteSlice(this.allocator.dupeZ(u8, file_path) catch bun.outOfMemory())
else
file_path;
@@ -409,7 +409,7 @@ fn appendDirectoryAssumeCapacity(
};
const file_path_: string = if (comptime clone_file_path)
bun.asByteSlice(bun.handleOom(this.allocator.dupeZ(u8, file_path)))
bun.asByteSlice(this.allocator.dupeZ(u8, file_path) catch bun.outOfMemory())
else
file_path;
@@ -529,7 +529,7 @@ pub fn appendFileMaybeLock(
}
}
}
bun.handleOom(this.watchlist.ensureUnusedCapacity(this.allocator, 1 + @as(usize, @intCast(@intFromBool(parent_watch_item == null)))));
this.watchlist.ensureUnusedCapacity(this.allocator, 1 + @as(usize, @intCast(@intFromBool(parent_watch_item == null)))) catch bun.outOfMemory();
if (autowatch_parent_dir) {
parent_watch_item = parent_watch_item orelse switch (this.appendDirectoryAssumeCapacity(dir_fd, parent_dir, parent_dir_hash, clone_file_path)) {
@@ -595,7 +595,7 @@ pub fn addDirectory(
return .{ .result = @truncate(idx) };
}
bun.handleOom(this.watchlist.ensureUnusedCapacity(this.allocator, 1));
this.watchlist.ensureUnusedCapacity(this.allocator, 1) catch bun.outOfMemory();
return this.appendDirectoryAssumeCapacity(fd, file_path, hash, clone_file_path);
}

View File

@@ -3,16 +3,11 @@ pub const z_allocator = basic.z_allocator;
pub const freeWithoutSize = basic.freeWithoutSize;
pub const mimalloc = @import("./allocators/mimalloc.zig");
pub const MimallocArena = @import("./allocators/MimallocArena.zig");
pub const allocation_scope = @import("./allocators/allocation_scope.zig");
pub const AllocationScope = allocation_scope.AllocationScope;
pub const AllocationScopeIn = allocation_scope.AllocationScopeIn;
pub const AllocationScope = @import("./allocators/AllocationScope.zig");
pub const NullableAllocator = @import("./allocators/NullableAllocator.zig");
pub const MaxHeapAllocator = @import("./allocators/MaxHeapAllocator.zig");
pub const MemoryReportingAllocator = @import("./allocators/MemoryReportingAllocator.zig");
pub const LinuxMemFdAllocator = @import("./allocators/LinuxMemFdAllocator.zig");
pub const MaybeOwned = @import("./allocators/maybe_owned.zig").MaybeOwned;
pub fn isSliceInBufferT(comptime T: type, slice: []const T, buffer: []const T) bool {
return (@intFromPtr(buffer.ptr) <= @intFromPtr(slice.ptr) and
@@ -233,7 +228,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
const Self = @This();
allocator: std.mem.Allocator,
allocator: Allocator,
mutex: Mutex = .{},
head: *OverflowBlock,
tail: OverflowBlock,
@@ -249,7 +244,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
instance = bun.handleOom(bun.default_allocator.create(Self));
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
@@ -321,7 +316,7 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
backing_buf: [count * item_length]u8,
backing_buf_used: u64,
overflow_list: Overflow,
allocator: std.mem.Allocator,
allocator: Allocator,
slice_buf: [count][]const u8,
slice_buf_used: u16,
mutex: Mutex = .{},
@@ -335,7 +330,7 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
instance = bun.handleOom(bun.default_allocator.create(Self));
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
@@ -504,7 +499,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
index: IndexMap,
overflow_list: Overflow,
allocator: std.mem.Allocator,
allocator: Allocator,
mutex: Mutex = .{},
backing_buf: [count]ValueType,
backing_buf_used: u16,
@@ -518,7 +513,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
instance = bun.handleOom(bun.default_allocator.create(Self));
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.index = IndexMap{};
instance.allocator = allocator;
instance.overflow_list.zero();
@@ -671,7 +666,7 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
pub fn init(allocator: std.mem.Allocator) *Self {
if (!instance_loaded) {
instance = bun.handleOom(bun.default_allocator.create(Self));
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
@@ -775,118 +770,35 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
};
}
/// Checks whether `allocator` is the default allocator.
pub fn isDefault(allocator: std.mem.Allocator) bool {
pub fn isDefault(allocator: Allocator) bool {
return allocator.vtable == c_allocator.vtable;
}
// The following functions operate on generic allocators. A generic allocator is a type that
// satisfies the `GenericAllocator` interface:
//
// ```
// const GenericAllocator = struct {
// // Required.
// pub fn allocator(self: Self) std.mem.Allocator;
//
// // Optional, to allow default-initialization. `.{}` will also be tried.
// pub fn init() Self;
//
// // Optional, if this allocator owns auxiliary resources that need to be deinitialized.
// pub fn deinit(self: *Self) void;
//
// // Optional. Defining a borrowed type makes it clear who owns the allocator and prevents
// // `deinit` from being called twice.
// pub const Borrowed: type;
// pub fn borrow(self: Self) Borrowed;
// };
// ```
//
// Generic allocators must support being moved. They cannot contain self-references, and they cannot
// serve allocations from a buffer that exists within the allocator itself (have your allocator type
// contain a pointer to the buffer instead).
//
// As an exception, `std.mem.Allocator` is also treated as a generic allocator, and receives
// special handling in the following functions to achieve this.
/// Gets the `std.mem.Allocator` for a given generic allocator.
pub fn asStd(allocator: anytype) std.mem.Allocator {
return if (comptime @TypeOf(allocator) == std.mem.Allocator)
allocator
else
allocator.allocator();
}
/// A borrowed version of an allocator.
/// Allocate memory for a value of type `T` using the provided allocator, and initialize the memory
/// with `value`.
///
/// Some allocators have a `deinit` method that would be invalid to call multiple times (e.g.,
/// `AllocationScope` and `MimallocArena`).
///
/// If multiple structs or functions need access to the same allocator, we want to avoid simply
/// passing the allocator by value, as this could easily lead to `deinit` being called multiple
/// times if we forget who really owns the allocator.
///
/// Passing a pointer is not always a good approach, as this results in a performance penalty for
/// zero-sized allocators, and adds another level of indirection in all cases.
///
/// This function allows allocators that have a concept of being "owned" to define a "borrowed"
/// version of the allocator. If no such type is defined, it is assumed the allocator does not
/// own any data, and `Borrowed(Allocator)` is simply the same as `Allocator`.
pub fn Borrowed(comptime Allocator: type) type {
return if (comptime @hasDecl(Allocator, "Borrowed"))
Allocator.Borrowed
else
Allocator;
}
/// Borrows an allocator.
///
/// See `Borrowed` for the rationale.
pub fn borrow(allocator: anytype) Borrowed(@TypeOf(allocator)) {
return if (comptime @hasDecl(@TypeOf(allocator), "Borrowed"))
allocator.borrow()
else
allocator;
}
/// A type that behaves like `?Allocator`. This function will either return `?Allocator` itself,
/// or an optimized type that behaves like `?Allocator`.
///
/// Use `initNullable` and `unpackNullable` to work with the returned type.
pub fn Nullable(comptime Allocator: type) type {
return if (comptime Allocator == std.mem.Allocator)
NullableAllocator
else if (comptime @hasDecl(Allocator, "Nullable"))
Allocator.Nullable
else
?Allocator;
}
/// Creates a `Nullable(Allocator)` from an optional `Allocator`.
pub fn initNullable(comptime Allocator: type, allocator: ?Allocator) Nullable(Allocator) {
return if (comptime Allocator == std.mem.Allocator or @hasDecl(Allocator, "Nullable"))
.init(allocator)
else
allocator;
}
/// Turns a `Nullable(Allocator)` back into an optional `Allocator`.
pub fn unpackNullable(comptime Allocator: type, allocator: Nullable(Allocator)) ?Allocator {
return if (comptime Allocator == std.mem.Allocator or @hasDecl(Allocator, "Nullable"))
.get()
else
allocator;
}
/// The default allocator. This is a zero-sized type whose `allocator` method returns
/// `bun.default_allocator`.
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Default = struct {
pub fn allocator(self: Default) std.mem.Allocator {
_ = self;
return c_allocator;
/// If `allocator` is `bun.default_allocator`, this will internally use `bun.tryNew` to benefit from
/// the added assertions.
pub fn create(comptime T: type, allocator: Allocator, value: T) OOM!*T {
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
return bun.tryNew(T, value);
}
};
const ptr = try allocator.create(T);
ptr.* = value;
return ptr;
}
/// Free memory previously allocated by `create`.
///
/// The memory must have been allocated by the `create` function in this namespace, not
/// directly by `allocator.create`.
pub fn destroy(allocator: Allocator, ptr: anytype) void {
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
bun.destroy(ptr);
} else {
allocator.destroy(ptr);
}
}
const basic = if (bun.use_mimalloc)
@import("./allocators/basic.zig")
@@ -895,6 +807,7 @@ else
const Environment = @import("./env.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const OOM = bun.OOM;

View File

@@ -0,0 +1,260 @@
//! AllocationScope wraps another allocator, providing leak and invalid free assertions.
//! It also allows measuring how much memory a scope has allocated.
const AllocationScope = @This();
pub const enabled = bun.Environment.enableAllocScopes;
parent: Allocator,
state: if (enabled) struct {
mutex: bun.Mutex,
total_memory_allocated: usize,
allocations: std.AutoHashMapUnmanaged([*]const u8, Allocation),
frees: std.AutoArrayHashMapUnmanaged([*]const u8, Free),
/// Once `frees` fills up, entries are overwritten from start to end.
free_overwrite_index: std.math.IntFittingRange(0, max_free_tracking + 1),
} else void,
pub const max_free_tracking = 2048 - 1;
pub const Allocation = struct {
allocated_at: StoredTrace,
len: usize,
extra: Extra,
};
pub const Free = struct {
allocated_at: StoredTrace,
freed_at: StoredTrace,
};
pub const Extra = union(enum) {
none,
ref_count: *RefCountDebugData(false),
ref_count_threadsafe: *RefCountDebugData(true),
const RefCountDebugData = @import("../ptr/ref_count.zig").DebugData;
};
pub fn init(parent: Allocator) AllocationScope {
return if (comptime enabled)
.{
.parent = parent,
.state = .{
.total_memory_allocated = 0,
.allocations = .empty,
.frees = .empty,
.free_overwrite_index = 0,
.mutex = .{},
},
}
else
.{ .parent = parent, .state = {} };
}
pub fn deinit(scope: *AllocationScope) void {
if (comptime enabled) {
scope.state.mutex.lock();
defer scope.state.allocations.deinit(scope.parent);
const count = scope.state.allocations.count();
if (count == 0) return;
Output.errGeneric("Allocation scope leaked {d} allocations ({})", .{
count,
bun.fmt.size(scope.state.total_memory_allocated, .{}),
});
var it = scope.state.allocations.iterator();
var n: usize = 0;
while (it.next()) |entry| {
Output.prettyErrorln("- {any}, len {d}, at:", .{ entry.key_ptr.*, entry.value_ptr.len });
bun.crash_handler.dumpStackTrace(entry.value_ptr.allocated_at.trace(), trace_limits);
switch (entry.value_ptr.extra) {
.none => {},
inline else => |t| t.onAllocationLeak(@constCast(entry.key_ptr.*[0..entry.value_ptr.len])),
}
n += 1;
if (n >= 8) {
Output.prettyErrorln("(only showing first 10 leaks)", .{});
break;
}
}
Output.panic("Allocation scope leaked {}", .{bun.fmt.size(scope.state.total_memory_allocated, .{})});
}
}
pub fn allocator(scope: *AllocationScope) Allocator {
return if (comptime enabled) .{ .ptr = scope, .vtable = &vtable } else scope.parent;
}
const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = &std.mem.Allocator.noResize,
.remap = &std.mem.Allocator.noRemap,
.free = free,
};
// Smaller traces since AllocationScope prints so many
pub const trace_limits: bun.crash_handler.WriteStackTraceLimits = .{
.frame_count = 6,
.stop_at_jsc_llint = true,
.skip_stdlib = true,
};
pub const free_trace_limits: bun.crash_handler.WriteStackTraceLimits = .{
.frame_count = 3,
.stop_at_jsc_llint = true,
.skip_stdlib = true,
};
fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
const scope: *AllocationScope = @ptrCast(@alignCast(ctx));
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
scope.state.allocations.ensureUnusedCapacity(scope.parent, 1) catch
return null;
const result = scope.parent.vtable.alloc(scope.parent.ptr, len, alignment, ret_addr) orelse
return null;
scope.trackAllocationAssumeCapacity(result[0..len], ret_addr, .none);
return result;
}
fn trackAllocationAssumeCapacity(scope: *AllocationScope, buf: []const u8, ret_addr: usize, extra: Extra) void {
const trace = StoredTrace.capture(ret_addr);
scope.state.allocations.putAssumeCapacityNoClobber(buf.ptr, .{
.allocated_at = trace,
.len = buf.len,
.extra = extra,
});
scope.state.total_memory_allocated += buf.len;
}
fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
const scope: *AllocationScope = @ptrCast(@alignCast(ctx));
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
const invalid = scope.trackFreeAssumeLocked(buf, ret_addr);
scope.parent.vtable.free(scope.parent.ptr, buf, alignment, ret_addr);
// If asan did not catch the free, panic now.
if (invalid) @panic("Invalid free");
}
fn trackFreeAssumeLocked(scope: *AllocationScope, buf: []const u8, ret_addr: usize) bool {
if (scope.state.allocations.fetchRemove(buf.ptr)) |entry| {
scope.state.total_memory_allocated -= entry.value.len;
free_entry: {
scope.state.frees.put(scope.parent, buf.ptr, .{
.allocated_at = entry.value.allocated_at,
.freed_at = StoredTrace.capture(ret_addr),
}) catch break :free_entry;
// Store a limited amount of free entries
if (scope.state.frees.count() >= max_free_tracking) {
const i = scope.state.free_overwrite_index;
scope.state.free_overwrite_index = @mod(scope.state.free_overwrite_index + 1, max_free_tracking);
scope.state.frees.swapRemoveAt(i);
}
}
return false;
} else {
bun.Output.errGeneric("Invalid free, pointer {any}, len {d}", .{ buf.ptr, buf.len });
if (scope.state.frees.get(buf.ptr)) |free_entry_const| {
var free_entry = free_entry_const;
bun.Output.printErrorln("Pointer allocated here:", .{});
bun.crash_handler.dumpStackTrace(free_entry.allocated_at.trace(), trace_limits);
bun.Output.printErrorln("Pointer first freed here:", .{});
bun.crash_handler.dumpStackTrace(free_entry.freed_at.trace(), free_trace_limits);
}
// do not panic because address sanitizer will catch this case better.
// the log message is in case there is a situation where address
// sanitizer does not catch the invalid free.
return true;
}
}
pub fn assertOwned(scope: *AllocationScope, ptr: anytype) void {
if (comptime !enabled) return;
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
.c, .one, .many => ptr,
.slice => if (ptr.len > 0) ptr.ptr else return,
});
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
_ = scope.state.allocations.getPtr(cast_ptr) orelse
@panic("this pointer was not owned by the allocation scope");
}
pub fn assertUnowned(scope: *AllocationScope, ptr: anytype) void {
if (comptime !enabled) return;
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
.c, .one, .many => ptr,
.slice => if (ptr.len > 0) ptr.ptr else return,
});
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
if (scope.state.allocations.getPtr(cast_ptr)) |owned| {
Output.warn("Owned pointer allocated here:");
bun.crash_handler.dumpStackTrace(owned.allocated_at.trace(), trace_limits, trace_limits);
}
@panic("this pointer was owned by the allocation scope when it was not supposed to be");
}
/// Track an arbitrary pointer. Extra data can be stored in the allocation,
/// which will be printed when a leak is detected.
pub fn trackExternalAllocation(scope: *AllocationScope, ptr: []const u8, ret_addr: ?usize, extra: Extra) void {
if (comptime !enabled) return;
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
scope.state.allocations.ensureUnusedCapacity(scope.parent, 1) catch bun.outOfMemory();
trackAllocationAssumeCapacity(scope, ptr, ptr.len, ret_addr orelse @returnAddress(), extra);
}
/// Call when the pointer from `trackExternalAllocation` is freed.
/// Returns true if the free was invalid.
pub fn trackExternalFree(scope: *AllocationScope, slice: anytype, ret_addr: ?usize) bool {
if (comptime !enabled) return false;
const ptr: []const u8 = switch (@typeInfo(@TypeOf(slice))) {
.pointer => |p| switch (p.size) {
.slice => brk: {
if (p.child != u8) @compileError("This function only supports []u8 or [:sentinel]u8 types, you passed in: " ++ @typeName(@TypeOf(slice)));
if (p.sentinel_ptr == null) break :brk slice;
// Ensure we include the sentinel value
break :brk slice[0 .. slice.len + 1];
},
else => @compileError("This function only supports []u8 or [:sentinel]u8 types, you passed in: " ++ @typeName(@TypeOf(slice))),
},
else => @compileError("This function only supports []u8 or [:sentinel]u8 types, you passed in: " ++ @typeName(@TypeOf(slice))),
};
// Empty slice usually means invalid pointer
if (ptr.len == 0) return false;
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
return trackFreeAssumeLocked(scope, ptr, ret_addr orelse @returnAddress());
}
pub fn setPointerExtra(scope: *AllocationScope, ptr: *anyopaque, extra: Extra) void {
if (comptime !enabled) return;
scope.state.mutex.lock();
defer scope.state.mutex.unlock();
const allocation = scope.state.allocations.getPtr(ptr) orelse
@panic("Pointer not owned by allocation scope");
allocation.extra = extra;
}
pub inline fn downcast(a: Allocator) ?*AllocationScope {
return if (enabled and a.vtable == &vtable)
@ptrCast(@alignCast(a.ptr))
else
null;
}
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const Output = bun.Output;
const StoredTrace = bun.crash_handler.StoredTrace;

View File

@@ -1,95 +1,29 @@
//! This type is a `GenericAllocator`; see `src/allocators.zig`.
const Self = @This();
#heap: if (safety_checks) Owned(*DebugHeap) else *mimalloc.Heap,
heap: HeapPtr,
/// Uses the default thread-local heap. This type is zero-sized.
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Default = struct {
pub fn allocator(self: Default) std.mem.Allocator {
_ = self;
return Borrowed.getDefault().allocator();
}
};
/// Borrowed version of `MimallocArena`, returned by `MimallocArena.borrow`.
/// Using this type makes it clear who actually owns the `MimallocArena`, and prevents
/// `deinit` from being called twice.
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Borrowed = struct {
#heap: BorrowedHeap,
pub fn allocator(self: Borrowed) std.mem.Allocator {
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
}
pub fn getDefault() Borrowed {
return .{ .#heap = getThreadHeap() };
}
pub fn gc(self: Borrowed) void {
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
}
pub fn helpCatchMemoryIssues(self: Borrowed) void {
if (comptime bun.FeatureFlags.help_catch_memory_issues) {
self.gc();
bun.mimalloc.mi_collect(false);
}
}
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
}
fn fromOpaque(ptr: *anyopaque) Borrowed {
return .{ .#heap = @ptrCast(@alignCast(ptr)) };
}
fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
return if (comptime safety_checks) self.#heap.inner else self.#heap;
}
fn assertThreadLock(self: Borrowed) void {
if (comptime safety_checks) self.#heap.thread_lock.assertLocked();
}
fn alignedAlloc(self: Borrowed, len: usize, alignment: Alignment) ?[*]u8 {
log("Malloc: {d}\n", .{len});
const heap = self.getMimallocHeap();
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
else
mimalloc.mi_heap_malloc(heap, len);
if (comptime bun.Environment.isDebug) {
const usable = mimalloc.mi_malloc_usable_size(ptr);
if (usable < len) {
std.debug.panic("mimalloc: allocated size is too small: {d} < {d}", .{ usable, len });
}
}
return if (ptr) |p|
@as([*]u8, @ptrCast(p))
else
null;
}
};
const BorrowedHeap = if (safety_checks) *DebugHeap else *mimalloc.Heap;
const HeapPtr = if (safety_checks) *DebugHeap else *mimalloc.Heap;
const DebugHeap = struct {
inner: *mimalloc.Heap,
thread_lock: bun.safety.ThreadLock,
};
fn getMimallocHeap(self: Self) *mimalloc.Heap {
return if (comptime safety_checks) self.heap.inner else self.heap;
}
fn fromOpaque(ptr: *anyopaque) Self {
return .{ .heap = bun.cast(HeapPtr, ptr) };
}
fn assertThreadLock(self: Self) void {
if (comptime safety_checks) self.heap.thread_lock.assertLocked();
}
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
fn getThreadHeap() BorrowedHeap {
fn getThreadHeap() HeapPtr {
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
if (thread_heap == null) {
thread_heap = .{
@@ -102,27 +36,23 @@ fn getThreadHeap() BorrowedHeap {
const log = bun.Output.scoped(.mimalloc, .hidden);
pub fn allocator(self: Self) std.mem.Allocator {
return self.borrow().allocator();
}
pub fn borrow(self: Self) Borrowed {
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
}
/// Internally, mimalloc calls mi_heap_get_default()
/// to get the default heap.
/// It uses pthread_getspecific to do that.
/// We can save those extra calls if we just do it once in here
pub fn getThreadLocalDefault() std.mem.Allocator {
return Borrowed.getDefault().allocator();
pub fn getThreadLocalDefault() Allocator {
return Allocator{ .ptr = getThreadHeap(), .vtable = &c_allocator_vtable };
}
pub fn backingAllocator(_: Self) std.mem.Allocator {
pub fn backingAllocator(_: Self) Allocator {
return getThreadLocalDefault();
}
pub fn dumpThreadStats(_: Self) void {
pub fn allocator(self: Self) Allocator {
return Allocator{ .ptr = self.heap, .vtable = &c_allocator_vtable };
}
pub fn dumpThreadStats(_: *Self) void {
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
@@ -133,7 +63,7 @@ pub fn dumpThreadStats(_: Self) void {
bun.Output.flush();
}
pub fn dumpStats(_: Self) void {
pub fn dumpStats(_: *Self) void {
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
@@ -145,9 +75,9 @@ pub fn dumpStats(_: Self) void {
}
pub fn deinit(self: *Self) void {
const mimalloc_heap = self.borrow().getMimallocHeap();
const mimalloc_heap = self.getMimallocHeap();
if (comptime safety_checks) {
self.#heap.deinit();
bun.destroy(self.heap);
}
mimalloc.mi_heap_destroy(mimalloc_heap);
self.* = undefined;
@@ -155,43 +85,70 @@ pub fn deinit(self: *Self) void {
pub fn init() Self {
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
if (comptime !safety_checks) return .{ .#heap = mimalloc_heap };
const heap: Owned(*DebugHeap) = .new(.{
.inner = mimalloc_heap,
.thread_lock = .initLocked(),
});
return .{ .#heap = heap };
const heap = if (comptime safety_checks)
bun.new(DebugHeap, .{
.inner = mimalloc_heap,
.thread_lock = .initLocked(),
})
else
mimalloc_heap;
return .{ .heap = heap };
}
pub fn gc(self: Self) void {
self.borrow().gc();
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
}
pub fn helpCatchMemoryIssues(self: Self) void {
self.borrow().helpCatchMemoryIssues();
pub inline fn helpCatchMemoryIssues(self: Self) void {
if (comptime bun.FeatureFlags.help_catch_memory_issues) {
self.gc();
bun.mimalloc.mi_collect(false);
}
}
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
return self.borrow().ownsPtr(ptr);
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
}
fn alignedAlloc(self: Self, len: usize, alignment: Alignment) ?[*]u8 {
log("Malloc: {d}\n", .{len});
const heap = self.getMimallocHeap();
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
else
mimalloc.mi_heap_malloc(heap, len);
if (comptime bun.Environment.isDebug) {
const usable = mimalloc.mi_malloc_usable_size(ptr);
if (usable < len) {
std.debug.panic("mimalloc: allocated size is too small: {d} < {d}", .{ usable, len });
}
}
return if (ptr) |p|
@as([*]u8, @ptrCast(p))
else
null;
}
fn alignedAllocSize(ptr: [*]u8) usize {
return mimalloc.mi_malloc_usable_size(ptr);
}
fn vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
fn alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
const self = fromOpaque(ptr);
self.assertThreadLock();
return self.alignedAlloc(len, alignment);
return alignedAlloc(self, len, alignment);
}
fn vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
const self: Borrowed = .fromOpaque(ptr);
fn resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
const self = fromOpaque(ptr);
self.assertThreadLock();
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}
fn vtable_free(
fn free(
_: *anyopaque,
buf: []u8,
alignment: Alignment,
@@ -230,8 +187,8 @@ fn vtable_free(
/// `ret_addr` is optionally provided as the first return address of the
/// allocation call stack. If the value is `0` it means no return address
/// has been provided.
fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
const self: Borrowed = .fromOpaque(ptr);
fn remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
const self = fromOpaque(ptr);
self.assertThreadLock();
const heap = self.getMimallocHeap();
const aligned_size = alignment.toByteUnits();
@@ -239,22 +196,23 @@ fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize
return @ptrCast(value);
}
pub fn isInstance(alloc: std.mem.Allocator) bool {
return alloc.vtable == &c_allocator_vtable;
pub fn isInstance(allocator_: Allocator) bool {
return allocator_.vtable == &c_allocator_vtable;
}
const c_allocator_vtable = std.mem.Allocator.VTable{
.alloc = vtable_alloc,
.resize = vtable_resize,
.remap = vtable_remap,
.free = vtable_free,
const c_allocator_vtable = Allocator.VTable{
.alloc = &Self.alloc,
.resize = &Self.resize,
.remap = &Self.remap,
.free = &Self.free,
};
const std = @import("std");
const Alignment = std.mem.Alignment;
const bun = @import("bun");
const assert = bun.assert;
const mimalloc = bun.mimalloc;
const Owned = bun.ptr.Owned;
const safety_checks = bun.Environment.ci_assert;
const Alignment = std.mem.Alignment;
const Allocator = std.mem.Allocator;

View File

@@ -4,7 +4,8 @@ const NullableAllocator = @This();
ptr: *anyopaque = undefined,
// Utilize the null pointer optimization on the vtable instead of
// the regular `ptr` because `ptr` may be undefined.
// the regular ptr because some allocator implementations might tag their
// `ptr` property.
vtable: ?*const std.mem.Allocator.VTable = null,
pub inline fn init(allocator: ?std.mem.Allocator) NullableAllocator {

View File

@@ -1,555 +0,0 @@
//! AllocationScope wraps another allocator, providing leak and invalid free assertions.
//! It also allows measuring how much memory a scope has allocated.
const allocation_scope = @This();
/// An allocation scope with a dynamically typed parent allocator. Prefer using a concrete type,
/// like `AllocationScopeIn(bun.DefaultAllocator)`.
pub const AllocationScope = AllocationScopeIn(std.mem.Allocator);
pub const Allocation = struct {
allocated_at: StoredTrace,
len: usize,
extra: Extra,
};
pub const Free = struct {
allocated_at: StoredTrace,
freed_at: StoredTrace,
};
pub const Extra = struct {
ptr: *anyopaque,
vtable: ?*const VTable,
pub const none: Extra = .{ .ptr = undefined, .vtable = null };
pub const VTable = struct {
onAllocationLeak: *const fn (*anyopaque, data: []u8) void,
};
};
pub const Stats = struct {
total_memory_allocated: usize,
num_allocations: usize,
};
pub const FreeError = error{
/// Tried to free memory that wasn't allocated by this `AllocationScope`, or was already freed.
NotAllocated,
};
pub const enabled = bun.Environment.enableAllocScopes;
pub const max_free_tracking = 2048 - 1;
const History = struct {
const Self = @This();
total_memory_allocated: usize = 0,
/// Allocated by `State.parent`.
allocations: std.AutoHashMapUnmanaged([*]const u8, Allocation) = .empty,
/// Allocated by `State.parent`.
frees: std.AutoArrayHashMapUnmanaged([*]const u8, Free) = .empty,
/// Once `frees` fills up, entries are overwritten from start to end.
free_overwrite_index: std.math.IntFittingRange(0, max_free_tracking + 1) = 0,
/// `allocator` should be `State.parent`.
fn deinit(self: *Self, allocator: std.mem.Allocator) void {
self.allocations.deinit(allocator);
self.frees.deinit(allocator);
self.* = undefined;
}
};
const LockedState = struct {
const Self = @This();
/// Should be the same as `State.parent`.
parent: std.mem.Allocator,
history: *History,
fn alloc(self: Self, len: usize, alignment: std.mem.Alignment, ret_addr: usize) bun.OOM![*]u8 {
const result = self.parent.rawAlloc(len, alignment, ret_addr) orelse
return error.OutOfMemory;
errdefer self.parent.rawFree(result[0..len], alignment, ret_addr);
try self.trackAllocation(result[0..len], ret_addr, .none);
return result;
}
fn free(self: Self, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
const success = if (self.trackFree(buf, ret_addr))
true
else |err| switch (err) {
error.NotAllocated => false,
};
if (success or bun.Environment.enable_asan) {
self.parent.rawFree(buf, alignment, ret_addr);
}
if (!success) {
// If asan did not catch the free, panic now.
std.debug.panic("Invalid free: {*}", .{buf});
}
}
fn assertOwned(self: Self, ptr: anytype) void {
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
.c, .one, .many => ptr,
.slice => if (ptr.len > 0) ptr.ptr else return,
});
if (!self.history.allocations.contains(cast_ptr)) {
@panic("this pointer was not owned by the allocation scope");
}
}
fn assertUnowned(self: Self, ptr: anytype) void {
const cast_ptr: [*]const u8 = @ptrCast(switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
.c, .one, .many => ptr,
.slice => if (ptr.len > 0) ptr.ptr else return,
});
if (self.history.allocations.getPtr(cast_ptr)) |owned| {
Output.warn("Owned pointer allocated here:");
bun.crash_handler.dumpStackTrace(
owned.allocated_at.trace(),
trace_limits,
trace_limits,
);
@panic("this pointer was owned by the allocation scope when it was not supposed to be");
}
}
fn trackAllocation(self: Self, buf: []const u8, ret_addr: usize, extra: Extra) bun.OOM!void {
const trace = StoredTrace.capture(ret_addr);
try self.history.allocations.putNoClobber(self.parent, buf.ptr, .{
.allocated_at = trace,
.len = buf.len,
.extra = extra,
});
self.history.total_memory_allocated += buf.len;
}
fn trackFree(self: Self, buf: []const u8, ret_addr: usize) FreeError!void {
const entry = self.history.allocations.fetchRemove(buf.ptr) orelse {
Output.errGeneric("Invalid free, pointer {any}, len {d}", .{ buf.ptr, buf.len });
if (self.history.frees.getPtr(buf.ptr)) |free_entry| {
Output.printErrorln("Pointer allocated here:", .{});
bun.crash_handler.dumpStackTrace(free_entry.allocated_at.trace(), trace_limits);
Output.printErrorln("Pointer first freed here:", .{});
bun.crash_handler.dumpStackTrace(free_entry.freed_at.trace(), free_trace_limits);
}
// do not panic because address sanitizer will catch this case better.
// the log message is in case there is a situation where address
// sanitizer does not catch the invalid free.
return error.NotAllocated;
};
self.history.total_memory_allocated -= entry.value.len;
// Store a limited amount of free entries
if (self.history.frees.count() >= max_free_tracking) {
const i = self.history.free_overwrite_index;
self.history.free_overwrite_index =
@mod(self.history.free_overwrite_index + 1, max_free_tracking);
self.history.frees.swapRemoveAt(i);
}
self.history.frees.put(self.parent, buf.ptr, .{
.allocated_at = entry.value.allocated_at,
.freed_at = StoredTrace.capture(ret_addr),
}) catch |err| bun.handleOom(err);
}
};
const State = struct {
const Self = @This();
/// This field should not be modified. Therefore, it doesn't need to be protected by the mutex.
parent: std.mem.Allocator,
history: bun.threading.Guarded(History),
fn init(parent_alloc: std.mem.Allocator) Self {
return .{
.parent = parent_alloc,
.history = .init(.{}),
};
}
fn lock(self: *Self) LockedState {
return .{
.parent = self.parent,
.history = self.history.lock(),
};
}
fn unlock(self: *Self) void {
self.history.unlock();
}
fn deinit(self: *Self) void {
defer self.* = undefined;
var history = self.history.intoUnprotected();
defer history.deinit();
const count = history.allocations.count();
if (count == 0) return;
Output.errGeneric("Allocation scope leaked {d} allocations ({})", .{
count,
bun.fmt.size(history.total_memory_allocated, .{}),
});
var it = history.allocations.iterator();
var n: usize = 0;
while (it.next()) |entry| : (n += 1) {
if (n >= 10) {
Output.prettyErrorln("(only showing first 10 leaks)", .{});
break;
}
Output.prettyErrorln(
"- {any}, len {d}, at:",
.{ entry.key_ptr.*, entry.value_ptr.len },
);
bun.crash_handler.dumpStackTrace(
entry.value_ptr.allocated_at.trace(),
trace_limits,
);
const extra = entry.value_ptr.extra;
if (extra.vtable) |extra_vtable| {
extra_vtable.onAllocationLeak(
extra.ptr,
@constCast(entry.key_ptr.*[0..entry.value_ptr.len]),
);
}
}
Output.panic(
"Allocation scope leaked {}",
.{bun.fmt.size(history.total_memory_allocated, .{})},
);
}
fn trackExternalAllocation(self: *Self, ptr: []const u8, ret_addr: ?usize, extra: Extra) void {
const locked = self.lock();
defer self.unlock();
locked.trackAllocation(ptr, ret_addr orelse @returnAddress(), extra) catch |err|
bun.handleOom(err);
}
fn trackExternalFree(self: *Self, slice: anytype, ret_addr: ?usize) FreeError!void {
const invalidType = struct {
fn invalidType() noreturn {
@compileError(std.fmt.comptimePrint(
"This function only supports []u8 or [:sentinel]u8 types, you passed in: {s}",
.{@typeName(@TypeOf(slice))},
));
}
}.invalidType;
const ptr: []const u8 = switch (@typeInfo(@TypeOf(slice))) {
.pointer => |p| switch (p.size) {
.slice => brk: {
if (p.child != u8) invalidType();
if (p.sentinel_ptr == null) break :brk slice;
// Ensure we include the sentinel value
break :brk slice[0 .. slice.len + 1];
},
else => invalidType(),
},
else => invalidType(),
};
// Empty slice usually means invalid pointer
if (ptr.len == 0) return;
const locked = self.lock();
defer self.unlock();
return locked.trackFree(ptr, ret_addr orelse @returnAddress());
}
fn setPointerExtra(self: *Self, ptr: *anyopaque, extra: Extra) void {
const locked = self.lock();
defer self.unlock();
const allocation = locked.history.allocations.getPtr(@ptrCast(ptr)) orelse
@panic("Pointer not owned by allocation scope");
allocation.extra = extra;
}
};
/// An allocation scope that uses a specific kind of parent allocator.
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub fn AllocationScopeIn(comptime Allocator: type) type {
const BorrowedAllocator = bun.allocators.Borrowed(Allocator);
// Borrowed version of `AllocationScope`. Access this type as `AllocationScope.Borrowed`.
const BorrowedScope = struct {
const Self = @This();
#parent: BorrowedAllocator,
#state: if (enabled) *State else void,
pub fn allocator(self: Self) std.mem.Allocator {
return if (comptime enabled)
.{ .ptr = self.#state, .vtable = &vtable }
else
bun.allocators.asStd(self.#parent);
}
pub fn parent(self: Self) BorrowedAllocator {
return self.#parent;
}
/// Deinitializes a borrowed allocation scope. This does not deinitialize the
/// `AllocationScope` itself; only the owner of the `AllocationScope` should do that.
///
/// This method doesn't need to be called unless `bun.allocators.Borrowed(Allocator)` has
/// a `deinit` method.
pub fn deinit(self: *Self) void {
bun.memory.deinit(&self.#parent);
self.* = undefined;
}
pub fn stats(self: Self) Stats {
if (comptime !enabled) @compileError("AllocationScope must be enabled");
const state = self.#state.lock();
defer self.#state.unlock();
return .{
.total_memory_allocated = state.history.total_memory_allocated,
.num_allocations = state.history.allocations.count(),
};
}
pub fn assertOwned(self: Self, ptr: anytype) void {
if (comptime !enabled) return;
const state = self.#state.lock();
defer self.#state.unlock();
state.assertOwned(ptr);
}
pub fn assertUnowned(self: Self, ptr: anytype) void {
if (comptime !enabled) return;
const state = self.#state.lock();
defer self.#state.unlock();
state.assertUnowned(ptr);
}
pub fn trackExternalAllocation(
self: Self,
ptr: []const u8,
ret_addr: ?usize,
extra: Extra,
) void {
if (comptime enabled) self.#state.trackExternalAllocation(ptr, ret_addr, extra);
}
pub fn trackExternalFree(self: Self, slice: anytype, ret_addr: ?usize) FreeError!void {
return if (comptime enabled) self.#state.trackExternalFree(slice, ret_addr);
}
pub fn setPointerExtra(self: Self, ptr: *anyopaque, extra: Extra) void {
if (comptime enabled) self.#state.setPointerExtra(ptr, extra);
}
fn downcastImpl(
std_alloc: std.mem.Allocator,
parent_alloc: if (Allocator == std.mem.Allocator)
?BorrowedAllocator
else
BorrowedAllocator,
) Self {
const state = if (comptime enabled) blk: {
bun.assertf(
std_alloc.vtable == &vtable,
"allocator is not an allocation scope (has vtable {*})",
.{std_alloc.vtable},
);
const state: *State = @ptrCast(@alignCast(std_alloc.ptr));
break :blk state;
};
const current_std_parent = if (comptime enabled)
state.parent
else
std_alloc;
const new_parent = if (comptime Allocator == std.mem.Allocator)
parent_alloc orelse current_std_parent
else
parent_alloc;
const new_std_parent = bun.allocators.asStd(new_parent);
bun.safety.alloc.assertEqFmt(
current_std_parent,
new_std_parent,
"tried to downcast allocation scope with wrong parent allocator",
.{},
);
return .{ .#parent = new_parent, .#state = state };
}
/// Converts an `std.mem.Allocator` into a borrowed allocation scope, with a given parent
/// allocator.
///
/// Requirements:
///
/// * `std_alloc` must have come from `AllocationScopeIn(Allocator).allocator` (or the
/// equivalent method on a `Borrowed` instance).
///
/// * `parent_alloc` must be equivalent to the (borrowed) parent allocator of the original
/// allocation scope (that is, the return value of `AllocationScopeIn(Allocator).parent`).
/// In particular, `bun.allocators.asStd` must return the same value for each allocator.
pub fn downcastIn(std_alloc: std.mem.Allocator, parent_alloc: BorrowedAllocator) Self {
return downcastImpl(std_alloc, parent_alloc);
}
/// Converts an `std.mem.Allocator` into a borrowed allocation scope.
///
/// Requirements:
///
/// * `std_alloc` must have come from `AllocationScopeIn(Allocator).allocator` (or the
/// equivalent method on a `Borrowed` instance).
///
/// * One of the following must be true:
///
/// 1. `Allocator` is `std.mem.Allocator`.
///
/// 2. The parent allocator of the original allocation scope is equivalent to a
/// default-initialized borrowed `Allocator`, as returned by
/// `bun.memory.initDefault(bun.allocators.Borrowed(Allocator))`. This is the case
/// for `bun.DefaultAllocator`.
pub fn downcast(std_alloc: std.mem.Allocator) Self {
return downcastImpl(std_alloc, if (comptime Allocator == std.mem.Allocator)
null
else
bun.memory.initDefault(BorrowedAllocator));
}
};
return struct {
const Self = @This();
#parent: Allocator,
#state: if (Self.enabled) Owned(*State) else void,
pub const enabled = allocation_scope.enabled;
/// Borrowed version of `AllocationScope`, returned by `AllocationScope.borrow`.
/// Using this type makes it clear who actually owns the `AllocationScope`, and prevents
/// `deinit` from being called twice.
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub const Borrowed = BorrowedScope;
pub fn init(parent_alloc: Allocator) Self {
return .{
.#parent = parent_alloc,
.#state = if (comptime Self.enabled) .new(.init(
bun.allocators.asStd(parent_alloc),
)),
};
}
pub fn initDefault() Self {
return .init(bun.memory.initDefault(Allocator));
}
/// Borrows this `AllocationScope`. Use this method instead of copying `self`, as that makes
/// it hard to know who owns the `AllocationScope`, and could lead to `deinit` being called
/// twice.
pub fn borrow(self: Self) Borrowed {
return .{
.#parent = self.parent(),
.#state = if (comptime Self.enabled) self.#state.get(),
};
}
pub fn allocator(self: Self) std.mem.Allocator {
return self.borrow().allocator();
}
pub fn deinit(self: *Self) void {
bun.memory.deinit(&self.#parent);
if (comptime Self.enabled) self.#state.deinit();
self.* = undefined;
}
pub fn parent(self: Self) BorrowedAllocator {
return bun.allocators.borrow(self.#parent);
}
pub fn stats(self: Self) Stats {
return self.borrow().stats();
}
pub fn assertOwned(self: Self, ptr: anytype) void {
self.borrow().assertOwned(ptr);
}
pub fn assertUnowned(self: Self, ptr: anytype) void {
self.borrow().assertUnowned(ptr);
}
/// Track an arbitrary pointer. Extra data can be stored in the allocation, which will be
/// printed when a leak is detected.
pub fn trackExternalAllocation(
self: Self,
ptr: []const u8,
ret_addr: ?usize,
extra: Extra,
) void {
self.borrow().trackExternalAllocation(ptr, ret_addr, extra);
}
/// Call when the pointer from `trackExternalAllocation` is freed.
pub fn trackExternalFree(self: Self, slice: anytype, ret_addr: ?usize) FreeError!void {
return self.borrow().trackExternalFree(slice, ret_addr);
}
pub fn setPointerExtra(self: Self, ptr: *anyopaque, extra: Extra) void {
return self.borrow().setPointerExtra(ptr, extra);
}
};
}
const vtable: std.mem.Allocator.VTable = .{
.alloc = vtable_alloc,
.resize = std.mem.Allocator.noResize,
.remap = std.mem.Allocator.noRemap,
.free = vtable_free,
};
// Smaller traces since AllocationScope prints so many
pub const trace_limits: bun.crash_handler.WriteStackTraceLimits = .{
.frame_count = 6,
.stop_at_jsc_llint = true,
.skip_stdlib = true,
};
pub const free_trace_limits: bun.crash_handler.WriteStackTraceLimits = .{
.frame_count = 3,
.stop_at_jsc_llint = true,
.skip_stdlib = true,
};
fn vtable_alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
const raw_state: *State = @ptrCast(@alignCast(ctx));
const state = raw_state.lock();
defer raw_state.unlock();
return state.alloc(len, alignment, ret_addr) catch null;
}
fn vtable_free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
const raw_state: *State = @ptrCast(@alignCast(ctx));
const state = raw_state.lock();
defer raw_state.unlock();
state.free(buf, alignment, ret_addr);
}
pub inline fn isInstance(allocator: std.mem.Allocator) bool {
return (comptime enabled) and allocator.vtable == &vtable;
}
const std = @import("std");
const bun = @import("bun");
const Output = bun.Output;
const Owned = bun.ptr.Owned;
const StoredTrace = bun.crash_handler.StoredTrace;

View File

@@ -1,112 +0,0 @@
/// This type can be used with `bun.ptr.Owned` to model "maybe owned" pointers:
///
/// ```
/// // Either owned by the default allocator, or borrowed
/// const MaybeOwnedFoo = bun.ptr.Owned(*Foo, bun.allocators.MaybeOwned(bun.DefaultAllocator));
///
/// var owned_foo: MaybeOwnedFoo = .new(makeFoo());
/// var borrowed_foo: MaybeOwnedFoo = .fromRawIn(some_foo_ptr, .initBorrowed());
///
/// owned_foo.deinit(); // calls `Foo.deinit` and frees the memory
/// borrowed_foo.deinit(); // no-op
/// ```
///
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
pub fn MaybeOwned(comptime Allocator: type) type {
return struct {
const Self = @This();
_parent: bun.allocators.Nullable(Allocator),
/// Same as `.initBorrowed()`. This allocator cannot be used to allocate memory; a panic
/// will occur.
pub const borrowed = .initBorrowed();
/// Creates a `MaybeOwned` allocator that owns memory.
///
/// Allocations are forwarded to a default-initialized `Allocator`.
pub fn init() Self {
return .initOwned(bun.memory.initDefault(Allocator));
}
/// Creates a `MaybeOwned` allocator that owns memory, and forwards to a specific
/// allocator.
///
/// Allocations are forwarded to `parent_alloc`.
pub fn initOwned(parent_alloc: Allocator) Self {
return .initRaw(parent_alloc);
}
/// Creates a `MaybeOwned` allocator that does not own any memory. This allocator cannot
/// be used to allocate new memory (a panic will occur), and its implementation of `free`
/// is a no-op.
pub fn initBorrowed() Self {
return .initRaw(null);
}
pub fn deinit(self: *Self) void {
var maybe_parent = self.intoParent();
if (maybe_parent) |*parent_alloc| {
bun.memory.deinit(parent_alloc);
}
}
pub fn isOwned(self: Self) bool {
return self.rawParent() != null;
}
pub fn allocator(self: Self) std.mem.Allocator {
const maybe_parent = self.rawParent();
return if (maybe_parent) |parent_alloc|
bun.allocators.asStd(parent_alloc)
else
.{ .ptr = undefined, .vtable = &null_vtable };
}
const BorrowedParent = bun.allocators.Borrowed(Allocator);
pub fn parent(self: Self) ?BorrowedParent {
const maybe_parent = self.rawParent();
return if (maybe_parent) |parent_alloc|
bun.allocators.borrow(parent_alloc)
else
null;
}
pub fn intoParent(self: *Self) ?Allocator {
defer self.* = undefined;
return self.rawParent();
}
/// Used by smart pointer types and allocator wrappers. See `bun.allocators.borrow`.
pub const Borrowed = MaybeOwned(BorrowedParent);
pub fn borrow(self: Self) Borrowed {
return .{ ._parent = bun.allocators.initNullable(BorrowedParent, self.parent()) };
}
fn initRaw(parent_alloc: ?Allocator) Self {
return .{ ._parent = bun.allocators.initNullable(Allocator, parent_alloc) };
}
fn rawParent(self: Self) ?Allocator {
return bun.allocators.unpackNullable(Allocator, self._parent);
}
};
}
fn nullAlloc(ptr: *anyopaque, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 {
_ = .{ ptr, len, alignment, ret_addr };
std.debug.panic("cannot allocate with a borrowed `MaybeOwned` allocator", .{});
}
const null_vtable: std.mem.Allocator.VTable = .{
.alloc = nullAlloc,
.resize = std.mem.Allocator.noResize,
.remap = std.mem.Allocator.noRemap,
.free = std.mem.Allocator.noFree,
};
const bun = @import("bun");
const std = @import("std");
const Alignment = std.mem.Alignment;

View File

@@ -112,7 +112,6 @@ pub const Features = struct {
pub var unsupported_uv_function: usize = 0;
pub var exited: usize = 0;
pub var yarn_migration: usize = 0;
pub var yaml_parse: usize = 0;
comptime {
@export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" });

74
src/api/schema.d.ts generated vendored
View File

@@ -21,58 +21,46 @@ export const enum Loader {
css = 5,
file = 6,
json = 7,
jsonc = 8,
toml = 9,
wasm = 10,
napi = 11,
base64 = 12,
dataurl = 13,
text = 14,
bunsh = 15,
sqlite = 16,
sqlite_embedded = 17,
html = 18,
yaml = 19,
toml = 8,
wasm = 9,
napi = 10,
base64 = 11,
dataurl = 12,
text = 13,
sqlite = 14,
html = 15,
}
export const LoaderKeys: {
1: "jsx";
jsx: "jsx";
2: "js";
js: "js";
3: "ts";
ts: "ts";
4: "tsx";
tsx: "tsx";
5: "css";
css: "css";
6: "file";
file: "file";
7: "json";
8: "jsonc";
9: "toml";
10: "wasm";
11: "napi";
12: "base64";
13: "dataurl";
14: "text";
15: "bunsh";
16: "sqlite";
17: "sqlite_embedded";
18: "html";
19: "yaml";
jsx: 1;
js: 2;
ts: 3;
tsx: 4;
css: 5;
file: 6;
json: 7;
jsonc: 8;
toml: 9;
wasm: 10;
napi: 11;
base64: 12;
dataurl: 13;
text: 14;
bunsh: 15;
sqlite: 16;
sqlite_embedded: 17;
html: 18;
yaml: 19;
json: "json";
8: "toml";
toml: "toml";
9: "wasm";
wasm: "wasm";
10: "napi";
napi: "napi";
11: "base64";
base64: "base64";
12: "dataurl";
dataurl: "dataurl";
13: "text";
text: "text";
14: "sqlite";
sqlite: "sqlite";
15: "html";
"html": "html";
};
export const enum FrameworkEntryPointType {
client = 1,

122
src/api/schema.js generated
View File

@@ -1,42 +1,34 @@
const Loader = {
"1": "jsx",
"2": "js",
"3": "ts",
"4": "tsx",
"5": "css",
"6": "file",
"7": "json",
"8": "jsonc",
"9": "toml",
"10": "wasm",
"11": "napi",
"12": "base64",
"13": "dataurl",
"14": "text",
"15": "bunsh",
"16": "sqlite",
"17": "sqlite_embedded",
"18": "html",
"19": "yaml",
jsx: 1,
js: 2,
ts: 3,
tsx: 4,
css: 5,
file: 6,
json: 7,
jsonc: 8,
toml: 9,
wasm: 10,
napi: 11,
base64: 12,
dataurl: 13,
text: 14,
bunsh: 15,
sqlite: 16,
sqlite_embedded: 17,
html: 18,
yaml: 19,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"10": 10,
"11": 11,
"12": 12,
"13": 13,
"14": 14,
"15": 15,
"jsx": 1,
"js": 2,
"ts": 3,
"tsx": 4,
"css": 5,
"file": 6,
"json": 7,
"toml": 8,
"wasm": 9,
"napi": 10,
"base64": 11,
"dataurl": 12,
"text": 13,
"sqlite": 14,
"html": 15,
};
const LoaderKeys = {
"1": "jsx",
@@ -46,37 +38,29 @@ const LoaderKeys = {
"5": "css",
"6": "file",
"7": "json",
"8": "jsonc",
"9": "toml",
"10": "wasm",
"11": "napi",
"12": "base64",
"13": "dataurl",
"14": "text",
"15": "bunsh",
"16": "sqlite",
"17": "sqlite_embedded",
"18": "html",
"19": "yaml",
jsx: "jsx",
js: "js",
ts: "ts",
tsx: "tsx",
css: "css",
file: "file",
json: "json",
jsonc: "jsonc",
toml: "toml",
wasm: "wasm",
napi: "napi",
base64: "base64",
dataurl: "dataurl",
text: "text",
bunsh: "bunsh",
sqlite: "sqlite",
sqlite_embedded: "sqlite_embedded",
html: "html",
yaml: "yaml",
"8": "toml",
"9": "wasm",
"10": "napi",
"11": "base64",
"12": "dataurl",
"13": "text",
"14": "sqlite",
"15": "html",
"jsx": "jsx",
"js": "js",
"ts": "ts",
"tsx": "tsx",
"css": "css",
"file": "file",
"json": "json",
"toml": "toml",
"wasm": "wasm",
"napi": "napi",
"base64": "base64",
"dataurl": "dataurl",
"text": "text",
"sqlite": "sqlite",
"html": "html",
};
const FrameworkEntryPointType = {
"1": 1,

View File

@@ -322,26 +322,22 @@ pub const FileWriter = Writer(std.fs.File);
pub const api = struct {
pub const Loader = enum(u8) {
_none = 255,
jsx = 1,
js = 2,
ts = 3,
tsx = 4,
css = 5,
file = 6,
json = 7,
jsonc = 8,
toml = 9,
wasm = 10,
napi = 11,
base64 = 12,
dataurl = 13,
text = 14,
bunsh = 15,
sqlite = 16,
sqlite_embedded = 17,
html = 18,
yaml = 19,
_none,
jsx,
js,
ts,
tsx,
css,
file,
json,
toml,
wasm,
napi,
base64,
dataurl,
text,
sqlite,
html,
_,
pub fn jsonStringify(self: @This(), writer: anytype) !void {
@@ -799,9 +795,6 @@ pub const api = struct {
/// import_source
import_source: []const u8,
/// side_effects
side_effects: bool = false,
pub fn decode(reader: anytype) anyerror!Jsx {
var this = std.mem.zeroes(Jsx);
@@ -810,7 +803,6 @@ pub const api = struct {
this.fragment = try reader.readValue([]const u8);
this.development = try reader.readValue(bool);
this.import_source = try reader.readValue([]const u8);
this.side_effects = try reader.readValue(bool);
return this;
}
@@ -820,7 +812,6 @@ pub const api = struct {
try writer.writeValue(@TypeOf(this.fragment), this.fragment);
try writer.writeInt(@as(u8, @intFromBool(this.development)));
try writer.writeValue(@TypeOf(this.import_source), this.import_source);
try writer.writeInt(@as(u8, @intFromBool(this.side_effects)));
}
};
@@ -2825,7 +2816,7 @@ pub const api = struct {
token: []const u8,
pub fn dupe(this: NpmRegistry, allocator: std.mem.Allocator) NpmRegistry {
const buf = bun.handleOom(allocator.alloc(u8, this.url.len + this.username.len + this.password.len + this.token.len));
const buf = allocator.alloc(u8, this.url.len + this.username.len + this.password.len + this.token.len) catch bun.outOfMemory();
var out: NpmRegistry = .{
.url = "",
@@ -3050,8 +3041,6 @@ pub const api = struct {
node_linker: ?bun.install.PackageManager.Options.NodeLinker = null,
security_scanner: ?[]const u8 = null,
pub fn decode(reader: anytype) anyerror!BunInstall {
var this = std.mem.zeroes(BunInstall);

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