Compare commits

..

5 Commits

Author SHA1 Message Date
autofix-ci[bot]
8712fce75f [autofix.ci] apply automated fixes 2025-07-15 23:05:44 +00:00
Claude Bot
05b5fce694 Fix CA loading order to match Node.js behavior (additive, not replacement)
The system CA implementation now correctly follows Node.js certificate
loading order instead of replacing bundled certificates:

Node.js loading order:
1. Default root certs (bundled Mozilla certificates)
2. System certs (when --use-system-ca is enabled)
3. Extra certs (NODE_EXTRA_CA_CERTS)

Key changes:
- Always load bundled root certificates first
- Add system CAs as additional certificates when --use-system-ca is set
- Keep NODE_EXTRA_CA_CERTS loading last
- System CAs are now additive, not replacement
- Updated tests to reflect additive behavior

This matches PR #21092 behavior where system CAs supplement rather than
replace the bundled certificate store, providing both comprehensive
coverage and system-specific trust settings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 23:01:08 +00:00
Claude Bot
4777992fe7 Remove BUN_USE_SYSTEM_CA environment variable support
Make --use-system-ca CLI flag the only way to enable system CA loading.
This simplifies the implementation and removes potential confusion
between environment variables and CLI flags.

Changes:
- Remove environment variable fallback from Bun__useSystemCA()
- Update tests to only test CLI flag functionality
- Add tests for default behavior and flag position independence
- Update comments to clarify CLI flag only support

The implementation now only respects the --use-system-ca CLI flag,
making the behavior more predictable and consistent with other
Bun CLI features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:52:43 +00:00
Claude Bot
8b950ca95a Update macOS system CA implementation to match PR #21092 with dynamic loading
This comprehensive update adapts the implementation from PR #21092 to use
dynamic loading with bun.sys.dlopen instead of static linking.

Key changes:
- Complete Security framework function coverage with dynamic loading
- Comprehensive trust evaluation logic matching PR #21092
- CLI flag support: --use-system-ca (primary) + env var fallback
- Advanced certificate validation using SecTrustEvaluateWithError
- Trust settings evaluation across user and admin domains
- SSL policy-specific certificate filtering
- Proper SecItemCopyMatching implementation for keychain access
- Enhanced error handling and warning system

Architecture:
- Zig: Dynamic framework loading + function pointer management
- C++: Trust evaluation logic + certificate processing
- CLI: --use-system-ca flag with precedence over env vars
- Backward compatibility with BUN_USE_SYSTEM_CA environment variable

Benefits over static linking:
- No build system changes required
- Runtime-only dependency on Security framework
- Follows existing Bun patterns (fs_events.zig)
- Graceful degradation if frameworks unavailable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:50:05 +00:00
Claude Bot
448b9c7ff2 Add macOS system CA loading support
This implementation uses Zig's bun.sys.dlopen to dynamically load
the macOS Security framework and CoreFoundation functions, then
implements the CA loading logic in C++ for better integration
with the existing OpenSSL certificate store.

Key features:
- Uses BUN_USE_SYSTEM_CA environment variable to enable feature
- Dynamically loads Security.framework without static linking
- Integrates system CAs with existing bundled and extra CAs
- Follows existing Bun patterns for framework loading
- Platform-specific code only runs on macOS

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:19:27 +00:00
1089 changed files with 37990 additions and 62170 deletions

View File

@@ -127,11 +127,8 @@ const testPlatforms = [
{ os: "linux", arch: "x64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.21", tier: "latest" },
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.21", tier: "latest" },
@@ -569,7 +566,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10,
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
timeout_in_minutes: profile === "asan" ? 45 : 30,
command:
os === "windows"
? `node .\\scripts\\runner.node.mjs ${args.join(" ")}`

View File

@@ -360,8 +360,9 @@ JSC_DEFINE_HOST_FUNCTION(x509CertificateConstructorConstruct, (JSGlobalObject *
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure());
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(
globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure());
scope.release();
}
return JSValue::encode(createX509Certificate(vm, globalObject, structure, arg));

View File

@@ -1,30 +1,28 @@
name: autofix.ci
name: format
permissions:
contents: read
contents: write
on:
workflow_call:
workflow_dispatch:
pull_request:
merge_group:
push:
branches: ["main"]
env:
BUN_VERSION: "1.2.11"
LLVM_VERSION: "19.1.7"
LLVM_VERSION_MAJOR: "19"
jobs:
autofix:
format:
name: Format
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global core.autocrlf true
@@ -46,13 +44,25 @@ jobs:
version: 0.14.0
- name: Zig Format
run: |
bun scripts/zig-remove-unreferenced-top-level-decls.ts src/
zig fmt src
./scripts/sort-imports.ts src
bun scripts/sortImports src
zig fmt src
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "`bun run zig-format`"
- name: Prettier Format
run: |
bun run prettier
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "`bun run prettier`"
- name: Clang Format
run: |
bun run clang-format
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
- name: Commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "`bun run clang-format`"

View File

@@ -1,99 +0,0 @@
name: Update hdrhistogram
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check hdrhistogram version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildHdrHistogram.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildHdrHistogram.cmake"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in BuildHdrHistogram.cmake"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/HdrHistogram/HdrHistogram_c/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/HdrHistogram/HdrHistogram_c/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/HdrHistogram/HdrHistogram_c/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildHdrHistogram.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildHdrHistogram.cmake
commit-message: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-cares-${{ github.run_number }}
body: |
## What does this PR do?
Updates hdrhistogram to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/HdrHistogram/HdrHistogram_c/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-cares.yml)

View File

@@ -1,99 +0,0 @@
name: Update highway
on:
schedule:
- cron: "0 4 * * 0"
workflow_dispatch:
jobs:
check-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Check highway version
id: check-version
run: |
set -euo pipefail
# Extract the commit hash from the line after COMMIT
CURRENT_VERSION=$(awk '/[[:space:]]*COMMIT[[:space:]]*$/{getline; gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0); print}' cmake/targets/BuildHighway.cmake)
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find COMMIT line in BuildHighway.cmake"
exit 1
fi
# Validate that it looks like a git hash
if ! [[ $CURRENT_VERSION =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid git hash format in BuildHighway.cmake"
echo "Found: $CURRENT_VERSION"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
LATEST_RELEASE=$(curl -sL https://api.github.com/repos/google/highway/releases/latest)
if [ -z "$LATEST_RELEASE" ]; then
echo "Error: Failed to fetch latest release from GitHub API"
exit 1
fi
LATEST_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "Error: Could not extract tag name from GitHub API response"
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/google/highway/git/refs/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/google/highway/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
exit 1
fi
if ! [[ $LATEST_SHA =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: Invalid SHA format received from GitHub"
echo "Found: $LATEST_SHA"
echo "Expected: 40 character hexadecimal string"
exit 1
fi
echo "latest=$LATEST_SHA" >> $GITHUB_OUTPUT
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
- name: Update version if needed
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
run: |
set -euo pipefail
# Handle multi-line format where COMMIT and its value are on separate lines
sed -i -E '/[[:space:]]*COMMIT[[:space:]]*$/{n;s/[[:space:]]*([0-9a-f]+)[[:space:]]*$/ ${{ steps.check-version.outputs.latest }}/}' cmake/targets/BuildHighway.cmake
- name: Create Pull Request
if: success() && steps.check-version.outputs.current != steps.check-version.outputs.latest
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
add-paths: |
cmake/targets/BuildHighway.cmake
commit-message: "deps: update highway to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update highway to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-cares-${{ github.run_number }}
body: |
## What does this PR do?
Updates highway to version ${{ steps.check-version.outputs.tag }}
Compare: https://github.com/google/highway/compare/${{ steps.check-version.outputs.current }}...${{ steps.check-version.outputs.latest }}
Auto-updated by [this workflow](https://github.com/oven-sh/bun/actions/workflows/update-cares.yml)

View File

@@ -1,47 +0,0 @@
name: VSCode Extension Publish
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 0.0.25) - Check the marketplace for the latest version"
required: true
type: string
jobs:
publish:
name: "Publish to VS Code Marketplace"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: "1.2.18"
- name: Install dependencies (root)
run: bun install
- name: Install dependencies
run: bun install
working-directory: packages/bun-vscode
- name: Set Version
run: bun pm version ${{ github.event.inputs.version }} --no-git-tag-version --allow-same-version
working-directory: packages/bun-vscode
- name: Build (inspector protocol)
run: bun install && bun run build
working-directory: packages/bun-inspector-protocol
- name: Build (vscode extension)
run: bun run build
working-directory: packages/bun-vscode
- name: Publish
if: success()
run: bunx vsce publish
env:
VSCE_PAT: ${{ secrets.VSCODE_EXTENSION }}
working-directory: packages/bun-vscode/extension

View File

@@ -168,5 +168,5 @@
"WebKit/WebInspectorUI": true,
},
"git.detectSubmodules": false,
// "bun.test.customScript": "./build/debug/bun-debug test"
"bun.test.customScript": "bun-debug test"
}

View File

@@ -4,9 +4,9 @@ This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed
### Build Commands
- **Build debug version**: `bun bd`
- **Build debug version**: `bun bd` or `bun run build:debug`
- Creates a debug build at `./build/debug/bun-debug`
- Compilation takes ~5 minutes. Don't timeout, be patient.
- Compilation takes ~2.5 minutes
- **Run tests with your debug build**: `bun bd test <test-file>`
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
- **Run any command with debug build**: `bun bd <command>`

View File

@@ -160,7 +160,6 @@ In particular, these are:
- `./src/codegen/generate-jssink.ts` -- Generates `build/debug/codegen/JSSink.cpp`, `build/debug/codegen/JSSink.h` which implement various classes for interfacing with `ReadableStream`. This is internally how `FileSink`, `ArrayBufferSink`, `"type": "direct"` streams and other code related to streams works.
- `./src/codegen/generate-classes.ts` -- Generates `build/debug/codegen/ZigGeneratedClasses*`, which generates Zig & C++ bindings for JavaScriptCore classes implemented in Zig. In `**/*.classes.ts` files, we define the interfaces for various classes, methods, prototypes, getters/setters etc which the code generator reads to generate boilerplate code implementing the JavaScript objects in C++ and wiring them up to Zig
- `./src/codegen/cppbind.ts` -- Generates automatic Zig bindings for C++ functions marked with `[[ZIG_EXPORT]]` attributes.
- `./src/codegen/bundle-modules.ts` -- Bundles built-in modules like `node:fs`, `bun:ffi` into files we can include in the final binary. In development, these can be reloaded without rebuilding Zig (you still need to run `bun run build`, but it re-reads the transpiled files from disk afterwards). In release builds, these are embedded into the binary.
- `./src/codegen/bundle-functions.ts` -- Bundles globally-accessible functions implemented in JavaScript/TypeScript like `ReadableStream`, `WritableStream`, and a handful more. These are used similarly to the builtin modules, but the output more closely aligns with what WebKit/Safari does for Safari's built-in functions so that we can copy-paste the implementations from WebKit as a starting point.

2
LATEST
View File

@@ -1 +1 @@
1.2.19
1.2.18

Binary file not shown.

View File

@@ -28,7 +28,9 @@ if (+(existingUsers?.[0]?.count ?? existingUsers?.count) < 100) {
}));
// Insert all users
await sql`INSERT INTO users_bun_bench ${sql(users)}`;
await sql`
INSERT INTO users_bun_bench (first_name, last_name, email, dob) ${sql(users)}
`;
}
const type = isBun ? "Bun.sql" : "postgres";

View File

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

View File

@@ -752,13 +752,6 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
});
}
}
{
const cppImport = b.createModule(.{
.root_source_file = (std.Build.LazyPath{ .cwd_relative = opts.codegen_path }).path(b, "cpp.zig"),
});
mod.addImport("cpp", cppImport);
cppImport.addImport("bun", mod);
}
inline for (.{
.{ .import = "completions-bash", .file = b.path("completions/bun.bash") },
.{ .import = "completions-zsh", .file = b.path("completions/bun.zsh") },

View File

@@ -4,8 +4,6 @@
"": {
"name": "bun",
"devDependencies": {
"@lezer/common": "^1.2.3",
"@lezer/cpp": "^1.1.3",
"esbuild": "^0.21.4",
"mitata": "^0.1.11",
"peechy": "0.4.34",
@@ -89,14 +87,6 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="],
"@lezer/cpp": ["@lezer/cpp@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w=="],
"@lezer/highlight": ["@lezer/highlight@1.2.1", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA=="],
"@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="],
"@types/bun": ["@types/bun@workspace:packages/@types/bun"],
"@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],

View File

@@ -7,6 +7,3 @@
# Instead, we can only scan the test directory for Bun's runtime tests
root = "test"
preload = "./test/preload.ts"
[install]
linker = "isolated"

View File

@@ -95,7 +95,7 @@ if(LINUX)
optionx(ENABLE_VALGRIND BOOL "If Valgrind support should be enabled" DEFAULT OFF)
endif()
if(DEBUG AND ((APPLE AND ARCH STREQUAL "aarch64") OR LINUX))
if(DEBUG AND APPLE AND ARCH STREQUAL "aarch64")
set(DEFAULT_ASAN ON)
else()
set(DEFAULT_ASAN OFF)
@@ -139,10 +139,10 @@ endif()
optionx(REVISION STRING "The git revision of the build" DEFAULT ${DEFAULT_REVISION})
# Used in process.version, process.versions.node, napi, and elsewhere
setx(NODEJS_VERSION "24.3.0")
optionx(NODEJS_VERSION STRING "The version of Node.js to report" DEFAULT "24.3.0")
# Used in process.versions.modules and compared while loading V8 modules
setx(NODEJS_ABI_VERSION "137")
optionx(NODEJS_ABI_VERSION STRING "The ABI version of Node.js to report" DEFAULT "137")
if(APPLE)
set(DEFAULT_STATIC_SQLITE OFF)

View File

@@ -1,3 +1,4 @@
src/bake/bake.bind.ts
src/bake/bake.d.ts
src/bake/bake.private.d.ts
src/bake/bun-framework-react/index.ts

View File

@@ -1,4 +1,4 @@
src/bake.bind.ts
src/bake/bake.bind.ts
src/bake/DevServer.bind.ts
src/bun.js/api/BunObject.bind.ts
src/bun.js/bindgen_test.bind.ts

View File

@@ -8,14 +8,11 @@ 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

@@ -1,16 +1,15 @@
src/allocators.zig
src/allocators/AllocationScope.zig
src/allocators/basic.zig
src/allocators/LinuxMemFdAllocator.zig
src/allocators/MaxHeapAllocator.zig
src/allocators/linux_memfd_allocator.zig
src/allocators/max_heap_allocator.zig
src/allocators/memory_allocator.zig
src/allocators/MemoryReportingAllocator.zig
src/allocators/mimalloc_arena.zig
src/allocators/mimalloc.zig
src/allocators/MimallocArena.zig
src/allocators/NullableAllocator.zig
src/analytics.zig
src/analytics/schema.zig
src/analytics/analytics_schema.zig
src/analytics/analytics_thread.zig
src/api/schema.zig
src/ast.zig
src/ast/Ast.zig
src/ast/ASTMemoryAllocator.zig
src/ast/B.zig
@@ -34,30 +33,20 @@ src/ast/UseDirective.zig
src/async/posix_event_loop.zig
src/async/stub_event_loop.zig
src/async/windows_event_loop.zig
src/bake.zig
src/baby_list.zig
src/bake/bake.zig
src/bake/DevServer.zig
src/bake/DevServer/Assets.zig
src/bake/DevServer/DirectoryWatchStore.zig
src/bake/DevServer/ErrorReportRequest.zig
src/bake/DevServer/HmrSocket.zig
src/bake/DevServer/HotReloadEvent.zig
src/bake/DevServer/IncrementalGraph.zig
src/bake/DevServer/memory_cost.zig
src/bake/DevServer/PackedMap.zig
src/bake/DevServer/RouteBundle.zig
src/bake/DevServer/SerializedFailure.zig
src/bake/DevServer/SourceMapStore.zig
src/bake/DevServer/WatcherAtomics.zig
src/bake/FrameworkRouter.zig
src/bake/production.zig
src/base64/base64.zig
src/bit_set.zig
src/bits.zig
src/boringssl.zig
src/brotli.zig
src/btjs.zig
src/bun.js.zig
src/bun_js.zig
src/bun.js/api.zig
src/bun.js/api/bun/dns.zig
src/bun.js/api/bun/dns_resolver.zig
src/bun.js/api/bun/h2_frame_parser.zig
src/bun.js/api/bun/lshpack.zig
src/bun.js/api/bun/process.zig
@@ -107,7 +96,6 @@ src/bun.js/api/Timer/EventLoopTimer.zig
src/bun.js/api/Timer/ImmediateObject.zig
src/bun.js/api/Timer/TimeoutObject.zig
src/bun.js/api/Timer/TimerObjectInternals.zig
src/bun.js/api/Timer/WTFTimer.zig
src/bun.js/api/TOMLObject.zig
src/bun.js/api/UnsafeObject.zig
src/bun.js/bindgen_test.zig
@@ -248,12 +236,14 @@ src/bun.js/ResolveMessage.zig
src/bun.js/RuntimeTranspilerCache.zig
src/bun.js/SavedSourceMap.zig
src/bun.js/Strong.zig
src/bun.js/system_ca.zig
src/bun.js/test/diff_format.zig
src/bun.js/test/expect.zig
src/bun.js/test/jest.zig
src/bun.js/test/pretty_format.zig
src/bun.js/test/snapshot.zig
src/bun.js/test/test.zig
src/bun.js/unbounded_queue.zig
src/bun.js/uuid.zig
src/bun.js/virtual_machine_exports.zig
src/bun.js/VirtualMachine.zig
@@ -292,6 +282,7 @@ src/bun.js/webcore/streams.zig
src/bun.js/webcore/TextDecoder.zig
src/bun.js/webcore/TextEncoder.zig
src/bun.js/webcore/TextEncoderStreamEncoder.zig
src/bun.js/WTFTimer.zig
src/bun.zig
src/bundler/AstBuilder.zig
src/bundler/bundle_v2.zig
@@ -315,14 +306,12 @@ src/bundler/linker_context/generateCodeForLazyExport.zig
src/bundler/linker_context/generateCompileResultForCssChunk.zig
src/bundler/linker_context/generateCompileResultForHtmlChunk.zig
src/bundler/linker_context/generateCompileResultForJSChunk.zig
src/bundler/linker_context/OutputFileListBuilder.zig
src/bundler/linker_context/postProcessCSSChunk.zig
src/bundler/linker_context/postProcessHTMLChunk.zig
src/bundler/linker_context/postProcessJSChunk.zig
src/bundler/linker_context/prepareCssAstsForChunk.zig
src/bundler/linker_context/renameSymbolsInChunk.zig
src/bundler/linker_context/scanImportsAndExports.zig
src/bundler/linker_context/StaticRouteVisitor.zig
src/bundler/linker_context/writeOutputFilesToDisk.zig
src/bundler/LinkerContext.zig
src/bundler/LinkerGraph.zig
@@ -355,11 +344,9 @@ src/cli/pack_command.zig
src/cli/package_manager_command.zig
src/cli/patch_command.zig
src/cli/patch_commit_command.zig
src/cli/pm_pkg_command.zig
src/cli/pm_trusted_command.zig
src/cli/pm_version_command.zig
src/cli/pm_view_command.zig
src/cli/pm_why_command.zig
src/cli/publish_command.zig
src/cli/remove_command.zig
src/cli/run_command.zig
@@ -368,15 +355,8 @@ src/cli/test_command.zig
src/cli/test/Scanner.zig
src/cli/unlink_command.zig
src/cli/update_command.zig
src/cli/update_interactive_command.zig
src/cli/upgrade_command.zig
src/cli/why_command.zig
src/codegen/process_windows_translate_c.zig
src/collections.zig
src/collections/baby_list.zig
src/collections/bit_set.zig
src/collections/hive_array.zig
src/collections/multi_array_list.zig
src/compile_target.zig
src/comptime_string_map.zig
src/copy_file.zig
@@ -525,19 +505,23 @@ src/env.zig
src/errno/darwin_errno.zig
src/errno/linux_errno.zig
src/errno/windows_errno.zig
src/exact_size_matcher.zig
src/fd.zig
src/feature_flags.zig
src/fmt.zig
src/fs.zig
src/fs/stat_hash.zig
src/futex.zig
src/generated_perf_trace_events.zig
src/generated_versions_list.zig
src/glob.zig
src/glob/GlobWalker.zig
src/glob/match.zig
src/Global.zig
src/grapheme.zig
src/heap_breakdown.zig
src/highway.zig
src/hive_array.zig
src/hmac.zig
src/HTMLScanner.zig
src/http.zig
@@ -555,7 +539,6 @@ src/http/HTTPThread.zig
src/http/InitError.zig
src/http/InternalState.zig
src/http/Method.zig
src/http/mime_type_list_enum.zig
src/http/MimeType.zig
src/http/ProxyTunnel.zig
src/http/SendFile.zig
@@ -581,7 +564,6 @@ src/install/install_binding.zig
src/install/install.zig
src/install/integrity.zig
src/install/isolated_install.zig
src/install/isolated_install/FileCopier.zig
src/install/isolated_install/Hardlinker.zig
src/install/isolated_install/Installer.zig
src/install/isolated_install/Store.zig
@@ -632,10 +614,6 @@ src/install/resolvers/folder_resolver.zig
src/install/versioned_url.zig
src/install/windows-shim/BinLinkingShim.zig
src/install/windows-shim/bun_shim_impl.zig
src/interchange.zig
src/interchange/json.zig
src/interchange/toml.zig
src/interchange/toml/lexer.zig
src/io/heap.zig
src/io/io.zig
src/io/MaxBuf.zig
@@ -644,12 +622,14 @@ src/io/PipeReader.zig
src/io/pipes.zig
src/io/PipeWriter.zig
src/io/source.zig
src/js_ast.zig
src/js_lexer_tables.zig
src/js_lexer.zig
src/js_lexer/identifier.zig
src/js_parser.zig
src/js_printer.zig
src/jsc_stub.zig
src/json_parser.zig
src/libarchive/libarchive-bindings.zig
src/libarchive/libarchive.zig
src/linear_fifo.zig
@@ -661,6 +641,8 @@ src/main_test.zig
src/main_wasm.zig
src/main.zig
src/meta.zig
src/multi_array_list.zig
src/Mutex.zig
src/napi/napi.zig
src/node_fallbacks.zig
src/open.zig
@@ -672,7 +654,6 @@ src/paths.zig
src/paths/EnvPath.zig
src/paths/path_buffer_pool.zig
src/paths/Path.zig
src/pe.zig
src/perf.zig
src/pool.zig
src/Progress.zig
@@ -835,36 +816,30 @@ src/sql/postgres/types/PostgresString.zig
src/sql/postgres/types/Tag.zig
src/StandaloneModuleGraph.zig
src/StaticHashMap.zig
src/string_immutable.zig
src/string_types.zig
src/string.zig
src/string/escapeHTML.zig
src/string/HashedString.zig
src/string/immutable.zig
src/string/immutable/escapeHTML.zig
src/string/immutable/exact_size_matcher.zig
src/string/immutable/grapheme.zig
src/string/immutable/paths.zig
src/string/immutable/unicode.zig
src/string/immutable/visible.zig
src/string/MutableString.zig
src/string/paths.zig
src/string/PathString.zig
src/string/SmolStr.zig
src/string/StringBuilder.zig
src/string/StringJoiner.zig
src/string/unicode.zig
src/string/visible.zig
src/string/WTFStringImpl.zig
src/sync.zig
src/sys_uv.zig
src/sys.zig
src/system_timer.zig
src/test/fixtures.zig
src/test/recover.zig
src/threading.zig
src/threading/channel.zig
src/threading/Condition.zig
src/threading/Futex.zig
src/threading/guarded_value.zig
src/threading/Mutex.zig
src/threading/ThreadPool.zig
src/threading/unbounded_queue.zig
src/threading/WaitGroup.zig
src/thread_pool.zig
src/tmp.zig
src/toml/toml_lexer.zig
src/toml/toml_parser.zig
src/tracy.zig
src/trait.zig
src/transpiler.zig

View File

@@ -255,10 +255,6 @@ set(BUN_ZIG_GENERATED_CLASSES_SCRIPT ${CWD}/src/codegen/generate-classes.ts)
absolute_sources(BUN_ZIG_GENERATED_CLASSES_SOURCES ${CWD}/cmake/sources/ZigGeneratedClassesSources.txt)
# hand written cpp source files. Full list of "source" code (including codegen) is in BUN_CPP_SOURCES
absolute_sources(BUN_CXX_SOURCES ${CWD}/cmake/sources/CxxSources.txt)
absolute_sources(BUN_C_SOURCES ${CWD}/cmake/sources/CSources.txt)
set(BUN_ZIG_GENERATED_CLASSES_OUTPUTS
${CODEGEN_PATH}/ZigGeneratedClasses.h
${CODEGEN_PATH}/ZigGeneratedClasses.cpp
@@ -312,27 +308,6 @@ set(BUN_JAVASCRIPT_OUTPUTS
${CWD}/src/bun.js/bindings/GeneratedJS2Native.zig
)
set(BUN_CPP_OUTPUTS
${CODEGEN_PATH}/cpp.zig
)
register_command(
TARGET
bun-cppbind
COMMENT
"Generating C++ --> Zig bindings"
COMMAND
${BUN_EXECUTABLE}
${CWD}/src/codegen/cppbind.ts
${CWD}/src
${CODEGEN_PATH}
SOURCES
${BUN_JAVASCRIPT_CODEGEN_SOURCES}
${BUN_CXX_SOURCES}
OUTPUTS
${BUN_CPP_OUTPUTS}
)
register_command(
TARGET
bun-js-modules
@@ -562,7 +537,6 @@ set(BUN_ZIG_GENERATED_SOURCES
${BUN_ERROR_CODE_OUTPUTS}
${BUN_ZIG_GENERATED_CLASSES_OUTPUTS}
${BUN_JAVASCRIPT_OUTPUTS}
${BUN_CPP_OUTPUTS}
)
# In debug builds, these are not embedded, but rather referenced at runtime.
@@ -632,7 +606,6 @@ register_command(
TARGETS
clone-zig
clone-zstd
bun-cppbind
SOURCES
${BUN_ZIG_SOURCES}
${BUN_ZIG_GENERATED_SOURCES}
@@ -645,6 +618,10 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "build.zig")
set(BUN_USOCKETS_SOURCE ${CWD}/packages/bun-usockets)
# hand written cpp source files. Full list of "source" code (including codegen) is in BUN_CPP_SOURCES
absolute_sources(BUN_CXX_SOURCES ${CWD}/cmake/sources/CxxSources.txt)
absolute_sources(BUN_C_SOURCES ${CWD}/cmake/sources/CSources.txt)
if(WIN32)
list(APPEND BUN_CXX_SOURCES ${CWD}/src/bun.js/bindings/windows/rescle.cpp)
list(APPEND BUN_CXX_SOURCES ${CWD}/src/bun.js/bindings/windows/rescle-binding.cpp)
@@ -708,7 +685,7 @@ if(WIN32)
${CODEGEN_PATH}/windows-app-info.rc
@ONLY
)
set(WINDOWS_RESOURCES ${CODEGEN_PATH}/windows-app-info.rc ${CWD}/src/bun.exe.manifest)
set(WINDOWS_RESOURCES ${CODEGEN_PATH}/windows-app-info.rc)
endif()
# --- Executable ---
@@ -981,16 +958,6 @@ if(APPLE)
-Wl,-map,${bun}.linker-map
)
if(DEBUG)
target_link_options(${bun} PUBLIC
# Suppress ALL linker warnings on macOS.
# The intent is to only suppress linker alignment warnings.
# As of July 21st, 2025 there doesn't seem to be a more specific suppression just for linker alignment warnings.
# If you find one, please update this to only be for linker alignment.
-Wl,-w
)
endif()
# don't strip in debug, this seems to be needed so that the Zig std library
# `*dbHelper` DWARF symbols (used by LLDB for pretty printing) are in the
# output executable

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
libarchive/libarchive
COMMIT
7118f97c26bf0b2f426728b482f86508efc81d02
898dc8319355b7e985f68a9819f182aaed61b53a
)
register_cmake_command(
@@ -20,14 +20,11 @@ register_cmake_command(
-DENABLE_WERROR=OFF
-DENABLE_BZip2=OFF
-DENABLE_CAT=OFF
-DENABLE_CPIO=OFF
-DENABLE_UNZIP=OFF
-DENABLE_EXPAT=OFF
-DENABLE_ICONV=OFF
-DENABLE_LIBB2=OFF
-DENABLE_LibGCC=OFF
-DENABLE_LIBXML2=OFF
-DENABLE_WIN32_XMLLITE=OFF
-DENABLE_LZ4=OFF
-DENABLE_LZMA=OFF
-DENABLE_LZO=OFF

View File

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

View File

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

View File

@@ -274,23 +274,6 @@ If no connection URL is provided, the system checks for the following individual
| `PGPASSWORD` | - | (empty) | Database password |
| `PGDATABASE` | - | username | Database name |
## Runtime Preconnection
Bun can preconnect to PostgreSQL at startup to improve performance by establishing database connections before your application code runs. This is useful for reducing connection latency on the first database query.
```bash
# Enable PostgreSQL preconnection
bun --sql-preconnect index.js
# Works with DATABASE_URL environment variable
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js
# Can be combined with other runtime flags
bun --sql-preconnect --hot index.js
```
The `--sql-preconnect` flag will automatically establish a PostgreSQL connection using your configured environment variables at startup. If the connection fails, it won't crash your application - the error will be handled gracefully.
## Connection Options
You can configure your database connection manually by passing options to the SQL constructor:

View File

@@ -88,20 +88,6 @@ The order of the `--target` flag does not matter, as long as they're delimited b
On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version.
## Build-time constants
Use the `--define` flag to inject build-time constants into your executable, such as version numbers, build timestamps, or configuration values:
```bash
$ bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli
```
These constants are embedded directly into your compiled binary at build time, providing zero runtime overhead and enabling dead code elimination optimizations.
{% callout type="info" %}
For comprehensive examples and advanced patterns, see the [Build-time constants guide](/guides/runtime/build-time-constants).
{% /callout %}
## Deploying to production
Compiled executables reduce memory usage and improve Bun's start time.

View File

@@ -183,30 +183,6 @@ Bun supports installing dependencies from Git, GitHub, and local or remotely-hos
}
```
## Installation strategies
Bun supports two package installation strategies that determine how dependencies are organized in `node_modules`:
### Hoisted installs (default for single projects)
The traditional npm/Yarn approach that flattens dependencies into a shared `node_modules` directory:
```bash
$ bun install --linker hoisted
```
### Isolated installs
A pnpm-like approach that creates strict dependency isolation to prevent phantom dependencies:
```bash
$ bun install --linker isolated
```
Isolated installs create a central package store in `node_modules/.bun/` with symlinks in the top-level `node_modules`. This ensures packages can only access their declared dependencies.
For complete documentation on isolated installs, refer to [Package manager > Isolated installs](https://bun.com/docs/install/isolated).
## Configuration
The default behavior of `bun install` can be configured in `bunfig.toml`. The default values are shown below.
@@ -237,15 +213,11 @@ dryRun = false
# equivalent to `--concurrent-scripts` flag
concurrentScripts = 16 # (cpu count or GOMAXPROCS) x2
# installation strategy: "hoisted" or "isolated"
# default: "hoisted"
linker = "hoisted"
```
## CI/CD
Use the official [`oven-sh/setup-bun`](https://github.com/oven-sh/setup-bun) action to install `bun` in a GitHub Actions pipeline:
Looking to speed up your CI? Use the official [`oven-sh/setup-bun`](https://github.com/oven-sh/setup-bun) action to install `bun` in a GitHub Actions pipeline.
```yaml#.github/workflows/release.yml
name: bun-types
@@ -264,31 +236,4 @@ jobs:
run: bun run build
```
For CI/CD environments that want to enforce reproducible builds, use `bun ci` to fail the build if the package.json is out of sync with the lockfile:
```bash
$ bun ci
```
This is equivalent to `bun install --frozen-lockfile`. It installs exact versions from `bun.lock` and fails if `package.json` doesn't match the lockfile. To use `bun ci` or `bun install --frozen-lockfile`, you must commit `bun.lock` to version control.
And instead of running `bun install`, run `bun ci`.
```yaml#.github/workflows/release.yml
name: bun-types
jobs:
build:
name: build-app
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun ci
- name: Build app
run: bun run build
```
{% bunCLIUsage command="install" /%}

View File

@@ -8,70 +8,15 @@ To create a tarball of the current workspace:
$ bun pm pack
```
This command creates a `.tgz` file containing all files that would be published to npm, following the same rules as `npm pack`.
Options for the `pack` command:
## Examples
Basic usage:
```bash
$ bun pm pack
# Creates my-package-1.0.0.tgz in current directory
```
Quiet mode for scripting:
```bash
$ TARBALL=$(bun pm pack --quiet)
$ echo "Created: $TARBALL"
# Output: Created: my-package-1.0.0.tgz
```
Custom destination:
```bash
$ bun pm pack --destination ./dist
# Saves tarball in ./dist/ directory
```
## Options
- `--dry-run`: Perform all tasks except writing the tarball to disk. Shows what would be included.
- `--destination <dir>`: Specify the directory where the tarball will be saved.
- `--filename <name>`: Specify an exact file name for the tarball to be saved at.
- `--dry-run`: Perform all tasks except writing the tarball to disk.
- `--destination`: Specify the directory where the tarball will be saved.
- `--filename`: Specify an exact file name for the tarball to be saved at.
- `--ignore-scripts`: Skip running pre/postpack and prepare scripts.
- `--gzip-level <0-9>`: Set a custom compression level for gzip, ranging from 0 to 9 (default is 9).
- `--quiet`: Only output the tarball filename, suppressing verbose output. Ideal for scripts and automation.
- `--gzip-level`: Set a custom compression level for gzip, ranging from 0 to 9 (default is 9).
> **Note:** `--filename` and `--destination` cannot be used at the same time.
## Output Modes
**Default output:**
```bash
$ bun pm pack
bun pack v1.2.19
packed 131B package.json
packed 40B index.js
my-package-1.0.0.tgz
Total files: 2
Shasum: f2451d6eb1e818f500a791d9aace80b394258a90
Unpacked size: 171B
Packed size: 249B
```
**Quiet output:**
```bash
$ bun pm pack --quiet
my-package-1.0.0.tgz
```
The `--quiet` flag is particularly useful for automation workflows where you need to capture the generated tarball filename for further processing.
> Note `--filename` and `--destination` cannot be used at the same time
## bin
@@ -248,38 +193,3 @@ v1.0.1
```
Supports `patch`, `minor`, `major`, `premajor`, `preminor`, `prepatch`, `prerelease`, `from-git`, or specific versions like `1.2.3`. By default creates git commit and tag unless `--no-git-tag-version` was used to skip.
## pkg
Manage `package.json` data with get, set, delete, and fix operations.
All commands support dot and bracket notation:
```bash
scripts.build # dot notation
contributors[0] # array access
workspaces.0 # dot with numeric index
scripts[test:watch] # bracket for special chars
```
Examples:
```bash
# set
$ bun pm pkg get name # single property
$ bun pm pkg get name version # multiple properties
$ bun pm pkg get # entire package.json
$ bun pm pkg get scripts.build # nested property
# set
$ bun pm pkg set name="my-package" # simple property
$ bun pm pkg set scripts.test="jest" version=2.0.0 # multiple properties
$ bun pm pkg set {"private":"true"} --json # JSON values with --json flag
# delete
$ bun pm pkg delete description # single property
$ bun pm pkg delete scripts.test contributors[0] # multiple/nested
# fix
$ bun pm pkg fix # auto-fix common issues
```

View File

@@ -248,33 +248,4 @@ $ bun test foo
Any test file in the directory with an _absolute path_ that contains one of the targets will run. Glob patterns are not yet supported. -->
## AI Agent Integration
When using Bun's test runner with AI coding assistants, you can enable quieter output to improve readability and reduce context noise. This feature minimizes test output verbosity while preserving essential failure information.
### Environment Variables
Set any of the following environment variables to enable AI-friendly output:
- `CLAUDECODE=1` - For Claude Code
- `REPL_ID=1` - For Replit
- `AGENT=1` - Generic AI agent flag
### Behavior
When an AI agent environment is detected:
- Only test failures are displayed in detail
- Passing, skipped, and todo test indicators are hidden
- Summary statistics remain intact
```bash
# Example: Enable quiet output for Claude Code
$ CLAUDECODE=1 bun test
# Still shows failures and summary, but hides verbose passing test output
```
This feature is particularly useful in AI-assisted development workflows where reduced output verbosity improves context efficiency while maintaining visibility into test failures.
{% bunCLIUsage command="test" /%}

View File

@@ -10,86 +10,6 @@ To update a specific dependency to the latest version:
$ bun update [package]
```
## `--interactive`
For a more controlled update experience, use the `--interactive` flag to select which packages to update:
```sh
$ bun update --interactive
$ bun update -i
```
This launches an interactive terminal interface that shows all outdated packages with their current and target versions. You can then select which packages to update.
### Interactive Interface
The interface displays packages grouped by dependency type:
```
? Select packages to update - Space to toggle, Enter to confirm, a to select all, n to select none, i to invert, l to toggle latest
dependencies Current Target Latest
□ react 17.0.2 18.2.0 18.3.1
□ lodash 4.17.20 4.17.21 4.17.21
devDependencies Current Target Latest
□ typescript 4.8.0 5.0.0 5.3.3
□ @types/node 16.11.7 18.0.0 20.11.5
optionalDependencies Current Target Latest
□ some-optional-package 1.0.0 1.1.0 1.2.0
```
**Sections:**
- Packages are grouped under section headers: `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`
- Each section shows column headers aligned with the package data
**Columns:**
- **Package**: Package name (may have suffix like ` dev`, ` peer`, ` optional` for clarity)
- **Current**: Currently installed version
- **Target**: Version that would be installed (respects semver constraints)
- **Latest**: Latest available version
### Keyboard Controls
**Selection:**
- **Space**: Toggle package selection
- **Enter**: Confirm selections and update
- **a/A**: Select all packages
- **n/N**: Select none
- **i/I**: Invert selection
**Navigation:**
- **↑/↓ Arrow keys** or **j/k**: Move cursor
- **l/L**: Toggle between target and latest version for current package
**Exit:**
- **Ctrl+C** or **Ctrl+D**: Cancel without updating
### Visual Indicators
- **☑** Selected packages (will be updated)
- **□** Unselected packages
- **>** Current cursor position
- **Colors**: Red (major), yellow (minor), green (patch) version changes
- **Underlined**: Currently selected update target
### Package Grouping
Packages are organized in sections by dependency type:
- **dependencies** - Regular runtime dependencies
- **devDependencies** - Development dependencies
- **peerDependencies** - Peer dependencies
- **optionalDependencies** - Optional dependencies
Within each section, individual packages may have additional suffixes (` dev`, ` peer`, ` optional`) for extra clarity.
## `--latest`
By default, `bun update` will update to the latest version of a dependency that satisfies the version range specified in your `package.json`.
@@ -100,8 +20,6 @@ To update to the latest version, regardless of if it's compatible with the curre
$ bun update --latest
```
In interactive mode, you can toggle individual packages between their target version (respecting semver) and latest version using the **l** key.
For example, with the following `package.json`:
```json

View File

@@ -1,67 +0,0 @@
The `bun why` command explains why a package is installed in your project by showing the dependency chain that led to its installation.
## Usage
```bash
$ bun why <package>
```
## Arguments
- `<package>`: The name of the package to explain. Supports glob patterns like `@org/*` or `*-lodash`.
## Options
- `--top`: Show only the top-level dependencies instead of the complete dependency tree.
- `--depth <number>`: Maximum depth of the dependency tree to display.
## Examples
Check why a specific package is installed:
```bash
$ bun why react
react@18.2.0
└─ my-app@1.0.0 (requires ^18.0.0)
```
Check why all packages with a specific pattern are installed:
```bash
$ bun why "@types/*"
@types/react@18.2.15
└─ dev my-app@1.0.0 (requires ^18.0.0)
@types/react-dom@18.2.7
└─ dev my-app@1.0.0 (requires ^18.0.0)
```
Show only top-level dependencies:
```bash
$ bun why express --top
express@4.18.2
└─ my-app@1.0.0 (requires ^4.18.2)
```
Limit the dependency tree depth:
```bash
$ bun why express --depth 2
express@4.18.2
└─ express-pollyfill@1.20.1 (requires ^4.18.2)
└─ body-parser@1.20.1 (requires ^1.20.1)
└─ accepts@1.3.8 (requires ^1.3.8)
└─ (deeper dependencies hidden)
```
## Understanding the Output
The output shows:
- The package name and version being queried
- The dependency chain that led to its installation
- The type of dependency (dev, peer, optional, or production)
- The version requirement specified in each package's dependencies
For nested dependencies, the command shows the complete dependency tree by default, with indentation indicating the relationship hierarchy.

View File

@@ -40,7 +40,6 @@ Open `prisma/schema.prisma` and add a simple `User` model.
```prisma-diff#prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../generated/prisma"
}
datasource db {
@@ -79,7 +78,7 @@ migrations/
Your database is now in sync with your schema.
✔ Generated Prisma Client (v6.11.1) to ./generated/prisma in 41ms
✔ Generated Prisma Client (v5.3.1) to ./node_modules/@prisma/client in 41ms
```
---

View File

@@ -1,293 +0,0 @@
---
name: Build-time constants with --define
---
The `--define` flag can be used with `bun build` and `bun build --compile` to inject build-time constants into your application. This is especially useful for embedding metadata like build versions, timestamps, or configuration flags directly into your compiled executables.
```sh
$ bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/index.ts --outfile myapp
```
---
## Why use build-time constants?
Build-time constants are embedded directly into your compiled code, making them:
- **Zero runtime overhead** - No environment variable lookups or file reads
- **Immutable** - Values are baked into the binary at compile time
- **Optimizable** - Dead code elimination can remove unused branches
- **Secure** - No external dependencies or configuration files to manage
This is similar to `gcc -D` or `#define` in C/C++, but for JavaScript/TypeScript.
---
## Basic usage
### With `bun build`
```sh
# Bundle with build-time constants
$ bun build --define BUILD_VERSION='"1.0.0"' --define NODE_ENV='"production"' src/index.ts --outdir ./dist
```
### With `bun build --compile`
```sh
# Compile to executable with build-time constants
$ bun build --compile --define BUILD_VERSION='"1.0.0"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli
```
### JavaScript API
```ts
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
define: {
BUILD_VERSION: '"1.0.0"',
BUILD_TIME: '"2024-01-15T10:30:00Z"',
DEBUG: "false",
},
});
```
---
## Common use cases
### Version information
Embed version and build metadata directly into your executable:
{% codetabs %}
```ts#src/version.ts
// These constants are replaced at build time
declare const BUILD_VERSION: string;
declare const BUILD_TIME: string;
declare const GIT_COMMIT: string;
export function getVersion() {
return {
version: BUILD_VERSION,
buildTime: BUILD_TIME,
commit: GIT_COMMIT,
};
}
```
```sh#Build command
$ bun build --compile \
--define BUILD_VERSION='"1.2.3"' \
--define BUILD_TIME='"2024-01-15T10:30:00Z"' \
--define GIT_COMMIT='"abc123"' \
src/cli.ts --outfile mycli
```
{% /codetabs %}
### Feature flags
Use build-time constants to enable/disable features:
```ts
// Replaced at build time
declare const ENABLE_ANALYTICS: boolean;
declare const ENABLE_DEBUG: boolean;
function trackEvent(event: string) {
if (ENABLE_ANALYTICS) {
// This entire block is removed if ENABLE_ANALYTICS is false
console.log("Tracking:", event);
}
}
if (ENABLE_DEBUG) {
console.log("Debug mode enabled");
}
```
```sh
# Production build - analytics enabled, debug disabled
$ bun build --compile --define ENABLE_ANALYTICS=true --define ENABLE_DEBUG=false src/app.ts --outfile app-prod
# Development build - both enabled
$ bun build --compile --define ENABLE_ANALYTICS=false --define ENABLE_DEBUG=true src/app.ts --outfile app-dev
```
### Configuration
Replace configuration objects at build time:
```ts
declare const CONFIG: {
apiUrl: string;
timeout: number;
retries: number;
};
// CONFIG is replaced with the actual object at build time
const response = await fetch(CONFIG.apiUrl, {
timeout: CONFIG.timeout,
});
```
```sh
$ bun build --compile --define 'CONFIG={"apiUrl":"https://api.example.com","timeout":5000,"retries":3}' src/app.ts --outfile app
```
---
## Advanced patterns
### Environment-specific builds
Create different executables for different environments:
```json
{
"scripts": {
"build:dev": "bun build --compile --define NODE_ENV='\"development\"' --define API_URL='\"http://localhost:3000\"' src/app.ts --outfile app-dev",
"build:staging": "bun build --compile --define NODE_ENV='\"staging\"' --define API_URL='\"https://staging.example.com\"' src/app.ts --outfile app-staging",
"build:prod": "bun build --compile --define NODE_ENV='\"production\"' --define API_URL='\"https://api.example.com\"' src/app.ts --outfile app-prod"
}
}
```
### Using shell commands for dynamic values
Generate build-time constants from shell commands:
```sh
# Use git to get current commit and timestamp
$ bun build --compile \
--define BUILD_VERSION="\"$(git describe --tags --always)\"" \
--define BUILD_TIME="\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" \
--define GIT_COMMIT="\"$(git rev-parse HEAD)\"" \
src/cli.ts --outfile mycli
```
### Build automation script
Create a build script that automatically injects build metadata:
```ts
// build.ts
import { $ } from "bun";
const version = await $`git describe --tags --always`.text();
const buildTime = new Date().toISOString();
const gitCommit = await $`git rev-parse HEAD`.text();
await Bun.build({
entrypoints: ["./src/cli.ts"],
outdir: "./dist",
define: {
BUILD_VERSION: JSON.stringify(version.trim()),
BUILD_TIME: JSON.stringify(buildTime),
GIT_COMMIT: JSON.stringify(gitCommit.trim()),
},
});
console.log(`Built with version ${version.trim()}`);
```
---
## Important considerations
### Value format
Values must be valid JSON that will be parsed and inlined as JavaScript expressions:
```sh
# ✅ Strings must be JSON-quoted
--define VERSION='"1.0.0"'
# ✅ Numbers are JSON literals
--define PORT=3000
# ✅ Booleans are JSON literals
--define DEBUG=true
# ✅ Objects and arrays (use single quotes to wrap the JSON)
--define 'CONFIG={"host":"localhost","port":3000}'
# ✅ Arrays work too
--define 'FEATURES=["auth","billing","analytics"]'
# ❌ This won't work - missing quotes around string
--define VERSION=1.0.0
```
### Property keys
You can use property access patterns as keys, not just simple identifiers:
```sh
# ✅ Replace process.env.NODE_ENV with "production"
--define 'process.env.NODE_ENV="production"'
# ✅ Replace process.env.API_KEY with the actual key
--define 'process.env.API_KEY="abc123"'
# ✅ Replace nested properties
--define 'window.myApp.version="1.0.0"'
# ✅ Replace array access
--define 'process.argv[2]="--production"'
```
This is particularly useful for environment variables:
```ts
// Before compilation
if (process.env.NODE_ENV === "production") {
console.log("Production mode");
}
// After compilation with --define 'process.env.NODE_ENV="production"'
if ("production" === "production") {
console.log("Production mode");
}
// After optimization
console.log("Production mode");
```
### TypeScript declarations
For TypeScript projects, declare your constants to avoid type errors:
```ts
// types/build-constants.d.ts
declare const BUILD_VERSION: string;
declare const BUILD_TIME: string;
declare const NODE_ENV: "development" | "staging" | "production";
declare const DEBUG: boolean;
```
### Cross-platform compatibility
When building for multiple platforms, constants work the same way:
```sh
# Linux
$ bun build --compile --target=bun-linux-x64 --define PLATFORM='"linux"' src/app.ts --outfile app-linux
# macOS
$ bun build --compile --target=bun-darwin-x64 --define PLATFORM='"darwin"' src/app.ts --outfile app-macos
# Windows
$ bun build --compile --target=bun-windows-x64 --define PLATFORM='"windows"' src/app.ts --outfile app-windows.exe
```
---
## Related
- [Define constants at runtime](/guides/runtime/define-constant) - Using `--define` with `bun run`
- [Building executables](/bundler/executables) - Complete guide to `bun build --compile`
- [Bundler API](/bundler) - Full bundler documentation including `define` option

View File

@@ -52,8 +52,6 @@ In your root-level `package.json`, add a `catalog` or `catalogs` field within th
}
```
If you put `catalog` or `catalogs` at the top level of the `package.json` file, that will work too.
### 2. Reference Catalog Versions in Workspace Packages
In your workspace packages, use the `catalog:` protocol to reference versions:

View File

@@ -81,14 +81,6 @@ $ bun install --verbose # debug logging
$ bun install --silent # no logging
```
To use isolated installs instead of the default hoisted strategy:
```bash
$ bun install --linker isolated
```
Isolated installs create strict dependency isolation similar to pnpm, preventing phantom dependencies and ensuring more deterministic builds. For complete documentation, see [Isolated installs](https://bun.com/docs/install/isolated).
{% details summary="Configuring behavior" %}
The default behavior of `bun install` can be configured in `bunfig.toml`:
@@ -118,10 +110,6 @@ dryRun = false
# equivalent to `--concurrent-scripts` flag
concurrentScripts = 16 # (cpu count or GOMAXPROCS) x2
# installation strategy: "hoisted" or "isolated"
# default: "hoisted"
linker = "hoisted"
```
{% /details %}

View File

@@ -1,195 +0,0 @@
Bun provides an alternative package installation strategy called **isolated installs** that creates strict dependency isolation similar to pnpm's approach. This mode prevents phantom dependencies and ensures reproducible, deterministic builds.
## What are isolated installs?
Isolated installs create a non-hoisted dependency structure where packages can only access their explicitly declared dependencies. This differs from the traditional "hoisted" installation strategy used by npm and Yarn, where dependencies are flattened into a shared `node_modules` directory.
### Key benefits
- **Prevents phantom dependencies** — Packages cannot accidentally import dependencies they haven't declared
- **Deterministic resolution** — Same dependency tree regardless of what else is installed
- **Better for monorepos** — Workspace isolation prevents cross-contamination between packages
- **Reproducible builds** — More predictable resolution behavior across environments
## Using isolated installs
### Command line
Use the `--linker` flag to specify the installation strategy:
```bash
# Use isolated installs
$ bun install --linker isolated
# Use traditional hoisted installs
$ bun install --linker hoisted
```
### Configuration file
Set the default linker strategy in your `bunfig.toml`:
```toml
[install]
linker = "isolated"
```
### Default behavior
By default, Bun uses the **hoisted** installation strategy for all projects. To use isolated installs, you must explicitly specify the `--linker isolated` flag or set it in your configuration file.
## How isolated installs work
### Directory structure
Instead of hoisting dependencies, isolated installs create a two-tier structure:
```
node_modules/
├── .bun/ # Central package store
│ ├── package@1.0.0/ # Versioned package installations
│ │ └── node_modules/
│ │ └── package/ # Actual package files
│ ├── @scope+package@2.1.0/ # Scoped packages (+ replaces /)
│ │ └── node_modules/
│ │ └── @scope/
│ │ └── package/
│ └── ...
└── package-name -> .bun/package@1.0.0/node_modules/package # Symlinks
```
### Resolution algorithm
1. **Central store** — All packages are installed in `node_modules/.bun/package@version/` directories
2. **Symlinks** — Top-level `node_modules` contains symlinks pointing to the central store
3. **Peer resolution** — Complex peer dependencies create specialized directory names
4. **Deduplication** — Packages with identical package IDs and peer dependency sets are shared
### Workspace handling
In monorepos, workspace dependencies are handled specially:
- **Workspace packages** — Symlinked directly to their source directories, not the store
- **Workspace dependencies** — Can access other workspace packages in the monorepo
- **External dependencies** — Installed in the isolated store with proper isolation
## Comparison with hoisted installs
| Aspect | Hoisted (npm/Yarn) | Isolated (pnpm-like) |
| ------------------------- | ------------------------------------------ | --------------------------------------- |
| **Dependency access** | Packages can access any hoisted dependency | Packages only see declared dependencies |
| **Phantom dependencies** | ❌ Possible | ✅ Prevented |
| **Disk usage** | ✅ Lower (shared installs) | ✅ Similar (uses symlinks) |
| **Determinism** | ❌ Less deterministic | ✅ More deterministic |
| **Node.js compatibility** | ✅ Standard behavior | ✅ Compatible via symlinks |
| **Best for** | Single projects, legacy code | Monorepos, strict dependency management |
## Advanced features
### Peer dependency handling
Isolated installs handle peer dependencies through sophisticated resolution:
```bash
# Package with peer dependencies creates specialized paths
node_modules/.bun/package@1.0.0_react@18.2.0/
```
The directory name encodes both the package version and its peer dependency versions, ensuring each unique combination gets its own installation.
### Backend strategies
Bun uses different file operation strategies for performance:
- **Clonefile** (macOS) — Copy-on-write filesystem clones for maximum efficiency
- **Hardlink** (Linux/Windows) — Hardlinks to save disk space
- **Copyfile** (fallback) — Full file copies when other methods aren't available
### Debugging isolated installs
Enable verbose logging to understand the installation process:
```bash
$ bun install --linker isolated --verbose
```
This shows:
- Store entry creation
- Symlink operations
- Peer dependency resolution
- Deduplication decisions
## Troubleshooting
### Compatibility issues
Some packages may not work correctly with isolated installs due to:
- **Hardcoded paths** — Packages that assume a flat `node_modules` structure
- **Dynamic imports** — Runtime imports that don't follow Node.js resolution
- **Build tools** — Tools that scan `node_modules` directly
If you encounter issues, you can:
1. **Switch to hoisted mode** for specific projects:
```bash
$ bun install --linker hoisted
```
2. **Report compatibility issues** to help improve isolated install support
### Performance considerations
- **Install time** — May be slightly slower due to symlink operations
- **Disk usage** — Similar to hoisted (uses symlinks, not file copies)
- **Memory usage** — Higher during install due to complex peer resolution
## Migration guide
### From npm/Yarn
```bash
# Remove existing node_modules and lockfiles
$ rm -rf node_modules package-lock.json yarn.lock
# Install with isolated linker
$ bun install --linker isolated
```
### From pnpm
Isolated installs are conceptually similar to pnpm, so migration should be straightforward:
```bash
# Remove pnpm files
$ rm -rf node_modules pnpm-lock.yaml
# Install with Bun's isolated linker
$ bun install --linker isolated
```
The main difference is that Bun uses symlinks in `node_modules` while pnpm uses a global store with symlinks.
## When to use isolated installs
**Use isolated installs when:**
- Working in monorepos with multiple packages
- Strict dependency management is required
- Preventing phantom dependencies is important
- Building libraries that need deterministic dependencies
**Use hoisted installs when:**
- Working with legacy code that assumes flat `node_modules`
- Compatibility with existing build tools is required
- Working in environments where symlinks aren't well supported
- You prefer the simpler traditional npm behavior
## Related documentation
- [Package manager > Workspaces](https://bun.com/docs/install/workspaces) — Monorepo workspace management
- [Package manager > Lockfile](https://bun.com/docs/install/lockfile) — Understanding Bun's lockfile format
- [CLI > install](https://bun.com/docs/cli/install) — Complete `bun install` command reference

View File

@@ -176,16 +176,10 @@ export default {
page("cli/pm", "`bun pm`", {
description: "Utilities relating to package management with Bun.",
}),
page("cli/why", "`bun why`", {
description: "Explains why a package is installed in your project.",
}),
page("install/cache", "Global cache", {
description:
"Bun's package manager installs all packages into a shared global cache to avoid redundant re-downloads.",
}),
page("install/isolated", "Isolated installs", {
description: "Create strict dependency isolation, preventing phantom dependencies.",
}),
page("install/workspaces", "Workspaces", {
description: "Bun's package manager supports workspaces and monorepo development workflows.",
}),

View File

@@ -20,7 +20,7 @@ this one:
Given a file implementing a simple function, such as `add`
```zig#src/bun.js/math.zig
pub fn add(global: *jsc.JSGlobalObject, a: i32, b: i32) !i32 {
pub fn add(global: *JSC.JSGlobalObject, a: i32, b: i32) !i32 {
return std.math.add(i32, a, b) catch {
// Binding functions can return `error.OutOfMemory` and `error.JSError`.
// Others like `error.Overflow` from `std.math.add` must be converted.
@@ -33,7 +33,7 @@ const gen = bun.gen.math; // "math" being this file's basename
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
const JSC = bun.JSC;
```
Then describe the API schema using a `.bind.ts` function. The binding file goes next to the Zig file.

View File

@@ -195,24 +195,6 @@ Whether to skip test files when computing coverage statistics. Default `false`.
coverageSkipTestFiles = false
```
### `test.coveragePathIgnorePatterns`
Exclude specific files or file patterns from coverage reports using glob patterns. Can be a single string pattern or an array of patterns.
```toml
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"
# Multiple patterns
coveragePathIgnorePatterns = [
"**/*.spec.ts",
"**/*.test.ts",
"src/utils/**",
"*.config.js"
]
```
### `test.coverageReporter`
By default, coverage reports will be printed to the console. For persistent code coverage reports in CI environments and for other tools use `lcov`.

View File

@@ -71,26 +71,6 @@ coverageThreshold = { lines = 0.9, functions = 0.8, statements = 0.85 }
Setting any of these enables `fail_on_low_coverage`, causing the test run to fail if coverage is below the threshold.
#### coveragePathIgnorePatterns
Exclude specific files or file patterns from coverage reports using glob patterns:
```toml
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"
# Multiple patterns
coveragePathIgnorePatterns = [
"**/*.spec.ts",
"**/*.test.ts",
"src/utils/**",
"*.config.js"
]
```
Files matching any of these patterns will be excluded from coverage calculation and reporting. See the [coverage documentation](./coverage.md) for more details and examples.
#### coverageIgnoreSourcemaps
Internally, Bun transpiles every file. That means code coverage must also go through sourcemaps before they can be reported. We expose this as a flag to allow you to opt out of this behavior, but it will be confusing because during the transpilation process, Bun may move code around and change variable names. This option is mostly useful for debugging coverage issues.

View File

@@ -57,18 +57,7 @@ coverageThreshold = { lines = 0.9, functions = 0.9, statements = 0.9 }
Setting any of these thresholds enables `fail_on_low_coverage`, causing the test run to fail if coverage is below the threshold.
### Sourcemaps
Internally, Bun transpiles all files by default, so Bun automatically generates an internal [source map](https://web.dev/source-maps/) that maps lines of your original source code onto Bun's internal representation. If for any reason you want to disable this, set `test.coverageIgnoreSourcemaps` to `true`; this will rarely be desirable outside of advanced use cases.
```toml
[test]
coverageIgnoreSourcemaps = true # default false
```
### Exclude files from coverage
#### Skip test files
### Exclude test files from coverage
By default, test files themselves are included in coverage reports. You can exclude them with:
@@ -79,33 +68,15 @@ coverageSkipTestFiles = true # default false
This will exclude files matching test patterns (e.g., _.test.ts, _\_spec.js) from the coverage report.
#### Ignore specific paths and patterns
### Sourcemaps
You can exclude specific files or file patterns from coverage reports using `coveragePathIgnorePatterns`:
Internally, Bun transpiles all files by default, so Bun automatically generates an internal [source map](https://web.dev/source-maps/) that maps lines of your original source code onto Bun's internal representation. If for any reason you want to disable this, set `test.coverageIgnoreSourcemaps` to `true`; this will rarely be desirable outside of advanced use cases.
```toml
[test]
# Single pattern
coveragePathIgnorePatterns = "**/*.spec.ts"
# Multiple patterns
coveragePathIgnorePatterns = [
"**/*.spec.ts",
"**/*.test.ts",
"src/utils/**",
"*.config.js"
]
coverageIgnoreSourcemaps = true # default false
```
This option accepts glob patterns and works similarly to Jest's `collectCoverageFrom` ignore patterns. Files matching any of these patterns will be excluded from coverage calculation and reporting in both text and LCOV outputs.
Common use cases:
- Exclude utility files: `"src/utils/**"`
- Exclude configuration files: `"*.config.js"`
- Exclude specific test patterns: `"**/*.spec.ts"`
- Exclude build artifacts: `"dist/**"`
### Coverage defaults
By default, coverage reports:
@@ -113,7 +84,6 @@ By default, coverage reports:
1. Exclude `node_modules` directories
2. Exclude files loaded via non-JS/TS loaders (e.g., .css, .txt) unless a custom JS loader is specified
3. Include test files themselves (can be disabled with `coverageSkipTestFiles = true` as shown above)
4. Can exclude additional files with `coveragePathIgnorePatterns` as shown above
### Coverage reporters

View File

@@ -2,15 +2,16 @@ const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const Features = bun.analytics.Features;
const C = bun.C;
const Features = @import("../src/analytics/analytics_thread.zig").Features;
// zig run --main-pkg-path ../ ./features.zig
pub fn main() anyerror!void {

View File

@@ -1,13 +1,14 @@
const std = @import("std");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const clap = @import("../src/deps/zig-clap/clap.zig");
const URL = @import("../src/url.zig").URL;
@@ -195,7 +196,7 @@ pub fn main() anyerror!void {
response_body: MutableString = undefined,
context: HTTP.HTTPChannelContext = undefined,
};
const Batch = bun.ThreadPool.Batch;
const Batch = @import("../src/thread_pool.zig").Batch;
var groups = try default_allocator.alloc(Group, args.count);
var repeat_i: usize = 0;
while (repeat_i < args.repeat + 1) : (repeat_i += 1) {

View File

@@ -1,14 +1,15 @@
// most of this file is copy pasted from other files in misctools
const std = @import("std");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const clap = @import("../src/deps/zig-clap/clap.zig");
const URL = @import("../src/url.zig").URL;

View File

@@ -2,14 +2,15 @@ const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
// zig build-exe -Doptimize=ReleaseFast --main-pkg-path ../ ./readlink-getfd.zig
pub fn main() anyerror!void {

View File

@@ -2,14 +2,15 @@ const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
// zig build-exe -Doptimize=ReleaseFast --main-pkg-path ../ ./readlink-getfd.zig
pub fn main() anyerror!void {

View File

@@ -2,14 +2,15 @@ const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const string = []const u8;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = [:0]const u8;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const Archive = @import("../src/libarchive/libarchive.zig").Archive;
const Zlib = @import("../src/zlib.zig");

View File

@@ -1,14 +1,12 @@
{
"private": true,
"name": "bun",
"version": "1.2.20",
"version": "1.2.19",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"
],
"devDependencies": {
"@lezer/common": "^1.2.3",
"@lezer/cpp": "^1.1.3",
"esbuild": "^0.21.4",
"mitata": "^0.1.11",
"peechy": "0.4.34",
@@ -30,8 +28,8 @@
"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/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:debug": "bun scripts/glob-sources.mjs > /dev/null && bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug",
"build:debug:asan": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON -B build/debug-asan",
"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",
"build:assert": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_ASSERTIONS=ON -DENABLE_LOGS=ON -B build/release-assert",

View File

@@ -2512,19 +2512,6 @@ declare module "bun" {
* This defaults to `true`.
*/
throw?: boolean;
/**
* Custom tsconfig.json file path to use for path resolution.
* Equivalent to `--tsconfig-override` in the CLI.
* @example
* ```ts
* await Bun.build({
* entrypoints: ['./src/index.ts'],
* tsconfig: './custom-tsconfig.json'
* });
* ```
*/
tsconfig?: string;
}
/**

View File

@@ -8,6 +8,97 @@
static const int root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]);
extern "C" void BUN__warn__extra_ca_load_failed(const char* filename, const char* error_msg);
extern "C" void BUN__warn__system_ca_load_failed(const char* error_msg);
// macOS Security framework types and constants
#ifdef __APPLE__
typedef long CFIndex;
typedef unsigned char Boolean;
typedef int OSStatus;
typedef void* CFTypeRef;
typedef void* CFArrayRef;
typedef void* CFDataRef;
typedef void* CFStringRef;
typedef void* CFDictionaryRef;
typedef void* CFErrorRef;
typedef void* CFAllocatorRef;
typedef void* SecCertificateRef;
typedef void* SecTrustRef;
typedef void* SecPolicyRef;
// Trust settings domains
enum {
kSecTrustSettingsDomainUser = 0,
kSecTrustSettingsDomainAdmin = 1,
kSecTrustSettingsDomainSystem = 2
};
// Trust results
enum {
kSecTrustSettingsResultInvalid = 0,
kSecTrustSettingsResultTrustRoot = 1,
kSecTrustSettingsResultTrustAsRoot = 2,
kSecTrustSettingsResultDeny = 3,
kSecTrustSettingsResultUnspecified = 4
};
// CFNumber types
enum {
kCFNumberSInt32Type = 3
};
// CFString encoding
enum {
kCFStringEncodingUTF8 = 0x08000100
};
// Function pointers structure - matches Zig MacOSCAFunctions
struct MacOSCAFunctions {
// Security framework functions
OSStatus (*SecTrustCopyAnchorCertificates)(CFArrayRef* anchor_certs);
CFDataRef (*SecCertificateCopyData)(SecCertificateRef cert_ref);
OSStatus (*SecItemCopyMatching)(CFDictionaryRef query, CFTypeRef* result);
OSStatus (*SecTrustSettingsCopyTrustSettings)(SecCertificateRef cert_ref, int domain);
SecPolicyRef (*SecPolicyCreateSSL)(Boolean server, CFStringRef hostname);
OSStatus (*SecTrustCreateWithCertificates)(CFTypeRef certificates, CFTypeRef policies, SecTrustRef* trust);
Boolean (*SecTrustEvaluateWithError)(SecTrustRef trust, CFErrorRef* error);
CFDictionaryRef (*SecPolicyCopyProperties)(SecPolicyRef policy);
// CoreFoundation functions
CFIndex (*CFArrayGetCount)(CFArrayRef array);
CFTypeRef (*CFArrayGetValueAtIndex)(CFArrayRef array, CFIndex index);
const unsigned char* (*CFDataGetBytePtr)(CFDataRef data);
CFIndex (*CFDataGetLength)(CFDataRef data);
void (*CFRelease)(CFTypeRef ref);
CFDictionaryRef (*CFDictionaryCreate)(CFAllocatorRef allocator, CFTypeRef* keys, CFTypeRef* values, CFIndex num_values, void* key_callbacks, void* value_callbacks);
CFStringRef (*CFStringCreateWithCString)(CFAllocatorRef allocator, const char* c_str, unsigned int encoding);
CFArrayRef (*CFArrayCreate)(CFAllocatorRef allocator, CFTypeRef* values, CFIndex num_values, void* callbacks);
CFTypeRef (*CFDictionaryGetValue)(CFDictionaryRef dictionary, CFTypeRef key);
unsigned long (*CFGetTypeID)(CFTypeRef ref);
unsigned long (*CFStringGetTypeID)(void);
unsigned long (*CFNumberGetTypeID)(void);
Boolean (*CFStringGetCString)(CFStringRef str, char* buffer, CFIndex buffer_size, unsigned int encoding);
Boolean (*CFNumberGetValue)(CFTypeRef number, int type, void* value_ptr);
// Constants
CFStringRef* kSecClass;
CFStringRef* kSecClassCertificate;
CFStringRef* kSecMatchLimit;
CFStringRef* kSecMatchLimitAll;
CFStringRef* kSecReturnRef;
CFStringRef* kCFBooleanTrue;
CFStringRef* kSecTrustSettingsResult;
CFStringRef* kSecTrustSettingsPolicy;
CFStringRef* kSecTrustSettingsPolicyString;
CFStringRef* kSecTrustSettingsApplication;
CFStringRef* kSecPolicyOid;
CFStringRef* kSecPolicyAppleSSL;
};
extern "C" MacOSCAFunctions* Bun__getMacOSCAFunctions();
#endif
extern "C" int Bun__useSystemCA();
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
@@ -40,6 +131,256 @@ us_ssl_ctx_get_X509_without_callback_from(struct us_cert_string_t content) {
return x;
}
#ifdef __APPLE__
// Helper function to check if trust settings indicate the certificate is trusted for SSL policy
static bool us_internal_is_trust_settings_trusted_for_policy(MacOSCAFunctions* ca_funcs, CFArrayRef trust_settings, SecPolicyRef ssl_policy) {
if (!trust_settings || !ca_funcs) {
// Empty trust settings means "use system defaults"
return true;
}
CFIndex count = ca_funcs->CFArrayGetCount(trust_settings);
if (count == 0) {
// Empty trust settings array means "use system defaults"
return true;
}
CFDictionaryRef ssl_policy_properties = ca_funcs->SecPolicyCopyProperties(ssl_policy);
CFStringRef ssl_policy_oid = NULL;
if (ssl_policy_properties && ca_funcs->kSecPolicyOid && *(ca_funcs->kSecPolicyOid)) {
ssl_policy_oid = (CFStringRef)ca_funcs->CFDictionaryGetValue(ssl_policy_properties, *(ca_funcs->kSecPolicyOid));
}
bool is_trusted = true; // Default to trusted
for (CFIndex i = 0; i < count; i++) {
CFDictionaryRef trust_dict = (CFDictionaryRef)ca_funcs->CFArrayGetValueAtIndex(trust_settings, i);
if (!trust_dict) continue;
// Check if this trust setting applies to SSL policy
if (ca_funcs->kSecTrustSettingsPolicy && *(ca_funcs->kSecTrustSettingsPolicy)) {
CFTypeRef policy = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsPolicy));
if (policy) {
// If policy is specified, check if it matches SSL
CFStringRef policy_string = NULL;
if (ca_funcs->kSecTrustSettingsPolicyString && *(ca_funcs->kSecTrustSettingsPolicyString)) {
policy_string = (CFStringRef)ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsPolicyString));
}
bool policy_matches = false;
if (policy_string && ssl_policy_oid) {
char policy_str[256];
char ssl_oid_str[256];
if (ca_funcs->CFStringGetCString(policy_string, policy_str, sizeof(policy_str), kCFStringEncodingUTF8) &&
ca_funcs->CFStringGetCString(ssl_policy_oid, ssl_oid_str, sizeof(ssl_oid_str), kCFStringEncodingUTF8)) {
policy_matches = (strcmp(policy_str, ssl_oid_str) == 0);
}
}
if (!policy_matches) {
continue; // This trust setting doesn't apply to SSL
}
}
}
// Check if application is specified
if (ca_funcs->kSecTrustSettingsApplication && *(ca_funcs->kSecTrustSettingsApplication)) {
CFTypeRef application = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsApplication));
if (application) {
// If application is specified, this trust setting only applies to that app
continue;
}
}
// Get the trust result
if (ca_funcs->kSecTrustSettingsResult && *(ca_funcs->kSecTrustSettingsResult)) {
CFTypeRef result_ref = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsResult));
if (result_ref) {
if (ca_funcs->CFGetTypeID(result_ref) == ca_funcs->CFNumberGetTypeID()) {
int result_value;
if (ca_funcs->CFNumberGetValue(result_ref, kCFNumberSInt32Type, &result_value)) {
switch (result_value) {
case kSecTrustSettingsResultDeny:
is_trusted = false;
break;
case kSecTrustSettingsResultTrustRoot:
case kSecTrustSettingsResultTrustAsRoot:
is_trusted = true;
break;
case kSecTrustSettingsResultUnspecified:
default:
// Use system default
break;
}
}
}
}
}
}
if (ssl_policy_properties) {
ca_funcs->CFRelease(ssl_policy_properties);
}
return is_trusted;
}
// Check if a certificate is trusted for the SSL policy
static bool us_internal_is_certificate_trusted_for_policy(MacOSCAFunctions* ca_funcs, SecCertificateRef cert) {
if (!ca_funcs || !cert) {
return false;
}
// Create SSL policy
SecPolicyRef ssl_policy = ca_funcs->SecPolicyCreateSSL(false, NULL); // Client SSL policy
if (!ssl_policy) {
return false;
}
bool is_trusted = false;
// Check trust settings in user and admin domains
for (int domain : {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin}) {
CFArrayRef trust_settings = NULL;
OSStatus status = ca_funcs->SecTrustSettingsCopyTrustSettings(cert, domain);
if (status == 0 && trust_settings) {
if (us_internal_is_trust_settings_trusted_for_policy(ca_funcs, trust_settings, ssl_policy)) {
is_trusted = true;
ca_funcs->CFRelease(trust_settings);
break;
}
ca_funcs->CFRelease(trust_settings);
} else if (status == -25263) { // errSecItemNotFound
// No explicit trust settings - use system default evaluation
CFArrayRef cert_array = ca_funcs->CFArrayCreate(NULL, (CFTypeRef*)&cert, 1, NULL);
if (cert_array) {
SecTrustRef trust = NULL;
CFArrayRef policies = ca_funcs->CFArrayCreate(NULL, (CFTypeRef*)&ssl_policy, 1, NULL);
if (policies) {
if (ca_funcs->SecTrustCreateWithCertificates(cert_array, policies, &trust) == 0) {
CFErrorRef error = NULL;
if (ca_funcs->SecTrustEvaluateWithError(trust, &error)) {
is_trusted = true;
}
if (error) {
ca_funcs->CFRelease(error);
}
ca_funcs->CFRelease(trust);
}
ca_funcs->CFRelease(policies);
}
ca_funcs->CFRelease(cert_array);
}
break;
}
}
ca_funcs->CFRelease(ssl_policy);
return is_trusted;
}
// Load system certificates from macOS keychain using comprehensive trust evaluation
static STACK_OF(X509) *us_internal_init_system_certs_from_macos_keychain() {
MacOSCAFunctions* ca_funcs = Bun__getMacOSCAFunctions();
if (!ca_funcs) {
BUN__warn__system_ca_load_failed("Could not load macOS Security framework");
return NULL;
}
// Verify we have all required constants
if (!ca_funcs->kSecClass || !*(ca_funcs->kSecClass) ||
!ca_funcs->kSecClassCertificate || !*(ca_funcs->kSecClassCertificate) ||
!ca_funcs->kSecMatchLimit || !*(ca_funcs->kSecMatchLimit) ||
!ca_funcs->kSecMatchLimitAll || !*(ca_funcs->kSecMatchLimitAll) ||
!ca_funcs->kSecReturnRef || !*(ca_funcs->kSecReturnRef) ||
!ca_funcs->kCFBooleanTrue || !*(ca_funcs->kCFBooleanTrue)) {
BUN__warn__system_ca_load_failed("Required Security framework constants not available");
return NULL;
}
// Create query dictionary to get all certificates
CFTypeRef keys[3] = {
*(ca_funcs->kSecClass),
*(ca_funcs->kSecMatchLimit),
*(ca_funcs->kSecReturnRef)
};
CFTypeRef values[3] = {
*(ca_funcs->kSecClassCertificate),
*(ca_funcs->kSecMatchLimitAll),
*(ca_funcs->kCFBooleanTrue)
};
CFDictionaryRef query = ca_funcs->CFDictionaryCreate(NULL, keys, values, 3, NULL, NULL);
if (!query) {
BUN__warn__system_ca_load_failed("Could not create certificate query");
return NULL;
}
CFTypeRef result = NULL;
OSStatus status = ca_funcs->SecItemCopyMatching(query, &result);
ca_funcs->CFRelease(query);
if (status != 0 || !result) {
BUN__warn__system_ca_load_failed("Could not retrieve system certificates");
return NULL;
}
CFArrayRef cert_array = (CFArrayRef)result;
CFIndex cert_count = ca_funcs->CFArrayGetCount(cert_array);
STACK_OF(X509) *stack = sk_X509_new_null();
if (!stack) {
ca_funcs->CFRelease(cert_array);
BUN__warn__system_ca_load_failed("Could not create certificate stack");
return NULL;
}
int loaded_count = 0;
for (CFIndex i = 0; i < cert_count; i++) {
SecCertificateRef cert_ref = (SecCertificateRef)ca_funcs->CFArrayGetValueAtIndex(cert_array, i);
if (!cert_ref) continue;
// Check if certificate is trusted for SSL
if (!us_internal_is_certificate_trusted_for_policy(ca_funcs, cert_ref)) {
continue;
}
// Get certificate data
CFDataRef cert_data = ca_funcs->SecCertificateCopyData(cert_ref);
if (!cert_data) continue;
const unsigned char* data_ptr = ca_funcs->CFDataGetBytePtr(cert_data);
CFIndex data_len = ca_funcs->CFDataGetLength(cert_data);
if (data_len > 0) {
const unsigned char* data_ptr_copy = data_ptr;
X509* x509 = d2i_X509(NULL, &data_ptr_copy, (long)data_len);
if (x509) {
if (sk_X509_push(stack, x509)) {
loaded_count++;
} else {
X509_free(x509);
}
}
}
ca_funcs->CFRelease(cert_data);
}
ca_funcs->CFRelease(cert_array);
if (loaded_count == 0) {
sk_X509_pop_free(stack, X509_free);
BUN__warn__system_ca_load_failed("No trusted system certificates found");
return NULL;
}
return stack;
}
#endif
static STACK_OF(X509) *us_ssl_ctx_load_all_certs_from_file(const char *filename) {
BIO *in = NULL;
STACK_OF(X509) *certs = NULL;
@@ -100,7 +441,8 @@ end:
static void us_internal_init_root_certs(
X509 *root_cert_instances[root_certs_size],
STACK_OF(X509) *&root_extra_cert_instances) {
STACK_OF(X509) *&root_extra_cert_instances,
STACK_OF(X509) *&system_cert_instances) {
static std::atomic_flag root_cert_instances_lock = ATOMIC_FLAG_INIT;
static std::atomic_bool root_cert_instances_initialized = 0;
@@ -122,6 +464,13 @@ static void us_internal_init_root_certs(
if (extra_certs && extra_certs[0]) {
root_extra_cert_instances = us_ssl_ctx_load_all_certs_from_file(extra_certs);
}
// Load system certificates if enabled
if (Bun__useSystemCA()) {
#ifdef __APPLE__
system_cert_instances = us_internal_init_system_certs_from_macos_keychain();
#endif
}
}
atomic_flag_clear_explicit(&root_cert_instances_lock,
@@ -136,12 +485,15 @@ extern "C" int us_internal_raw_root_certs(struct us_cert_string_t **out) {
struct us_default_ca_certificates {
X509 *root_cert_instances[root_certs_size];
STACK_OF(X509) *root_extra_cert_instances;
STACK_OF(X509) *system_cert_instances;
};
us_default_ca_certificates* us_get_default_ca_certificates() {
static us_default_ca_certificates default_ca_certificates = {{NULL}, NULL};
static us_default_ca_certificates default_ca_certificates = {{NULL}, NULL, NULL};
us_internal_init_root_certs(default_ca_certificates.root_cert_instances, default_ca_certificates.root_extra_cert_instances);
us_internal_init_root_certs(default_ca_certificates.root_cert_instances,
default_ca_certificates.root_extra_cert_instances,
default_ca_certificates.system_cert_instances);
return &default_ca_certificates;
}
@@ -150,6 +502,10 @@ STACK_OF(X509) *us_get_root_extra_cert_instances() {
return us_get_default_ca_certificates()->root_extra_cert_instances;
}
STACK_OF(X509) *us_get_system_cert_instances() {
return us_get_default_ca_certificates()->system_cert_instances;
}
extern "C" X509_STORE *us_get_default_ca_store() {
X509_STORE *store = X509_STORE_new();
if (store == NULL) {
@@ -164,8 +520,14 @@ extern "C" X509_STORE *us_get_default_ca_store() {
us_default_ca_certificates *default_ca_certificates = us_get_default_ca_certificates();
X509** root_cert_instances = default_ca_certificates->root_cert_instances;
STACK_OF(X509) *root_extra_cert_instances = default_ca_certificates->root_extra_cert_instances;
STACK_OF(X509) *system_cert_instances = default_ca_certificates->system_cert_instances;
// load all root_cert_instances on the default ca store
// Node.js loads certificates in this order:
// 1. Default root certs (bundled Mozilla certs)
// 2. System certs (when --use-system-ca is enabled)
// 3. Extra certs (NODE_EXTRA_CA_CERTS)
// 1. Always load bundled root certificates first
for (size_t i = 0; i < root_certs_size; i++) {
X509 *cert = root_cert_instances[i];
if (cert == NULL)
@@ -174,6 +536,16 @@ extern "C" X509_STORE *us_get_default_ca_store() {
X509_STORE_add_cert(store, cert);
}
// 2. Add system certificates when --use-system-ca flag is enabled
if (Bun__useSystemCA() && system_cert_instances) {
for (int i = 0; i < sk_X509_num(system_cert_instances); i++) {
X509 *cert = sk_X509_value(system_cert_instances, i);
X509_up_ref(cert);
X509_STORE_add_cert(store, cert);
}
}
// 3. Always include extra CAs from NODE_EXTRA_CA_CERTS last
if (root_extra_cert_instances) {
for (int i = 0; i < sk_X509_num(root_extra_cert_instances); i++) {
X509 *cert = sk_X509_value(root_extra_cert_instances, i);
@@ -183,4 +555,4 @@ extern "C" X509_STORE *us_get_default_ca_store() {
}
return store;
}
}

View File

@@ -27,17 +27,11 @@ At its core is the _Bun runtime_, a fast JavaScript runtime designed as a drop-i
- Run scripts from package.json
- Visual lockfile viewer for old binary lockfiles (`bun.lockb`)
## Bun test runner integration
Run and debug tests directly from VSCode's Testing panel. The extension automatically discovers test files, shows inline test status, and provides rich error messages with diffs.
![Test runner example](https://raw.githubusercontent.com/oven-sh/bun/refs/heads/main/packages/bun-vscode/assets/bun-test.gif)
## In-editor error messages
When running programs with Bun from a Visual Studio Code terminal, Bun will connect to the extension and report errors as they happen, at the exact location they happened. We recommend using this feature with `bun --watch` so you can see errors on every save.
![Error messages example](https://raw.githubusercontent.com/oven-sh/bun/refs/heads/main/packages/bun-vscode/assets/error-messages.gif)
![Error messages example](https://raw.githubusercontent.com/oven-sh/bun/refs/heads/main/packages/bun-vscode/error-messages.gif)
<div align="center">
<sup>In the example above VSCode is saving on every keypress. Under normal configuration you'd only see errors on every save.</sup>
@@ -101,9 +95,6 @@ You can use the following configurations to debug JavaScript and TypeScript file
// The URL of the WebSocket inspector to attach to.
// This value can be retrieved by using `bun --inspect`.
"url": "ws://localhost:6499/",
// Optional path mapping for remote debugging
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
},
],
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 MiB

View File

@@ -102,6 +102,8 @@
"@types/ws": ["@types/ws@8.5.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ=="],
"@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
"@vscode/debugadapter": ["@vscode/debugadapter@1.61.0", "", { "dependencies": { "@vscode/debugprotocol": "1.61.0" } }, "sha512-VDGLUFDVAdnftUebZe4uQCIFUbJ7rTc2Grps4D/CXl+qyzTZSQLv5VADEOZ6kBYG4SvlnMLql5vPQ0G6XvUCvQ=="],
"@vscode/debugadapter-testsupport": ["@vscode/debugadapter-testsupport@1.61.0", "", { "dependencies": { "@vscode/debugprotocol": "1.61.0" } }, "sha512-M/8aNX1aFvupd+SP0NLEVLKUK9y52BuCK5vKO2gzdpSoRUR2fR8oFbGkTie+/p2Yrcswnuf7hFx0xWkV9avRdg=="],

View File

Before

Width:  |  Height:  |  Size: 462 KiB

After

Width:  |  Height:  |  Size: 462 KiB

View File

@@ -10,13 +10,15 @@
"devDependencies": {
"@types/bun": "^1.1.10",
"@types/vscode": "^1.60.0",
"@types/xml2js": "^0.4.14",
"@vscode/debugadapter": "^1.56.0",
"@vscode/debugadapter-testsupport": "^1.56.0",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^2.20.1",
"esbuild": "^0.19.2",
"typescript": "^5.0.0"
"typescript": "^5.0.0",
"xml2js": "^0.6.2"
},
"activationEvents": [
"onStartupFinished"
@@ -71,7 +73,7 @@
},
"bun.test.filePattern": {
"type": "string",
"default": "**/*{.test.,.spec.,_test_,_spec_}{js,ts,tsx,jsx,mts,cts,cjs,mjs}",
"default": "**/*{.test.,.spec.,_test_,_spec_}{js,ts,tsx,jsx,mts,cts}",
"description": "Glob pattern to find test files"
},
"bun.test.customFlag": {
@@ -81,14 +83,8 @@
},
"bun.test.customScript": {
"type": "string",
"default": "bun test",
"default": "",
"description": "Custom script to use instead of `bun test`, for example script from `package.json`"
},
"bun.test.enable": {
"type": "boolean",
"description": "If the test explorer should be enabled and integrated with your editor",
"scope": "window",
"default": true
}
}
},
@@ -283,14 +279,6 @@
"type": "boolean",
"description": "If the debugger should stop on the first line of the program.",
"default": false
},
"localRoot": {
"type": "string",
"description": "The local path that maps to \"remoteRoot\" when attaching to a remote Bun process."
},
"remoteRoot": {
"type": "string",
"description": "The remote path to the code when attaching. File paths reported by Bun that start with this path will be mapped back to 'localRoot'."
}
}
}

View File

@@ -1,6 +1,5 @@
import { DebugSession, OutputEvent } from "@vscode/debugadapter";
import { tmpdir } from "node:os";
import * as path from "node:path";
import { join } from "node:path";
import * as vscode from "vscode";
import {
@@ -221,7 +220,7 @@ class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory
session: vscode.DebugSession,
): Promise<vscode.ProviderResult<vscode.DebugAdapterDescriptor>> {
const { configuration } = session;
const { request, url, __untitledName, localRoot, remoteRoot } = configuration;
const { request, url, __untitledName } = configuration;
if (request === "attach") {
for (const [adapterUrl, adapter] of adapters) {
@@ -231,10 +230,7 @@ class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory
}
}
const adapter = new FileDebugSession(session.id, __untitledName, {
localRoot,
remoteRoot,
});
const adapter = new FileDebugSession(session.id, __untitledName);
await adapter.initialize();
return new vscode.DebugAdapterInlineImplementation(adapter);
}
@@ -279,11 +275,6 @@ interface RuntimeExceptionThrownEvent {
};
}
interface PathMapping {
localRoot?: string;
remoteRoot?: string;
}
class FileDebugSession extends DebugSession {
// If these classes are moved/published, we should make sure
// we remove these non-null assertions so consumers of
@@ -292,60 +283,18 @@ class FileDebugSession extends DebugSession {
sessionId?: string;
untitledDocPath?: string;
bunEvalPath?: string;
localRoot?: string;
remoteRoot?: string;
#isWindowsRemote = false;
constructor(sessionId?: string, untitledDocPath?: string, mapping?: PathMapping) {
constructor(sessionId?: string, untitledDocPath?: string) {
super();
this.sessionId = sessionId;
this.untitledDocPath = untitledDocPath;
if (mapping) {
this.localRoot = mapping.localRoot;
this.remoteRoot = mapping.remoteRoot;
if (typeof mapping.remoteRoot === "string") {
this.#isWindowsRemote = mapping.remoteRoot.includes("\\");
}
}
if (untitledDocPath) {
const cwd = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath ?? process.cwd();
this.bunEvalPath = join(cwd, "[eval]");
}
}
mapRemoteToLocal(p: string | undefined): string | undefined {
if (!p || !this.remoteRoot || !this.localRoot) return p;
const remoteModule = this.#isWindowsRemote ? path.win32 : path.posix;
let remoteRoot = remoteModule.normalize(this.remoteRoot);
if (!remoteRoot.endsWith(remoteModule.sep)) remoteRoot += remoteModule.sep;
let target = remoteModule.normalize(p);
const starts = this.#isWindowsRemote
? target.toLowerCase().startsWith(remoteRoot.toLowerCase())
: target.startsWith(remoteRoot);
if (starts) {
const rel = target.slice(remoteRoot.length);
const localRel = rel.split(remoteModule.sep).join(path.sep);
return path.join(this.localRoot, localRel);
}
return p;
}
mapLocalToRemote(p: string | undefined): string | undefined {
if (!p || !this.remoteRoot || !this.localRoot) return p;
let localRoot = path.normalize(this.localRoot);
if (!localRoot.endsWith(path.sep)) localRoot += path.sep;
let localPath = path.normalize(p);
if (localPath.startsWith(localRoot)) {
const rel = localPath.slice(localRoot.length);
const remoteModule = this.#isWindowsRemote ? path.win32 : path.posix;
const remoteRel = rel.split(path.sep).join(remoteModule.sep);
return remoteModule.join(this.remoteRoot, remoteRel);
}
return p;
}
async initialize() {
const uniqueId = this.sessionId ?? Math.random().toString(36).slice(2);
const url =
@@ -358,20 +307,14 @@ class FileDebugSession extends DebugSession {
if (untitledDocPath) {
this.adapter.on("Adapter.response", (response: DebugProtocolResponse) => {
if (response.body?.source?.path) {
if (response.body.source.path === bunEvalPath) {
response.body.source.path = untitledDocPath;
} else {
response.body.source.path = this.mapRemoteToLocal(response.body.source.path);
}
if (response.body?.source?.path === bunEvalPath) {
response.body.source.path = untitledDocPath;
}
if (Array.isArray(response.body?.breakpoints)) {
for (const bp of response.body.breakpoints) {
if (bp.source?.path === bunEvalPath) {
bp.source.path = untitledDocPath;
bp.verified = true;
} else if (bp.source?.path) {
bp.source.path = this.mapRemoteToLocal(bp.source.path);
}
}
}
@@ -379,35 +322,14 @@ class FileDebugSession extends DebugSession {
});
this.adapter.on("Adapter.event", (event: DebugProtocolEvent) => {
if (event.body?.source?.path) {
if (event.body.source.path === bunEvalPath) {
event.body.source.path = untitledDocPath;
} else {
event.body.source.path = this.mapRemoteToLocal(event.body.source.path);
}
if (event.body?.source?.path === bunEvalPath) {
event.body.source.path = untitledDocPath;
}
this.sendEvent(event);
});
} else {
this.adapter.on("Adapter.response", (response: DebugProtocolResponse) => {
if (response.body?.source?.path) {
response.body.source.path = this.mapRemoteToLocal(response.body.source.path);
}
if (Array.isArray(response.body?.breakpoints)) {
for (const bp of response.body.breakpoints) {
if (bp.source?.path) {
bp.source.path = this.mapRemoteToLocal(bp.source.path);
}
}
}
this.sendResponse(response);
});
this.adapter.on("Adapter.event", (event: DebugProtocolEvent) => {
if (event.body?.source?.path) {
event.body.source.path = this.mapRemoteToLocal(event.body.source.path);
}
this.sendEvent(event);
});
this.adapter.on("Adapter.response", response => this.sendResponse(response));
this.adapter.on("Adapter.event", event => this.sendEvent(event));
}
this.adapter.on("Adapter.reverseRequest", ({ command, arguments: args }) =>
@@ -423,15 +345,11 @@ class FileDebugSession extends DebugSession {
if (type === "request") {
const { untitledDocPath, bunEvalPath } = this;
const { command } = message;
if (command === "setBreakpoints" || command === "breakpointLocations") {
if (untitledDocPath && (command === "setBreakpoints" || command === "breakpointLocations")) {
const args = message.arguments as any;
if (untitledDocPath && args.source?.path === untitledDocPath) {
if (args.source?.path === untitledDocPath) {
args.source.path = bunEvalPath;
} else if (args.source?.path) {
args.source.path = this.mapLocalToRemote(args.source.path);
}
} else if (command === "source" && message.arguments?.source?.path) {
message.arguments.source.path = this.mapLocalToRemote(message.arguments.source.path);
}
this.adapter.emit("Adapter.request", message);
@@ -449,7 +367,7 @@ class TerminalDebugSession extends FileDebugSession {
signal!: TCPSocketSignal | UnixSignal;
constructor() {
super(undefined, undefined);
super();
}
async initialize() {

View File

@@ -1,864 +0,0 @@
import { describe, expect, test } from "bun:test";
import { MockTestController, MockWorkspaceFolder } from "./vscode-types.mock";
import "./vscode.mock";
import { makeTestController, makeWorkspaceFolder } from "./vscode.mock";
const { BunTestController } = await import("../bun-test-controller");
const mockTestController: MockTestController = makeTestController();
const mockWorkspaceFolder: MockWorkspaceFolder = makeWorkspaceFolder("/test/workspace");
const controller = new BunTestController(mockTestController, mockWorkspaceFolder, true);
const internal = controller._internal;
const { expandEachTests, parseTestBlocks, getBraceDepth } = internal;
describe("BunTestController (static file parser)", () => {
describe("expandEachTests", () => {
describe("$variable syntax", () => {
test("should not expand $variable patterns (Bun behavior)", () => {
const content = `test.each([
{ a: 1, b: 2, expected: 3 },
{ a: 5, b: 5, expected: 10 }
])('$a + $b = $expected', ({ a, b, expected }) => {})`;
const result = expandEachTests("test.each([", "$a + $b = $expected", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$a + $b = $expected");
});
test("should not expand string values with quotes", () => {
const content = `test.each([
{ name: "Alice", city: "NYC" },
{ name: "Bob", city: "LA" }
])('$name from $city', ({ name, city }) => {})`;
const result = expandEachTests("test.each([", "$name from $city", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$name from $city");
});
test("should not expand nested property access", () => {
const content = `test.each([
{ user: { name: "Alice", profile: { city: "NYC" } } },
{ user: { name: "Bob", profile: { city: "LA" } } }
])('$user.name from $user.profile.city', ({ user }) => {})`;
const result = expandEachTests("test.each([", "$user.name from $user.profile.city", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$user.name from $user.profile.city");
});
test("should not expand array indexing", () => {
const content = `test.each([
{ users: [{ name: "Alice" }, { name: "Bob" }] },
{ users: [{ name: "Carol" }, { name: "Dave" }] }
])('first user: $users.0.name', ({ users }) => {})`;
const result = expandEachTests("test.each([", "first user: $users.0.name", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("first user: $users.0.name");
});
test("should return template as-is for missing properties", () => {
const content = `test.each([
{ a: 1 },
{ a: 2 }
])('$a and $missing', ({ a }) => {})`;
const result = expandEachTests("test.each([", "$a and $missing", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$a and $missing");
});
test("should handle edge cases with special identifiers", () => {
const content = `test.each([
{ _valid: "ok", $dollar: "yes", _123mix: "mixed" }
])('$_valid | $$dollar | $_123mix', (obj) => {})`;
const result = expandEachTests("test.each([", "$_valid | $$dollar | $_123mix", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$_valid | $$dollar | $_123mix");
});
test("should handle invalid identifiers as literals", () => {
const content = `test.each([
{ valid: "test" }
])('$valid | $123invalid | $has-dash', (obj) => {})`;
const result = expandEachTests("test.each([", "$valid | $123invalid | $has-dash", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$valid | $123invalid | $has-dash");
});
});
describe("% formatters", () => {
test("should handle %i for integers", () => {
const content = `test.each([
[1, 2, 3],
[5, 5, 10]
])('%i + %i = %i', (a, b, expected) => {})`;
const result = expandEachTests("test.each([", "%i + %i = %i", content, 0, "test", 1);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("1 + 2 = 3");
expect(result[1].name).toBe("5 + 5 = 10");
});
test("should handle %s for strings", () => {
const content = `test.each([
["hello", "world"],
["foo", "bar"]
])('%s %s', (a, b) => {})`;
const result = expandEachTests("test.each([", "%s %s", content, 0, "test", 1);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("hello world");
expect(result[1].name).toBe("foo bar");
});
test("should handle %f and %d for numbers", () => {
const content = `test.each([
[1.5, 2.7],
[3.14, 2.71]
])('%f and %d', (a, b) => {})`;
const result = expandEachTests("test.each([", "%f and %d", content, 0, "test", 1);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("1.5 and 2.7");
expect(result[1].name).toBe("3.14 and 2.71");
});
test("should handle %o and %j for objects", () => {
const content = `test.each([
[{ a: 1 }, { b: 2 }]
])('%o and %j', (obj1, obj2) => {})`;
const result = expandEachTests("test.each([", "%o and %j", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("%o and %j");
});
test("should handle %# for index", () => {
const content = `test.each([
[1, 2],
[3, 4],
[5, 6]
])('Test #%#: %i + %i', (a, b) => {})`;
const result = expandEachTests("test.each([", "Test #%#: %i + %i", content, 0, "test", 1);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("Test #1: 1 + 2");
expect(result[1].name).toBe("Test #2: 3 + 4");
expect(result[2].name).toBe("Test #3: 5 + 6");
});
test("should handle %% for literal percent", () => {
const content = `test.each([
[50],
[100]
])('%i%% complete', (percent) => {})`;
const result = expandEachTests("test.each([", "%i%% complete", content, 0, "test", 1);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("50% complete");
expect(result[1].name).toBe("100% complete");
});
});
describe("describe.each", () => {
test("should work with describe.each", () => {
const content = `describe.each([
{ module: "fs", method: "readFile" },
{ module: "path", method: "join" }
])('$module module', ({ module, method }) => {})`;
const result = expandEachTests("describe.each([", "$module module", content, 0, "describe", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$module module");
expect(result[0].type).toBe("describe");
});
});
describe("error handling", () => {
test("should handle non-.each tests", () => {
const result = expandEachTests("test", "regular test", "test('regular test', () => {})", 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("regular test");
});
test("should handle malformed JSON", () => {
const content = `test.each([
{ invalid json }
])('test', () => {})`;
const result = expandEachTests("test.each([", "test", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test");
});
test("should handle non-array values", () => {
const content = `test.each({ not: "array" })('test', () => {})`;
const result = expandEachTests("test.each([", "test", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test");
});
});
describe("mixed formatters", () => {
test("should handle both $ and % in objects", () => {
const content = `test.each([
{ name: "Test", index: 0 }
])('$name #%#', (obj) => {})`;
const result = expandEachTests("test.each([", "$name #%#", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$name #%#");
});
});
describe("edge cases", () => {
test("should handle complex nested objects", () => {
const content = `test.each([
{
user: {
profile: {
address: {
city: "NYC",
coords: { lat: 40.7128, lng: -74.0060 }
}
}
}
}
])('User from $user.profile.address.city at $user.profile.address.coords.lat', ({ user }) => {})`;
const result = expandEachTests(
"test.each([",
"User from $user.profile.address.city at $user.profile.address.coords.lat",
content,
0,
"test",
1,
);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("User from $user.profile.address.city at $user.profile.address.coords.lat");
});
test("should handle arrays with inline comments", () => {
const content = `test.each([
{ a: 1 }, // first test
{ a: 2 }, // second test
// { a: 3 }, // commented out test
{ a: 4 } /* final test */
])('test $a', ({ a }) => {})`;
const result = expandEachTests("test.each([", "test $a", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test $a");
});
test("should handle arrays with multiline comments", () => {
const content = `test.each([
{ name: "test1" },
/* This is a
multiline comment
that spans several lines */
{ name: "test2" },
/**
* JSDoc style comment
* with multiple lines
*/
{ name: "test3" }
])('$name', ({ name }) => {})`;
const result = expandEachTests("test.each([", "$name", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("$name");
});
test("should handle malformed array syntax gracefully", () => {
const content = `test.each([
{ a: 1 },
{ a: 2,,, }, // extra commas
{ a: 3, }, // trailing comma
{ a: 4 },,, // extra trailing commas
])('test $a', ({ a }) => {})`;
const result = expandEachTests("test.each([", "test $a", content, 0, "test", 1);
expect(result.length).toBeGreaterThanOrEqual(1);
});
test("should handle strings with comment-like content", () => {
const content = `test.each([
{ comment: "// this is not a comment" },
{ comment: "/* neither is this */" },
{ url: "https://example.com/path" }
])('Test: $comment $url', (data) => {})`;
const result = expandEachTests("test.each([", "Test: $comment $url", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Test: $comment $url");
});
test("should handle special characters in strings", () => {
const content = `test.each([
{ char: "\\n" },
{ char: "\\t" },
{ char: "\\"" },
{ char: "\\'" },
{ char: "\\\\" },
{ char: "\`" }
])('Special char: $char', ({ char }) => {})`;
const result = expandEachTests("test.each([", "Special char: $char", content, 0, "test", 1);
expect(result.length).toBeGreaterThanOrEqual(1);
});
test("should handle empty arrays", () => {
const content = `test.each([])('should handle empty', () => {})`;
const result = expandEachTests("test.each([", "should handle empty", content, 0, "test", 1);
expect(result).toHaveLength(0);
});
test("should handle undefined and null values", () => {
const content = `test.each([
{ value: undefined },
{ value: null },
{ value: false },
{ value: 0 },
{ value: "" }
])('Value: $value', ({ value }) => {})`;
const result = expandEachTests("test.each([", "Value: $value", content, 0, "test", 1);
if (result.length === 1) {
expect(result[0].name).toBe("Value: $value");
} else {
expect(result).toHaveLength(5);
expect(result[0].name).toBe("Value: undefined");
expect(result[1].name).toBe("Value: null");
expect(result[2].name).toBe("Value: false");
expect(result[3].name).toBe("Value: 0");
expect(result[4].name).toBe("Value: ");
}
});
test("should handle circular references gracefully", () => {
const content = `test.each([
{ a: { b: "[Circular]" } },
{ a: { b: { c: "[Circular]" } } }
])('Circular: $a.b', ({ a }) => {})`;
const result = expandEachTests("test.each([", "Circular: $a.b", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Circular: $a.b");
});
test("should handle very long property paths", () => {
const content = `test.each([
{
a: {
b: {
c: {
d: {
e: {
f: {
g: "deeply nested"
}
}
}
}
}
}
}
])('Value: $a.b.c.d.e.f.g', (data) => {})`;
const result = expandEachTests("test.each([", "Value: $a.b.c.d.e.f.g", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Value: $a.b.c.d.e.f.g");
});
test("should handle syntax errors in array", () => {
const content = `test.each([
{ a: 1 }
{ a: 2 } // missing comma
{ a: 3 }
])('test $a', ({ a }) => {})`;
const result = expandEachTests("test.each([", "test $a", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test $a");
});
test("should handle arrays with trailing commas", () => {
const content = `test.each([
{ a: 1 },
{ a: 2 },
])('test $a', ({ a }) => {})`;
const result = expandEachTests("test.each([", "test $a", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test $a");
});
test("should handle mixed data types in arrays", () => {
const content = `test.each([
["string", 123, true, null, undefined],
[{ obj: true }, [1, 2, 3], new Date("2024-01-01")]
])('test %s %i %s %s %s', (...args) => {})`;
const result = expandEachTests("test.each([", "test %s %i %s %s %s", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("test %s %i %s %s %s");
});
test("should handle regex-like strings", () => {
const content = `test.each([
{ pattern: "/^test.*$/" },
{ pattern: "\\\\d{3}-\\\\d{4}" },
{ pattern: "[a-zA-Z]+" }
])('Pattern: $pattern', ({ pattern }) => {})`;
const result = expandEachTests("test.each([", "Pattern: $pattern", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Pattern: $pattern");
});
test("should handle invalid property access gracefully", () => {
const content = `test.each([
{ a: { b: null } },
{ a: null },
{ },
{ a: { } }
])('Access: $a.b.c.d', (data) => {})`;
const result = expandEachTests("test.each([", "Access: $a.b.c.d", content, 0, "test", 1);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Access: $a.b.c.d");
});
test("should handle object methods and computed properties", () => {
const content = `test.each([
{ fn: function() {}, method() {}, arrow: () => {} },
{ ["computed"]: "value", [Symbol.for("sym")]: "symbol" }
])('Object with methods', (obj) => {})`;
const result = expandEachTests("test.each([", "Object with methods", content, 0, "test", 1);
expect(result.length).toBeGreaterThanOrEqual(1);
});
});
});
describe("parseTestBlocks", () => {
test("should parse simple test blocks", () => {
const content = `
test("should add numbers", () => {
expect(1 + 1).toBe(2);
});
test("should multiply numbers", () => {
expect(2 * 3).toBe(6);
});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("should add numbers");
expect(result[0].type).toBe("test");
expect(result[1].name).toBe("should multiply numbers");
expect(result[1].type).toBe("test");
});
test("should parse describe blocks with nested tests", () => {
const content = `
describe("Math operations", () => {
test("addition", () => {
expect(1 + 1).toBe(2);
});
test("subtraction", () => {
expect(5 - 3).toBe(2);
});
});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Math operations");
expect(result[0].type).toBe("describe");
expect(result[0].children).toHaveLength(2);
expect(result[0].children[0].name).toBe("addition");
expect(result[0].children[1].name).toBe("subtraction");
});
test("should handle test modifiers", () => {
const content = `
test.skip("skipped test", () => {});
test.todo("todo test", () => {});
test.only("only test", () => {});
test.failing("failing test", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(4);
expect(result[0].name).toBe("skipped test");
expect(result[1].name).toBe("todo test");
expect(result[2].name).toBe("only test");
expect(result[3].name).toBe("failing test");
});
test("should handle conditional tests", () => {
const content = `
test.if(true)("conditional test", () => {});
test.skipIf(false)("skip if test", () => {});
test.todoIf(true)("todo if test", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("conditional test");
expect(result[1].name).toBe("skip if test");
expect(result[2].name).toBe("todo if test");
});
test("should ignore comments", () => {
const content = `
// This is a comment with test("fake test", () => {})
/* Multi-line comment
test("another fake test", () => {})
*/
test("real test", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("real test");
});
test("should handle nested describe blocks", () => {
const content = `
describe("Outer", () => {
describe("Inner", () => {
test("deeply nested", () => {});
});
test("shallow test", () => {});
});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("Outer");
expect(result[0].children).toHaveLength(2);
expect(result[0].children[0].name).toBe("Inner");
expect(result[0].children[0].children).toHaveLength(1);
expect(result[0].children[0].children[0].name).toBe("deeply nested");
expect(result[0].children[1].name).toBe("shallow test");
});
test("should handle it() as alias for test()", () => {
const content = `
it("should work with it", () => {});
it.skip("should skip with it", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(2);
expect(result[0].name).toBe("should work with it");
expect(result[0].type).toBe("test");
expect(result[1].name).toBe("should skip with it");
});
test("should handle different quote types", () => {
const content = `
test('single quotes', () => {});
test("double quotes", () => {});
test(\`template literals\`, () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("single quotes");
expect(result[1].name).toBe("double quotes");
expect(result[2].name).toBe("template literals");
});
test("should handle escaped quotes in test names", () => {
const content = `
test("test with \\"escaped\\" quotes", () => {});
test('test with \\'escaped\\' quotes', () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(2);
expect(result[0].name).toBe('test with "escaped" quotes');
expect(result[1].name).toBe("test with 'escaped' quotes");
});
test("should handle comments within test names", () => {
const content = `
test("test with // comment syntax", () => {});
test("test with /* comment */ syntax", () => {});
test("test with URL https://example.com", () => {});
`;
const result = parseTestBlocks(content);
expect(result.length).toBeGreaterThanOrEqual(1);
const hasCommentSyntax = result.some(r => r.name.includes("comment syntax"));
const hasURL = result.some(r => r.name.includes("https://example.com"));
expect(hasCommentSyntax || hasURL).toBe(true);
});
test("should ignore code that looks like tests in strings", () => {
const content = `
const str = "test('fake test', () => {})";
const template = \`describe("fake describe", () => {})\`;
// Real test
test("real test", () => {
const example = 'test("nested fake", () => {})';
});
`;
const result = parseTestBlocks(content);
expect(result.length).toBeGreaterThanOrEqual(1);
expect(result.some(r => r.name === "real test")).toBe(true);
});
test("should handle tests with complex modifier chains", () => {
const content = `
test.skip.failing("skipped failing test", () => {});
test.only.todo("only todo test", () => {});
describe.skip.each([1, 2])("skip each %i", (n) => {});
it.failing.each([{a: 1}])("failing each $a", ({a}) => {});
`;
const result = parseTestBlocks(content);
expect(result.length).toBeGreaterThan(0);
});
test("should handle weird spacing and formatting", () => {
const content = `
test ( "extra spaces" , ( ) => { } ) ;
test
(
"multiline test"
,
(
)
=>
{
}
)
;
test\t(\t"tabs"\t,\t()\t=>\t{}\t);
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("extra spaces");
expect(result[1].name).toBe("multiline test");
expect(result[2].name).toBe("tabs");
});
test("should handle test.each with complex patterns", () => {
const content = `
test.each([
[1, 2, 3],
[4, 5, 9]
])("when %i + %i, result should be %i", (a, b, expected) => {});
describe.each([
{ db: "postgres" },
{ db: "mysql" }
])("Database $db", ({ db }) => {
test("should connect", () => {});
});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(3);
expect(result[0].name).toBe("when 1 + 2, result should be 3");
expect(result[0].type).toBe("test");
expect(result[1].name).toBe("when 4 + 5, result should be 9");
expect(result[1].type).toBe("test");
expect(result[2].name).toBe("Database $db");
expect(result[2].type).toBe("describe");
});
test("should handle Unicode and emoji in test names", () => {
const content = `
test("测试中文", () => {});
test("テスト日本語", () => {});
test("тест русский", () => {});
test("🚀 rocket test", () => {});
test("Test with 🎉 celebration", () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(5);
expect(result[0].name).toBe("测试中文");
expect(result[1].name).toBe("テスト日本語");
expect(result[2].name).toBe("тест русский");
expect(result[3].name).toBe("🚀 rocket test");
expect(result[4].name).toBe("Test with 🎉 celebration");
});
test("should handle test names with interpolation-like syntax", () => {
const content = `
test("test with \${variable}", () => {});
test("test with \$dollar", () => {});
test("test with %percent", () => {});
test(\`template literal test\`, () => {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(4);
expect(result[0].name).toBe("test with ${variable}");
expect(result[1].name).toBe("test with $dollar");
expect(result[2].name).toBe("test with %percent");
expect(result[3].name).toBe("template literal test");
});
test("should handle async/await in test definitions", () => {
const content = `
test("sync test", () => {});
test("async test", async () => {});
test("test with await", async () => {
await something();
});
it("async it", async function() {});
`;
const result = parseTestBlocks(content);
expect(result).toHaveLength(4);
expect(result[0].name).toBe("sync test");
expect(result[1].name).toBe("async test");
expect(result[2].name).toBe("test with await");
expect(result[3].name).toBe("async it");
});
test("should handle generator functions and other ES6+ syntax", () => {
const content = `
test("generator test", function* () {
yield 1;
});
test.each\`
a | b | expected
\${1} | \${1} | \${2}
\${1} | \${2} | \${3}
\`('$a + $b = $expected', ({ a, b, expected }) => {});
`;
const result = parseTestBlocks(content);
expect(result.length).toBeGreaterThanOrEqual(1);
expect(result[0].name).toBe("generator test");
});
});
describe("getBraceDepth", () => {
test("should count braces correctly", () => {
const content = "{ { } }";
expect(getBraceDepth(content, 0, content.length)).toBe(0);
expect(getBraceDepth(content, 0, 3)).toBe(2);
expect(getBraceDepth(content, 0, 5)).toBe(1);
});
test("should ignore braces in strings", () => {
const content = '{ "string with { braces }" }';
expect(getBraceDepth(content, 0, content.length)).toBe(0);
});
test("should ignore braces in template literals", () => {
const content = "{ `template with { braces }` }";
expect(getBraceDepth(content, 0, content.length)).toBe(0);
});
test("should handle escaped quotes", () => {
const content = '{ "escaped \\" quote" }';
expect(getBraceDepth(content, 0, content.length)).toBe(0);
});
test("should handle mixed quotes", () => {
const content = `{ "double" + 'single' + \`template\` }`;
expect(getBraceDepth(content, 0, content.length)).toBe(0);
});
test("should handle nested braces", () => {
const content = "{ a: { b: { c: 1 } } }";
expect(getBraceDepth(content, 0, 10)).toBe(2);
expect(getBraceDepth(content, 0, 15)).toBe(3);
});
test("should handle complex template literals", () => {
const content = '{ `${foo({ bar: "baz" })} and ${nested.value}` }';
expect(getBraceDepth(content, 0, content.length)).toBe(0);
});
test("should handle edge cases", () => {
expect(getBraceDepth("", 0, 0)).toBe(0);
expect(getBraceDepth("{{{}}}", 0, 6)).toBe(0);
expect(getBraceDepth("{{{", 0, 3)).toBe(3);
expect(getBraceDepth("}}}", 0, 3)).toBe(-3);
const templateContent = "{ `${foo}` + `${bar}` }";
expect(getBraceDepth(templateContent, 0, templateContent.length)).toBe(0);
});
});
});

View File

@@ -1,570 +0,0 @@
/**
* Mock VSCode types and classes for testing
* These should be as close as possible to the real VSCode API
*/
export interface MockUri {
readonly scheme: string;
readonly authority: string;
readonly path: string;
readonly query: string;
readonly fragment: string;
readonly fsPath: string;
toString(): string;
}
export class MockUri implements MockUri {
constructor(
public readonly scheme: string,
public readonly authority: string,
public readonly path: string,
public readonly query: string,
public readonly fragment: string,
public readonly fsPath: string,
) {}
static file(path: string): MockUri {
return new MockUri("file", "", path, "", "", path);
}
toString(): string {
return `${this.scheme}://${this.authority}${this.path}`;
}
}
export class MockPosition {
constructor(
public readonly line: number,
public readonly character: number,
) {}
}
export class MockRange {
constructor(
public readonly start: MockPosition,
public readonly end: MockPosition,
) {}
}
export class MockLocation {
constructor(
public readonly uri: MockUri,
public readonly range: MockRange,
) {}
}
export class MockTestTag {
constructor(public readonly id: string) {}
}
export class MockTestMessage {
public location?: MockLocation;
public actualOutput?: string;
public expectedOutput?: string;
constructor(public message: string | MockMarkdownString) {}
static diff(message: string, expected: string, actual: string): MockTestMessage {
const msg = new MockTestMessage(message);
msg.expectedOutput = expected;
msg.actualOutput = actual;
return msg;
}
}
export class MockMarkdownString {
constructor(public value: string = "") {}
appendCodeblock(code: string, language?: string): MockMarkdownString {
this.value += `\n\`\`\`${language || ""}\n${code}\n\`\`\``;
return this;
}
appendMarkdown(value: string): MockMarkdownString {
this.value += value;
return this;
}
appendText(value: string): MockMarkdownString {
this.value += value.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&");
return this;
}
}
export interface MockTestItem {
readonly id: string;
readonly uri?: MockUri;
readonly children: MockTestItemCollection;
readonly parent?: MockTestItem;
label: string;
description?: string;
tags: readonly MockTestTag[];
canResolveChildren: boolean;
busy: boolean;
range?: MockRange;
error?: string | MockMarkdownString;
}
export interface MockTestItemCollection {
readonly size: number;
add(item: MockTestItem): void;
replace(items: readonly MockTestItem[]): void;
forEach(callback: (item: MockTestItem, id: string, collection: MockTestItemCollection) => void): void;
get(itemId: string): MockTestItem | undefined;
delete(itemId: string): void;
[Symbol.iterator](): Iterator<[string, MockTestItem]>;
}
export class MockTestItemCollection implements MockTestItemCollection {
private items = new Map<string, MockTestItem>();
get size(): number {
return this.items.size;
}
add(item: MockTestItem): void {
this.items.set(item.id, item);
}
replace(items: readonly MockTestItem[]): void {
this.items.clear();
for (const item of items) {
this.items.set(item.id, item);
}
}
forEach(callback: (item: MockTestItem, id: string, collection: MockTestItemCollection) => void): void {
this.items.forEach((item, id) => callback(item, id, this));
}
get(itemId: string): MockTestItem | undefined {
return this.items.get(itemId);
}
delete(itemId: string): void {
this.items.delete(itemId);
}
[Symbol.iterator](): Iterator<[string, MockTestItem]> {
return this.items[Symbol.iterator]();
}
clear(): void {
this.items.clear();
}
set(id: string, item: MockTestItem): void {
this.items.set(id, item);
}
values(): IterableIterator<MockTestItem> {
return this.items.values();
}
keys(): IterableIterator<string> {
return this.items.keys();
}
entries(): IterableIterator<[string, MockTestItem]> {
return this.items.entries();
}
}
export class MockTestItem implements MockTestItem {
public canResolveChildren: boolean = false;
public busy: boolean = false;
public description?: string;
public range?: MockRange;
public error?: string | MockMarkdownString;
public readonly children: MockTestItemCollection;
constructor(
public readonly id: string,
public label: string,
public readonly uri?: MockUri,
public readonly parent?: MockTestItem,
public tags: readonly MockTestTag[] = [],
) {
this.children = new MockTestItemCollection();
}
}
export interface MockTestController {
readonly items: MockTestItemCollection;
createTestItem(id: string, label: string, uri?: MockUri): MockTestItem;
createRunProfile(
label: string,
kind: MockTestRunProfileKind,
runHandler: (request: MockTestRunRequest, token: MockCancellationToken) => void | Promise<void>,
isDefault?: boolean,
): MockTestRunProfile;
createTestRun(request: MockTestRunRequest, name?: string, persist?: boolean): MockTestRun;
invalidateTestResults(items?: readonly MockTestItem[]): void;
resolveHandler?: (item: MockTestItem | undefined) => Promise<void> | void;
refreshHandler?: (token?: MockCancellationToken) => Promise<void> | void;
}
export class MockTestController implements MockTestController {
public readonly items: MockTestItemCollection;
public resolveHandler?: (item: MockTestItem | undefined) => Promise<void> | void;
public refreshHandler?: (token?: MockCancellationToken) => Promise<void> | void;
constructor(
public readonly id: string,
public readonly label: string,
) {
this.items = new MockTestItemCollection();
}
createTestItem(id: string, label: string, uri?: MockUri): MockTestItem {
return new MockTestItem(id, label, uri);
}
createRunProfile(
label: string,
kind: MockTestRunProfileKind,
runHandler: (request: MockTestRunRequest, token: MockCancellationToken) => void | Promise<void>,
isDefault?: boolean,
): MockTestRunProfile {
return new MockTestRunProfile(label, kind, runHandler, isDefault);
}
createTestRun(request: MockTestRunRequest, name?: string, persist?: boolean): MockTestRun {
return new MockTestRun(name, persist);
}
invalidateTestResults(items?: readonly MockTestItem[]): void {
// Mock implementation - in real VSCode this would invalidate test results
}
dispose(): void {
this.items.clear();
}
}
export enum MockTestRunProfileKind {
Run = 1,
Debug = 2,
Coverage = 3,
}
export interface MockTestRunProfile {
readonly label: string;
readonly kind: MockTestRunProfileKind;
readonly isDefault: boolean;
readonly runHandler: (request: MockTestRunRequest, token: MockCancellationToken) => void | Promise<void>;
dispose(): void;
}
export class MockTestRunProfile implements MockTestRunProfile {
constructor(
public readonly label: string,
public readonly kind: MockTestRunProfileKind,
public readonly runHandler: (request: MockTestRunRequest, token: MockCancellationToken) => void | Promise<void>,
public readonly isDefault: boolean = false,
) {}
dispose(): void {
// No-op for mock
}
}
export interface MockTestRunRequest {
readonly include?: readonly MockTestItem[];
readonly exclude?: readonly MockTestItem[];
readonly profile?: MockTestRunProfile;
}
export class MockTestRunRequest implements MockTestRunRequest {
constructor(
public readonly include?: readonly MockTestItem[],
public readonly exclude?: readonly MockTestItem[],
public readonly profile?: MockTestRunProfile,
) {}
}
export interface MockTestRun {
readonly name?: string;
readonly token: MockCancellationToken;
appendOutput(output: string, location?: MockLocation, test?: MockTestItem): void;
end(): void;
enqueued(test: MockTestItem): void;
errored(test: MockTestItem, message: MockTestMessage | readonly MockTestMessage[], duration?: number): void;
failed(test: MockTestItem, message: MockTestMessage | readonly MockTestMessage[], duration?: number): void;
passed(test: MockTestItem, duration?: number): void;
skipped(test: MockTestItem): void;
started(test: MockTestItem): void;
}
export class MockTestRun implements MockTestRun {
public readonly token: MockCancellationToken;
private _ended: boolean = false;
constructor(
public readonly name?: string,
public readonly persist: boolean = true,
) {
this.token = new MockCancellationToken();
}
appendOutput(output: string, location?: MockLocation, test?: MockTestItem): void {
if (this._ended) return;
// For mock, just store output - in real VS Code this would appear in test output
}
end(): void {
this._ended = true;
}
enqueued(test: MockTestItem): void {
if (this._ended) return;
// Mock implementation
}
errored(test: MockTestItem, message: MockTestMessage | readonly MockTestMessage[], duration?: number): void {
if (this._ended) return;
// Mock implementation
}
failed(test: MockTestItem, message: MockTestMessage | readonly MockTestMessage[], duration?: number): void {
if (this._ended) return;
// Mock implementation
}
passed(test: MockTestItem, duration?: number): void {
if (this._ended) return;
// Mock implementation
}
skipped(test: MockTestItem): void {
if (this._ended) return;
// Mock implementation
}
started(test: MockTestItem): void {
if (this._ended) return;
// Mock implementation
}
}
export interface MockCancellationToken {
readonly isCancellationRequested: boolean;
onCancellationRequested(listener: () => void): MockDisposable;
}
export class MockCancellationToken implements MockCancellationToken {
private _isCancellationRequested: boolean = false;
private _listeners: (() => void)[] = [];
get isCancellationRequested(): boolean {
return this._isCancellationRequested;
}
onCancellationRequested(listener: () => void): MockDisposable {
this._listeners.push(listener);
return new MockDisposable(() => {
const index = this._listeners.indexOf(listener);
if (index >= 0) {
this._listeners.splice(index, 1);
}
});
}
cancel(): void {
this._isCancellationRequested = true;
this._listeners.forEach(listener => listener());
}
}
export interface MockDisposable {
dispose(): void;
}
export class MockDisposable implements MockDisposable {
constructor(private readonly disposeFunc?: () => void) {}
dispose(): void {
this.disposeFunc?.();
}
}
export interface MockTextDocument {
readonly uri: MockUri;
readonly fileName: string;
readonly isUntitled: boolean;
readonly languageId: string;
readonly version: number;
readonly isDirty: boolean;
readonly isClosed: boolean;
readonly eol: MockEndOfLine;
readonly lineCount: number;
getText(range?: MockRange): string;
getWordRangeAtPosition(position: MockPosition, regex?: RegExp): MockRange | undefined;
lineAt(line: number | MockPosition): MockTextLine;
offsetAt(position: MockPosition): number;
positionAt(offset: number): MockPosition;
save(): Promise<boolean>;
validatePosition(position: MockPosition): MockPosition;
validateRange(range: MockRange): MockRange;
}
export enum MockEndOfLine {
LF = 1,
CRLF = 2,
}
export interface MockTextLine {
readonly lineNumber: number;
readonly text: string;
readonly range: MockRange;
readonly rangeIncludingLineBreak: MockRange;
readonly firstNonWhitespaceCharacterIndex: number;
readonly isEmptyOrWhitespace: boolean;
}
export interface MockWorkspaceFolder {
readonly uri: MockUri;
readonly name: string;
readonly index: number;
}
export class MockWorkspaceFolder implements MockWorkspaceFolder {
constructor(
public readonly uri: MockUri,
public readonly name: string,
public readonly index: number = 0,
) {}
}
export interface MockFileSystemWatcher extends MockDisposable {
readonly ignoreCreateEvents: boolean;
readonly ignoreChangeEvents: boolean;
readonly ignoreDeleteEvents: boolean;
onDidCreate(listener: (uri: MockUri) => void): MockDisposable;
onDidChange(listener: (uri: MockUri) => void): MockDisposable;
onDidDelete(listener: (uri: MockUri) => void): MockDisposable;
}
export class MockFileSystemWatcher implements MockFileSystemWatcher {
public readonly ignoreCreateEvents: boolean = false;
public readonly ignoreChangeEvents: boolean = false;
public readonly ignoreDeleteEvents: boolean = false;
private _createListeners: ((uri: MockUri) => void)[] = [];
private _changeListeners: ((uri: MockUri) => void)[] = [];
private _deleteListeners: ((uri: MockUri) => void)[] = [];
onDidCreate(listener: (uri: MockUri) => void): MockDisposable {
this._createListeners.push(listener);
return new MockDisposable(() => {
const index = this._createListeners.indexOf(listener);
if (index >= 0) this._createListeners.splice(index, 1);
});
}
onDidChange(listener: (uri: MockUri) => void): MockDisposable {
this._changeListeners.push(listener);
return new MockDisposable(() => {
const index = this._changeListeners.indexOf(listener);
if (index >= 0) this._changeListeners.splice(index, 1);
});
}
onDidDelete(listener: (uri: MockUri) => void): MockDisposable {
this._deleteListeners.push(listener);
return new MockDisposable(() => {
const index = this._deleteListeners.indexOf(listener);
if (index >= 0) this._deleteListeners.splice(index, 1);
});
}
dispose(): void {
this._createListeners.length = 0;
this._changeListeners.length = 0;
this._deleteListeners.length = 0;
}
// Helper methods for testing
triggerCreate(uri: MockUri): void {
this._createListeners.forEach(listener => listener(uri));
}
triggerChange(uri: MockUri): void {
this._changeListeners.forEach(listener => listener(uri));
}
triggerDelete(uri: MockUri): void {
this._deleteListeners.forEach(listener => listener(uri));
}
}
export interface MockRelativePattern {
readonly base: string;
readonly pattern: string;
}
export class MockRelativePattern implements MockRelativePattern {
constructor(
public readonly base: string | MockWorkspaceFolder,
public readonly pattern: string,
) {}
get baseUri(): MockUri {
if (typeof this.base === "string") {
return MockUri.file(this.base);
}
return this.base.uri;
}
}
export interface MockConfiguration {
get<T>(section: string, defaultValue?: T): T | undefined;
has(section: string): boolean;
inspect<T>(section: string): MockConfigurationInspect<T> | undefined;
update(section: string, value: any, configurationTarget?: MockConfigurationTarget): Promise<void>;
}
export interface MockConfigurationInspect<T> {
readonly key: string;
readonly defaultValue?: T;
readonly globalValue?: T;
readonly workspaceValue?: T;
readonly workspaceFolderValue?: T;
}
export enum MockConfigurationTarget {
Global = 1,
Workspace = 2,
WorkspaceFolder = 3,
}
export class MockConfiguration implements MockConfiguration {
private _values = new Map<string, any>();
get<T>(section: string, defaultValue?: T): T | undefined {
return this._values.get(section) ?? defaultValue;
}
has(section: string): boolean {
return this._values.has(section);
}
inspect<T>(section: string): MockConfigurationInspect<T> | undefined {
return {
key: section,
defaultValue: undefined,
globalValue: this._values.get(section),
workspaceValue: undefined,
workspaceFolderValue: undefined,
};
}
async update(section: string, value: any, configurationTarget?: MockConfigurationTarget): Promise<void> {
this._values.set(section, value);
}
// Helper for testing
setValue(section: string, value: any): void {
this._values.set(section, value);
}
}

View File

@@ -1,56 +0,0 @@
import { mock } from "bun:test";
import {
MockConfiguration,
MockDisposable,
MockFileSystemWatcher,
MockLocation,
MockMarkdownString,
MockPosition,
MockRange,
MockRelativePattern,
MockTestController,
MockTestMessage,
MockTestRunProfileKind,
MockTestTag,
MockUri,
MockWorkspaceFolder,
} from "./vscode-types.mock";
mock.module("vscode", () => ({
window: {
createOutputChannel: () => ({
appendLine: () => {},
}),
visibleTextEditors: [],
},
workspace: {
getConfiguration: (section?: string) => new MockConfiguration(),
onDidOpenTextDocument: () => new MockDisposable(),
textDocuments: [],
createFileSystemWatcher: (pattern: string | MockRelativePattern) => new MockFileSystemWatcher(),
findFiles: async (include: string, exclude?: string, maxResults?: number, token?: any) => {
return []; // Mock implementation
},
},
Uri: MockUri,
TestTag: MockTestTag,
Position: MockPosition,
Range: MockRange,
Location: MockLocation,
TestMessage: MockTestMessage,
MarkdownString: MockMarkdownString,
TestRunProfileKind: MockTestRunProfileKind,
RelativePattern: MockRelativePattern,
debug: {
addBreakpoints: () => {},
startDebugging: async () => true,
},
}));
export function makeTestController(): MockTestController {
return new MockTestController("test-controller", "Test Controller");
}
export function makeWorkspaceFolder(path: string): MockWorkspaceFolder {
return new MockWorkspaceFolder(MockUri.file(path), path.split("/").pop() || "workspace", 0);
}

View File

@@ -17,7 +17,7 @@ export const debug = vscode.window.createOutputChannel("Bun - Test Runner");
export type TestNode = {
name: string;
type: "describe" | "test";
type: "describe" | "test" | "it";
line: number;
children: TestNode[];
parent?: TestNode;
@@ -51,15 +51,11 @@ export class BunTestController implements vscode.Disposable {
private currentRunType: "file" | "individual" = "file";
private requestedTestIds: Set<string> = new Set();
private discoveredTestIds: Set<string> = new Set();
private executedTestCount: number = 0;
private totalTestsStarted: number = 0;
constructor(
private readonly testController: vscode.TestController,
private readonly workspaceFolder: vscode.WorkspaceFolder,
readonly isTest: boolean = false,
) {
if (isTest) return;
this.setupTestController();
this.setupWatchers();
this.setupOpenDocumentListener();
@@ -71,7 +67,10 @@ export class BunTestController implements vscode.Disposable {
try {
this.signal = await this.createSignal();
await this.signal.ready;
debug.appendLine(`Signal initialized at: ${this.signal.url}`);
this.signal.on("Signal.Socket.connect", (socket: net.Socket) => {
debug.appendLine("Bun connected to signal socket");
this.handleSocketConnection(socket, this.currentRun!);
});
@@ -90,9 +89,8 @@ export class BunTestController implements vscode.Disposable {
};
this.testController.refreshHandler = async token => {
const files = await this.discoverInitialTests(token, false);
const files = await this.discoverInitialTests(token);
if (!files?.length) return;
if (token.isCancellationRequested) return;
const filePaths = new Set(files.map(f => f.fsPath));
for (const [, testItem] of this.testController.items) {
@@ -136,21 +134,15 @@ export class BunTestController implements vscode.Disposable {
}
private isTestFile(document: vscode.TextDocument): boolean {
return (
document?.uri?.scheme === "file" && /\.(test|spec)\.(js|jsx|ts|tsx|cjs|mjs|mts|cts)$/.test(document.uri.fsPath)
);
return document?.uri?.scheme === "file" && /\.(test|spec)\.(js|jsx|ts|tsx|cjs|mts)$/.test(document.uri.fsPath);
}
private async discoverInitialTests(
cancellationToken?: vscode.CancellationToken,
reset: boolean = true,
): Promise<vscode.Uri[] | undefined> {
private async discoverInitialTests(cancellationToken?: vscode.CancellationToken): Promise<vscode.Uri[] | undefined> {
try {
const tests = await this.findTestFiles(cancellationToken);
this.createFileTestItems(tests, reset);
this.createFileTestItems(tests);
return tests;
} catch (error) {
debug.appendLine(`Error in discoverInitialTests: ${error}`);
} catch {
return undefined;
}
}
@@ -187,8 +179,6 @@ export class BunTestController implements vscode.Disposable {
const ignoreGlobs = new Set(["**/node_modules/**"]);
for (const ignore of ignores) {
if (cancellationToken?.isCancellationRequested) return [];
try {
const content = await fs.readFile(ignore.fsPath, { encoding: "utf8" });
const lines = content
@@ -205,15 +195,13 @@ export class BunTestController implements vscode.Disposable {
ignoreGlobs.add(path.join(cwd.trim(), line.trim()));
}
}
} catch {
debug.appendLine(`Error in buildIgnoreGlobs: ${ignore.fsPath}`);
}
} catch {}
}
return [...ignoreGlobs.values()];
}
private createFileTestItems(files: vscode.Uri[], reset: boolean = true): void {
private createFileTestItems(files: vscode.Uri[]): void {
if (files.length === 0) {
return;
}
@@ -226,9 +214,7 @@ export class BunTestController implements vscode.Disposable {
path.relative(this.workspaceFolder.uri.fsPath, file.fsPath) || file.fsPath,
file,
);
if (reset) {
fileTestItem.children.replace([]);
}
fileTestItem.children.replace([]);
fileTestItem.canResolveChildren = true;
this.testController.items.add(fileTestItem);
}
@@ -288,13 +274,7 @@ export class BunTestController implements vscode.Disposable {
return { bunCommand, testArgs };
}
private async discoverTests(
testItem?: vscode.TestItem | false,
filePath?: string,
cancellationToken?: vscode.CancellationToken,
): Promise<void> {
if (cancellationToken?.isCancellationRequested) return;
private async discoverTests(testItem?: vscode.TestItem | false, filePath?: string): Promise<void> {
let targetPath = filePath;
if (!targetPath && testItem) {
targetPath = testItem?.uri?.fsPath || this.workspaceFolder.uri.fsPath;
@@ -317,24 +297,17 @@ export class BunTestController implements vscode.Disposable {
);
this.testController.items.add(fileTestItem);
}
if (!this.currentRun) {
fileTestItem.children.replace([]);
}
fileTestItem.children.replace([]);
fileTestItem.canResolveChildren = false;
this.addTestNodes(testNodes, fileTestItem, targetPath);
} catch {
debug.appendLine(`Error in discoverTests: ${targetPath}`);
}
} catch {}
}
private parseTestBlocks(fileContent: string): TestNode[] {
const cleanContent = fileContent
.replace(/\/\*[\s\S]*?\*\//g, match => match.replace(/[^\n\r]/g, " "))
.replace(/('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)|\/\/.*$/gm, (match, str) => {
if (str) return str;
return " ".repeat(match.length);
});
.replace(/\/\/.*$/gm, match => " ".repeat(match.length));
const testRegex =
/\b(describe|test|it)(?:\.(?:skip|todo|failing|only))?(?:\.(?:if|todoIf|skipIf)\s*\([^)]*\))?(?:\.each\s*\([^)]*\))?\s*\(\s*(['"`])((?:\\\2|.)*?)\2\s*(?:,|\))/g;
@@ -346,7 +319,6 @@ export class BunTestController implements vscode.Disposable {
match = testRegex.exec(cleanContent);
while (match !== null) {
const [full, type, , name] = match;
const _type = type === "it" ? "test" : type;
const line = cleanContent.slice(0, match.index).split("\n").length - 1;
while (
@@ -357,14 +329,7 @@ export class BunTestController implements vscode.Disposable {
stack.pop();
}
const expandedNodes = this.expandEachTests(
full,
name,
cleanContent,
match.index,
_type as TestNode["type"],
line,
);
const expandedNodes = this.expandEachTests(full, name, cleanContent, match.index, type as TestNode["type"], line);
for (const node of expandedNodes) {
if (stack.length === 0) {
@@ -468,16 +433,16 @@ export class BunTestController implements vscode.Disposable {
throw new Error("Not an array");
}
return eachValues.map((val, testIndex) => {
let testName = name.replace(/%%/g, "%").replace(/%#/g, (testIndex + 1).toString());
return eachValues.map(val => {
let testName = name;
if (Array.isArray(val)) {
let idx = 0;
testName = testName.replace(/%[isfdojp#%]/g, () => {
testName = testName.replace(/%[isfd]/g, () => {
const v = val[idx++];
return typeof v === "object" ? JSON.stringify(v) : String(v);
});
} else {
testName = testName.replace(/%[isfdojp#%]/g, () => {
testName = testName.replace(/%[isfd]/g, () => {
return typeof val === "object" ? JSON.stringify(val) : String(val);
});
}
@@ -510,22 +475,19 @@ export class BunTestController implements vscode.Disposable {
: this.escapeTestName(node.name);
const testId = `${filePath}#${nodePath}`;
let testItem = parent.children.get(testId);
if (!testItem) {
testItem = this.testController.createTestItem(testId, this.stripAnsi(node.name), vscode.Uri.file(filePath));
const testItem = this.testController.createTestItem(testId, this.stripAnsi(node.name), vscode.Uri.file(filePath));
if (node.type) testItem.tags = [new vscode.TestTag(node.type)];
testItem.tags = [new vscode.TestTag(node.type === "describe" ? "describe" : "test")];
if (typeof node.line === "number") {
testItem.range = new vscode.Range(
new vscode.Position(node.line, 0),
new vscode.Position(node.line, node.name.length),
);
}
parent.children.add(testItem);
if (typeof node.line === "number") {
testItem.range = new vscode.Range(
new vscode.Position(node.line, 0),
new vscode.Position(node.line, node.name.length),
);
}
parent.children.add(testItem);
if (node.children.length > 0) {
this.addTestNodes(node.children, testItem, filePath, nodePath);
}
@@ -538,7 +500,7 @@ export class BunTestController implements vscode.Disposable {
}
private escapeTestName(source: string): string {
return source.replace(/[^\w \-\u0080-\uFFFF]/g, "\\$&");
return source.replace(/[^a-zA-Z0-9_\ ]/g, "\\$&");
}
private async createSignal(): Promise<UnixSignal | TCPSocketSignal> {
@@ -555,23 +517,6 @@ export class BunTestController implements vscode.Disposable {
token: vscode.CancellationToken,
isDebug: boolean,
): Promise<void> {
if (this.currentRun) {
this.closeAllActiveProcesses();
this.disconnectInspector();
if (this.currentRun) {
this.currentRun.appendOutput("\n\x1b[33mCancelled: Starting new test run\x1b[0m\n");
this.currentRun.end();
this.currentRun = null;
}
}
this.totalTestsStarted++;
if (this.totalTestsStarted > 15) {
this.closeAllActiveProcesses();
this.disconnectInspector();
this.signal?.close();
this.signal = null;
}
const run = this.testController.createTestRun(request);
token.onCancellationRequested(() => {
@@ -580,14 +525,6 @@ export class BunTestController implements vscode.Disposable {
this.disconnectInspector();
});
if ("onDidDispose" in run) {
(run.onDidDispose as vscode.Event<void>)(() => {
run?.end?.();
this.closeAllActiveProcesses();
this.disconnectInspector();
});
}
const queue: vscode.TestItem[] = [];
if (request.include) {
@@ -610,9 +547,7 @@ export class BunTestController implements vscode.Disposable {
await this.runTestsWithInspector(queue, run, token);
} catch (error) {
for (const test of queue) {
const msg = new vscode.TestMessage(`Error: ${error}`);
msg.location = new vscode.Location(test.uri!, test.range || new vscode.Range(0, 0, 0, 0));
run.errored(test, msg);
run.errored(test, new vscode.TestMessage(`Error: ${error}`));
}
} finally {
run.end();
@@ -622,11 +557,8 @@ export class BunTestController implements vscode.Disposable {
private async runTestsWithInspector(
tests: vscode.TestItem[],
run: vscode.TestRun,
token: vscode.CancellationToken,
_token: vscode.CancellationToken,
): Promise<void> {
const time = performance.now();
if (token.isCancellationRequested) return;
this.disconnectInspector();
const allFiles = new Set<string>();
@@ -637,20 +569,13 @@ export class BunTestController implements vscode.Disposable {
}
if (allFiles.size === 0) {
const errorMsg = "No test files found to run.";
run.appendOutput(`\x1b[31mError: ${errorMsg}\x1b[0m\n`);
for (const test of tests) {
const msg = new vscode.TestMessage(errorMsg);
msg.location = new vscode.Location(test.uri!, test.range || new vscode.Range(0, 0, 0, 0));
run.errored(test, msg);
}
throw new Error(errorMsg);
run.appendOutput("No test files found to run.\n");
return;
}
for (const test of tests) {
if (token.isCancellationRequested) return;
if (test.uri && test.canResolveChildren) {
await this.discoverTests(test, undefined, token);
await this.discoverTests(test);
}
}
@@ -659,7 +584,6 @@ export class BunTestController implements vscode.Disposable {
this.requestedTestIds.clear();
this.discoveredTestIds.clear();
this.executedTestCount = 0;
for (const test of tests) {
this.requestedTestIds.add(test.id);
}
@@ -683,38 +607,21 @@ export class BunTestController implements vscode.Disposable {
resolve();
};
const handleCancel = () => {
clearTimeout(timeout);
this.signal!.off("Signal.Socket.connect", handleConnect);
reject(new Error("Test run cancelled"));
};
token.onCancellationRequested(handleCancel);
this.signal!.once("Signal.Socket.connect", handleConnect);
});
const { bunCommand, testArgs } = this.getBunExecutionConfig();
let args = [...testArgs, ...allFiles];
let printedArgs = `\x1b[34;1m>\x1b[0m \x1b[34;1m${bunCommand} ${testArgs.join(" ")}\x1b[2m`;
for (const file of allFiles) {
const f = path.relative(this.workspaceFolder.uri.fsPath, file) || file;
if (f.includes(" ")) {
printedArgs += ` ".${path.sep}${f}"`;
} else {
printedArgs += ` .${path.sep}${f}`;
}
}
let args = [...testArgs, ...Array.from(allFiles)];
if (isIndividualTestRun) {
const pattern = this.buildTestNamePattern(tests);
if (pattern) {
args.push("--test-name-pattern", pattern);
printedArgs += `\x1b[0m\x1b[2m --test-name-pattern "${pattern}"\x1b[0m`;
args.push("--test-name-pattern", process.platform === "win32" ? `"${pattern}"` : pattern);
}
}
run.appendOutput(printedArgs + "\x1b[0m\r\n\r\n");
run.appendOutput(`\r\n\x1b[34m>\x1b[0m \x1b[2m${bunCommand} ${args.join(" ")}\x1b[0m\r\n\r\n`);
args.push(`--inspect-wait=${this.signal!.url}`);
for (const test of tests) {
if (isIndividualTestRun || tests.length === 1) {
@@ -724,52 +631,34 @@ export class BunTestController implements vscode.Disposable {
}
}
let inspectorUrl: string | undefined =
this.signal.url.startsWith("ws") || this.signal.url.startsWith("tcp")
? `${this.signal!.url}?wait=1`
: `${this.signal!.url}`;
// right now there isnt a way to tell socket method to wait for the connection
if (!inspectorUrl?.includes("?wait=1")) {
args.push(`--inspect-wait=${this.signal!.url}`);
inspectorUrl = undefined;
}
const proc = spawn(bunCommand, args, {
cwd: this.workspaceFolder.uri.fsPath,
env: {
...process.env,
BUN_DEBUG_QUIET_LOGS: "1",
FORCE_COLOR: "1",
BUN_INSPECT: inspectorUrl,
...process.env,
NO_COLOR: "0",
},
});
this.activeProcesses.add(proc);
let stdout = "";
proc.on("exit", (code, signal) => {
if (code !== 0 && code !== 1) {
debug.appendLine(`Test process failed: exit ${code}, signal ${signal}`);
}
debug.appendLine(`Process exited with code ${code}, signal ${signal}`);
});
proc.on("error", error => {
stdout += `Process error: ${error.message}\n`;
debug.appendLine(`Process error: ${error.message}`);
});
proc.stdout?.on("data", data => {
const dataStr = data.toString();
stdout += dataStr;
const formattedOutput = dataStr.replace(/\n/g, "\r\n");
run.appendOutput(formattedOutput);
});
proc.stderr?.on("data", data => {
const dataStr = data.toString();
stdout += dataStr;
const formattedOutput = dataStr.replace(/\n/g, "\r\n");
run.appendOutput(formattedOutput);
});
@@ -777,57 +666,35 @@ export class BunTestController implements vscode.Disposable {
try {
await socketPromise;
} catch (error) {
debug.appendLine(`Connection failed: ${error} (URL: ${this.signal!.url})`);
debug.appendLine(`Failed to establish inspector connection: ${error}`);
debug.appendLine(`Signal URL was: ${this.signal!.url}`);
debug.appendLine(`Command was: ${bunCommand} ${args.join(" ")}`);
throw error;
}
await new Promise<void>((resolve, reject) => {
const handleClose = (code: number | null) => {
proc.on("close", code => {
this.activeProcesses.delete(proc);
if (code === 0 || code === 1) {
resolve();
} else {
reject(new Error(`Process exited with code ${code}. Please check the console for more details.`));
reject(new Error(`Process exited with code ${code}`));
}
};
});
const handleError = (error: Error) => {
proc.on("error", error => {
this.activeProcesses.delete(proc);
reject(error);
};
const handleCancel = () => {
proc.kill("SIGTERM");
this.activeProcesses.delete(proc);
reject(new Error("Test run cancelled"));
};
proc.on("close", handleClose);
proc.on("error", handleError);
token.onCancellationRequested(handleCancel);
});
}).finally(() => {
if (this.discoveredTestIds.size === 0) {
const errorMsg =
"No tests were executed. This could mean:\r\n- All tests were filtered out\r\n- The test runner crashed before running tests\r\n- No tests match the pattern";
run.appendOutput(`\n\x1b[31m\x1b[1mError:\x1b[0m\x1b[31m ${errorMsg}\x1b[0m\n`);
for (const test of tests) {
if (!this.testResultHistory.has(test.id)) {
const msg = new vscode.TestMessage(errorMsg + "\n\n----------\n" + stdout + "\n----------\n");
msg.location = new vscode.Location(test.uri!, test.range || new vscode.Range(0, 0, 0, 0));
run.errored(test, msg);
}
}
if (isIndividualTestRun) {
this.applyPreviousResults(tests, run);
}
if (this.discoveredTestIds.size > 0 && this.executedTestCount > 0) {
if (isIndividualTestRun) {
this.applyPreviousResults(tests, run);
this.cleanupUndiscoveredTests(tests);
} else {
this.cleanupStaleTests(tests);
}
if (isIndividualTestRun) {
this.cleanupUndiscoveredTests(tests);
} else {
this.cleanupStaleTests(tests);
}
if (this.activeProcesses.has(proc)) {
@@ -837,7 +704,6 @@ export class BunTestController implements vscode.Disposable {
this.disconnectInspector();
this.currentRun = null;
debug.appendLine(`Test run completed in ${performance.now() - time}ms`);
});
}
@@ -859,7 +725,7 @@ export class BunTestController implements vscode.Disposable {
run.passed(item, previousResult.duration);
break;
case "failed":
run.failed(item, [], previousResult.duration);
run.failed(item, previousResult.message || new vscode.TestMessage("Test failed"), previousResult.duration);
break;
case "skipped":
run.skipped(item);
@@ -897,11 +763,16 @@ export class BunTestController implements vscode.Disposable {
this.handleLifecycleError(event, run);
});
this.debugAdapter.on("Inspector.event", e => {
debug.appendLine(`Received inspector event: ${e.method}`);
});
this.debugAdapter.on("Inspector.error", e => {
debug.appendLine(`Inspector error: ${e}`);
});
socket.on("close", () => {
debug.appendLine("Inspector connection closed");
this.debugAdapter = null;
});
@@ -928,6 +799,7 @@ export class BunTestController implements vscode.Disposable {
const { id: inspectorTestId, url: sourceURL, name, type, parentId, line } = params;
if (!sourceURL) {
debug.appendLine(`Warning: Test found without URL: ${name}`);
return;
}
@@ -942,6 +814,8 @@ export class BunTestController implements vscode.Disposable {
this.inspectorToVSCode.set(inspectorTestId, testItem);
this.vscodeToInspector.set(testItem.id, inspectorTestId);
this.discoveredTestIds.add(testItem.id);
} else {
debug.appendLine(`Could not find VS Code test item for: ${name} in ${path.basename(filePath)}`);
}
}
@@ -1057,7 +931,6 @@ export class BunTestController implements vscode.Disposable {
if (!testItem) return;
const duration = elapsed / 1000000;
this.executedTestCount++;
if (
this.currentRunType === "individual" &&
@@ -1086,6 +959,7 @@ export class BunTestController implements vscode.Disposable {
break;
case "skip":
case "todo":
case "skipped_because_label":
run.skipped(testItem);
this.testResultHistory.set(testItem.id, { status: "skipped" });
break;
@@ -1096,8 +970,6 @@ export class BunTestController implements vscode.Disposable {
run.failed(testItem, timeoutMsg, duration);
this.testResultHistory.set(testItem.id, { status: "failed", message: timeoutMsg, duration });
break;
case "skipped_because_label":
break;
}
}
@@ -1206,10 +1078,7 @@ export class BunTestController implements vscode.Disposable {
const lines = messageLinesRaw;
const errorLine = lines[0].trim();
const messageLines = lines
.slice(1)
.filter(line => line.trim())
.join("\n");
const messageLines = lines.slice(1).join("\n");
const errorType = errorLine.replace(/^(E|e)rror: /, "").trim();
@@ -1221,8 +1090,8 @@ export class BunTestController implements vscode.Disposable {
const regex = /^Expected:\s*([\s\S]*?)\nReceived:\s*([\s\S]*?)$/;
let testMessage = vscode.TestMessage.diff(
errorLine,
messageLines.trim().match(regex)?.[1].trim() || "",
messageLines.trim().match(regex)?.[2].trim() || "",
messageLines.match(regex)?.[1].trim() || "",
messageLines.match(regex)?.[2].trim() || "",
);
if (!messageLines.match(regex)) {
const code = messageLines
@@ -1284,7 +1153,7 @@ export class BunTestController implements vscode.Disposable {
lastEffortMsg = lastEffortMsg.reverse();
}
const msg = errorType.startsWith("expect")
const msg = errorLine.startsWith("error: expect")
? `${lastEffortMsg.join("\n")}\n${errorLine.trim()}`.trim()
: `${errorLine.trim()}\n${messageLines}`.trim();
@@ -1332,15 +1201,12 @@ export class BunTestController implements vscode.Disposable {
t = t.replaceAll(/\$\{[^}]+\}/g, ".*?");
t = t.replaceAll(/\\\$\\\{[^}]+\\\}/g, ".*?");
t = t.replaceAll(/\\%[isfdojp#%]|(\\%)|(\\#)/g, ".*?");
t = t.replaceAll(/\$[\w\.\[\]]+/g, ".*?");
t = t.replaceAll(/\\%[isfd]/g, ".*?");
if (test?.tags?.some(tag => tag.id === "test" || tag.id === "it")) {
if (test.tags.some(tag => tag.id === "test" || tag.id === "it")) {
testNames.push(`^ ${t}$`);
} else if (test?.tags?.some(tag => tag.id === "describe")) {
testNames.push(`^ ${t} `);
} else {
testNames.push(t);
testNames.push(`^ ${t} `);
}
}
@@ -1376,13 +1242,7 @@ export class BunTestController implements vscode.Disposable {
const isIndividualTestRun = this.shouldUseTestNamePattern(tests);
if (testFiles.size === 0) {
const errorMsg = "No test files found to debug.";
run.appendOutput(`\x1b[31mError: ${errorMsg}\x1b[0m\n`);
for (const test of tests) {
const msg = new vscode.TestMessage(errorMsg);
msg.location = new vscode.Location(test.uri!, test.range || new vscode.Range(0, 0, 0, 0));
run.errored(test, msg);
}
run.appendOutput("No test files found to debug.\n");
run.end();
return;
}
@@ -1408,7 +1268,7 @@ export class BunTestController implements vscode.Disposable {
const pattern = this.buildTestNamePattern(tests);
if (pattern) {
args.push("--test-name-pattern", pattern);
args.push("--test-name-pattern", process.platform === "win32" ? `"${pattern}"` : pattern);
}
}
@@ -1429,12 +1289,9 @@ export class BunTestController implements vscode.Disposable {
if (!res) throw new Error("Failed to start debugging session");
} catch (error) {
for (const test of tests) {
const msg = new vscode.TestMessage(`Error starting debugger: ${error}`);
msg.location = new vscode.Location(test.uri!, test.range || new vscode.Range(0, 0, 0, 0));
run.errored(test, msg);
run.errored(test, new vscode.TestMessage(`Error starting debugger: ${error}`));
}
}
run.appendOutput("\n\x1b[33mDebug session started. Please open the debug console to see its output.\x1b[0m\r\n");
run.end();
}
@@ -1461,32 +1318,6 @@ export class BunTestController implements vscode.Disposable {
}
this.disposables = [];
}
// a sus way to expose internal functions to the test suite
public get _internal() {
return {
expandEachTests: this.expandEachTests.bind(this),
parseTestBlocks: this.parseTestBlocks.bind(this),
getBraceDepth: this.getBraceDepth.bind(this),
buildTestNamePattern: this.buildTestNamePattern.bind(this),
stripAnsi: this.stripAnsi.bind(this),
processErrorData: this.processErrorData.bind(this),
escapeTestName: this.escapeTestName.bind(this),
shouldUseTestNamePattern: this.shouldUseTestNamePattern.bind(this),
isTestFile: this.isTestFile.bind(this),
customFilePattern: this.customFilePattern.bind(this),
getBunExecutionConfig: this.getBunExecutionConfig.bind(this),
findTestByPath: this.findTestByPath.bind(this),
findTestByName: this.findTestByName.bind(this),
createTestItem: this.createTestItem.bind(this),
createErrorMessage: this.createErrorMessage.bind(this),
cleanupTestItem: this.cleanupTestItem.bind(this),
};
}
}
function windowsVscodeUri(uri: string): string {

View File

@@ -7,14 +7,8 @@ export async function registerTests(context: vscode.ExtensionContext) {
return;
}
const config = vscode.workspace.getConfiguration("bun.test");
const enable = config.get<boolean>("enable", true);
if (!enable) {
return;
}
try {
const controller = vscode.tests.createTestController("bun", "Bun Tests");
const controller = vscode.tests.createTestController("bun-tests", "Bun Tests");
context.subscriptions.push(controller);
const bunTestController = new BunTestController(controller, workspaceFolder);

View File

@@ -2,8 +2,8 @@
+++ CMakeLists.txt
@@ -1,5 +1,5 @@
#
-cmake_minimum_required(VERSION 3.17 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.17...3.30 FATAL_ERROR)
PROJECT(libarchive C)
#
-CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12 FATAL_ERROR)
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12...3.5 FATAL_ERROR)
if(POLICY CMP0065)
cmake_policy(SET CMP0065 NEW) #3.4 don't use `-rdynamic` with executables
endif()

View File

@@ -1,29 +1,22 @@
--- a/libarchive/archive_write_add_filter_gzip.c 2025-07-21 06:29:58.505101515 +0000
+++ b/libarchive/archive_write_add_filter_gzip.c 2025-07-21 06:44:09.023676935 +0000
@@ -59,12 +59,13 @@
--- a/libarchive/archive_write_add_filter_gzip.c
+++ b/libarchive/archive_write_add_filter_gzip.c
@@ -58,6 +58,7 @@ archive_write_set_compression_gzip(struct archive *a)
struct private_data {
int compression_level;
int timestamp;
char *original_filename;
+ unsigned char os;
+ unsigned char os;
#ifdef HAVE_ZLIB_H
z_stream stream;
int64_t total_in;
unsigned char *compressed;
size_t compressed_buffer_size;
- unsigned long crc;
+ uint32_t crc;
#else
struct archive_write_program_data *pdata;
#endif
@@ -108,6 +109,7 @@
@@ -106,6 +107,7 @@ archive_write_add_filter_gzip(struct archive *_a)
archive_set_error(&a->archive, ENOMEM, "Out of memory");
return (ARCHIVE_FATAL);
}
f->data = data;
+ data->os = 3; /* default Unix */
f->data = data;
f->open = &archive_compressor_gzip_open;
f->options = &archive_compressor_gzip_options;
f->close = &archive_compressor_gzip_close;
@@ -177,6 +179,30 @@
@@ -166,6 +168,30 @@ archive_compressor_gzip_options(struct archive_write_filter *f, const char *key,
return (ARCHIVE_OK);
}
@@ -54,7 +47,7 @@
/* Note: The "warn" return is just to inform the options
* supervisor that we didn't handle it. It will generate
* a suitable error if no one used this option. */
@@ -236,7 +262,7 @@
@@ -226,7 +252,7 @@ archive_compressor_gzip_open(struct archive_write_filter *f)
data->compressed[8] = 4;
else
data->compressed[8] = 0;

View File

@@ -1,4 +1,4 @@
# Version: 10
# Version: 9
# A script that installs the dependencies needed to build and test Bun.
# This should work on Windows 10 or newer with PowerShell.

View File

@@ -1,5 +1,5 @@
#!/bin/sh
# Version: 17
# Version: 15
# A script that installs the dependencies needed to build and test Bun.
# This should work on macOS and Linux with a POSIX shell.
@@ -1510,12 +1510,12 @@ configure_core_dumps() {
# disable apport.service if it exists since it will override the core_pattern
if which systemctl >/dev/null; then
if systemctl list-unit-files apport.service >/dev/null; then
execute_sudo "$systemctl" disable --now apport.service
execute_sudo "$systemctl" disable --now apport.service || true
fi
fi
# load the new configuration (ignore permission errors)
execute_sudo sysctl -p "$sysctl_file"
execute_sudo sysctl -p "$sysctl_file" || true
# ensure that a regular user will be able to run sysctl
if [ -d /sbin ]; then
@@ -1538,17 +1538,6 @@ clean_system() {
done
}
ensure_no_tmpfs() {
if ! [ "$os" = "linux" ]; then
return
fi
if ! [ "$distro" = "ubuntu" ]; then
return
fi
execute_sudo systemctl mask tmp.mount
}
main() {
check_features "$@"
check_operating_system
@@ -1566,7 +1555,6 @@ main() {
configure_core_dumps
fi
clean_system
ensure_no_tmpfs
}
main "$@"

View File

@@ -44,21 +44,17 @@ if (!fs.existsSync(join(dir, "bun-profile")) || !fs.existsSync(join(dir, `bun-${
await Bun.$`bash -c ${`age -d -i <(echo "$AGE_CORES_IDENTITY")`} < ${cores} | tar -zxvC ${dir}`;
console.log("moving cores out of nested directory");
for await (const file of new Bun.Glob("bun-cores-*/*.core").scan(dir)) {
for await (const file of new Bun.Glob("bun-cores-*/bun-*.core").scan(dir)) {
fs.renameSync(join(dir, file), join(dir, basename(file)));
}
} else {
console.log(`already downloaded in ${dir}`);
}
const desiredCore = join(dir, (await new Bun.Glob(`*${pid}.core`).scan(dir).next()).value);
const args = [debuggerPath, "--core", desiredCore, join(dir, "bun-profile")];
console.log("launching debugger:");
console.log(args.map(Bun.$.escape).join(" "));
console.log(`${debuggerPath} --core ${join(dir, `bun-${pid}.core`)} ${join(dir, "bun-profile")}`);
const proc = Bun.spawn(args, {
const proc = await Bun.spawn([debuggerPath, "--core", join(dir, `bun-${pid}.core`), join(dir, "bun-profile")], {
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",

View File

@@ -20,7 +20,7 @@ async function globSources(output, patterns, excludes = []) {
const sources =
paths
.map(path => normalize(relative(root, path).replaceAll("\\", "/")))
.map(path => normalize(relative(root, path)))
.sort((a, b) => a.localeCompare(b))
.join("\n")
.trim() + "\n";

View File

@@ -1,109 +0,0 @@
/**
* p-limit@6.2.0
* https://github.com/sindresorhus/p-limit
* MIT (c) Sindre Sorhus
*/
import Queue from "./yocto-queue.mjs";
export default function pLimit(concurrency) {
validateConcurrency(concurrency);
const queue = new Queue();
let activeCount = 0;
const resumeNext = () => {
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
// Since `pendingCount` has been decreased by one, increase `activeCount` by one.
activeCount++;
}
};
const next = () => {
activeCount--;
resumeNext();
};
const run = async (function_, resolve, arguments_) => {
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {}
next();
};
const enqueue = (function_, resolve, arguments_) => {
// Queue `internalResolve` instead of the `run` function
// to preserve asynchronous context.
new Promise(internalResolve => {
queue.enqueue(internalResolve);
}).then(run.bind(undefined, function_, resolve, arguments_));
(async () => {
// This function needs to wait until the next microtask before comparing
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
// after the `internalResolve` function is dequeued and called. The comparison in the if-statement
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
await Promise.resolve();
if (activeCount < concurrency) {
resumeNext();
}
})();
};
const generator = (function_, ...arguments_) =>
new Promise(resolve => {
enqueue(function_, resolve, arguments_);
});
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount,
},
pendingCount: {
get: () => queue.size,
},
clearQueue: {
value() {
queue.clear();
},
},
concurrency: {
get: () => concurrency,
set(newConcurrency) {
validateConcurrency(newConcurrency);
concurrency = newConcurrency;
queueMicrotask(() => {
// eslint-disable-next-line no-unmodified-loop-condition
while (activeCount < concurrency && queue.size > 0) {
resumeNext();
}
});
},
},
});
return generator;
}
export function limitFunction(function_, option) {
const { concurrency } = option;
const limit = pLimit(concurrency);
return (...arguments_) => limit(() => function_(...arguments_));
}
function validateConcurrency(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
}
}

View File

@@ -20,7 +20,6 @@ import {
readdirSync,
readFileSync,
realpathSync,
rmSync,
statSync,
symlinkSync,
unlink,
@@ -28,10 +27,9 @@ import {
writeFileSync,
} from "node:fs";
import { readFile } from "node:fs/promises";
import { availableParallelism, userInfo } from "node:os";
import { userInfo } from "node:os";
import { basename, dirname, extname, join, relative, sep } from "node:path";
import { parseArgs } from "node:util";
import pLimit from "./p-limit.mjs";
import {
getAbi,
getAbiVersion,
@@ -64,7 +62,6 @@ import {
unzip,
uploadArtifact,
} from "./utils.mjs";
let isQuiet = false;
const cwd = import.meta.dirname ? dirname(import.meta.dirname) : process.cwd();
const testsPath = join(cwd, "test");
@@ -155,10 +152,6 @@ const { values: options, positionals: filters } = parseArgs({
type: "boolean",
default: isBuildkite && isLinux,
},
["parallel"]: {
type: "boolean",
default: false,
},
},
});
@@ -347,10 +340,6 @@ async function runTests() {
const failedResults = [];
const maxAttempts = 1 + (parseInt(options["retries"]) || 0);
const parallelism = options["parallel"] ? availableParallelism() : 1;
console.log("parallelism", parallelism);
const limit = pLimit(parallelism);
/**
* @param {string} title
* @param {function} fn
@@ -360,21 +349,17 @@ async function runTests() {
const index = ++i;
let result, failure, flaky;
let attempt = 1;
for (; attempt <= maxAttempts; attempt++) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
if (attempt > 1) {
await new Promise(resolve => setTimeout(resolve, 5000 + Math.random() * 10_000));
}
let grouptitle = `${getAnsi("gray")}[${index}/${total}]${getAnsi("reset")} ${title}`;
if (attempt > 1) grouptitle += ` ${getAnsi("gray")}[attempt #${attempt}]${getAnsi("reset")}`;
if (parallelism > 1) {
console.log(grouptitle);
result = await fn();
} else {
result = await startGroup(grouptitle, fn);
}
result = await startGroup(
attempt === 1
? `${getAnsi("gray")}[${index}/${total}]${getAnsi("reset")} ${title}`
: `${getAnsi("gray")}[${index}/${total}]${getAnsi("reset")} ${title} ${getAnsi("gray")}[attempt #${attempt}]${getAnsi("reset")}`,
fn,
);
const { ok, stdoutPreview, error } = result;
if (ok) {
@@ -389,7 +374,6 @@ async function runTests() {
const color = attempt >= maxAttempts ? "red" : "yellow";
const label = `${getAnsi(color)}[${index}/${total}] ${title} - ${error}${getAnsi("reset")}`;
startGroup(label, () => {
if (parallelism > 1) return;
process.stderr.write(stdoutPreview);
});
@@ -410,15 +394,14 @@ async function runTests() {
// Group flaky tests together, regardless of the title
const context = flaky ? "flaky" : title;
const style = flaky || title.startsWith("vendor") ? "warning" : "error";
if (!flaky) attempt = 1; // no need to show the retries count on failures, we know it maxed out
if (title.startsWith("vendor")) {
const content = formatTestToMarkdown({ ...failure, testPath: title }, false, attempt - 1);
const content = formatTestToMarkdown({ ...failure, testPath: title });
if (content) {
reportAnnotationToBuildKite({ context, label: title, content, style });
}
} else {
const content = formatTestToMarkdown(failure, false, attempt - 1);
const content = formatTestToMarkdown(failure);
if (content) {
reportAnnotationToBuildKite({ context, label: title, content, style });
}
@@ -428,10 +411,10 @@ async function runTests() {
if (isGithubAction) {
const summaryPath = process.env["GITHUB_STEP_SUMMARY"];
if (summaryPath) {
const longMarkdown = formatTestToMarkdown(failure, false, attempt - 1);
const longMarkdown = formatTestToMarkdown(failure);
appendFileSync(summaryPath, longMarkdown);
}
const shortMarkdown = formatTestToMarkdown(failure, true, attempt - 1);
const shortMarkdown = formatTestToMarkdown(failure, true);
appendFileSync("comment.md", shortMarkdown);
}
@@ -450,62 +433,48 @@ async function runTests() {
}
if (!failedResults.length) {
await Promise.all(
tests.map(testPath =>
limit(() => {
const absoluteTestPath = join(testsPath, testPath);
const title = relative(cwd, absoluteTestPath).replaceAll(sep, "/");
if (isNodeTest(testPath)) {
const testContent = readFileSync(absoluteTestPath, "utf-8");
const runWithBunTest =
title.includes("needs-test") || testContent.includes("bun:test") || testContent.includes("node:test");
const subcommand = runWithBunTest ? "test" : "run";
const env = {
FORCE_COLOR: "0",
NO_COLOR: "1",
BUN_DEBUG_QUIET_LOGS: "1",
};
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateExceptions(testPath)) {
env.BUN_JSC_validateExceptionChecks = "1";
}
return runTest(title, async () => {
const { ok, error, stdout } = await spawnBun(execPath, {
cwd: cwd,
args: [
subcommand,
"--config=" + join(import.meta.dirname, "../bunfig.node-test.toml"),
absoluteTestPath,
],
timeout: getNodeParallelTestTimeout(title),
env,
stdout: parallelism > 1 ? () => {} : chunk => pipeTestStdout(process.stdout, chunk),
stderr: parallelism > 1 ? () => {} : chunk => pipeTestStdout(process.stderr, chunk),
});
const mb = 1024 ** 3;
const stdoutPreview = stdout.slice(0, mb).split("\n").slice(0, 50).join("\n");
return {
testPath: title,
ok: ok,
status: ok ? "pass" : "fail",
error: error,
errors: [],
tests: [],
stdout: stdout,
stdoutPreview: stdoutPreview,
};
});
} else {
return runTest(title, async () =>
spawnBunTest(execPath, join("test", testPath), {
cwd,
stdout: parallelism > 1 ? () => {} : chunk => pipeTestStdout(process.stdout, chunk),
stderr: parallelism > 1 ? () => {} : chunk => pipeTestStdout(process.stderr, chunk),
}),
);
}
}),
),
);
for (const testPath of tests) {
const absoluteTestPath = join(testsPath, testPath);
const title = relative(cwd, absoluteTestPath).replaceAll(sep, "/");
if (isNodeTest(testPath)) {
const testContent = readFileSync(absoluteTestPath, "utf-8");
const runWithBunTest =
title.includes("needs-test") || testContent.includes("bun:test") || testContent.includes("node:test");
const subcommand = runWithBunTest ? "test" : "run";
const env = {
FORCE_COLOR: "0",
NO_COLOR: "1",
BUN_DEBUG_QUIET_LOGS: "1",
};
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateExceptions(testPath)) {
env.BUN_JSC_validateExceptionChecks = "1";
}
await runTest(title, async () => {
const { ok, error, stdout } = await spawnBun(execPath, {
cwd: cwd,
args: [subcommand, "--config=" + join(import.meta.dirname, "../bunfig.node-test.toml"), absoluteTestPath],
timeout: getNodeParallelTestTimeout(title),
env,
stdout: chunk => pipeTestStdout(process.stdout, chunk),
stderr: chunk => pipeTestStdout(process.stderr, chunk),
});
const mb = 1024 ** 3;
const stdoutPreview = stdout.slice(0, mb).split("\n").slice(0, 50).join("\n");
return {
testPath: title,
ok: ok,
status: ok ? "pass" : "fail",
error: error,
errors: [],
tests: [],
stdout: stdout,
stdoutPreview: stdoutPreview,
};
});
} else {
await runTest(title, async () => spawnBunTest(execPath, join("test", testPath)));
}
}
}
if (vendorTests?.length) {
@@ -547,7 +516,7 @@ async function runTests() {
if (isGithubAction) {
reportOutputToGitHubAction("failing_tests_count", failedResults.length);
const markdown = formatTestToMarkdown(failedResults, false, 0);
const markdown = formatTestToMarkdown(failedResults);
reportOutputToGitHubAction("failing_tests", markdown);
}
@@ -1009,11 +978,11 @@ async function spawnBun(execPath, { args, cwd, timeout, env, stdout, stderr }) {
stderr,
});
} finally {
try {
rmSync(tmpdirPath, { recursive: true, force: true });
} catch (error) {
console.warn(error);
}
// try {
// rmSync(tmpdirPath, { recursive: true, force: true });
// } catch (error) {
// console.warn(error);
// }
}
}
@@ -1089,7 +1058,7 @@ async function spawnBunTest(execPath, testPath, options = { cwd }) {
const env = {
GITHUB_ACTIONS: "true", // always true so annotations are parsed
};
if ((basename(execPath).includes("asan") || !isCI) && shouldValidateExceptions(relative(cwd, absPath))) {
if (basename(execPath).includes("asan") && shouldValidateExceptions(relative(cwd, absPath))) {
env.BUN_JSC_validateExceptionChecks = "1";
}
@@ -1098,8 +1067,8 @@ async function spawnBunTest(execPath, testPath, options = { cwd }) {
cwd: options["cwd"],
timeout: isReallyTest ? timeout : 30_000,
env,
stdout: options.stdout,
stderr: options.stderr,
stdout: chunk => pipeTestStdout(process.stdout, chunk),
stderr: chunk => pipeTestStdout(process.stderr, chunk),
});
const { tests, errors, stdout: stdoutPreview } = parseTestStdout(stdout, testPath);
@@ -1761,10 +1730,9 @@ function getTestLabel() {
/**
* @param {TestResult | TestResult[]} result
* @param {boolean} concise
* @param {number} retries
* @returns {string}
*/
function formatTestToMarkdown(result, concise, retries) {
function formatTestToMarkdown(result, concise) {
const results = Array.isArray(result) ? result : [result];
const buildLabel = getTestLabel();
const buildUrl = getBuildUrl();
@@ -1808,9 +1776,6 @@ function formatTestToMarkdown(result, concise, retries) {
if (platform) {
markdown += ` on ${platform}`;
}
if (retries > 0) {
markdown += ` (${retries} ${retries === 1 ? "retry" : "retries"})`;
}
if (concise) {
markdown += "</li>\n";

70
scripts/sort-imports.ts → scripts/sortImports.ts Executable file → Normal file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env bun
import { readdirSync } from "fs";
import path from "path";
@@ -17,9 +16,10 @@ const usage = String.raw`
Usage: bun scripts/sortImports [options] <files...>
Options:
--help Show this help message
--include-pub Also sort ${"`pub`"} imports
--keep-unused Don't remove unused imports
--help Show this help message
--no-include-pub Exclude pub imports from sorting
--no-remove-unused Don't remove unused imports
--include-unsorted Process files even if they don't have @sortImports marker
Examples:
bun scripts/sortImports src
@@ -34,9 +34,9 @@ if (filePaths.length === 0) {
}
const config = {
includePub: args.includes("--include-pub"),
removeUnused: !args.includes("--keep-unused"),
normalizePaths: "./",
includePub: !args.includes("--no-include-pub"),
removeUnused: !args.includes("--no-remove-unused"),
includeUnsorted: args.includes("--include-unsorted"),
};
// Type definitions
@@ -68,11 +68,11 @@ function parseDeclarations(
const line = lines[i];
if (line === "// @sortImports") {
lines[i] = DELETED_LINE;
lines[i] = "";
continue;
}
const inlineDeclPattern = /^(?:pub )?const ([a-zA-Z0-9_]+) = (.+);(\s*\/\/[^\n]*)?$/;
const inlineDeclPattern = /^(?:pub )?const ([a-zA-Z0-9_]+) = (.+);$/;
const match = line.match(inlineDeclPattern);
if (!match) continue;
@@ -275,6 +275,8 @@ function sortGroupsAndDeclarations(groups: Map<string, Group>): string[] {
// Generate the sorted output
function generateSortedOutput(lines: string[], groups: Map<string, Group>, sortedGroupKeys: string[]): string[] {
const outputLines = [...lines];
outputLines.push("");
outputLines.push("// @sortImports");
for (const groupKey of sortedGroupKeys) {
const groupDeclarations = groups.get(groupKey)!;
@@ -286,36 +288,22 @@ function generateSortedOutput(lines: string[], groups: Map<string, Group>, sorte
// Add declarations to output and mark original lines for removal
for (const declaration of groupDeclarations.declarations) {
outputLines.push(declaration.whole);
outputLines[declaration.index] = DELETED_LINE;
outputLines[declaration.index] = "";
}
}
return outputLines;
}
function extractThisDeclaration(declarations: Map<string, Declaration>): Declaration | null {
for (const declaration of declarations.values()) {
if (declaration.value === "@This()") {
declarations.delete(declaration.key);
return declaration;
}
}
return null;
}
const DELETED_LINE = "%DELETED_LINE%";
// Main execution function for a single file
async function processFile(filePath: string): Promise<void> {
const originalFileContents = await Bun.file(filePath).text();
let fileContents = originalFileContents;
if (config.normalizePaths === "") {
fileContents = fileContents.replaceAll(`@import("./`, `@import("`);
} else if (config.normalizePaths === "./") {
fileContents = fileContents.replaceAll(/@import\("([A-Za-z0-9_-][^"]*\.zig)"\)/g, '@import("./$1")');
fileContents = fileContents.replaceAll(`@import("./../`, `@import("../`);
if (!config.includeUnsorted && !originalFileContents.includes("// @sortImports")) {
return;
}
console.log(`Processing: ${filePath}`);
let needsRecurse = true;
while (needsRecurse) {
@@ -324,7 +312,6 @@ async function processFile(filePath: string): Promise<void> {
const lines = fileContents.split("\n");
const { declarations, unusedLineIndices } = parseDeclarations(lines, fileContents);
const thisDeclaration = extractThisDeclaration(declarations);
const groups = groupDeclarationsByImportPath(declarations);
promoteItemsWithChildGroups(groups);
@@ -336,33 +323,13 @@ async function processFile(filePath: string): Promise<void> {
// Remove unused declarations
if (config.removeUnused) {
for (const line of unusedLineIndices) {
sortedLines[line] = DELETED_LINE;
sortedLines[line] = "";
needsRecurse = true;
}
}
if (thisDeclaration) {
sortedLines[thisDeclaration.index] = DELETED_LINE;
}
if (thisDeclaration) {
let firstNonFileCommentLine = 0;
for (const line of sortedLines) {
if (line.startsWith("//!")) {
firstNonFileCommentLine++;
} else {
break;
}
}
const insert = [thisDeclaration.whole, ""];
if (firstNonFileCommentLine > 0) insert.unshift("");
sortedLines.splice(firstNonFileCommentLine, 0, ...insert);
}
fileContents = sortedLines.join("\n");
}
// Remove deleted lines
fileContents = fileContents.replaceAll(DELETED_LINE + "\n", "");
// fileContents = fileContents.replaceAll(DELETED_LINE, ""); // any remaining lines
// Remove any leading newlines
fileContents = fileContents.replace(/^\n+/, "");
@@ -376,6 +343,7 @@ async function processFile(filePath: string): Promise<void> {
if (fileContents === "\n") fileContents = "";
if (fileContents === originalFileContents) {
console.log(`✓ No changes: ${filePath}`);
return;
}
@@ -401,7 +369,7 @@ async function main() {
successCount++;
} catch (error) {
errorCount++;
console.error(`Failed to process ${path.join(filePath, file)}:\n`, error);
console.error(`Failed to process ${filePath}`);
}
}
continue;
@@ -412,7 +380,7 @@ async function main() {
successCount++;
} catch (error) {
errorCount++;
console.error(`Failed to process ${filePath}:\n`, error);
console.error(`Failed to process ${filePath}`);
}
}

View File

@@ -2702,14 +2702,7 @@ export function reportAnnotationToBuildKite({ context, label, content, style = "
source: "buildkite",
level: "error",
});
reportAnnotationToBuildKite({
context,
label: `${label}-error`,
content: errorContent,
style,
priority,
attempt: attempt + 1,
});
reportAnnotationToBuildKite({ label: `${label}-error`, content: errorContent, attempt: attempt + 1 });
}
/**
@@ -2857,14 +2850,6 @@ export function printEnvironment() {
}
});
}
if (isLinux) {
startGroup("Memory", () => {
const shell = which(["sh", "bash"]);
if (shell) {
spawnSync([shell, "-c", "free -m -w"], { stdio: "inherit" });
}
});
}
if (isWindows) {
startGroup("Disk (win)", () => {
const shell = which(["pwsh"]);
@@ -2872,14 +2857,6 @@ export function printEnvironment() {
spawnSync([shell, "-c", "get-psdrive"], { stdio: "inherit" });
}
});
startGroup("Memory", () => {
const shell = which(["pwsh"]);
if (shell) {
spawnSync([shell, "-c", "Get-Counter '\\Memory\\Available MBytes'"], { stdio: "inherit" });
console.log();
spawnSync([shell, "-c", "Get-CimInstance Win32_PhysicalMemory"], { stdio: "inherit" });
}
});
}
}

View File

@@ -1,90 +0,0 @@
/**
* yocto-queue@1.2.1
* https://github.com/sindresorhus/yocto-queue
* MIT (c) Sindre Sorhus
*/
/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/
class Node {
value;
next;
constructor(value) {
this.value = value;
}
}
export default class Queue {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) {
return;
}
this.#head = this.#head.next;
this.#size--;
return current.value;
}
peek() {
if (!this.#head) {
return;
}
return this.#head.value;
// TODO: Node.js 18.
// return this.#head?.value;
}
clear() {
this.#head = undefined;
this.#tail = undefined;
this.#size = 0;
}
get size() {
return this.#size;
}
*[Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
*drain() {
while (this.#head) {
yield this.dequeue();
}
}
}

View File

@@ -0,0 +1,155 @@
import * as fs from "fs";
import * as path from "path";
/**
* Removes unreferenced top-level const declarations from a Zig file
* Handles patterns like: const <IDENTIFIER> = @import(...) or const <IDENTIFIER> = ...
*/
export function removeUnreferencedImports(content: string): string {
let modified = true;
let result = content;
// Keep iterating until no more changes are made
while (modified) {
modified = false;
const lines = result.split("\n");
const newLines: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Match top-level const declarations: const <IDENTIFIER> = ...
const constMatch = line.match(/^const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=(.*)$/);
if (constMatch) {
const identifier = constMatch[1];
const assignmentPart = constMatch[2];
// Skip lines that contain '{' in the assignment (likely structs/objects)
if (assignmentPart.includes("{")) {
newLines.push(line);
continue;
}
// Check if this identifier is referenced anywhere else in the file
const isReferenced = isIdentifierReferenced(identifier, lines, i);
if (!isReferenced) {
// Skip this line (delete it)
modified = true;
console.log(`Removing unreferenced import: ${identifier}`);
continue;
}
}
newLines.push(line);
}
result = newLines.join("\n");
}
return result;
}
/**
* Check if an identifier is referenced anywhere in the file except at the declaration line
*/
function isIdentifierReferenced(identifier: string, lines: string[], declarationLineIndex: number): boolean {
// Create a regex that matches the identifier as a whole word
// This prevents matching partial words (e.g. "std" shouldn't match "stdx")
const identifierRegex = new RegExp(`\\b${escapeRegex(identifier)}\\b`);
for (let i = 0; i < lines.length; i++) {
// Skip the declaration line itself
if (i === declarationLineIndex) {
continue;
}
const line = lines[i];
// Check if the identifier appears in this line
if (identifierRegex.test(line)) {
return true;
}
}
return false;
}
/**
* Escape special regex characters in a string
*/
function escapeRegex(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Process a single Zig file
*/
export function processZigFile(filePath: string): void {
try {
const content = fs.readFileSync(filePath, "utf-8");
const cleaned = removeUnreferencedImports(content);
if (content !== cleaned) {
fs.writeFileSync(filePath, cleaned);
console.log(`Cleaned: ${filePath}`);
} else {
console.log(`No changes: ${filePath}`);
}
} catch (error) {
console.error(`Error processing ${filePath}:`, error);
}
}
/**
* Process multiple Zig files or directories
*/
export function processFiles(paths: string[]): void {
for (const inputPath of paths) {
const stat = fs.statSync(inputPath);
if (stat.isDirectory()) {
// Process all .zig files in directory recursively
processDirectory(inputPath);
} else if (inputPath.endsWith(".zig")) {
processZigFile(inputPath);
} else {
console.warn(`Skipping non-Zig file: ${inputPath}`);
}
}
}
/**
* Recursively process all .zig files in a directory
*/
function processDirectory(dirPath: string): void {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
processDirectory(fullPath);
} else if (entry.name.endsWith(".zig")) {
processZigFile(fullPath);
}
}
}
// CLI usage
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log("Usage: bun zig-remove-unreferenced-top-level-decls.ts <file1.zig> [file2.zig] [directory]...");
console.log("");
console.log("Examples:");
console.log(" bun zig-remove-unreferenced-top-level-decls.ts file.zig");
console.log(" bun zig-remove-unreferenced-top-level-decls.ts src/");
console.log(" bun zig-remove-unreferenced-top-level-decls.ts file1.zig file2.zig src/");
process.exit(1);
}
processFiles(args);
}

View File

@@ -1,4 +1,12 @@
const Global = @This();
const std = @import("std");
const Environment = @import("./env.zig");
const Output = @import("output.zig");
const use_mimalloc = bun.use_mimalloc;
const Mimalloc = bun.Mimalloc;
const bun = @import("bun");
const version_string = Environment.version_string;
/// Does not have the canary tag, because it is exposed in `Bun.version`
/// "1.0.0" or "1.0.0-debug"
@@ -166,10 +174,10 @@ pub const versions = @import("./generated_versions_list.zig");
// 2. if I want to configure allocator later
pub inline fn configureAllocator(_: AllocatorConfiguration) void {
// if (comptime !use_mimalloc) return;
// const mimalloc = bun.mimalloc;
// mimalloc.mi_option_set_enabled(mimalloc.mi_option_verbose, config.verbose);
// mimalloc.mi_option_set_enabled(mimalloc.mi_option_large_os_pages, config.long_running);
// if (!config.long_running) mimalloc.mi_option_set(mimalloc.mi_option_reset_delay, 0);
// const Mimalloc = @import("./allocators/mimalloc.zig");
// Mimalloc.mi_option_set_enabled(Mimalloc.mi_option_verbose, config.verbose);
// Mimalloc.mi_option_set_enabled(Mimalloc.mi_option_large_os_pages, config.long_running);
// if (!config.long_running) Mimalloc.mi_option_set(Mimalloc.mi_option_reset_delay, 0);
}
pub fn notimpl() noreturn {
@@ -183,17 +191,20 @@ pub fn crash() noreturn {
Global.exit(1);
}
const Global = @This();
const string = bun.string;
pub const BunInfo = struct {
bun_version: string,
platform: analytics.GenerateHeader.GeneratePlatform.Platform,
platform: Analytics.GenerateHeader.GeneratePlatform.Platform,
const analytics = bun.analytics;
const JSON = bun.json;
const JSAst = bun.ast;
const Analytics = @import("./analytics/analytics_thread.zig");
const JSON = bun.JSON;
const JSAst = bun.JSAst;
pub fn generate(comptime Bundler: type, _: Bundler, allocator: std.mem.Allocator) !JSAst.Expr {
const info = BunInfo{
.bun_version = Global.package_json_version,
.platform = analytics.GenerateHeader.GeneratePlatform.forOS(),
.platform = Analytics.GenerateHeader.GeneratePlatform.forOS(),
};
return try JSON.toAST(allocator, BunInfo, info);
@@ -208,7 +219,7 @@ comptime {
}
pub export fn Bun__onExit() void {
bun.jsc.Node.FSEvents.closeAndWait();
bun.JSC.Node.FSEvents.closeAndWait();
runExitCallbacks();
Output.flush();
@@ -220,15 +231,3 @@ pub export fn Bun__onExit() void {
comptime {
_ = Bun__onExit;
}
const string = []const u8;
const Output = @import("./output.zig");
const std = @import("std");
const Environment = @import("./env.zig");
const version_string = Environment.version_string;
const bun = @import("bun");
const Mimalloc = bun.mimalloc;
const use_mimalloc = bun.use_mimalloc;

View File

@@ -1,4 +1,10 @@
const HTMLScanner = @This();
const std = @import("std");
const bun = @import("bun");
const ImportRecord = @import("./import_record.zig").ImportRecord;
const ImportKind = @import("./import_record.zig").ImportKind;
const lol = @import("./deps/lol-html.zig");
const logger = bun.logger;
const fs = bun.fs;
allocator: std.mem.Allocator,
import_records: ImportRecord.List = .{},
@@ -297,12 +303,4 @@ pub fn HTMLProcessor(
};
}
const lol = @import("./deps/lol-html.zig");
const std = @import("std");
const ImportKind = @import("./import_record.zig").ImportKind;
const ImportRecord = @import("./import_record.zig").ImportRecord;
const bun = @import("bun");
const fs = bun.fs;
const logger = bun.logger;
const HTMLScanner = @This();

View File

@@ -1,8 +1,6 @@
//! This is a copy-pasta of std.Thread.Mutex with some changes.
//! - No assert with unreachable
//! - uses bun.Futex instead of std.Thread.Futex
//! Synchronized with std as of Zig 0.14.1
//!
//! Mutex is a synchronization primitive which enforces atomic access to a shared region of code known as the "critical section".
//! It does this by blocking ensuring only one thread is in the critical section at any given point in time by blocking the others.
//! Mutex can be statically initialized and is at most `@sizeOf(u64)` large.
@@ -24,7 +22,12 @@
//! }
//! ```
const Mutex = @This();
const std = @import("std");
const builtin = @import("builtin");
const bun = @import("bun");
const assert = bun.assert;
const Thread = std.Thread;
const Futex = bun.Futex;
impl: Impl = .{},
@@ -53,12 +56,13 @@ const Impl = if (builtin.mode == .Debug and !builtin.single_threaded)
else
ReleaseImpl;
pub const ReleaseImpl = if (builtin.os.tag == .windows)
WindowsImpl
else if (builtin.os.tag.isDarwin())
DarwinImpl
else
FutexImpl;
pub const ReleaseImpl =
if (builtin.os.tag == .windows)
WindowsImpl
else if (builtin.os.tag.isDarwin())
DarwinImpl
else
FutexImpl;
pub const ExternImpl = ReleaseImpl.Type;
@@ -90,8 +94,8 @@ const DebugImpl = struct {
}
};
/// SRWLOCK on windows is almost always faster than Futex solution.
/// It also implements an efficient Condition with requeue support for us.
// SRWLOCK on windows is almost always faster than Futex solution.
// It also implements an efficient Condition with requeue support for us.
const WindowsImpl = struct {
srwlock: Type = .{},
@@ -112,7 +116,7 @@ const WindowsImpl = struct {
pub const Type = windows.SRWLOCK;
};
/// os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions.
// os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions.
const DarwinImpl = struct {
oul: Type = .{},
@@ -200,6 +204,8 @@ const FutexImpl = struct {
pub const Type = u32;
};
const Mutex = @This();
pub fn spinCycle() void {}
// These have to be a size known to C.
@@ -213,12 +219,3 @@ export fn Bun__unlock(ptr: *ReleaseImpl) void {
}
export const Bun__lock__size: usize = @sizeOf(ReleaseImpl);
const builtin = @import("builtin");
const bun = @import("bun");
const Futex = bun.Futex;
const assert = bun.assert;
const std = @import("std");
const Thread = std.Thread;

View File

@@ -1,5 +1,3 @@
const OutputFile = @This();
// Instead of keeping files in-memory, we:
// 1. Write directly to disk
// 2. (Optional) move the file to the destination
@@ -15,31 +13,15 @@ hash: u64 = 0,
is_executable: bool = false,
source_map_index: u32 = std.math.maxInt(u32),
bytecode_index: u32 = std.math.maxInt(u32),
output_kind: jsc.API.BuildArtifact.OutputKind,
output_kind: JSC.API.BuildArtifact.OutputKind,
/// Relative
dest_path: []const u8 = "",
side: ?bun.bake.Side,
/// This is only set for the JS bundle, and not files associated with an
/// entrypoint like sourcemaps and bytecode
entry_point_index: ?u32,
referenced_css_chunks: []const Index = &.{},
referenced_css_files: []const Index = &.{},
source_index: Index.Optional = .none,
bake_extra: BakeExtra = .{},
pub const zero_value = OutputFile{
.loader = .file,
.src_path = Fs.Path.init(""),
.value = .noop,
.output_kind = .chunk,
.side = null,
.entry_point_index = null,
};
pub const BakeExtra = struct {
is_route: bool = false,
fully_static: bool = false,
bake_is_runtime: bool = false,
};
pub const Index = bun.GenericIndex(u32, OutputFile);
@@ -48,7 +30,7 @@ pub fn deinit(this: *OutputFile) void {
bun.default_allocator.free(this.src_path.text);
bun.default_allocator.free(this.dest_path);
bun.default_allocator.free(this.referenced_css_chunks);
bun.default_allocator.free(this.referenced_css_files);
}
// Depending on:
@@ -117,13 +99,6 @@ pub const Value = union(Kind) {
}
}
pub fn asSlice(v: Value) []const u8 {
return switch (v) {
.buffer => |buf| buf.bytes,
else => "",
};
}
pub fn toBunString(v: Value) bun.String {
return switch (v) {
.noop => bun.String.empty,
@@ -154,14 +129,14 @@ pub const Value = union(Kind) {
pub const SavedFile = struct {
pub fn toJS(
globalThis: *jsc.JSGlobalObject,
globalThis: *JSC.JSGlobalObject,
path: []const u8,
byte_size: usize,
) jsc.JSValue {
) JSC.JSValue {
const mime_type = globalThis.bunVM().mimeType(path);
const store = jsc.WebCore.Blob.Store.initFile(
jsc.Node.PathOrFileDescriptor{
.path = jsc.Node.PathLike{
const store = JSC.WebCore.Blob.Store.initFile(
JSC.Node.PathOrFileDescriptor{
.path = JSC.Node.PathLike{
.string = bun.PathString.init(path),
},
},
@@ -169,12 +144,12 @@ pub const SavedFile = struct {
bun.default_allocator,
) catch unreachable;
var blob = bun.default_allocator.create(jsc.WebCore.Blob) catch unreachable;
blob.* = jsc.WebCore.Blob.initWithStore(store, globalThis);
var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis);
if (mime_type) |mime| {
blob.content_type = mime.value;
}
blob.size = @as(jsc.WebCore.Blob.SizeType, @truncate(byte_size));
blob.size = @as(JSC.WebCore.Blob.SizeType, @truncate(byte_size));
blob.allocator = bun.default_allocator;
return blob.toJS(globalThis);
}
@@ -215,7 +190,7 @@ pub const Options = struct {
size: ?usize = null,
input_path: []const u8 = "",
display_size: u32 = 0,
output_kind: jsc.API.BuildArtifact.OutputKind,
output_kind: JSC.API.BuildArtifact.OutputKind,
is_executable: bool,
data: union(enum) {
buffer: struct {
@@ -231,8 +206,7 @@ pub const Options = struct {
},
side: ?bun.bake.Side,
entry_point_index: ?u32,
referenced_css_chunks: []const Index = &.{},
bake_extra: BakeExtra = .{},
referenced_css_files: []const Index = &.{},
};
pub fn init(options: Options) OutputFile {
@@ -266,8 +240,7 @@ pub fn init(options: Options) OutputFile {
},
.side = options.side,
.entry_point_index = options.entry_point_index,
.referenced_css_chunks = options.referenced_css_chunks,
.bake_extra = options.bake_extra,
.referenced_css_files = options.referenced_css_files,
};
}
@@ -289,7 +262,7 @@ pub fn writeToDisk(f: OutputFile, root_dir: std.fs.Dir, root_dir_path: []const u
}
var path_buf: bun.PathBuffer = undefined;
_ = try jsc.Node.fs.NodeFS.writeFileWithPathBuffer(&path_buf, .{
_ = try JSC.Node.fs.NodeFS.writeFileWithPathBuffer(&path_buf, .{
.data = .{ .buffer = .{
.buffer = .{
.ptr = @constCast(value.bytes.ptr),
@@ -344,20 +317,20 @@ pub fn copyTo(file: *const OutputFile, _: string, rel_path: []const u8, dir: Fil
pub fn toJS(
this: *OutputFile,
owned_pathname: ?[]const u8,
globalObject: *jsc.JSGlobalObject,
) bun.jsc.JSValue {
globalObject: *JSC.JSGlobalObject,
) bun.JSC.JSValue {
return switch (this.value) {
.move, .pending => @panic("Unexpected pending output file"),
.noop => .js_undefined,
.copy => |copy| brk: {
const file_blob = jsc.WebCore.Blob.Store.initFile(
const file_blob = JSC.WebCore.Blob.Store.initFile(
if (copy.fd.isValid())
jsc.Node.PathOrFileDescriptor{
JSC.Node.PathOrFileDescriptor{
.fd = copy.fd,
}
else
jsc.Node.PathOrFileDescriptor{
.path = jsc.Node.PathLike{ .string = bun.PathString.init(globalObject.allocator().dupe(u8, copy.pathname) catch unreachable) },
JSC.Node.PathOrFileDescriptor{
.path = JSC.Node.PathLike{ .string = bun.PathString.init(globalObject.allocator().dupe(u8, copy.pathname) catch unreachable) },
},
this.loader.toMimeType(&.{owned_pathname orelse ""}),
globalObject.allocator(),
@@ -365,8 +338,8 @@ pub fn toJS(
Output.panic("error: Unable to create file blob: \"{s}\"", .{@errorName(err)});
};
var build_output = bun.new(jsc.API.BuildArtifact, .{
.blob = jsc.WebCore.Blob.initWithStore(file_blob, globalObject),
var build_output = bun.new(JSC.API.BuildArtifact, .{
.blob = JSC.WebCore.Blob.initWithStore(file_blob, globalObject),
.hash = this.hash,
.loader = this.input_loader,
.output_kind = this.output_kind,
@@ -383,12 +356,12 @@ pub fn toJS(
break :brk build_output.toJS(globalObject);
},
.saved => brk: {
var build_output = bun.default_allocator.create(jsc.API.BuildArtifact) catch @panic("Unable to allocate Artifact");
var build_output = bun.default_allocator.create(JSC.API.BuildArtifact) catch @panic("Unable to allocate Artifact");
const path_to_use = owned_pathname orelse this.src_path.text;
const file_blob = jsc.WebCore.Blob.Store.initFile(
jsc.Node.PathOrFileDescriptor{
.path = jsc.Node.PathLike{ .string = bun.PathString.init(owned_pathname orelse (bun.default_allocator.dupe(u8, this.src_path.text) catch unreachable)) },
const file_blob = JSC.WebCore.Blob.Store.initFile(
JSC.Node.PathOrFileDescriptor{
.path = JSC.Node.PathLike{ .string = bun.PathString.init(owned_pathname orelse (bun.default_allocator.dupe(u8, this.src_path.text) catch unreachable)) },
},
this.loader.toMimeType(&.{owned_pathname orelse ""}),
globalObject.allocator(),
@@ -403,8 +376,8 @@ pub fn toJS(
},
};
build_output.* = jsc.API.BuildArtifact{
.blob = jsc.WebCore.Blob.initWithStore(file_blob, globalObject),
build_output.* = JSC.API.BuildArtifact{
.blob = JSC.WebCore.Blob.initWithStore(file_blob, globalObject),
.hash = this.hash,
.loader = this.input_loader,
.output_kind = this.output_kind,
@@ -414,7 +387,7 @@ pub fn toJS(
break :brk build_output.toJS(globalObject);
},
.buffer => |buffer| brk: {
var blob = jsc.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalObject);
var blob = JSC.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalObject);
if (blob.store) |store| {
store.mime_type = this.loader.toMimeType(&.{owned_pathname orelse ""});
blob.content_type = store.mime_type.value;
@@ -422,10 +395,10 @@ pub fn toJS(
blob.content_type = this.loader.toMimeType(&.{owned_pathname orelse ""}).value;
}
blob.size = @as(jsc.WebCore.Blob.SizeType, @truncate(buffer.bytes.len));
blob.size = @as(JSC.WebCore.Blob.SizeType, @truncate(buffer.bytes.len));
var build_output = bun.default_allocator.create(jsc.API.BuildArtifact) catch @panic("Unable to allocate Artifact");
build_output.* = jsc.API.BuildArtifact{
var build_output = bun.default_allocator.create(JSC.API.BuildArtifact) catch @panic("Unable to allocate Artifact");
build_output.* = JSC.API.BuildArtifact{
.blob = blob,
.hash = this.hash,
.loader = this.input_loader,
@@ -448,20 +421,20 @@ pub fn toJS(
pub fn toBlob(
this: *OutputFile,
allocator: std.mem.Allocator,
globalThis: *jsc.JSGlobalObject,
) !jsc.WebCore.Blob {
globalThis: *JSC.JSGlobalObject,
) !JSC.WebCore.Blob {
return switch (this.value) {
.move, .pending => @panic("Unexpected pending output file"),
.noop => @panic("Cannot convert noop output file to blob"),
.copy => |copy| brk: {
const file_blob = try jsc.WebCore.Blob.Store.initFile(
const file_blob = try JSC.WebCore.Blob.Store.initFile(
if (copy.fd.isValid())
jsc.Node.PathOrFileDescriptor{
JSC.Node.PathOrFileDescriptor{
.fd = copy.fd,
}
else
jsc.Node.PathOrFileDescriptor{
.path = jsc.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, copy.pathname) catch unreachable) },
JSC.Node.PathOrFileDescriptor{
.path = JSC.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, copy.pathname) catch unreachable) },
},
this.loader.toMimeType(&.{ this.dest_path, this.src_path.text }),
allocator,
@@ -474,12 +447,12 @@ pub fn toBlob(
},
};
break :brk jsc.WebCore.Blob.initWithStore(file_blob, globalThis);
break :brk JSC.WebCore.Blob.initWithStore(file_blob, globalThis);
},
.saved => brk: {
const file_blob = try jsc.WebCore.Blob.Store.initFile(
jsc.Node.PathOrFileDescriptor{
.path = jsc.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, this.src_path.text) catch unreachable) },
const file_blob = try JSC.WebCore.Blob.Store.initFile(
JSC.Node.PathOrFileDescriptor{
.path = JSC.Node.PathLike{ .string = bun.PathString.init(allocator.dupe(u8, this.src_path.text) catch unreachable) },
},
this.loader.toMimeType(&.{ this.dest_path, this.src_path.text }),
allocator,
@@ -492,10 +465,10 @@ pub fn toBlob(
},
};
break :brk jsc.WebCore.Blob.initWithStore(file_blob, globalThis);
break :brk JSC.WebCore.Blob.initWithStore(file_blob, globalThis);
},
.buffer => |buffer| brk: {
var blob = jsc.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalThis);
var blob = JSC.WebCore.Blob.init(@constCast(buffer.bytes), buffer.allocator, globalThis);
if (blob.store) |store| {
store.mime_type = this.loader.toMimeType(&.{ this.dest_path, this.src_path.text });
blob.content_type = store.mime_type.value;
@@ -510,22 +483,22 @@ pub fn toBlob(
},
};
blob.size = @as(jsc.WebCore.Blob.SizeType, @truncate(buffer.bytes.len));
blob.size = @as(JSC.WebCore.Blob.SizeType, @truncate(buffer.bytes.len));
break :brk blob;
},
};
}
const OutputFile = @This();
const string = []const u8;
const resolve_path = @import("./resolver/resolve_path.zig");
const resolver = @import("./resolver/resolver.zig");
const std = @import("std");
const Loader = @import("./options.zig").Loader;
const bun = @import("bun");
const Environment = bun.Environment;
const FileDescriptorType = bun.FileDescriptor;
const std = @import("std");
const bun = @import("bun");
const JSC = bun.JSC;
const Fs = bun.fs;
const jsc = bun.jsc;
const Output = bun.Global.Output;
const Loader = @import("./options.zig").Loader;
const resolver = @import("./resolver/resolver.zig");
const resolve_path = @import("./resolver/resolve_path.zig");
const Output = @import("./Global.zig").Output;
const Environment = bun.Environment;

View File

@@ -14,7 +14,12 @@
//! * `refresh_rate_ms`
//! * `initial_delay_ms`
const std = @import("std");
const builtin = @import("builtin");
const windows = std.os.windows;
const assert = bun.assert;
const Progress = @This();
const bun = @import("bun");
/// `null` if the current node (and its children) should
/// not print on update()
@@ -448,10 +453,3 @@ test "basic functionality" {
node.end();
}
}
const builtin = @import("builtin");
const std = @import("std");
const windows = std.os.windows;
const bun = @import("bun");
const assert = bun.assert;

View File

@@ -1,6 +1,19 @@
//! Originally, we tried using LIEF to inject the module graph into a MachO segment
//! But this incurred a fixed 350ms overhead on every build, which is unacceptable
//! so we give up on codesigning support on macOS for now until we can find a better solution
const bun = @import("bun");
const std = @import("std");
const Schema = bun.Schema.Api;
const strings = bun.strings;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const Syscall = bun.sys;
const SourceMap = bun.sourcemap;
const StringPointer = bun.StringPointer;
const macho = bun.macho;
const w = std.os.windows;
pub const StandaloneModuleGraph = struct {
bytes: []const u8 = "",
@@ -124,19 +137,6 @@ pub const StandaloneModuleGraph = struct {
}
};
const PE = struct {
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32;
pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
pub fn getData() ?[]const u8 {
const length = Bun__getStandaloneModuleGraphPELength();
if (length == 0) return null;
const data_ptr = Bun__getStandaloneModuleGraphPEData() orelse return null;
return data_ptr[0..length];
}
};
pub const File = struct {
name: []const u8 = "",
loader: bun.options.Loader,
@@ -182,7 +182,7 @@ pub const StandaloneModuleGraph = struct {
return this.wtf_string.dupeRef();
}
pub fn blob(this: *File, globalObject: *bun.jsc.JSGlobalObject) *bun.webcore.Blob {
pub fn blob(this: *File, globalObject: *bun.JSC.JSGlobalObject) *bun.webcore.Blob {
if (this.cached_blob == null) {
const store = bun.webcore.Blob.Store.init(@constCast(this.contents), bun.default_allocator);
// make it never free
@@ -682,48 +682,6 @@ pub const StandaloneModuleGraph = struct {
}
return cloned_executable_fd;
},
.windows => {
const input_result = bun.sys.File.readToEnd(.{ .handle = cloned_executable_fd }, bun.default_allocator);
if (input_result.err) |err| {
Output.prettyErrorln("Error reading standalone module graph: {}", .{err});
cleanup(zname, cloned_executable_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);
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);
Global.exit(1);
};
input_result.bytes.deinit();
switch (Syscall.setFileOffset(cloned_executable_fd, 0)) {
.err => |err| {
Output.prettyErrorln("Error seeking to start of temporary file: {}", .{err});
cleanup(zname, cloned_executable_fd);
Global.exit(1);
},
else => {},
}
var file = bun.sys.File{ .handle = cloned_executable_fd };
const writer = file.writer();
pe_file.write(writer) catch |err| {
Output.prettyErrorln("Error writing PE file: {}", .{err});
cleanup(zname, cloned_executable_fd);
Global.exit(1);
};
// Set executable permissions when running on POSIX hosts, even for Windows targets
if (comptime !Environment.isWindows) {
_ = bun.c.fchmod(cloned_executable_fd.native(), 0o777);
}
return cloned_executable_fd;
},
else => {
var total_byte_count: usize = undefined;
if (Environment.isWindows) {
@@ -930,22 +888,6 @@ pub const StandaloneModuleGraph = struct {
return try StandaloneModuleGraph.fromBytes(allocator, @constCast(macho_bytes), offsets);
}
if (comptime Environment.isWindows) {
const pe_bytes = PE.getData() orelse return null;
if (pe_bytes.len < @sizeOf(Offsets) + trailer.len) {
Output.debugWarn("bun standalone module graph is too small to be valid", .{});
return null;
}
const pe_bytes_slice = pe_bytes[pe_bytes.len - @sizeOf(Offsets) - trailer.len ..];
const trailer_bytes = pe_bytes[pe_bytes.len - trailer.len ..][0..trailer.len];
if (!bun.strings.eqlComptime(trailer_bytes, trailer)) {
Output.debugWarn("bun standalone module graph has invalid trailer", .{});
return null;
}
const offsets = std.mem.bytesAsValue(Offsets, pe_bytes_slice).*;
return try StandaloneModuleGraph.fromBytes(allocator, @constCast(pe_bytes), offsets);
}
// Do not invoke libuv here.
const self_exe = openSelf() catch return null;
defer self_exe.close();
@@ -1226,13 +1168,13 @@ pub const StandaloneModuleGraph = struct {
// the allocator given to the JS parser is not respected for all parts
// of the parse, so we need to remember to reset the ast store
bun.ast.Expr.Data.Store.reset();
bun.ast.Stmt.Data.Store.reset();
bun.JSAst.Expr.Data.Store.reset();
bun.JSAst.Stmt.Data.Store.reset();
defer {
bun.ast.Expr.Data.Store.reset();
bun.ast.Stmt.Data.Store.reset();
bun.JSAst.Expr.Data.Store.reset();
bun.JSAst.Stmt.Data.Store.reset();
}
var json = bun.json.parse(&json_src, &log, arena, false) catch
var json = bun.JSON.parse(&json_src, &log, arena, false) catch
return error.InvalidSourceMap;
const mappings_str = json.get("mappings") orelse
@@ -1310,18 +1252,3 @@ pub const StandaloneModuleGraph = struct {
bun.assert(header_list.items.len == string_payload_start_location);
}
};
const std = @import("std");
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;
const Syscall = bun.sys;
const macho = bun.macho;
const pe = bun.pe;
const strings = bun.strings;
const Schema = bun.schema.api;

View File

@@ -1,5 +1,13 @@
// https://github.com/lithdew/rheia/blob/162293d0f0e8d6572a8954c0add83f13f76b3cc6/hash_map.zig
// Apache License 2.0
const std = @import("std");
const mem = std.mem;
const math = std.math;
const testing = std.testing;
const bun = @import("bun");
const assert = bun.assert;
pub fn AutoHashMap(comptime K: type, comptime V: type, comptime max_load_percentage: comptime_int) type {
return HashMap(K, V, std.hash_map.AutoContext(K), max_load_percentage);
@@ -777,11 +785,3 @@ test "SortedHashMap: collision test" {
try testing.expectEqual(@as(usize, 1), map.delete(prefix ++ [_]u8{1}).?);
try testing.expectEqual(@as(usize, 2), map.delete(prefix ++ [_]u8{2}).?);
}
const bun = @import("bun");
const assert = bun.assert;
const std = @import("std");
const math = std.math;
const mem = std.mem;
const testing = std.testing;

View File

@@ -1,7 +1,5 @@
//! Bun's cross-platform filesystem watcher. Runs on its own thread.
const Watcher = @This();
const DebugLogScope = bun.Output.Scoped(.watcher, false);
const log = DebugLogScope.log;
@@ -128,6 +126,7 @@ pub fn getHash(filepath: string) HashType {
pub const WatchItemIndex = u16;
pub const max_eviction_count = 8096;
const WindowsWatcher = @import("./watcher/WindowsWatcher.zig");
// TODO: some platform-specific behavior is implemented in
// this file instead of the platform-specific file.
// ideally, the constants above can be inlined
@@ -289,7 +288,7 @@ pub fn flushEvictions(this: *Watcher) void {
}
}
fn watchLoop(this: *Watcher) bun.jsc.Maybe(void) {
fn watchLoop(this: *Watcher) bun.JSC.Maybe(void) {
while (this.running) {
// individual platform implementation will call onFileUpdate
switch (Platform.watchLoopCycle(this)) {
@@ -309,7 +308,7 @@ fn appendFileAssumeCapacity(
parent_hash: HashType,
package_json: ?*PackageJSON,
comptime clone_file_path: bool,
) bun.jsc.Maybe(void) {
) bun.JSC.Maybe(void) {
if (comptime Environment.isWindows) {
// on windows we can only watch items that are in the directory tree of the top level dir
const rel = bun.path.isParentOrEqual(this.fs.top_level_dir, file_path);
@@ -390,7 +389,7 @@ fn appendDirectoryAssumeCapacity(
file_path: string,
hash: HashType,
comptime clone_file_path: bool,
) bun.jsc.Maybe(WatchItemIndex) {
) bun.JSC.Maybe(WatchItemIndex) {
if (comptime Environment.isWindows) {
// on windows we can only watch items that are in the directory tree of the top level dir
const rel = bun.path.isParentOrEqual(this.fs.top_level_dir, file_path);
@@ -501,7 +500,7 @@ pub fn appendFileMaybeLock(
package_json: ?*PackageJSON,
comptime clone_file_path: bool,
comptime lock: bool,
) bun.jsc.Maybe(void) {
) bun.JSC.Maybe(void) {
if (comptime lock) this.mutex.lock();
defer if (comptime lock) this.mutex.unlock();
bun.assert(file_path.len > 1);
@@ -577,7 +576,7 @@ pub fn appendFile(
dir_fd: bun.FileDescriptor,
package_json: ?*PackageJSON,
comptime clone_file_path: bool,
) bun.jsc.Maybe(void) {
) bun.JSC.Maybe(void) {
return appendFileMaybeLock(this, fd, file_path, hash, loader, dir_fd, package_json, clone_file_path, true);
}
@@ -587,7 +586,7 @@ pub fn addDirectory(
file_path: string,
hash: HashType,
comptime clone_file_path: bool,
) bun.jsc.Maybe(WatchItemIndex) {
) bun.JSC.Maybe(WatchItemIndex) {
this.mutex.lock();
defer this.mutex.unlock();
@@ -609,7 +608,7 @@ pub fn addFile(
dir_fd: bun.FileDescriptor,
package_json: ?*PackageJSON,
comptime clone_file_path: bool,
) bun.jsc.Maybe(void) {
) bun.JSC.Maybe(void) {
// This must lock due to concurrent transpiler
this.mutex.lock();
defer this.mutex.unlock();
@@ -674,16 +673,13 @@ pub fn onMaybeWatchDirectory(watch: *Watcher, file_path: string, dir_fd: bun.Sto
}
}
const string = []const u8;
const WindowsWatcher = @import("./watcher/WindowsWatcher.zig");
const options = @import("./options.zig");
const std = @import("std");
const PackageJSON = @import("./resolver/package_json.zig").PackageJSON;
const bun = @import("bun");
const Environment = bun.Environment;
const FeatureFlags = bun.FeatureFlags;
const Mutex = bun.Mutex;
const string = bun.string;
const Output = bun.Output;
const Environment = bun.Environment;
const strings = bun.strings;
const FeatureFlags = bun.FeatureFlags;
const options = @import("./options.zig");
const Mutex = bun.Mutex;
const PackageJSON = @import("./resolver/package_json.zig").PackageJSON;

View File

@@ -1,12 +1,8 @@
pub const c_allocator = @import("./allocators/basic.zig").c_allocator;
pub const z_allocator = @import("./allocators/basic.zig").z_allocator;
pub const mimalloc = @import("./allocators/mimalloc.zig");
pub const MimallocArena = @import("./allocators/MimallocArena.zig");
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");
const std = @import("std");
const Environment = @import("./env.zig");
const bun = @import("bun");
const OOM = bun.OOM;
pub fn isSliceInBufferT(comptime T: type, slice: []const T, buffer: []const T) bool {
return (@intFromPtr(buffer.ptr) <= @intFromPtr(slice.ptr) and
@@ -84,14 +80,9 @@ fn OverflowGroup(comptime Block: type) type {
const max = 4095;
const UsedSize = std.math.IntFittingRange(0, max + 1);
const default_allocator = bun.default_allocator;
used: UsedSize,
allocated: UsedSize,
ptrs: [max]*Block,
pub inline fn zero(this: *Overflow) void {
this.used = 0;
this.allocated = 0;
}
used: UsedSize = 0,
allocated: UsedSize = 0,
ptrs: [max]*Block = undefined,
pub fn tail(this: *Overflow) *Block {
if (this.allocated > 0 and this.ptrs[this.used].isFull()) {
@@ -103,7 +94,7 @@ fn OverflowGroup(comptime Block: type) type {
if (this.allocated <= this.used) {
this.ptrs[this.allocated] = default_allocator.create(Block) catch unreachable;
this.ptrs[this.allocated].zero();
this.ptrs[this.allocated].* = Block{};
this.allocated +%= 1;
}
@@ -122,12 +113,8 @@ pub fn OverflowList(comptime ValueType: type, comptime count: comptime_int) type
const SizeType = std.math.IntFittingRange(0, count);
const Block = struct {
used: SizeType,
items: [count]ValueType,
pub inline fn zero(this: *Block) void {
this.used = 0;
}
used: SizeType = 0,
items: [count]ValueType = undefined,
pub inline fn isFull(block: *const Block) bool {
return block.used >= @as(SizeType, count);
@@ -142,13 +129,8 @@ pub fn OverflowList(comptime ValueType: type, comptime count: comptime_int) type
}
};
const Overflow = OverflowGroup(Block);
list: Overflow,
count: u31,
pub inline fn zero(this: *This) void {
this.list.zero();
this.count = 0;
}
list: Overflow = Overflow{},
count: u31 = 0,
pub inline fn len(this: *const This) u31 {
return this.count;
@@ -205,17 +187,9 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
return struct {
const ChunkSize = 256;
const OverflowBlock = struct {
used: std.atomic.Value(u16),
data: [ChunkSize]ValueType,
prev: ?*OverflowBlock,
pub inline fn zero(this: *OverflowBlock) void {
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
this.used = std.atomic.Value(u16).init(0);
this.prev = null;
}
used: std.atomic.Value(u16) = std.atomic.Value(u16).init(0),
data: [ChunkSize]ValueType = undefined,
prev: ?*OverflowBlock = null,
pub fn append(this: *OverflowBlock, item: ValueType) !*ValueType {
const index = this.used.fetchAdd(1, .acq_rel);
@@ -230,10 +204,10 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
allocator: Allocator,
mutex: Mutex = .{},
head: *OverflowBlock,
tail: OverflowBlock,
backing_buf: [count]ValueType,
used: u32,
head: *OverflowBlock = undefined,
tail: OverflowBlock = OverflowBlock{},
backing_buf: [count]ValueType = undefined,
used: u32 = 0,
pub var instance: *Self = undefined;
pub var loaded = false;
@@ -245,14 +219,11 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
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
instance.allocator = allocator;
instance.mutex = .{};
instance.tail.zero();
instance.* = Self{
.allocator = allocator,
.tail = OverflowBlock{},
};
instance.head = &instance.tail;
instance.used = 0;
loaded = true;
}
@@ -271,7 +242,7 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
instance.used += 1;
return self.head.append(value) catch brk: {
var new_block = try self.allocator.create(OverflowBlock);
new_block.zero();
new_block.* = OverflowBlock{};
new_block.prev = self.head;
self.head = new_block;
break :brk self.head.append(value);
@@ -294,6 +265,8 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type {
};
}
const Mutex = bun.Mutex;
/// Append-only list.
/// Stores an initial count in .bss section of the object file
/// Overflows to heap when count is exceeded.
@@ -314,12 +287,12 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
const Allocator = std.mem.Allocator;
const Self = @This();
backing_buf: [count * item_length]u8,
backing_buf_used: u64,
overflow_list: Overflow,
backing_buf: [count * item_length]u8 = undefined,
backing_buf_used: u64 = undefined,
overflow_list: Overflow = Overflow{},
allocator: Allocator,
slice_buf: [count][]const u8,
slice_buf_used: u16,
slice_buf: [count][]const u8 = undefined,
slice_buf_used: u16 = 0,
mutex: Mutex = .{},
pub var instance: *Self = undefined;
var loaded: bool = false;
@@ -332,14 +305,10 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
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
instance.allocator = allocator;
instance.backing_buf_used = 0;
instance.slice_buf_used = 0;
instance.overflow_list.zero();
instance.mutex = .{};
instance.* = Self{
.allocator = allocator,
.backing_buf_used = 0,
};
loaded = true;
}
@@ -500,11 +469,11 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
const Overflow = OverflowList(ValueType, count / 4);
index: IndexMap,
overflow_list: Overflow,
overflow_list: Overflow = Overflow{},
allocator: Allocator,
mutex: Mutex = .{},
backing_buf: [count]ValueType,
backing_buf_used: u16,
backing_buf: [count]ValueType = undefined,
backing_buf_used: u16 = 0,
pub var instance: *Self = undefined;
@@ -512,15 +481,11 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
pub fn init(allocator: std.mem.Allocator) *Self {
if (!loaded) {
// Avoid struct initialization syntax.
// This makes Bun start about 1ms faster.
// https://github.com/ziglang/zig/issues/24313
instance = bun.default_allocator.create(Self) catch bun.outOfMemory();
instance.index = IndexMap{};
instance.allocator = allocator;
instance.overflow_list.zero();
instance.backing_buf_used = 0;
instance.mutex = .{};
instance.* = Self{
.index = IndexMap{},
.allocator = allocator,
};
loaded = true;
}
@@ -669,12 +634,9 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
pub fn init(allocator: std.mem.Allocator) *Self {
if (!instance_loaded) {
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
instance.map = BSSMapType.init(allocator);
instance.key_list_buffer_used = 0;
instance.key_list_overflow.zero();
instance.* = Self{
.map = BSSMapType.init(allocator),
};
instance_loaded = true;
}
@@ -771,10 +733,3 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_
}
};
}
const Environment = @import("./env.zig");
const std = @import("std");
const bun = @import("bun");
const OOM = bun.OOM;
const Mutex = bun.threading.Mutex;

View File

@@ -1,6 +1,5 @@
//! 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;
@@ -254,7 +253,6 @@ pub inline fn downcast(a: Allocator) ?*AllocationScope {
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,189 +0,0 @@
//! When cloning large amounts of data potentially multiple times, we can
//! leverage copy-on-write memory to avoid actually copying the data. To do that
//! on Linux, we need to use a memfd, which is a Linux-specific feature.
//!
//! The steps are roughly:
//!
//! 1. Create a memfd
//! 2. Write the data to the memfd
//! 3. Map the memfd into memory
//!
//! Then, to clone the data later, we can just call `mmap` again.
//!
//! The big catch is that mmap(), memfd_create(), write() all have overhead. And
//! often we will re-use virtual memory within the process. This does not reuse
//! the virtual memory. So we should only really use this for large blobs of
//! data that we expect to be cloned multiple times. Such as Blob in FormData.
const Self = @This();
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
pub const new = bun.TrivialNew(@This());
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
ref_count: RefCount,
fd: bun.FileDescriptor = .invalid,
size: usize = 0,
var memfd_counter = std.atomic.Value(usize).init(0);
fn deinit(self: *Self) void {
self.fd.close();
bun.destroy(self);
}
pub fn allocator(self: *Self) std.mem.Allocator {
return .{
.ptr = self,
.vtable = AllocatorInterface.VTable,
};
}
pub fn from(allocator_: std.mem.Allocator) ?*Self {
if (allocator_.vtable == AllocatorInterface.VTable) {
return @alignCast(@ptrCast(allocator_.ptr));
}
return null;
}
const AllocatorInterface = struct {
fn alloc(_: *anyopaque, _: usize, _: std.mem.Alignment, _: usize) ?[*]u8 {
// it should perform no allocations or resizes
return null;
}
fn free(
ptr: *anyopaque,
buf: []u8,
_: std.mem.Alignment,
_: usize,
) void {
var self: *Self = @alignCast(@ptrCast(ptr));
defer self.deref();
bun.sys.munmap(@alignCast(@ptrCast(buf))).unwrap() catch |err| {
bun.Output.debugWarn("Failed to munmap memfd: {}", .{err});
};
}
pub const VTable = &std.mem.Allocator.VTable{
.alloc = &AllocatorInterface.alloc,
.resize = &std.mem.Allocator.noResize,
.remap = &std.mem.Allocator.noRemap,
.free = &free,
};
};
pub fn alloc(self: *Self, len: usize, offset: usize, flags: std.posix.MAP) bun.jsc.Maybe(bun.webcore.Blob.Store.Bytes) {
var size = len;
// size rounded up to nearest page
size = std.mem.alignForward(usize, size, std.heap.pageSize());
var flags_mut = flags;
flags_mut.TYPE = .SHARED;
switch (bun.sys.mmap(
null,
@min(size, self.size),
std.posix.PROT.READ | std.posix.PROT.WRITE,
flags_mut,
self.fd,
offset,
)) {
.result => |slice| {
return .{
.result = bun.webcore.Blob.Store.Bytes{
.cap = @truncate(slice.len),
.ptr = slice.ptr,
.len = @truncate(len),
.allocator = self.allocator(),
},
};
},
.err => |errno| {
return .{ .err = errno };
},
}
}
pub fn shouldUse(bytes: []const u8) bool {
if (comptime !bun.Environment.isLinux) {
return false;
}
if (bun.jsc.VirtualMachine.is_smol_mode) {
return bytes.len >= 1024 * 1024 * 1;
}
// This is a net 2x - 4x slowdown to new Blob([huge])
// so we must be careful
return bytes.len >= 1024 * 1024 * 8;
}
pub fn create(bytes: []const u8) bun.jsc.Maybe(bun.webcore.Blob.Store.Bytes) {
if (comptime !bun.Environment.isLinux) {
unreachable;
}
var label_buf: [128]u8 = undefined;
const label = std.fmt.bufPrintZ(&label_buf, "memfd-num-{d}", .{memfd_counter.fetchAdd(1, .monotonic)}) catch "";
// Using huge pages was slower.
const fd = switch (bun.sys.memfd_create(label, std.os.linux.MFD.CLOEXEC)) {
.err => |err| return .{ .err = bun.sys.Error.fromCode(err.getErrno(), .open) },
.result => |fd| fd,
};
if (bytes.len > 0)
// Hint at the size of the file
_ = bun.sys.ftruncate(fd, @intCast(bytes.len));
// Dump all the bytes in there
var written: isize = 0;
var remain = bytes;
while (remain.len > 0) {
switch (bun.sys.pwrite(fd, remain, written)) {
.err => |err| {
if (err.getErrno() == .AGAIN) {
continue;
}
bun.Output.debugWarn("Failed to write to memfd: {}", .{err});
fd.close();
return .{ .err = err };
},
.result => |result| {
if (result == 0) {
bun.Output.debugWarn("Failed to write to memfd: EOF", .{});
fd.close();
return .{ .err = bun.sys.Error.fromCode(.NOMEM, .write) };
}
written += @intCast(result);
remain = remain[result..];
},
}
}
var linux_memfd_allocator = Self.new(.{
.fd = fd,
.ref_count = .init(),
.size = bytes.len,
});
switch (linux_memfd_allocator.alloc(bytes.len, 0, .{ .TYPE = .SHARED })) {
.result => |res| {
return .{ .result = res };
},
.err => |err| {
linux_memfd_allocator.deref();
return .{ .err = err };
},
}
}
const bun = @import("bun");
const std = @import("std");

View File

@@ -1,54 +0,0 @@
//! Single allocation only.
const Self = @This();
array_list: std.ArrayListAligned(u8, @alignOf(std.c.max_align_t)),
fn alloc(ptr: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
bun.assert(alignment.toByteUnits() <= @alignOf(std.c.max_align_t));
var self = bun.cast(*Self, ptr);
self.array_list.items.len = 0;
self.array_list.ensureTotalCapacity(len) catch return null;
self.array_list.items.len = len;
return self.array_list.items.ptr;
}
fn resize(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
_ = new_len;
_ = buf;
@panic("not implemented");
}
fn free(
_: *anyopaque,
_: []u8,
_: std.mem.Alignment,
_: usize,
) void {}
pub fn reset(self: *Self) void {
self.array_list.items.len = 0;
}
pub fn deinit(self: *Self) void {
self.array_list.deinit();
}
const vtable = std.mem.Allocator.VTable{
.alloc = &alloc,
.free = &free,
.resize = &resize,
.remap = &std.mem.Allocator.noRemap,
};
pub fn init(self: *Self, allocator: std.mem.Allocator) std.mem.Allocator {
self.array_list = .init(allocator);
return std.mem.Allocator{
.ptr = self,
.vtable = &vtable,
};
}
const bun = @import("bun");
const std = @import("std");

View File

@@ -1,5 +1,4 @@
const MemoryReportingAllocator = @This();
const log = bun.Output.scoped(.MEM, false);
child_allocator: std.mem.Allocator,
@@ -85,8 +84,7 @@ pub const VTable = std.mem.Allocator.VTable{
};
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
const Environment = bun.Environment;
const Output = bun.Output;
const jsc = bun.jsc;

View File

@@ -1,172 +0,0 @@
const Self = @This();
heap: ?*mimalloc.Heap = null,
const log = bun.Output.scoped(.mimalloc, true);
/// Internally, mimalloc calls mi_heap_get_default()
/// to get the default heap.
/// It uses pthread_getspecific to do that.
/// We can save those extra calls if we just do it once in here
pub fn getThreadlocalDefault() Allocator {
return Allocator{ .ptr = mimalloc.mi_heap_get_default(), .vtable = &c_allocator_vtable };
}
pub fn backingAllocator(self: Self) Allocator {
var arena = Self{ .heap = self.heap.?.backing() };
return arena.allocator();
}
pub fn allocator(self: Self) Allocator {
@setRuntimeSafety(false);
return Allocator{ .ptr = self.heap.?, .vtable = &c_allocator_vtable };
}
pub fn dumpThreadStats(self: *Self) void {
_ = self;
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
bun.Output.errorWriter().writeAll(text) catch {};
}
}.dump;
mimalloc.mi_thread_stats_print_out(dump_fn, null);
bun.Output.flush();
}
pub fn dumpStats(self: *Self) void {
_ = self;
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
bun.Output.errorWriter().writeAll(text) catch {};
}
}.dump;
mimalloc.mi_stats_print_out(dump_fn, null);
bun.Output.flush();
}
pub fn deinit(self: *Self) void {
mimalloc.mi_heap_destroy(bun.take(&self.heap).?);
}
pub fn init() !Self {
return .{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
}
pub fn gc(self: Self) void {
mimalloc.mi_heap_collect(self.heap orelse return, false);
}
pub inline fn helpCatchMemoryIssues(self: Self) void {
if (comptime FeatureFlags.help_catch_memory_issues) {
self.gc();
bun.mimalloc.mi_collect(false);
}
}
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
return mimalloc.mi_heap_check_owned(self.heap.?, ptr);
}
pub const supports_posix_memalign = true;
fn alignedAlloc(heap: *mimalloc.Heap, len: usize, alignment: mem.Alignment) ?[*]u8 {
log("Malloc: {d}\n", .{len});
const ptr: ?*anyopaque = if (mimalloc.canUseAlignedAlloc(len, alignment.toByteUnits()))
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
else
mimalloc.mi_heap_malloc(heap, len);
if (comptime 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 alloc(arena: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
const self = bun.cast(*mimalloc.Heap, arena);
return alignedAlloc(
self,
len,
alignment,
);
}
fn resize(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}
fn free(
_: *anyopaque,
buf: []u8,
alignment: mem.Alignment,
_: usize,
) void {
// mi_free_size internally just asserts the size
// so it's faster if we don't pass that value through
// but its good to have that assertion
if (comptime Environment.isDebug) {
assert(mimalloc.mi_is_in_heap_region(buf.ptr));
if (mimalloc.canUseAlignedAlloc(buf.len, alignment.toByteUnits()))
mimalloc.mi_free_size_aligned(buf.ptr, buf.len, alignment.toByteUnits())
else
mimalloc.mi_free_size(buf.ptr, buf.len);
} else {
mimalloc.mi_free(buf.ptr);
}
}
/// Attempt to expand or shrink memory, allowing relocation.
///
/// `memory.len` must equal the length requested from the most recent
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
/// equal the same value that was passed as the `alignment` parameter to
/// the original `alloc` call.
///
/// A non-`null` return value indicates the resize was successful. The
/// allocation may have same address, or may have been relocated. In either
/// case, the allocation now has size of `new_len`. A `null` return value
/// indicates that the resize would be equivalent to allocating new memory,
/// copying the bytes from the old memory, and then freeing the old memory.
/// In such case, it is more efficient for the caller to perform the copy.
///
/// `new_len` must be greater than zero.
///
/// `ret_addr` is optionally provided as the first return address of the
/// allocation call stack. If the value is `0` it means no return address
/// has been provided.
fn remap(self: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
const aligned_size = alignment.toByteUnits();
const value = mimalloc.mi_heap_realloc_aligned(@ptrCast(self), buf.ptr, new_len, aligned_size);
return @ptrCast(value);
}
const c_allocator_vtable = Allocator.VTable{
.alloc = &Self.alloc,
.resize = &Self.resize,
.remap = &Self.remap,
.free = &Self.free,
};
const Environment = @import("../env.zig");
const FeatureFlags = @import("../feature_flags.zig");
const std = @import("std");
const bun = @import("bun");
const assert = bun.assert;
const mimalloc = bun.mimalloc;
const mem = std.mem;
const Allocator = mem.Allocator;

View File

@@ -1,4 +1,6 @@
//! A nullable allocator the same size as `std.mem.Allocator`.
const std = @import("std");
const bun = @import("bun");
const NullableAllocator = @This();
@@ -44,6 +46,3 @@ comptime {
@compileError("Expected the sizes to be the same.");
}
}
const bun = @import("bun");
const std = @import("std");

View File

@@ -0,0 +1,187 @@
const bun = @import("bun");
const std = @import("std");
/// When cloning large amounts of data potentially multiple times, we can
/// leverage copy-on-write memory to avoid actually copying the data. To do that
/// on Linux, we need to use a memfd, which is a Linux-specific feature.
///
/// The steps are roughly:
///
/// 1. Create a memfd
/// 2. Write the data to the memfd
/// 3. Map the memfd into memory
///
/// Then, to clone the data later, we can just call `mmap` again.
///
/// The big catch is that mmap(), memfd_create(), write() all have overhead. And
/// often we will re-use virtual memory within the process. This does not reuse
/// the virtual memory. So we should only really use this for large blobs of
/// data that we expect to be cloned multiple times. Such as Blob in FormData.
pub const LinuxMemFdAllocator = struct {
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
pub const new = bun.TrivialNew(@This());
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
ref_count: RefCount,
fd: bun.FileDescriptor = .invalid,
size: usize = 0,
var memfd_counter = std.atomic.Value(usize).init(0);
fn deinit(this: *LinuxMemFdAllocator) void {
this.fd.close();
bun.destroy(this);
}
pub fn allocator(this: *LinuxMemFdAllocator) std.mem.Allocator {
return .{
.ptr = this,
.vtable = AllocatorInterface.VTable,
};
}
pub fn from(allocator_: std.mem.Allocator) ?*LinuxMemFdAllocator {
if (allocator_.vtable == AllocatorInterface.VTable) {
return @alignCast(@ptrCast(allocator_.ptr));
}
return null;
}
const AllocatorInterface = struct {
fn alloc(_: *anyopaque, _: usize, _: std.mem.Alignment, _: usize) ?[*]u8 {
// it should perform no allocations or resizes
return null;
}
fn free(
ptr: *anyopaque,
buf: []u8,
_: std.mem.Alignment,
_: usize,
) void {
var this: *LinuxMemFdAllocator = @alignCast(@ptrCast(ptr));
defer this.deref();
bun.sys.munmap(@alignCast(@ptrCast(buf))).unwrap() catch |err| {
bun.Output.debugWarn("Failed to munmap memfd: {}", .{err});
};
}
pub const VTable = &std.mem.Allocator.VTable{
.alloc = &AllocatorInterface.alloc,
.resize = &std.mem.Allocator.noResize,
.remap = &std.mem.Allocator.noRemap,
.free = &free,
};
};
pub fn alloc(this: *LinuxMemFdAllocator, len: usize, offset: usize, flags: std.posix.MAP) bun.JSC.Maybe(bun.webcore.Blob.Store.Bytes) {
var size = len;
// size rounded up to nearest page
size = std.mem.alignForward(usize, size, std.heap.pageSize());
var flags_mut = flags;
flags_mut.TYPE = .SHARED;
switch (bun.sys.mmap(
null,
@min(size, this.size),
std.posix.PROT.READ | std.posix.PROT.WRITE,
flags_mut,
this.fd,
offset,
)) {
.result => |slice| {
return .{
.result = bun.webcore.Blob.Store.Bytes{
.cap = @truncate(slice.len),
.ptr = slice.ptr,
.len = @truncate(len),
.allocator = this.allocator(),
},
};
},
.err => |errno| {
return .{ .err = errno };
},
}
}
pub fn shouldUse(bytes: []const u8) bool {
if (comptime !bun.Environment.isLinux) {
return false;
}
if (bun.JSC.VirtualMachine.is_smol_mode) {
return bytes.len >= 1024 * 1024 * 1;
}
// This is a net 2x - 4x slowdown to new Blob([huge])
// so we must be careful
return bytes.len >= 1024 * 1024 * 8;
}
pub fn create(bytes: []const u8) bun.JSC.Maybe(bun.webcore.Blob.Store.Bytes) {
if (comptime !bun.Environment.isLinux) {
unreachable;
}
var label_buf: [128]u8 = undefined;
const label = std.fmt.bufPrintZ(&label_buf, "memfd-num-{d}", .{memfd_counter.fetchAdd(1, .monotonic)}) catch "";
// Using huge pages was slower.
const fd = switch (bun.sys.memfd_create(label, std.os.linux.MFD.CLOEXEC)) {
.err => |err| return .{ .err = bun.sys.Error.fromCode(err.getErrno(), .open) },
.result => |fd| fd,
};
if (bytes.len > 0)
// Hint at the size of the file
_ = bun.sys.ftruncate(fd, @intCast(bytes.len));
// Dump all the bytes in there
var written: isize = 0;
var remain = bytes;
while (remain.len > 0) {
switch (bun.sys.pwrite(fd, remain, written)) {
.err => |err| {
if (err.getErrno() == .AGAIN) {
continue;
}
bun.Output.debugWarn("Failed to write to memfd: {}", .{err});
fd.close();
return .{ .err = err };
},
.result => |result| {
if (result == 0) {
bun.Output.debugWarn("Failed to write to memfd: EOF", .{});
fd.close();
return .{ .err = bun.sys.Error.fromCode(.NOMEM, .write) };
}
written += @intCast(result);
remain = remain[result..];
},
}
}
var linux_memfd_allocator = LinuxMemFdAllocator.new(.{
.fd = fd,
.ref_count = .init(),
.size = bytes.len,
});
switch (linux_memfd_allocator.alloc(bytes.len, 0, .{ .TYPE = .SHARED })) {
.result => |res| {
return .{ .result = res };
},
.err => |err| {
linux_memfd_allocator.deref();
return .{ .err = err };
},
}
}
};

View File

@@ -0,0 +1,53 @@
const bun = @import("bun");
const std = @import("std");
/// Single allocation only.
///
pub const MaxHeapAllocator = struct {
array_list: std.ArrayListAligned(u8, @alignOf(std.c.max_align_t)),
fn alloc(ptr: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
bun.assert(alignment.toByteUnits() <= @alignOf(std.c.max_align_t));
var this = bun.cast(*MaxHeapAllocator, ptr);
this.array_list.items.len = 0;
this.array_list.ensureTotalCapacity(len) catch return null;
this.array_list.items.len = len;
return this.array_list.items.ptr;
}
fn resize(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
_ = new_len;
_ = buf;
@panic("not implemented");
}
fn free(
_: *anyopaque,
_: []u8,
_: std.mem.Alignment,
_: usize,
) void {}
pub fn reset(this: *MaxHeapAllocator) void {
this.array_list.items.len = 0;
}
pub fn deinit(this: *MaxHeapAllocator) void {
this.array_list.deinit();
}
const vtable = std.mem.Allocator.VTable{
.alloc = &alloc,
.free = &free,
.resize = &resize,
.remap = &std.mem.Allocator.noRemap,
};
pub fn init(this: *MaxHeapAllocator, allocator: std.mem.Allocator) std.mem.Allocator {
this.array_list = .init(allocator);
return std.mem.Allocator{
.ptr = this,
.vtable = &vtable,
};
}
};

View File

@@ -1,4 +1,11 @@
const mem = @import("std").mem;
const std = @import("std");
const bun = @import("bun");
const log = bun.Output.scoped(.mimalloc, true);
const assert = bun.assert;
const Allocator = mem.Allocator;
const mimalloc = @import("./mimalloc.zig");
const Environment = @import("../env.zig");
fn mimalloc_free(
_: *anyopaque,
@@ -143,13 +150,3 @@ const z_allocator_vtable = Allocator.VTable{
.remap = &std.mem.Allocator.noRemap,
.free = &ZAllocator.free_with_z_allocator,
};
const Environment = @import("../env.zig");
const std = @import("std");
const bun = @import("bun");
const assert = bun.assert;
const mimalloc = bun.mimalloc;
const mem = @import("std").mem;
const Allocator = mem.Allocator;

View File

@@ -202,6 +202,7 @@ pub const MI_SMALL_WSIZE_MAX = @as(c_int, 128);
pub const MI_SMALL_SIZE_MAX = MI_SMALL_WSIZE_MAX * @import("std").zig.c_translation.sizeof(?*anyopaque);
pub const MI_ALIGNMENT_MAX = (@as(c_int, 16) * @as(c_int, 1024)) * @as(c_ulong, 1024);
const std = @import("std");
pub fn canUseAlignedAlloc(len: usize, alignment: usize) bool {
return alignment > 0 and std.math.isPowerOfTwo(alignment) and !mi_malloc_satisfies_alignment(alignment, len);
}
@@ -210,5 +211,3 @@ inline fn mi_malloc_satisfies_alignment(alignment: usize, size: usize) bool {
return (alignment == @sizeOf(*anyopaque) or
(alignment == MI_MAX_ALIGN_SIZE and size >= (MI_MAX_ALIGN_SIZE / 2)));
}
const std = @import("std");

View File

@@ -0,0 +1,169 @@
const mem = @import("std").mem;
const std = @import("std");
const mimalloc = @import("./mimalloc.zig");
const Environment = @import("../env.zig");
const FeatureFlags = @import("../feature_flags.zig");
const Allocator = mem.Allocator;
const assert = bun.assert;
const bun = @import("bun");
const log = bun.Output.scoped(.mimalloc, true);
pub const Arena = struct {
heap: ?*mimalloc.Heap = null,
/// Internally, mimalloc calls mi_heap_get_default()
/// to get the default heap.
/// It uses pthread_getspecific to do that.
/// We can save those extra calls if we just do it once in here
pub fn getThreadlocalDefault() Allocator {
return Allocator{ .ptr = mimalloc.mi_heap_get_default(), .vtable = &c_allocator_vtable };
}
pub fn backingAllocator(this: Arena) Allocator {
var arena = Arena{ .heap = this.heap.?.backing() };
return arena.allocator();
}
pub fn allocator(this: Arena) Allocator {
@setRuntimeSafety(false);
return Allocator{ .ptr = this.heap.?, .vtable = &c_allocator_vtable };
}
pub fn dumpThreadStats(_: *Arena) void {
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
bun.Output.errorWriter().writeAll(text) catch {};
}
}.dump;
mimalloc.mi_thread_stats_print_out(dump_fn, null);
bun.Output.flush();
}
pub fn dumpStats(_: *Arena) void {
const dump_fn = struct {
pub fn dump(textZ: [*:0]const u8, _: ?*anyopaque) callconv(.C) void {
const text = bun.span(textZ);
bun.Output.errorWriter().writeAll(text) catch {};
}
}.dump;
mimalloc.mi_stats_print_out(dump_fn, null);
bun.Output.flush();
}
pub fn deinit(this: *Arena) void {
mimalloc.mi_heap_destroy(bun.take(&this.heap).?);
}
pub fn init() !Arena {
const arena = Arena{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
return arena;
}
pub fn gc(this: Arena) void {
mimalloc.mi_heap_collect(this.heap orelse return, false);
}
pub inline fn helpCatchMemoryIssues(this: Arena) void {
if (comptime FeatureFlags.help_catch_memory_issues) {
this.gc();
bun.Mimalloc.mi_collect(false);
}
}
pub fn ownsPtr(this: Arena, ptr: *const anyopaque) bool {
return mimalloc.mi_heap_check_owned(this.heap.?, ptr);
}
pub const supports_posix_memalign = true;
fn alignedAlloc(heap: *mimalloc.Heap, len: usize, alignment: mem.Alignment) ?[*]u8 {
log("Malloc: {d}\n", .{len});
const ptr: ?*anyopaque = if (mimalloc.canUseAlignedAlloc(len, alignment.toByteUnits()))
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
else
mimalloc.mi_heap_malloc(heap, len);
if (comptime 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 alloc(arena: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 {
const this = bun.cast(*mimalloc.Heap, arena);
return alignedAlloc(
this,
len,
alignment,
);
}
fn resize(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool {
return mimalloc.mi_expand(buf.ptr, new_len) != null;
}
fn free(
_: *anyopaque,
buf: []u8,
alignment: mem.Alignment,
_: usize,
) void {
// mi_free_size internally just asserts the size
// so it's faster if we don't pass that value through
// but its good to have that assertion
if (comptime Environment.isDebug) {
assert(mimalloc.mi_is_in_heap_region(buf.ptr));
if (mimalloc.canUseAlignedAlloc(buf.len, alignment.toByteUnits()))
mimalloc.mi_free_size_aligned(buf.ptr, buf.len, alignment.toByteUnits())
else
mimalloc.mi_free_size(buf.ptr, buf.len);
} else {
mimalloc.mi_free(buf.ptr);
}
}
/// Attempt to expand or shrink memory, allowing relocation.
///
/// `memory.len` must equal the length requested from the most recent
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
/// equal the same value that was passed as the `alignment` parameter to
/// the original `alloc` call.
///
/// A non-`null` return value indicates the resize was successful. The
/// allocation may have same address, or may have been relocated. In either
/// case, the allocation now has size of `new_len`. A `null` return value
/// indicates that the resize would be equivalent to allocating new memory,
/// copying the bytes from the old memory, and then freeing the old memory.
/// In such case, it is more efficient for the caller to perform the copy.
///
/// `new_len` must be greater than zero.
///
/// `ret_addr` is optionally provided as the first return address of the
/// allocation call stack. If the value is `0` it means no return address
/// has been provided.
fn remap(this: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 {
const aligned_size = alignment.toByteUnits();
const value = mimalloc.mi_heap_realloc_aligned(@ptrCast(this), buf.ptr, new_len, aligned_size);
return @ptrCast(value);
}
};
const c_allocator_vtable = Allocator.VTable{
.alloc = &Arena.alloc,
.resize = &Arena.resize,
.remap = &Arena.remap,
.free = &Arena.free,
};

View File

@@ -1,3 +1,5 @@
const std = @import("std");
pub const Reader = struct {
const Self = @This();
pub const ReadError = error{EOF};
@@ -516,5 +518,3 @@ pub const analytics = struct {
}
};
};
const std = @import("std");

View File

@@ -1,3 +1,11 @@
const bun = @import("bun");
const Environment = bun.Environment;
const std = @import("std");
const Analytics = @import("./analytics_schema.zig").analytics;
const Semver = bun.Semver;
/// Enables analytics. This is used by:
/// - crash_handler.zig's `report` function to anonymously report crashes
///
@@ -254,7 +262,7 @@ pub const EventName = enum(u8) {
var random: std.rand.DefaultPrng = undefined;
const platform_arch = if (Environment.isAarch64) analytics.Architecture.arm else analytics.Architecture.x64;
const platform_arch = if (Environment.isAarch64) Analytics.Architecture.arm else Analytics.Architecture.x64;
// TODO: move this code somewhere more appropriate, and remove it from "analytics"
// The following code is not currently even used for analytics, just feature-detection
@@ -262,10 +270,10 @@ const platform_arch = if (Environment.isAarch64) analytics.Architecture.arm else
pub const GenerateHeader = struct {
pub const GeneratePlatform = struct {
var osversion_name: [32]u8 = undefined;
fn forMac() analytics.Platform {
fn forMac() Analytics.Platform {
@memset(&osversion_name, 0);
var platform = analytics.Platform{ .os = analytics.OperatingSystem.macos, .version = &[_]u8{}, .arch = platform_arch };
var platform = Analytics.Platform{ .os = Analytics.OperatingSystem.macos, .version = &[_]u8{}, .arch = platform_arch };
var len = osversion_name.len - 1;
// this previously used "kern.osrelease", which was the darwin xnu kernel version
// That is less useful than "kern.osproductversion", which is the macOS version
@@ -276,8 +284,8 @@ pub const GenerateHeader = struct {
}
pub var linux_os_name: std.c.utsname = undefined;
var platform_: analytics.Platform = undefined;
pub const Platform = analytics.Platform;
var platform_: Analytics.Platform = undefined;
pub const Platform = Analytics.Platform;
var linux_kernel_version: Semver.Version = undefined;
var run_once = std.once(struct {
fn run() void {
@@ -292,7 +300,7 @@ pub const GenerateHeader = struct {
linux_kernel_version = result.version.min();
} else if (Environment.isWindows) {
platform_ = Platform{
.os = analytics.OperatingSystem.windows,
.os = Analytics.OperatingSystem.windows,
.version = &[_]u8{},
.arch = platform_arch,
};
@@ -300,7 +308,7 @@ pub const GenerateHeader = struct {
}
}.run);
pub fn forOS() analytics.Platform {
pub fn forOS() Analytics.Platform {
run_once.call();
return platform_;
}
@@ -350,7 +358,7 @@ pub const GenerateHeader = struct {
};
}
fn forLinux() analytics.Platform {
fn forLinux() Analytics.Platform {
linux_os_name = std.mem.zeroes(@TypeOf(linux_os_name));
_ = std.c.uname(&linux_os_name);
@@ -360,17 +368,10 @@ pub const GenerateHeader = struct {
// Linux DESKTOP-P4LCIEM 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
if (std.mem.indexOf(u8, release, "microsoft") != null) {
return analytics.Platform{ .os = analytics.OperatingSystem.wsl, .version = release, .arch = platform_arch };
return Analytics.Platform{ .os = Analytics.OperatingSystem.wsl, .version = release, .arch = platform_arch };
}
return analytics.Platform{ .os = analytics.OperatingSystem.linux, .version = release, .arch = platform_arch };
return Analytics.Platform{ .os = Analytics.OperatingSystem.linux, .version = release, .arch = platform_arch };
}
};
};
const std = @import("std");
const analytics = @import("./analytics/schema.zig").analytics;
const bun = @import("bun");
const Environment = bun.Environment;
const Semver = bun.Semver;

View File

@@ -1,4 +1,4 @@
package api;
package Api;
smol Loader {
jsx = 1;
@@ -64,7 +64,7 @@ struct StackTrace {
message JSException {
string name = 1;
string message = 2;
uint16 runtime_type = 3;
uint8 code = 4;
@@ -103,7 +103,7 @@ message FallbackMessageContainer {
Problems problems = 4;
string cwd = 5;
}
smol ResolveMode {
disable = 1;
@@ -178,18 +178,18 @@ struct JavascriptBundle {
// These are sorted alphabetically so you can do binary search
JavascriptBundledModule[] modules;
JavascriptBundledPackage[] packages;
// This is ASCII-encoded so you can send it directly over HTTP
byte[] etag;
uint32 generated_at;
// generated by hashing all ${name}@${version} in sorted order
byte[] app_package_json_dependencies_hash;
byte[] import_from_name;
// This is what StringPointer refers to
// This is what StringPointer refers to
byte[] manifest_string;
}
@@ -359,7 +359,7 @@ smol SourceMapMode {
struct FileHandle {
string path;
uint size;
uint fd;
uint fd;
}
message Transform {
@@ -462,7 +462,7 @@ struct Log {
smol Reloader {
disable = 1;
// equivalent of CMD + R
// equivalent of CMD + R
live = 2;
// React Fast Refresh
fast_refresh = 3;
@@ -534,7 +534,7 @@ struct WebsocketMessageBuildSuccess {
Loader loader;
string module_path;
// This is the length of the blob that immediately follows this message.
uint32 blob_length;
}
@@ -630,4 +630,4 @@ struct TestResponseItem {
struct GetTestsResponse {
TestResponseItem[] tests;
byte[] contents;
}
}

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