Compare commits

...

11 Commits

Author SHA1 Message Date
Claude Bot
d32ba76b46 fix: support Blob and BunFile inputs in HTMLRewriter.transform()
HTMLRewriter.transform() only accepted Response, string, and ArrayBuffer
inputs despite the type definitions and docs promising Blob and BunFile
support. Add Blob detection to the transform_ function so that in-memory
Blobs return a transformed Blob and file-backed Blobs (BunFile) return a
Response (since they require async I/O through the ValueBufferer path).

Closes #17259

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 02:40:05 +00:00
robobun
6a8f33e7b1 fix(windows): close libuv pipes before freeing to prevent handle_queue corruption (#27124) 2026-02-19 00:29:43 -08:00
Jarred Sumner
c3ae343fc9 fix(windows): use-after-free in WindowsStreamingWriter (#27122) 2026-02-19 00:29:15 -08:00
robobun
1eef4368ea fix: increase robobun PR query limit from 200 to 1000 (#27126) 2026-02-19 00:20:50 -08:00
robobun
6e240de4e2 Add workflow to close stale robobun PRs older than 90 days (#27125) 2026-02-19 00:16:38 -08:00
SUZUKI Sosuke
e216be966e fix: avoid GC allocation inside ObjectInitializationScope (#27111)
## Summary
- Pre-convert strings to JSValues using `MarkedArgumentBuffer` before
entering `ObjectInitializationScope` in `JSC__JSObject__putRecord` and
`JSC__JSValue__putRecord`, since `jsString()` allocates GC cells which
is not allowed inside the scope
- Remove unused `ObjectInitializationScope` declaration in
`JSSQLStatement.cpp`'s `initializeColumnNames`

## Test plan
- [ ] Verify `bun bd test` passes for existing tests that exercise
`putRecord` paths (e.g., HTTP header handling, SQLite column names)
- [ ] Run with `BUN_JSC_validateExceptionChecks=1` to confirm no
exception scope violations

## Changelog
<!-- CHANGELOG:START -->
<!-- CHANGELOG:END -->

🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
9-shotted by claude-opus-4-6)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-02-18 22:54:59 -08:00
Jarred Sumner
e84bee5d58 Fixes #26979 (#27118)
### What does this PR do?

Fixes https://github.com/oven-sh/bun/issues/26979


### How did you verify your code works?

The test in https://github.com/oven-sh/bun/issues/26979 successfully
reproduced the issue. Thank you!
2026-02-18 21:44:57 -08:00
SUZUKI Sosuke
fb2f304100 fix(node:fs): remove unnecessary path buffer pool alloc on Windows (#27115)
## Summary

- Removes an unnecessary 64KB `path_buffer_pool` allocation in
`PathLike.sliceZWithForceCopy` on Windows for paths that already have a
drive letter
- For drive-letter paths (e.g. `C:\foo\bar`),
`resolveCWDWithExternalBufZ` just does a memcpy, so the intermediate
buffer is unnecessary — we can pass the input slice directly to
`normalizeBuf`
- Eliminates an OOM crash path where `ObjectPool.get()` would panic via
`catch unreachable` when the allocator fails

## Test plan

- [ ] Verify Windows CI passes (this code path is Windows-only)
- [ ] Verify node:fs operations with absolute Windows paths still work
correctly
- [ ] Monitor BUN-Z4V crash reports after deployment to confirm fix

## Context

Speculative fix for BUN-Z4V (124 occurrences on Windows) showing `Panic:
attempt to unwrap error: OutOfMemory` in `sliceZWithForceCopy` →
`path_buffer_pool.get()` → `allocBytesWithAlignment`. We have not been
able to reproduce the crash locally, but the code analysis shows the
allocation is unnecessary for the drive-letter path case.

## Changelog
<!-- CHANGELOG:START -->
Fixed a crash on Windows (`OutOfMemory` panic) in `node:fs` path
handling when the system is under memory pressure.
<!-- CHANGELOG:END -->

🤖 Generated with [Claude Code](https://claude.com/claude-code) (0%
8-shotted by claude-opus-4-6)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:16:16 -08:00
Jarred Sumner
a350d496cb Revert "fix(bundler): place standalone HTML scripts in <head> to preserve execution order (#27114)"
This reverts commit 9e32360195.
2026-02-18 19:08:03 -08:00
robobun
9e32360195 fix(bundler): place standalone HTML scripts in <head> to preserve execution order (#27114)
## Summary

- Fix standalone HTML mode (`--compile --target=browser`) placing
bundled JS as `<script type="module">` before `</body>`, which broke
execution order with existing inline body scripts
- Move bundled JS into `<head>` as a classic `<script>` (not
`type="module"`) so it executes synchronously before inline body
scripts, preserving the original script execution order
- Remove the now-unnecessary `addBodyTags()` function and associated
body script injection paths

Fixes #27113

## Test plan

- [x] Added regression test `test/regression/issue/27113.test.ts` that
verifies head scripts appear before `</head>` and don't use
`type="module"`
- [x] Updated existing standalone HTML tests in
`test/bundler/standalone.test.ts` to reflect the change from `<script
type="module">` to classic `<script>`
- [x] All 18 standalone tests pass (`bun bd test
test/bundler/standalone.test.ts`)
- [x] Regression test passes (`bun bd test
test/regression/issue/27113.test.ts`)

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

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 19:07:10 -08:00
Dylan Conway
9785af304c Windows arm64 CI (#26746)
### What does this PR do?
Sets up ci for windows arm64
### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-18 18:08:10 -08:00
68 changed files with 2376 additions and 639 deletions

View File

@@ -99,6 +99,23 @@ function getTargetLabel(target) {
* @property {string[]} [features]
*/
// Azure VM sizes for Windows CI runners.
// DDSv6 = x64, DPSv6 = ARM64 (Cobalt 100). Quota: 100 cores per family in eastus2.
const azureVmSizes = {
"windows-x64": {
build: "Standard_D16ds_v6", // 16 vCPU, 64 GiB — C++ build, link
test: "Standard_D4ds_v6", // 4 vCPU, 16 GiB — test shards
},
"windows-aarch64": {
build: "Standard_D16ps_v6", // 16 vCPU, 64 GiB — C++ build, link
test: "Standard_D4ps_v6", // 4 vCPU, 16 GiB — test shards
},
};
function getAzureVmSize(os, arch, tier = "build") {
return azureVmSizes[`${os}-${arch}`]?.[tier];
}
/**
* @type {Platform[]}
*/
@@ -114,8 +131,7 @@ const buildPlatforms = [
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23" },
{ os: "windows", arch: "x64", release: "2019" },
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
// TODO: Re-enable when Windows ARM64 VS component installation is resolved on Buildkite runners
// { os: "windows", arch: "aarch64", release: "2019" },
{ os: "windows", arch: "aarch64", release: "11" },
];
/**
@@ -138,8 +154,7 @@ const testPlatforms = [
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23", tier: "latest" },
{ os: "windows", arch: "x64", release: "2019", tier: "oldest" },
{ os: "windows", arch: "x64", release: "2019", baseline: true, tier: "oldest" },
// TODO: Enable when Windows ARM64 CI runners are ready
// { os: "windows", arch: "aarch64", release: "2019", tier: "oldest" },
{ os: "windows", arch: "aarch64", release: "11", tier: "latest" },
];
/**
@@ -304,15 +319,8 @@ function getCppAgent(platform, options) {
};
}
// Cross-compile Windows ARM64 from x64 runners
if (os === "windows" && arch === "aarch64") {
return getEc2Agent({ ...platform, arch: "x64" }, options, {
instanceType: "c7i.4xlarge",
});
}
return getEc2Agent(platform, options, {
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
instanceType: os === "windows" ? getAzureVmSize(os, arch) : arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
});
}
@@ -333,10 +341,8 @@ function getLinkBunAgent(platform, options) {
}
if (os === "windows") {
// Cross-compile Windows ARM64 from x64 runners
const agentPlatform = arch === "aarch64" ? { ...platform, arch: "x64" } : platform;
return getEc2Agent(agentPlatform, options, {
instanceType: "r7i.large",
return getEc2Agent(platform, options, {
instanceType: getAzureVmSize(os, arch),
});
}
@@ -363,7 +369,17 @@ function getZigPlatform() {
* @param {PipelineOptions} options
* @returns {Agent}
*/
function getZigAgent(_platform, options) {
function getZigAgent(platform, options) {
const { os, arch } = platform;
// Windows builds Zig natively on Azure
if (os === "windows") {
return getEc2Agent(platform, options, {
instanceType: getAzureVmSize(os, arch),
});
}
// Everything else cross-compiles from Linux aarch64
return getEc2Agent(getZigPlatform(), options, {
instanceType: "r8g.large",
});
@@ -388,7 +404,7 @@ function getTestAgent(platform, options) {
// TODO: delete this block when we upgrade to mimalloc v3
if (os === "windows") {
return getEc2Agent(platform, options, {
instanceType: "c7i.2xlarge",
instanceType: getAzureVmSize(os, arch, "test"),
cpuCount: 2,
threadsPerCore: 1,
});
@@ -465,17 +481,6 @@ function getBuildCommand(target, options, label) {
return `bun run build:${buildProfile}`;
}
/**
* Get extra flags needed when cross-compiling Windows ARM64 from x64.
* Applied to C++ and link steps (not Zig, which has its own toolchain handling).
*/
function getWindowsArm64CrossFlags(target) {
if (target.os === "windows" && target.arch === "aarch64") {
return " --toolchain windows-aarch64";
}
return "";
}
/**
* @param {Platform} platform
* @param {PipelineOptions} options
@@ -483,7 +488,6 @@ function getWindowsArm64CrossFlags(target) {
*/
function getBuildCppStep(platform, options) {
const command = getBuildCommand(platform, options);
const crossFlags = getWindowsArm64CrossFlags(platform);
return {
key: `${getTargetKey(platform)}-build-cpp`,
@@ -498,7 +502,7 @@ function getBuildCppStep(platform, options) {
// We used to build the C++ dependencies and bun in separate steps.
// However, as long as the zig build takes longer than both sequentially,
// it's cheaper to run them in the same step. Can be revisited in the future.
command: [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`],
command: [`${command} --target bun`, `${command} --target dependencies`],
};
}
@@ -524,7 +528,10 @@ function getBuildToolchain(target) {
* @returns {Step}
*/
function getBuildZigStep(platform, options) {
const { os, arch } = platform;
const toolchain = getBuildToolchain(platform);
// Native Windows builds don't need a cross-compilation toolchain
const toolchainArg = os === "windows" ? "" : ` --toolchain ${toolchain}`;
return {
key: `${getTargetKey(platform)}-build-zig`,
retry: getRetry(),
@@ -532,7 +539,7 @@ function getBuildZigStep(platform, options) {
agents: getZigAgent(platform, options),
cancel_on_build_failing: isMergeQueue(),
env: getBuildEnv(platform, options),
command: `${getBuildCommand(platform, options)} --target bun-zig --toolchain ${toolchain}`,
command: `${getBuildCommand(platform, options)} --target bun-zig${toolchainArg}`,
timeout_in_minutes: 35,
};
}
@@ -555,7 +562,7 @@ function getLinkBunStep(platform, options) {
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
...getBuildEnv(platform, options),
},
command: `${getBuildCommand(platform, options, "build-bun")}${getWindowsArm64CrossFlags(platform)} --target bun`,
command: `${getBuildCommand(platform, options, "build-bun")} --target bun`,
};
}
@@ -717,14 +724,14 @@ function getTestBunStep(platform, options, testOptions = {}) {
agents: getTestAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
parallelism: os === "darwin" ? 2 : 20,
parallelism: os === "darwin" ? 2 : os === "windows" ? 8 : 20,
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
env: {
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
},
command:
os === "windows"
? `node .\\scripts\\runner.node.mjs ${args.join(" ")}`
? `pwsh -NoProfile -File .\\scripts\\vs-shell.ps1 node .\\scripts\\runner.node.mjs ${args.join(" ")}`
: `./scripts/runner.node.mjs ${args.join(" ")}`,
};
}
@@ -739,6 +746,7 @@ function getBuildImageStep(platform, options) {
const { publishImages } = options;
const action = publishImages ? "publish-image" : "create-image";
const cloud = os === "windows" ? "azure" : "aws";
const command = [
"node",
"./scripts/machine.mjs",
@@ -747,7 +755,7 @@ function getBuildImageStep(platform, options) {
`--arch=${arch}`,
distro && `--distro=${distro}`,
`--release=${release}`,
"--cloud=aws",
`--cloud=${cloud}`,
"--ci",
"--authorized-org=oven-sh",
];
@@ -1169,9 +1177,10 @@ async function getPipelineOptions() {
skipBuilds: parseOption(/\[(skip builds?|no builds?|only tests?)\]/i),
forceBuilds: parseOption(/\[(force builds?)\]/i),
skipTests: parseOption(/\[(skip tests?|no tests?|only builds?)\]/i),
buildImages: parseOption(/\[(build images?)\]/i),
buildImages: parseOption(/\[(build (?:(?:windows|linux) )?images?)\]/i),
dryRun: parseOption(/\[(dry run)\]/i),
publishImages: parseOption(/\[(publish images?)\]/i),
publishImages: parseOption(/\[(publish (?:(?:windows|linux) )?images?)\]/i),
imageFilter: (commitMessage.match(/\[(?:build|publish) (windows|linux) images?\]/i) || [])[1]?.toLowerCase(),
buildPlatforms: Array.from(buildPlatformsMap.values()),
testPlatforms: Array.from(testPlatformsMap.values()),
};
@@ -1196,13 +1205,12 @@ async function getPipeline(options = {}) {
return;
}
const { buildPlatforms = [], testPlatforms = [], buildImages, publishImages } = options;
const { buildPlatforms = [], testPlatforms = [], buildImages, publishImages, imageFilter } = options;
const imagePlatforms = new Map(
buildImages || publishImages
? [...buildPlatforms, ...testPlatforms]
.filter(({ os }) => os !== "darwin")
// Windows ARM64 cross-compiles from x64 runners, no separate image needed
.filter(({ os, arch }) => !(os === "windows" && arch === "aarch64"))
.filter(({ os, distro }) => !imageFilter || os === imageFilter || distro === imageFilter)
.map(platform => [getImageKey(platform), platform])
: [],
);

View File

@@ -0,0 +1,30 @@
name: Close stale robobun PRs
on:
schedule:
- cron: "30 0 * * *"
workflow_dispatch:
jobs:
close-stale-robobun-prs:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pull-requests: write
steps:
- name: Close stale robobun PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
ninety_days_ago=$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)
gh pr list \
--author robobun \
--state open \
--json number,updatedAt \
--limit 1000 \
--jq ".[] | select(.updatedAt < \"$ninety_days_ago\") | .number" |
while read -r pr_number; do
echo "Closing PR #$pr_number (last updated before $ninety_days_ago)"
gh pr close "$pr_number" --comment "Closing this PR because it has been inactive for more than 90 days."
done

View File

@@ -161,6 +161,31 @@ test("(multi-file test) my feature", async () => {
- `src/sql/` - SQL database integrations
- `src/bake/` - Server-side rendering framework
#### Vendored Dependencies (`vendor/`)
Third-party C/C++ libraries are vendored locally and can be read from disk (these are not git submodules):
- `vendor/boringssl/` - BoringSSL (TLS/crypto)
- `vendor/brotli/` - Brotli compression
- `vendor/cares/` - c-ares (async DNS)
- `vendor/hdrhistogram/` - HdrHistogram (latency tracking)
- `vendor/highway/` - Google Highway (SIMD)
- `vendor/libarchive/` - libarchive (tar/zip)
- `vendor/libdeflate/` - libdeflate (fast deflate)
- `vendor/libuv/` - libuv (Windows event loop)
- `vendor/lolhtml/` - lol-html (HTML rewriter)
- `vendor/lshpack/` - ls-hpack (HTTP/2 HPACK)
- `vendor/mimalloc/` - mimalloc (memory allocator)
- `vendor/nodejs/` - Node.js headers (compatibility)
- `vendor/picohttpparser/` - PicoHTTPParser (HTTP parsing)
- `vendor/tinycc/` - TinyCC (FFI JIT compiler, fork: oven-sh/tinycc)
- `vendor/WebKit/` - WebKit/JavaScriptCore (JS engine)
- `vendor/zig/` - Zig compiler/stdlib
- `vendor/zlib/` - zlib (compression, cloudflare fork)
- `vendor/zstd/` - Zstandard (compression)
Build configuration for these is in `cmake/targets/Build*.cmake`.
### JavaScript Class Implementation (C++)
When implementing JavaScript classes in C++:

View File

@@ -434,7 +434,7 @@ function(register_command)
endif()
# SKIP_CODEGEN: Skip commands that use BUN_EXECUTABLE if all outputs exist
# This is used for Windows ARM64 builds where x64 bun crashes under emulation
# Useful for bootstrapping new platforms where bun may not be available
if(SKIP_CODEGEN AND CMD_EXECUTABLE STREQUAL "${BUN_EXECUTABLE}")
set(ALL_OUTPUTS_EXIST TRUE)
foreach(output ${CMD_OUTPUTS})
@@ -456,7 +456,7 @@ function(register_command)
endif()
return()
else()
message(FATAL_ERROR "SKIP_CODEGEN: Cannot skip ${CMD_TARGET} - missing outputs. Run codegen on x64 first.")
message(FATAL_ERROR "SKIP_CODEGEN: Cannot skip ${CMD_TARGET} - missing outputs.")
endif()
endif()
@@ -831,13 +831,6 @@ function(register_cmake_command)
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_${flag}=${MAKE_${flag}}")
endforeach()
# Workaround for CMake 4.1.0 bug: Force correct machine type for Windows ARM64
# Use toolchain file and set CMP0197 policy to prevent duplicate /machine: flags
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CWD}/cmake/toolchains/windows-aarch64.cmake")
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_POLICY_DEFAULT_CMP0197=NEW")
list(APPEND MAKE_EFFECTIVE_ARGS "-DCMAKE_PROJECT_INCLUDE=${CWD}/cmake/arm64-static-lib-fix.cmake")
endif()
if(DEFINED FRESH)
list(APPEND MAKE_EFFECTIVE_ARGS --fresh)

View File

@@ -4,7 +4,7 @@ endif()
optionx(BUN_LINK_ONLY BOOL "If only the linking step should be built" DEFAULT OFF)
optionx(BUN_CPP_ONLY BOOL "If only the C++ part of Bun should be built" DEFAULT OFF)
optionx(SKIP_CODEGEN BOOL "Skip JavaScript codegen (for Windows ARM64 debug)" DEFAULT OFF)
optionx(SKIP_CODEGEN BOOL "Skip JavaScript codegen (useful for bootstrapping new platforms)" DEFAULT OFF)
optionx(BUILDKITE BOOL "If Buildkite is enabled" DEFAULT OFF)
optionx(GITHUB_ACTIONS BOOL "If GitHub Actions is enabled" DEFAULT OFF)
@@ -58,18 +58,6 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
# Setting to NEW prevents duplicate /machine: flags being added to linker commands
if(WIN32 AND ARCH STREQUAL "aarch64")
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW)
set(CMAKE_MSVC_CMP0197 NEW)
# Set linker flags for exe/shared linking
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /machine:ARM64")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /machine:ARM64")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /machine:ARM64")
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /machine:ARM64")
endif()
# Windows Code Signing Option
if(WIN32)
optionx(ENABLE_WINDOWS_CODESIGNING BOOL "Enable Windows code signing with DigiCert KeyLocker" DEFAULT OFF)

View File

@@ -1,8 +0,0 @@
# This file is included after project() via CMAKE_PROJECT_INCLUDE
# It fixes the static library creation command to use ARM64 machine type
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")
# Override the static library creation commands to avoid spurious /machine:x64 flags
set(CMAKE_C_CREATE_STATIC_LIBRARY \"<CMAKE_AR> /nologo /machine:ARM64 /out:<TARGET> <OBJECTS>\" CACHE STRING \"\" FORCE)
set(CMAKE_CXX_CREATE_STATIC_LIBRARY \"<CMAKE_AR> /nologo /machine:ARM64 /out:<TARGET> <OBJECTS>\" CACHE STRING \"\" FORCE)
endif()

View File

@@ -21,12 +21,7 @@ if(NOT DEFINED CMAKE_HOST_SYSTEM_PROCESSOR)
endif()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64")
# Windows ARM64 can run x86_64 via emulation, and no native ARM64 Zig build exists yet
if(CMAKE_HOST_WIN32)
set(ZIG_ARCH "x86_64")
else()
set(ZIG_ARCH "aarch64")
endif()
set(ZIG_ARCH "aarch64")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|AMD64|x86_64|X86_64|x64|X64")
set(ZIG_ARCH "x86_64")
else()

View File

@@ -1,34 +0,0 @@
@echo off
setlocal enabledelayedexpansion
REM Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
REM This is a workaround for CMake 4.1.0 bug
REM Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
if defined LLVM_LIB (
set "LLVM_LIB_EXE=!LLVM_LIB!"
) else (
where llvm-lib.exe >nul 2>&1
if !ERRORLEVEL! equ 0 (
for /f "delims=" %%i in ('where llvm-lib.exe') do set "LLVM_LIB_EXE=%%i"
) else if exist "C:\Program Files\LLVM\bin\llvm-lib.exe" (
set "LLVM_LIB_EXE=C:\Program Files\LLVM\bin\llvm-lib.exe"
) else (
echo Error: Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH.
exit /b 1
)
)
set "ARGS="
for %%a in (%*) do (
set "ARG=%%a"
if /i "!ARG!"=="/machine:x64" (
REM Skip this argument
) else (
set "ARGS=!ARGS! %%a"
)
)
"!LLVM_LIB_EXE!" %ARGS%
exit /b %ERRORLEVEL%

View File

@@ -1,18 +0,0 @@
# Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
# This is a workaround for CMake 4.1.0 bug where both /machine:ARM64 and /machine:x64 are added
# Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
if ($env:LLVM_LIB) {
$llvmLib = $env:LLVM_LIB
} elseif (Get-Command llvm-lib.exe -ErrorAction SilentlyContinue) {
$llvmLib = (Get-Command llvm-lib.exe).Source
} elseif (Test-Path "C:\Program Files\LLVM\bin\llvm-lib.exe") {
$llvmLib = "C:\Program Files\LLVM\bin\llvm-lib.exe"
} else {
Write-Error "Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH."
exit 1
}
$filteredArgs = $args | Where-Object { $_ -ne "/machine:x64" }
& $llvmLib @filteredArgs
exit $LASTEXITCODE

View File

@@ -1,34 +0,0 @@
@echo off
setlocal enabledelayedexpansion
REM Wrapper for llvm-lib that strips conflicting /machine:x64 flag for ARM64 builds
REM This is a workaround for CMake 4.1.0 bug
REM Find llvm-lib.exe - check LLVM_LIB env var, then PATH, then known locations
if defined LLVM_LIB (
set "LLVM_LIB_EXE=!LLVM_LIB!"
) else (
where llvm-lib.exe >nul 2>&1
if !ERRORLEVEL! equ 0 (
for /f "delims=" %%i in ('where llvm-lib.exe') do set "LLVM_LIB_EXE=%%i"
) else if exist "C:\Program Files\LLVM\bin\llvm-lib.exe" (
set "LLVM_LIB_EXE=C:\Program Files\LLVM\bin\llvm-lib.exe"
) else (
echo Error: Cannot find llvm-lib.exe. Set LLVM_LIB environment variable or add LLVM to PATH.
exit /b 1
)
)
set NEWARGS=
for %%a in (%*) do (
set "ARG=%%a"
if /i "!ARG!"=="/machine:x64" (
REM Skip /machine:x64 argument
) else (
set "NEWARGS=!NEWARGS! %%a"
)
)
"!LLVM_LIB_EXE!" %NEWARGS%
exit /b %ERRORLEVEL%

View File

@@ -7,13 +7,6 @@ register_repository(
4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac
)
set(BORINGSSL_CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF)
# Disable ASM on Windows ARM64 to avoid mixing non-ARM object files into ARM64 libs
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
list(APPEND BORINGSSL_CMAKE_ARGS -DOPENSSL_NO_ASM=1)
endif()
register_cmake_command(
TARGET
boringssl
@@ -22,7 +15,7 @@ register_cmake_command(
ssl
decrepit
ARGS
${BORINGSSL_CMAKE_ARGS}
-DBUILD_SHARED_LIBS=OFF
INCLUDES
include
)

View File

@@ -341,6 +341,7 @@ register_command(
SOURCES
${BUN_JAVASCRIPT_CODEGEN_SOURCES}
${BUN_CXX_SOURCES}
${ESBUILD_EXECUTABLE}
OUTPUTS
${BUN_CPP_OUTPUTS}
)
@@ -362,7 +363,7 @@ register_command(
)
if(SKIP_CODEGEN)
# Skip JavaScript codegen - useful for Windows ARM64 debug builds where bun crashes
# Skip JavaScript codegen - useful for bootstrapping new platforms
message(STATUS "SKIP_CODEGEN is ON - skipping bun-js-modules codegen")
foreach(output ${BUN_JAVASCRIPT_OUTPUTS})
if(NOT EXISTS ${output})
@@ -680,8 +681,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
if(APPLE)
set(ZIG_CPU "apple_m1")
elseif(WIN32)
# Windows ARM64: use a specific CPU with NEON support
# Zig running under x64 emulation would detect wrong CPU with "native"
# Windows ARM64: use a specific CPU target for consistent builds
set(ZIG_CPU "cortex_a76")
else()
set(ZIG_CPU "native")
@@ -1457,8 +1457,6 @@ if(NOT BUN_CPP_ONLY)
# ==856230==See https://github.com/google/sanitizers/issues/856 for possible workarounds.
# the linked issue refers to very old kernels but this still happens to us on modern ones.
# disabling ASLR to run the binary works around it
# Skip post-build test/features when cross-compiling (can't run the target binary on the host)
if(NOT CMAKE_CROSSCOMPILING)
set(TEST_BUN_COMMAND_BASE ${BUILD_PATH}/${bunExe} --revision)
set(TEST_BUN_COMMAND_ENV_WRAP
${CMAKE_COMMAND} -E env BUN_DEBUG_QUIET_LOGS=1)
@@ -1507,7 +1505,6 @@ if(NOT BUN_CPP_ONLY)
${BUILD_PATH}/features.json
)
endif()
endif() # NOT CMAKE_CROSSCOMPILING
if(CMAKE_HOST_APPLE AND bunStrip)
register_command(
@@ -1554,10 +1551,7 @@ if(NOT BUN_CPP_ONLY)
string(REPLACE bun ${bunTriplet} bunPath ${bun})
endif()
set(bunFiles ${bunExe})
if(NOT CMAKE_CROSSCOMPILING)
list(APPEND bunFiles features.json)
endif()
set(bunFiles ${bunExe} features.json)
if(WIN32)
list(APPEND bunFiles ${bun}.pdb)
elseif(APPLE)

View File

@@ -26,7 +26,7 @@ if(RELEASE)
list(APPEND LOLHTML_BUILD_ARGS --release)
endif()
# Cross-compilation: tell cargo to target ARM64
# Explicitly tell cargo to target ARM64 on Windows ARM64
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
list(APPEND LOLHTML_BUILD_ARGS --target aarch64-pc-windows-msvc)
set(LOLHTML_LIBRARY ${LOLHTML_BUILD_PATH}/aarch64-pc-windows-msvc/${LOLHTML_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}lolhtml${CMAKE_STATIC_LIBRARY_SUFFIX})
@@ -57,11 +57,11 @@ if(WIN32)
if(MSVC_VERSIONS)
list(GET MSVC_VERSIONS -1 MSVC_LATEST) # Get the latest version
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
# Use Hostx64/arm64 for cross-compilation from x64, fall back to native
if(EXISTS "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
else()
# Prefer native HostARM64, fall back to Hostx64/arm64
if(EXISTS "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
else()
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
endif()
set(CARGO_LINKER_VAR "CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER")
set(MSVC_LIB_ARCH "arm64")

View File

@@ -7,6 +7,32 @@ register_repository(
886098f3f339617b4243b286f5ed364b9989e245
)
# cloudflare/zlib hardcodes STATIC_LIBRARY_FLAGS "/machine:x64" for 64-bit MSVC,
# which conflicts with ARM64 object files. Patch it after clone to use the correct
# machine type based on CMAKE_SYSTEM_PROCESSOR.
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
set(ZLIB_PATCH_SCRIPT "${BUILD_PATH}/zlib-arm64-patch.cmake")
file(WRITE ${ZLIB_PATCH_SCRIPT} "
file(READ \"\${ZLIB_CMAKELISTS}\" content)
string(REPLACE \"/machine:x64\" \"/machine:ARM64\" content \"\${content}\")
file(WRITE \"\${ZLIB_CMAKELISTS}\" \"\${content}\")
file(TOUCH \"\${ZLIB_PATCH_MARKER}\")
")
register_command(
COMMENT "Patching zlib for ARM64"
TARGET patch-zlib
COMMAND ${CMAKE_COMMAND}
-DZLIB_CMAKELISTS=${VENDOR_PATH}/zlib/CMakeLists.txt
-DZLIB_PATCH_MARKER=${VENDOR_PATH}/zlib/.arm64-patched
-P ${ZLIB_PATCH_SCRIPT}
SOURCES ${VENDOR_PATH}/zlib/.ref
OUTPUTS ${VENDOR_PATH}/zlib/.arm64-patched
)
if(TARGET clone-zlib)
add_dependencies(patch-zlib clone-zlib)
endif()
endif()
# https://gitlab.kitware.com/cmake/cmake/-/issues/25755
if(APPLE)
set(ZLIB_CMAKE_C_FLAGS "-fno-define-target-os-macros")
@@ -38,3 +64,8 @@ register_cmake_command(
INCLUDES
.
)
# Ensure zlib is patched before configure
if(TARGET patch-zlib)
add_dependencies(configure-zlib patch-zlib)
endif()

View File

@@ -4,34 +4,3 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)
set(CMAKE_CROSSCOMPILING ON)
# The rest only applies when building on Windows (C++ and link steps).
# The Zig step runs on Linux and only needs CMAKE_SYSTEM_NAME/PROCESSOR above.
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
# Ensure clang/clang-cl targets Windows ARM64 (otherwise ARM64-specific flags like
# -march=armv8-a are rejected as x86-only).
set(CMAKE_C_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
set(CMAKE_CXX_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
# ARM64 has lock-free atomics (highway's FindAtomics check can't run ARM64 test binary on x64)
set(ATOMICS_LOCK_FREE_INSTRUCTIONS TRUE CACHE BOOL "" FORCE)
set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
# Clear any inherited static linker flags that might have wrong machine types
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
# Use wrapper script for llvm-lib that strips /machine:x64 flags
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
endif()

View File

@@ -17,13 +17,7 @@ if (NOT CI)
set(BUN_EXECUTABLE ${BUN_EXECUTABLE} CACHE FILEPATH "Bun executable" FORCE)
endif()
# On Windows ARM64, we need to add --smol flag to avoid crashes when running
# x64 bun under WoW64 emulation
if(WIN32 AND ARCH STREQUAL "aarch64")
set(BUN_FLAGS "--smol" CACHE STRING "Extra flags for bun executable")
else()
set(BUN_FLAGS "" CACHE STRING "Extra flags for bun executable")
endif()
set(BUN_FLAGS "" CACHE STRING "Extra flags for bun executable")
# If this is not set, some advanced features are not checked.
# https://github.com/oven-sh/bun/blob/cd7f6a1589db7f1e39dc4e3f4a17234afbe7826c/src/bun.js/javascript.zig#L1069-L1072

View File

@@ -51,7 +51,7 @@ if(APPLE)
endif()
if(WIN32)
# Prefer standalone LLVM over VS-bundled (standalone supports cross-compilation)
# Prefer standalone LLVM over VS-bundled
list(APPEND LLVM_PATHS "C:/Program Files/LLVM/bin")
endif()

View File

@@ -9,8 +9,6 @@ if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 8af7958ff0e2a4787569edf64641a1ae7cfe074a)
endif()
# Use preview build URL for Windows ARM64 until the fix is merged to main
set(WEBKIT_PREVIEW_PR 140)
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
string(SUBSTRING ${WEBKIT_VERSION} 0 8 WEBKIT_VERSION_SHORT)

View File

@@ -20,7 +20,7 @@ else()
unsupported(CMAKE_SYSTEM_NAME)
endif()
set(ZIG_COMMIT "c1423ff3fc7064635773a4a4616c5bf986eb00fe")
set(ZIG_COMMIT "c031cbebf5b063210473ff5204a24ebfb2492c72")
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
if(CMAKE_BUILD_TYPE STREQUAL "Release")
@@ -55,7 +55,14 @@ optionx(ZIG_OBJECT_FORMAT "obj|bc" "Output file format for Zig object files" DEF
optionx(ZIG_LOCAL_CACHE_DIR FILEPATH "The path to local the zig cache directory" DEFAULT ${CACHE_PATH}/zig/local)
optionx(ZIG_GLOBAL_CACHE_DIR FILEPATH "The path to the global zig cache directory" DEFAULT ${CACHE_PATH}/zig/global)
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${CI})
# The ReleaseSafe Zig compiler for Windows ARM64 has an LLVM SEH epilogue bug
# (incorrect size for compiler_rt.rem_pio2_large epilogue). Use the default build instead.
if(CI AND WIN32 AND DEFAULT_ZIG_ARCH STREQUAL "aarch64")
set(DEFAULT_ZIG_COMPILER_SAFE OFF)
else()
set(DEFAULT_ZIG_COMPILER_SAFE ${CI})
endif()
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${DEFAULT_ZIG_COMPILER_SAFE})
setenv(ZIG_LOCAL_CACHE_DIR ${ZIG_LOCAL_CACHE_DIR})
setenv(ZIG_GLOBAL_CACHE_DIR ${ZIG_GLOBAL_CACHE_DIR})

View File

@@ -22,8 +22,9 @@ bun upgrade
- [Linux, arm64](https://www.npmjs.com/package/@oven/bun-linux-aarch64)
- [Linux, x64](https://www.npmjs.com/package/@oven/bun-linux-x64)
- [Linux, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-linux-x64-baseline)
- [Windows](https://www.npmjs.com/package/@oven/bun-windows-x64)
- [Windows (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-windows-x64-baseline)
- [Windows, x64](https://www.npmjs.com/package/@oven/bun-windows-x64)
- [Windows, x64 (without AVX2 instructions)](https://www.npmjs.com/package/@oven/bun-windows-x64-baseline)
- [Windows ARM64](https://www.npmjs.com/package/@oven/bun-windows-aarch64)
### Future Platforms

View File

@@ -95,12 +95,12 @@ export const platforms: Platform[] = [
bin: "bun-windows-x64-baseline",
exe: "bin/bun.exe",
},
// {
// os: "win32",
// arch: "arm64",
// bin: "bun-windows-aarch64",
// exe: "bin/bun.exe",
// },
{
os: "win32",
arch: "arm64",
bin: "bun-windows-aarch64",
exe: "bin/bun.exe",
},
];
export const supportedPlatforms: Platform[] = platforms

View File

@@ -2428,7 +2428,7 @@ declare module "bun" {
}
namespace Build {
type Architecture = "x64" | "arm64";
type Architecture = "x64" | "arm64" | "aarch64";
type Libc = "glibc" | "musl";
type SIMD = "baseline" | "modern";
type CompileTarget =

View File

@@ -828,7 +828,7 @@ struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_con
struct us_socket_t *new_s = s;
if (ext_size != -1) {
struct us_poll_t *pool_ref = &s->p;
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) + old_ext_size, sizeof(struct us_socket_t) + ext_size);
new_s = (struct us_socket_t *) us_poll_resize(pool_ref, loop, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + old_ext_size, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + ext_size);
if(new_s != s) {
/* Mark the old socket as closed */
s->flags.is_closed = 1;

665
scripts/azure.mjs Normal file
View File

@@ -0,0 +1,665 @@
// Azure REST API client for machine.mjs
// Used by the [build images] pipeline to create Windows VM images (x64 and ARM64)
import { getSecret, isCI } from "./utils.mjs";
/**
* @typedef {Object} AzureConfig
* @property {string} tenantId
* @property {string} clientId
* @property {string} clientSecret
* @property {string} subscriptionId
* @property {string} resourceGroup
* @property {string} location
* @property {string} galleryName
*/
/** @returns {AzureConfig} */
function getConfig() {
const env = (name, fallback) => {
if (isCI) {
try {
return getSecret(name, { required: !fallback }) || fallback;
} catch {
if (fallback) return fallback;
throw new Error(`Azure secret not found: ${name}`);
}
}
return process.env[name] || fallback;
};
return {
tenantId: env("AZURE_TENANT_ID"),
clientId: env("AZURE_CLIENT_ID"),
clientSecret: env("AZURE_CLIENT_SECRET"),
subscriptionId: env("AZURE_SUBSCRIPTION_ID"),
resourceGroup: env("AZURE_RESOURCE_GROUP", "BUN-CI"),
location: env("AZURE_LOCATION", "eastus2"),
galleryName: env("AZURE_GALLERY_NAME", "bunCIGallery2"),
};
}
let _config;
function config() {
return (_config ??= getConfig());
}
// ============================================================================
// Authentication
// ============================================================================
let _accessToken = null;
let _tokenExpiry = 0;
async function getAccessToken() {
if (_accessToken && Date.now() < _tokenExpiry - 300_000) {
return _accessToken;
}
const { tenantId, clientId, clientSecret } = config();
const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: "https://management.azure.com/.default",
}),
});
if (!response.ok) {
throw new Error(`[azure] Auth failed: ${response.status} ${await response.text()}`);
}
const data = await response.json();
_accessToken = data.access_token;
_tokenExpiry = Date.now() + data.expires_in * 1000;
return _accessToken;
}
// ============================================================================
// REST Client
// ============================================================================
/**
* @param {"GET"|"PUT"|"POST"|"PATCH"|"DELETE"} method
* @param {string} path - Relative path under management.azure.com, or absolute URL
* @param {object} [body]
* @param {string} [apiVersion]
*/
async function azureFetch(method, path, body, apiVersion = "2024-07-01") {
const token = await getAccessToken();
const url = path.startsWith("http") ? new URL(path) : new URL(`https://management.azure.com${path}`);
if (!url.searchParams.has("api-version")) {
url.searchParams.set("api-version", apiVersion);
}
const options = {
method,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
};
if (body && method !== "GET" && method !== "DELETE") {
options.body = JSON.stringify(body);
}
for (let attempt = 0; attempt < 3; attempt++) {
const response = await fetch(url, options);
if (response.status === 429 || response.status >= 500) {
const wait = Math.pow(2, attempt) * 1000;
console.warn(`[azure] ${method} ${path} returned ${response.status}, retrying in ${wait}ms...`);
await new Promise(r => setTimeout(r, wait));
continue;
}
// 202 Accepted — async operation, poll for completion
if (response.status === 202) {
const operationUrl = response.headers.get("Azure-AsyncOperation") || response.headers.get("Location");
if (operationUrl) {
return waitForOperation(operationUrl);
}
}
if (response.status === 204) {
return null;
}
if (!response.ok) {
const text = await response.text();
throw new Error(`[azure] ${method} ${path} failed: ${response.status} ${text}`);
}
const text = await response.text();
return text ? JSON.parse(text) : null;
}
throw new Error(`[azure] ${method} ${path} failed after 3 retries`);
}
async function waitForOperation(operationUrl, maxWaitMs = 3_600_000) {
const start = Date.now();
let fetchErrors = 0;
while (Date.now() - start < maxWaitMs) {
const token = await getAccessToken();
let response;
try {
response = await fetch(operationUrl, {
headers: { Authorization: `Bearer ${token}` },
});
} catch (err) {
fetchErrors++;
if (fetchErrors > 10) {
throw new Error(`[azure] Operation poll failed after ${fetchErrors} fetch errors`, { cause: err });
}
console.warn(`[azure] Operation poll fetch error (${fetchErrors}), retrying...`);
await new Promise(r => setTimeout(r, 10_000));
continue;
}
if (!response.ok) {
throw new Error(`[azure] Operation poll failed: ${response.status} ${await response.text()}`);
}
const data = await response.json();
if (data.status === "Succeeded") {
return data.properties?.output ?? data;
}
if (data.status === "Failed" || data.status === "Canceled") {
throw new Error(`[azure] Operation ${data.status}: ${data.error?.message ?? "unknown"}`);
}
await new Promise(r => setTimeout(r, 5000));
}
throw new Error(`[azure] Operation timed out after ${maxWaitMs}ms`);
}
// ============================================================================
// Resource helpers
// ============================================================================
function rgPath() {
const { subscriptionId, resourceGroup } = config();
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}`;
}
// ============================================================================
// Public IP
// ============================================================================
async function createPublicIp(name) {
const { location } = config();
console.log(`[azure] Creating public IP: ${name}`);
const result = await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${name}`, {
location,
sku: { name: "Standard" },
properties: {
publicIPAllocationMethod: "Static",
deleteOption: "Delete",
},
});
return result?.properties?.ipAddress;
}
async function deletePublicIp(name) {
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${name}`).catch(() => {});
}
// ============================================================================
// Network Security Group
// ============================================================================
// ============================================================================
// Network Interface
// ============================================================================
async function createNic(name, publicIpName, subnetId, nsgId) {
const { location } = config();
console.log(`[azure] Creating NIC: ${name}`);
const publicIpId = `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${publicIpName}`;
await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`, {
location,
properties: {
ipConfigurations: [
{
name: "ipconfig1",
properties: {
privateIPAllocationMethod: "Dynamic",
publicIPAddress: { id: publicIpId, properties: { deleteOption: "Delete" } },
subnet: { id: subnetId },
},
},
],
...(nsgId ? { networkSecurityGroup: { id: nsgId } } : {}),
},
});
return `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`;
}
async function deleteNic(name) {
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Network/networkInterfaces/${name}`).catch(() => {});
}
// ============================================================================
// Virtual Machines
// ============================================================================
/**
* @param {object} opts
* @param {string} opts.name
* @param {string} opts.vmSize
* @param {object} opts.imageReference
* @param {number} opts.osDiskSizeGB
* @param {string} opts.nicId
* @param {string} opts.adminUsername
* @param {string} opts.adminPassword
* @param {Record<string, string>} [opts.tags]
*/
async function createVm(opts) {
const { location } = config();
console.log(`[azure] Creating VM: ${opts.name} (${opts.vmSize})`);
const result = await azureFetch("PUT", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${opts.name}`, {
location,
tags: opts.tags,
properties: {
hardwareProfile: { vmSize: opts.vmSize },
storageProfile: {
imageReference: opts.imageReference,
osDisk: {
createOption: "FromImage",
diskSizeGB: opts.osDiskSizeGB,
deleteOption: "Delete",
managedDisk: { storageAccountType: "Premium_LRS" },
},
},
osProfile: {
computerName: opts.name.substring(0, 15),
adminUsername: opts.adminUsername,
adminPassword: opts.adminPassword,
},
securityProfile: {
securityType: "TrustedLaunch",
},
networkProfile: {
networkInterfaces: [{ id: opts.nicId, properties: { deleteOption: "Delete" } }],
},
},
});
return result;
}
async function getVm(name) {
try {
return await azureFetch(
"GET",
`${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}?$expand=instanceView`,
);
} catch {
return null;
}
}
async function getVmPowerState(name) {
const vm = await getVm(name);
const statuses = vm?.properties?.instanceView?.statuses ?? [];
const powerStatus = statuses.find(s => s.code?.startsWith("PowerState/"));
return powerStatus?.code;
}
async function stopVm(name) {
console.log(`[azure] Stopping VM: ${name}`);
await azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}/deallocate`);
}
async function generalizeVm(name) {
console.log(`[azure] Generalizing VM: ${name}`);
await azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}/generalize`);
}
async function deleteVm(name) {
console.log(`[azure] Deleting VM: ${name}`);
await azureFetch("DELETE", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${name}?forceDeletion=true`);
}
async function getPublicIpAddress(publicIpName) {
const result = await azureFetch("GET", `${rgPath()}/providers/Microsoft.Network/publicIPAddresses/${publicIpName}`);
return result?.properties?.ipAddress;
}
/**
* Run a PowerShell script on a Windows VM via Azure Run Command.
* This works even without SSH installed on the VM.
*/
async function runCommand(vmName, script) {
console.log(`[azure] Running command on VM: ${vmName}`);
return azureFetch("POST", `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${vmName}/runCommand`, {
commandId: "RunPowerShellScript",
script: Array.isArray(script) ? script : [script],
});
}
/**
* Install OpenSSH server and configure authorized keys on a Windows VM.
*/
// SSH is not used — all remote operations go through Azure Run Command API.
// ============================================================================
// Virtual Network
// ============================================================================
// ============================================================================
// Compute Gallery
// ============================================================================
const GALLERY_API_VERSION = "2024-03-03";
async function ensureImageDefinition(name, os, arch) {
const { location, galleryName } = config();
const path = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${name}`;
try {
const def = await azureFetch("GET", path, undefined, GALLERY_API_VERSION);
if (def) return;
} catch {}
console.log(`[azure] Creating image definition: ${name}`);
await azureFetch(
"PUT",
path,
{
location,
properties: {
osType: os === "windows" ? "Windows" : "Linux",
osState: "Generalized",
hyperVGeneration: "V2",
architecture: arch === "aarch64" ? "Arm64" : "x64",
identifier: {
publisher: "bun",
offer: `${os}-${arch}-ci`,
sku: name,
},
features: [
{ name: "DiskControllerTypes", value: "SCSI, NVMe" },
{ name: "SecurityType", value: "TrustedLaunch" },
],
},
},
GALLERY_API_VERSION,
);
}
async function createImageVersion(imageDefName, version, vmId) {
const { location, galleryName } = config();
const path = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}/versions/${version}`;
console.log(`[azure] Creating image version: ${imageDefName}/${version}`);
const result = await azureFetch(
"PUT",
path,
{
location,
properties: {
storageProfile: {
source: { virtualMachineId: vmId },
},
},
},
GALLERY_API_VERSION,
);
return result;
}
// ============================================================================
// Base Images
// ============================================================================
function getBaseImageReference(os, arch) {
if (os === "windows") {
if (arch === "aarch64") {
return {
publisher: "MicrosoftWindowsDesktop",
offer: "windows11preview-arm64",
sku: "win11-24h2-pro",
version: "latest",
};
}
// Windows Server 2019 x64 — oldest supported version
return {
publisher: "MicrosoftWindowsServer",
offer: "WindowsServer",
sku: "2019-datacenter-gensecond",
version: "latest",
};
}
throw new Error(`[azure] Unsupported OS: ${os}`);
}
function getVmSize(arch) {
return arch === "aarch64" ? "Standard_D4ps_v6" : "Standard_D4ds_v6";
}
// ============================================================================
// Exports
// ============================================================================
export const azure = {
get name() {
return "azure";
},
config,
/**
* @param {import("./machine.mjs").MachineOptions} options
* @returns {Promise<import("./machine.mjs").Machine>}
*/
async createMachine(options) {
const { os, arch, tags, sshKeys } = options;
const vmName = `bun-${os}-${arch}-${Date.now()}`;
const publicIpName = `${vmName}-ip`;
const nicName = `${vmName}-nic`;
const vmSize = options.instanceType || getVmSize(arch);
const diskSizeGB = options.diskSizeGb || (os === "windows" ? 150 : 40);
// Generate a random password for the admin account
const adminPassword = `P@${crypto.randomUUID().replace(/-/g, "").substring(0, 20)}!`;
const subnetId = `${rgPath()}/providers/Microsoft.Network/virtualNetworks/bun-ci-vnet/subnets/default`;
const nsgId = `${rgPath()}/providers/Microsoft.Network/networkSecurityGroups/bun-ci-ssh-nsg`;
await createPublicIp(publicIpName);
const nicId = await createNic(nicName, publicIpName, subnetId, nsgId);
// Create VM
const imageReference = options.imageId ? { id: options.imageId } : getBaseImageReference(os, arch);
await createVm({
name: vmName,
vmSize,
imageReference,
osDiskSizeGB: diskSizeGB,
nicId,
adminUsername: "bunadmin",
adminPassword,
tags: tags
? Object.fromEntries(
Object.entries(tags)
.filter(([_, v]) => v != null)
.map(([k, v]) => [k, String(v)]),
)
: undefined,
});
// Wait for public IP to be assigned
let publicIp;
for (let i = 0; i < 30; i++) {
publicIp = await getPublicIpAddress(publicIpName);
if (publicIp) break;
await new Promise(r => setTimeout(r, 5000));
}
if (!publicIp) {
throw new Error(`[azure] Failed to get public IP for ${vmName}`);
}
console.log(`[azure] VM created: ${vmName} at ${publicIp}`);
// Use Azure Run Command for all remote operations instead of SSH.
// This avoids the sshd startup issues on Azure Windows VMs.
const spawnFn = async (command, opts) => {
const script = command.join(" ");
console.log(`[azure] Run: ${script}`);
// Note: Azure Run Command output is limited to the last 4096 bytes.
// Full output is not available — only the tail is returned.
// value[0] = stdout (ComponentStatus/StdOut), value[1] = stderr (ComponentStatus/StdErr)
const result = await runCommand(vmName, [script]);
const values = result?.value ?? [];
const stdout = values[0]?.message ?? "";
const stderr = values[1]?.message ?? "";
if (opts?.stdio === "inherit") {
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
}
// Only use displayStatus to detect errors — stderr often contains non-error
// output (rustup progress, cargo warnings, PowerShell Write-Warning, etc.)
const hasError = values.some(v => v?.displayStatus === "Provisioning failed");
const exitCode = hasError ? 1 : 0;
return { exitCode, stdout, stderr };
};
const spawnSafeFn = async (command, opts) => {
const result = await spawnFn(command, opts);
if (result.exitCode !== 0) {
const msg = result.stderr || result.stdout || "Unknown error";
throw new Error(`[azure] Command failed (exit ${result.exitCode}): ${command.join(" ")}\n${msg}`);
}
return result;
};
const upload = async (source, destination) => {
// Read the file locally and write it on the VM via Run Command
const { readFileSync } = await import("node:fs");
const content = readFileSync(source, "utf-8");
// Escape for PowerShell — use base64 to avoid escaping issues
const b64 = Buffer.from(content).toString("base64");
const script = [
`$bytes = [Convert]::FromBase64String('${b64}')`,
`$dir = Split-Path '${destination}' -Parent`,
`if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null }`,
`[IO.File]::WriteAllBytes('${destination}', $bytes)`,
`Write-Host "Uploaded to ${destination} ($($bytes.Length) bytes)"`,
];
console.log(`[azure] Uploading ${source} -> ${destination}`);
await runCommand(vmName, script);
};
const attach = async () => {
console.log(`[azure] Attach not supported via Run Command (VM: ${vmName}, IP: ${publicIp})`);
};
const waitForSsh = async () => {
// No SSH needed — Run Command works immediately after VM is provisioned
// Just verify the VM is responsive
console.log(`[azure] Verifying VM is responsive...`);
await runCommand(vmName, ["Write-Host 'VM is ready'"]);
console.log(`[azure] VM is responsive`);
};
const snapshot = async label => {
const vmId = `${rgPath()}/providers/Microsoft.Compute/virtualMachines/${vmName}`;
// Run sysprep inside the VM before deallocating.
// This prepares Windows for generalization so the gallery image
// can be used to create new VMs with OS provisioning.
console.log(`[azure] Running sysprep on ${vmName}...`);
await runCommand(vmName, ["C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /shutdown /quiet"]);
// Wait for VM to shut down after sysprep (sysprep triggers shutdown)
for (let i = 0; i < 60; i++) {
const state = await getVmPowerState(vmName);
if (state === "PowerState/stopped" || state === "PowerState/deallocated") break;
await new Promise(r => setTimeout(r, 10000));
}
// Deallocate the VM
await stopVm(vmName);
// Wait for VM to be deallocated
for (let i = 0; i < 60; i++) {
const state = await getVmPowerState(vmName);
if (state === "PowerState/deallocated") break;
await new Promise(r => setTimeout(r, 5000));
}
await generalizeVm(vmName);
// Ensure gallery and image definition exist.
// Use the label as the image definition name — this matches what ci.mjs
// emits as the image-name agent tag, so robobun can look it up directly.
const imageDefName = label;
await ensureImageDefinition(imageDefName, os, arch);
// Create a single version "1.0.0" under this definition.
await createImageVersion(imageDefName, "1.0.0", vmId);
// Wait for image replication to complete before returning.
// Single-region replication typically takes 5-15 minutes.
const { galleryName } = config();
const versionPath = `${rgPath()}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}/versions/1.0.0`;
console.log(`[azure] Waiting for image replication...`);
for (let i = 0; i < 120; i++) {
const ver = await azureFetch("GET", versionPath, undefined, GALLERY_API_VERSION);
const state = ver?.properties?.provisioningState;
if (state === "Succeeded") {
console.log(`[azure] Image ready: ${imageDefName}/1.0.0`);
break;
}
if (state === "Failed") {
throw new Error(`[azure] Image replication failed: ${JSON.stringify(ver?.properties)}`);
}
if (i % 6 === 0) {
console.log(`[azure] Image replicating... (${i}m elapsed)`);
}
await new Promise(r => setTimeout(r, 10_000));
}
return label;
};
const terminate = async () => {
await deleteVm(vmName);
// Resources with deleteOption=Delete are cleaned up automatically
// But clean up anything that might be left
await deleteNic(nicName);
await deletePublicIp(publicIpName);
};
return {
cloud: "azure",
id: vmName,
imageId: options.imageId,
instanceType: vmSize,
region: config().location,
get publicIp() {
return publicIp;
},
spawn: spawnFn,
spawnSafe: spawnSafeFn,
upload,
attach,
snapshot,
waitForSsh,
close: terminate,
[Symbol.asyncDispose]: terminate,
};
},
};

View File

@@ -1,6 +1,7 @@
# Version: 12
# A script that installs the dependencies needed to build and test Bun.
# This should work on Windows 10 or newer with PowerShell.
# Version: 14
# A script that installs the dependencies needed to build and test Bun on Windows.
# Supports both x64 and ARM64 using Scoop for package management.
# Used by Azure [build images] pipeline.
# If this script does not work on your machine, please open an issue:
# https://github.com/oven-sh/bun/issues
@@ -11,7 +12,7 @@
param (
[Parameter(Mandatory = $false)]
[switch]$CI = $false,
[switch]$CI = ($env:CI -eq "true"),
[Parameter(Mandatory = $false)]
[switch]$Optimize = $CI
)
@@ -19,17 +20,26 @@ param (
$ErrorActionPreference = "Stop"
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
function Execute-Command {
$command = $args -join ' '
Write-Output "$ $command"
# Detect ARM64 from registry (works even under x64 emulation)
$realArch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE
$script:IsARM64 = $realArch -eq "ARM64"
& $args[0] $args[1..$args.Length]
if ((-not $?) -or ($LASTEXITCODE -ne 0 -and $null -ne $LASTEXITCODE)) {
throw "Command failed: $command"
# If we're on ARM64 but running under x64 emulation, re-launch as native ARM64.
# Azure Run Command uses x64-emulated PowerShell which breaks package installs.
if ($script:IsARM64 -and $env:PROCESSOR_ARCHITECTURE -ne "ARM64") {
$nativePS = "$env:SystemRoot\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
if (Test-Path $nativePS) {
Write-Output "Re-launching bootstrap as native ARM64 PowerShell..."
& $nativePS -NoProfile -ExecutionPolicy Bypass -File $MyInvocation.MyCommand.Path @PSBoundParameters
exit $LASTEXITCODE
}
}
# ============================================================================
# Utility functions
# ============================================================================
function Which {
param ([switch]$Required = $false)
@@ -46,16 +56,6 @@ function Which {
}
}
function Execute-Script {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path
)
$pwsh = Which pwsh powershell -Required
Execute-Command $pwsh $Path
}
function Download-File {
param (
[Parameter(Mandatory = $true, Position = 0)]
@@ -87,18 +87,6 @@ function Download-File {
return $Path
}
function Install-Chocolatey {
if (Which choco) {
return
}
Write-Output "Installing Chocolatey..."
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
$installScript = Download-File "https://community.chocolatey.org/install.ps1"
Execute-Script $installScript
Refresh-Path
}
function Refresh-Path {
$paths = @(
[System.Environment]::GetEnvironmentVariable("Path", "Machine"),
@@ -111,15 +99,19 @@ function Refresh-Path {
Where-Object { $_ -and (Test-Path $_) } |
Select-Object -Unique
$env:Path = ($uniquePaths -join ';').TrimEnd(';')
if ($env:ChocolateyInstall) {
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 -ErrorAction SilentlyContinue
}
}
function Add-To-Path {
$absolutePath = Resolve-Path $args[0]
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$PathToAdd,
[Parameter(Mandatory = $false)]
[ValidateSet("Machine", "User")]
[string]$Scope = "Machine"
)
$absolutePath = Resolve-Path $PathToAdd
$currentPath = [Environment]::GetEnvironmentVariable("Path", $Scope)
if ($currentPath -like "*$absolutePath*") {
return
}
@@ -140,8 +132,8 @@ function Add-To-Path {
}
}
Write-Output "Adding $absolutePath to PATH..."
[Environment]::SetEnvironmentVariable("Path", "$newPath", "Machine")
Write-Output "Adding $absolutePath to PATH ($Scope)..."
[Environment]::SetEnvironmentVariable("Path", "$newPath", $Scope)
Refresh-Path
}
@@ -158,104 +150,374 @@ function Set-Env {
[System.Environment]::SetEnvironmentVariable("$Name", "$Value", "Process")
}
function Install-Package {
# ============================================================================
# Scoop — ARM64-native package manager
# ============================================================================
function Install-Scoop {
if (Which scoop) {
return
}
Write-Output "Installing Scoop..."
# Scoop blocks admin installs unless -RunAsAdmin is passed.
# Install to a known global location so all users can access it.
$env:SCOOP = "C:\Scoop"
[Environment]::SetEnvironmentVariable("SCOOP", $env:SCOOP, "Machine")
iex "& {$(irm get.scoop.sh)} -RunAsAdmin -ScoopDir C:\Scoop"
Add-To-Path "C:\Scoop\shims"
Refresh-Path
Write-Output "Scoop version: $(scoop --version)"
}
function Install-Scoop-Package {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Name,
[Parameter(Mandatory = $false)]
[string]$Command = $Name,
[Parameter(Mandatory = $false)]
[string]$Version,
[Parameter(Mandatory = $false)]
[switch]$Force = $false,
[Parameter(Mandatory = $false)]
[string[]]$ExtraArgs = @()
[string]$Command = $Name
)
if (-not $Force `
-and (Which $Command) `
-and (-not $Version -or (& $Command --version) -like "*$Version*")) {
if (Which $Command) {
return
}
Write-Output "Installing $Name..."
$flags = @(
"--yes",
"--accept-license",
"--no-progress",
"--force"
)
if ($Version) {
$flags += "--version=$Version"
}
Execute-Command choco install $Name @flags @ExtraArgs
Write-Output "Installing $Name (via Scoop)..."
# Scoop post_install scripts can have non-fatal Remove-Item errors
# (e.g. 7zip ARM64 7zr.exe locked, llvm-arm64 missing Uninstall.exe).
# Suppress all error streams so they don't kill the bootstrap or Packer.
$prevErrorPref = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
scoop install $Name *>&1 | ForEach-Object { "$_" } | Write-Host
$ErrorActionPreference = $prevErrorPref
Refresh-Path
}
function Install-Packages {
foreach ($package in $args) {
Install-Package $package
}
}
function Install-Common-Software {
Install-Chocolatey
Install-Pwsh
Install-Git
Install-Packages curl 7zip nssm
Install-NodeJs
Install-Bun
Install-Cygwin
if ($CI) {
# FIXME: Installing tailscale causes the AWS metadata server to become unreachable
# Install-Tailscale
Install-Buildkite
}
}
function Install-Pwsh {
Install-Package powershell-core -Command pwsh
if ($CI) {
$shellPath = (Which pwsh -Required)
New-ItemProperty `
-Path "HKLM:\\SOFTWARE\\OpenSSH" `
-Name DefaultShell `
-Value $shellPath `
-PropertyType String `
-Force
}
}
# ============================================================================
# Scoop packages (native ARM64 binaries)
# ============================================================================
function Install-Git {
Install-Packages git
Install-Scoop-Package git
# Git for Windows ships Unix tools (cat, head, tail, etc.) in usr\bin
$gitUsrBin = "C:\Scoop\apps\git\current\usr\bin"
if (Test-Path $gitUsrBin) {
Add-To-Path $gitUsrBin
}
if ($CI) {
Execute-Command git config --system --add safe.directory "*"
Execute-Command git config --system core.autocrlf false
Execute-Command git config --system core.eol lf
Execute-Command git config --system core.longpaths true
git config --system --add safe.directory "*"
git config --system core.autocrlf false
git config --system core.eol lf
git config --system core.longpaths true
}
}
function Install-NodeJs {
Install-Package nodejs -Command node -Version "24.3.0"
# Pin to match the ABI version Bun expects (NODE_MODULE_VERSION 137).
# Latest Node (25.x) uses ABI 141 which breaks node-gyp tests.
Install-Scoop-Package "nodejs@24.3.0" -Command node
}
function Install-Bun {
Install-Package bun -Version "1.3.1"
function Install-CMake {
Install-Scoop-Package cmake
}
function Install-Llvm {
$LLVM_VERSION = "21.1.8"
if (Which clang-cl) {
return
}
if ($script:IsARM64) {
Install-Scoop-Package "llvm-arm64@$LLVM_VERSION" -Command clang-cl
} else {
Install-Scoop-Package "llvm@$LLVM_VERSION" -Command clang-cl
}
}
function Install-Ninja {
Install-Scoop-Package ninja
}
function Install-Python {
Install-Scoop-Package python
}
function Install-Go {
Install-Scoop-Package go
}
function Install-Ruby {
Install-Scoop-Package ruby
}
function Install-7zip {
Install-Scoop-Package 7zip -Command 7z
}
function Install-Make {
Install-Scoop-Package make
}
function Install-Cygwin {
Install-Package cygwin
Add-To-Path "C:\tools\cygwin\bin"
# Cygwin's default mirror (mirrors.kernel.org) can be unreachable from Azure.
# Make this non-fatal — the build will fail later if cygwin is actually needed.
try {
Install-Scoop-Package cygwin
# Cygwin binaries are at <scoop>/apps/cygwin/current/root/bin
$cygwinBin = "C:\Scoop\apps\cygwin\current\root\bin"
if (Test-Path $cygwinBin) {
Add-To-Path $cygwinBin # Machine scope (default) — survives Sysprep
}
} catch {
Write-Warning "Cygwin installation failed (non-fatal): $_"
}
}
function Install-Tailscale {
Install-Package tailscale
# ============================================================================
# Manual installs (not available or not ideal via Scoop)
# ============================================================================
function Install-Pwsh {
if (Which pwsh) {
return
}
$pwshArch = if ($script:IsARM64) { "arm64" } else { "x64" }
Write-Output "Installing PowerShell Core ($pwshArch)..."
$msi = Download-File "https://github.com/PowerShell/PowerShell/releases/download/v7.5.2/PowerShell-7.5.2-win-$pwshArch.msi" -Name "pwsh-$pwshArch.msi"
$process = Start-Process msiexec -ArgumentList "/i `"$msi`" /quiet /norestart ADD_PATH=1" -Wait -PassThru -NoNewWindow
if ($process.ExitCode -ne 0) {
throw "Failed to install PowerShell: code $($process.ExitCode)"
}
Remove-Item $msi -ErrorAction SilentlyContinue
Refresh-Path
}
function Install-OpenSSH {
$sshdService = Get-Service -Name sshd -ErrorAction SilentlyContinue
if ($sshdService) {
return
}
Write-Output "Installing OpenSSH Server..."
# Add-WindowsCapability requires DISM elevation which isn't available in Packer's
# WinRM session. Download and install from GitHub releases instead.
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq "Arm64") { "Arm64" } else { "Win64" }
$url = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.8.1.0p1-Preview/OpenSSH-${arch}.zip"
$zip = "$env:TEMP\OpenSSH.zip"
$dest = "$env:ProgramFiles\OpenSSH"
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath "$env:TEMP\OpenSSH" -Force
New-Item -Path $dest -ItemType Directory -Force | Out-Null
$extractedDir = Get-ChildItem -Path "$env:TEMP\OpenSSH" -Directory | Select-Object -First 1
Get-ChildItem -Path $extractedDir.FullName -Recurse | Move-Item -Destination $dest -Force
& "$dest\install-sshd.ps1"
& "$dest\FixHostFilePermissions.ps1" -Confirm:$false
Remove-Item $zip, "$env:TEMP\OpenSSH" -Recurse -Force -ErrorAction SilentlyContinue
# Configure sshd to start on boot (don't start now — host keys may not exist yet during image build)
Set-Service -Name sshd -StartupType Automatic
# Set default shell to pwsh
$pwshPath = (Which pwsh -ErrorAction SilentlyContinue)
if (-not $pwshPath) { $pwshPath = (Which powershell) }
if ($pwshPath) {
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
-Value $pwshPath -PropertyType String -Force
}
# Firewall rule for port 22
$rule = Get-NetFirewallRule -Name "OpenSSH-Server" -ErrorAction SilentlyContinue
if (-not $rule) {
New-NetFirewallRule -Profile Any -Name "OpenSSH-Server" `
-DisplayName "OpenSSH Server (sshd)" -Enabled True `
-Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}
# Configure sshd_config for key-based auth
$sshdConfigPath = "C:\ProgramData\ssh\sshd_config"
if (Test-Path $sshdConfigPath) {
$config = Get-Content $sshdConfigPath
$config = $config -replace '#PubkeyAuthentication yes', 'PubkeyAuthentication yes'
$config = $config -replace 'PasswordAuthentication yes', 'PasswordAuthentication no'
Set-Content -Path $sshdConfigPath -Value $config
}
Write-Output "OpenSSH Server installed and configured"
# Register a startup task that fetches oven-sh GitHub org members' SSH keys
# on every boot so any bun dev can SSH in.
$fetchScript = @'
try {
$members = Invoke-RestMethod -Uri "https://api.github.com/orgs/oven-sh/members" -Headers @{ "User-Agent" = "bun-ci" }
$keys = @()
foreach ($member in $members) {
if ($member.type -ne "User" -or -not $member.login) { continue }
try {
$userKeys = (Invoke-WebRequest -Uri "https://github.com/$($member.login).keys" -UseBasicParsing).Content
if ($userKeys) { $keys += $userKeys.Trim() }
} catch { }
}
if ($keys.Count -gt 0) {
$keysPath = "C:\ProgramData\ssh\administrators_authorized_keys"
Set-Content -Path $keysPath -Value ($keys -join "`n") -Force
icacls $keysPath /inheritance:r /grant "SYSTEM:(F)" /grant "Administrators:(R)" | Out-Null
}
} catch { }
'@
$scriptPath = "C:\ProgramData\ssh\fetch-ssh-keys.ps1"
Set-Content -Path $scriptPath -Value $fetchScript -Force
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "FetchSshKeys" -Action $action -Trigger $trigger `
-Settings $settings -User "SYSTEM" -RunLevel Highest -Force
Write-Output "Registered FetchSshKeys startup task"
}
function Install-Ccache {
if (Which ccache) {
return
}
$version = "4.12.2"
$archSuffix = if ($script:IsARM64) { "aarch64" } else { "x86_64" }
Write-Output "Installing ccache $version ($archSuffix)..."
$zip = Download-File "https://github.com/ccache/ccache/releases/download/v$version/ccache-$version-windows-$archSuffix.zip" -Name "ccache-$archSuffix.zip"
$extractDir = "$env:TEMP\ccache-extract"
Expand-Archive $zip $extractDir -Force
$installDir = "$env:ProgramFiles\ccache"
New-Item -Path $installDir -ItemType Directory -Force | Out-Null
Copy-Item "$extractDir\ccache-$version-windows-$archSuffix\*" $installDir -Recurse -Force
Remove-Item $zip -ErrorAction SilentlyContinue
Remove-Item $extractDir -Recurse -ErrorAction SilentlyContinue
Add-To-Path $installDir
}
function Install-Bun {
if (Which bun) {
return
}
if ($script:IsARM64) {
# No published ARM64 bun binary yet — download from our blob storage
Write-Output "Installing Bun (ARM64 from blob storage)..."
$zip = Download-File "https://buncistore.blob.core.windows.net/artifacts/bun-windows-aarch64.zip" -Name "bun-arm64.zip"
$extractDir = "$env:TEMP\bun-arm64"
Expand-Archive -Path $zip -DestinationPath $extractDir -Force
$bunExe = Get-ChildItem $extractDir -Recurse -Filter "*.exe" | Where-Object { $_.Name -match "bun" } | Select-Object -First 1
if ($bunExe) {
Copy-Item $bunExe.FullName "C:\Windows\System32\bun.exe" -Force
Write-Output "Bun ARM64 installed to C:\Windows\System32\bun.exe"
} else {
throw "Failed to find bun executable in ARM64 zip"
}
} else {
Write-Output "Installing Bun..."
$installScript = Download-File "https://bun.sh/install.ps1" -Name "bun-install.ps1"
$pwsh = Which pwsh powershell -Required
& $pwsh $installScript
Refresh-Path
# Copy to System32 so it survives Sysprep (user profile PATH is lost)
$bunPath = Which bun
if ($bunPath) {
Copy-Item $bunPath "C:\Windows\System32\bun.exe" -Force
Write-Output "Bun copied to C:\Windows\System32\bun.exe"
}
}
}
function Install-Rust {
if (Which rustc) {
return
}
$rustPath = Join-Path $env:ProgramFiles "Rust"
if (-not (Test-Path $rustPath)) {
New-Item -Path $rustPath -ItemType Directory | Out-Null
}
# Set install paths before running rustup so it installs directly
# to Program Files (avoids issues with SYSTEM user profile path)
$env:CARGO_HOME = "$rustPath\cargo"
$env:RUSTUP_HOME = "$rustPath\rustup"
Write-Output "Installing Rustup..."
$rustupInit = Download-File "https://win.rustup.rs/" -Name "rustup-init.exe"
Write-Output "Installing Rust..."
& $rustupInit -y
Write-Output "Setting environment variables for Rust..."
Set-Env "CARGO_HOME" "$rustPath\cargo"
Set-Env "RUSTUP_HOME" "$rustPath\rustup"
Add-To-Path "$rustPath\cargo\bin"
}
function Install-Visual-Studio {
param (
[Parameter(Mandatory = $false)]
[string]$Edition = "community"
)
Write-Output "Downloading Visual Studio installer..."
$vsInstaller = Download-File "https://aka.ms/vs/17/release/vs_$Edition.exe"
Write-Output "Installing Visual Studio..."
$vsInstallArgs = @(
"--passive",
"--norestart",
"--wait",
"--force",
"--locale en-US",
"--add Microsoft.VisualStudio.Workload.NativeDesktop",
"--includeRecommended"
)
$process = Start-Process $vsInstaller -ArgumentList ($vsInstallArgs -join ' ') -Wait -PassThru -NoNewWindow
# Exit code 3010 means "reboot required" which is not a real error
if ($process.ExitCode -ne 0 -and $process.ExitCode -ne 3010) {
throw "Failed to install Visual Studio: code $($process.ExitCode)"
}
}
function Install-PdbAddr2line {
cargo install --examples "pdb-addr2line@0.11.2"
# Also copy to System32 so it's always on PATH (like bun.exe)
$src = Join-Path $env:CARGO_HOME "bin\pdb-addr2line.exe"
if (Test-Path $src) {
Copy-Item $src "C:\Windows\System32\pdb-addr2line.exe" -Force
Write-Output "pdb-addr2line copied to C:\Windows\System32"
}
}
function Install-Nssm {
if (Which nssm) {
return
}
# Try Scoop first, fall back to our mirror if nssm.cc is down (503 errors)
Install-Scoop-Package nssm
if (-not (Which nssm)) {
Write-Output "Scoop install of nssm failed, downloading from mirror..."
$zip = Download-File "https://buncistore.blob.core.windows.net/artifacts/nssm-2.24-103-gdee49fc.zip" -Name "nssm.zip"
Expand-Archive -Path $zip -DestinationPath "C:\Windows\Temp\nssm" -Force
$nssm = Get-ChildItem "C:\Windows\Temp\nssm" -Recurse -Filter "nssm.exe" | Where-Object { $_.DirectoryName -like "*win64*" } | Select-Object -First 1
if ($nssm) {
Copy-Item $nssm.FullName "C:\Windows\System32\nssm.exe" -Force
Write-Output "nssm installed to C:\Windows\System32\nssm.exe"
} else {
throw "Failed to install nssm"
}
}
}
# ============================================================================
# Buildkite
# ============================================================================
function Create-Buildkite-Environment-Hooks {
param (
[Parameter(Mandatory = $true)]
@@ -278,6 +540,17 @@ function Create-Buildkite-Environment-Hooks {
"@ | Set-Content -Path $environmentHook -Encoding UTF8
Write-Output "Environment hook created at $environmentHook"
# pre-exit hook: logout from Tailscale so ephemeral nodes are removed
# instantly instead of waiting 30-60 minutes. This runs after the job
# finishes, which is after the SSH user wait loop in runner.node.mjs.
$preExitHook = Join-Path $hooksDir "pre-exit.ps1"
@"
if (Test-Path "C:\Program Files\Tailscale\tailscale.exe") {
& "C:\Program Files\Tailscale\tailscale.exe" logout 2>`$null
}
"@ | Set-Content -Path $preExitHook -Encoding UTF8
Write-Output "Pre-exit hook created at $preExitHook"
}
function Install-Buildkite {
@@ -288,7 +561,8 @@ function Install-Buildkite {
Write-Output "Installing Buildkite agent..."
$env:buildkiteAgentToken = "xxx"
$installScript = Download-File "https://raw.githubusercontent.com/buildkite/agent/main/install.ps1"
Execute-Script $installScript
$pwsh = Which pwsh powershell -Required
& $pwsh $installScript
Refresh-Path
if ($CI) {
@@ -300,96 +574,9 @@ function Install-Buildkite {
}
}
function Install-Build-Essentials {
Install-Visual-Studio
Install-Packages `
cmake `
make `
ninja `
python `
golang `
nasm `
ruby `
strawberryperl `
mingw
Install-Rust
Install-Ccache
# Needed to remap stack traces
Install-PdbAddr2line
Install-Llvm
}
function Install-Visual-Studio {
param (
[Parameter(Mandatory = $false)]
[string]$Edition = "community"
)
Write-Output "Downloading Visual Studio installer..."
$vsInstaller = Download-File "https://aka.ms/vs/17/release/vs_$Edition.exe"
Write-Output "Installing Visual Studio..."
$vsInstallArgs = @(
"--passive",
"--norestart",
"--wait",
"--force",
"--locale en-US",
"--add Microsoft.VisualStudio.Workload.NativeDesktop",
"--includeRecommended"
)
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $vsInstaller
$startInfo.Arguments = $vsInstallArgs -join ' '
$startInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start()
$process.WaitForExit()
if ($process.ExitCode -ne 0) {
throw "Failed to install Visual Studio: code $($process.ExitCode)"
}
}
function Install-Rust {
if (Which rustc) {
return
}
Write-Output "Installing Rustup..."
$rustupInit = Download-File "https://win.rustup.rs/" -Name "rustup-init.exe"
Write-Output "Installing Rust..."
Execute-Command $rustupInit -y
Write-Output "Moving Rust to $env:ProgramFiles..."
$rustPath = Join-Path $env:ProgramFiles "Rust"
if (-not (Test-Path $rustPath)) {
New-Item -Path $rustPath -ItemType Directory
}
Move-Item "$env:UserProfile\.cargo" "$rustPath\cargo" -Force
Move-Item "$env:UserProfile\.rustup" "$rustPath\rustup" -Force
Write-Output "Setting environment variables for Rust..."
Set-Env "CARGO_HOME" "$rustPath\cargo"
Set-Env "RUSTUP_HOME" "$rustPath\rustup"
Add-To-Path "$rustPath\cargo\bin"
}
function Install-Ccache {
Install-Package ccache
}
function Install-PdbAddr2line {
Execute-Command cargo install --examples "pdb-addr2line@0.11.2"
}
function Install-Llvm {
Install-Package llvm `
-Command clang-cl `
-Version "21.1.8"
Add-To-Path "$env:ProgramFiles\LLVM\bin"
}
# ============================================================================
# System optimization
# ============================================================================
function Optimize-System {
Disable-Windows-Defender
@@ -417,8 +604,11 @@ function Disable-Windows-Threat-Protection {
}
function Uninstall-Windows-Defender {
Write-Output "Uninstalling Windows Defender..."
Uninstall-WindowsFeature -Name Windows-Defender
# Requires a reboot — run before the windows-restart Packer provisioner.
if (Get-Command Uninstall-WindowsFeature -ErrorAction SilentlyContinue) {
Write-Output "Uninstalling Windows Defender..."
Uninstall-WindowsFeature -Name Windows-Defender
}
}
function Disable-Windows-Services {
@@ -432,8 +622,12 @@ function Disable-Windows-Services {
)
foreach ($service in $services) {
Stop-Service $service -Force
Set-Service $service -StartupType Disabled
try {
Stop-Service $service -Force -ErrorAction SilentlyContinue
Set-Service $service -StartupType Disabled -ErrorAction SilentlyContinue
} catch {
Write-Warning "Could not disable service: $service"
}
}
}
@@ -448,13 +642,98 @@ function Disable-Power-Management {
powercfg /change hibernate-timeout-dc 0
}
# ============================================================================
# Main
# ============================================================================
if ($Optimize) {
Optimize-System
}
Install-Common-Software
Install-Build-Essentials
# Scoop package manager
Install-Scoop
# Packages via Scoop (native ARM64 or x64 depending on architecture)
# 7zip must be installed before git — git depends on 7zip via Scoop,
# and 7zip's post_install has a cleanup error on ARM64 SYSTEM context.
Install-7zip
Install-Git
Install-NodeJs
Install-CMake
Install-Ninja
Install-Python
Install-Go
Install-Ruby
Install-Make
Install-Llvm
Install-Cygwin
Install-Nssm
Install-Scoop-Package perl
# x64-only packages (not needed on ARM64)
if (-not $script:IsARM64) {
Install-Scoop-Package nasm
Install-Scoop-Package mingw -Command gcc
}
function Install-Tailscale {
if (Which tailscale -ErrorAction SilentlyContinue) {
return
}
Write-Output "Installing Tailscale..."
$msi = "$env:TEMP\tailscale-setup.msi"
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq "Arm64") { "arm64" } else { "amd64" }
Invoke-WebRequest -Uri "https://pkgs.tailscale.com/stable/tailscale-setup-latest-${arch}.msi" -OutFile $msi -UseBasicParsing
Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /quiet /norestart" -Wait
Remove-Item $msi -ErrorAction SilentlyContinue
Refresh-Path
Write-Output "Tailscale installed"
# Register a startup task that reads the tailscale authkey from Azure IMDS
# tags and joins the tailnet. The key is set by robobun as a VM tag.
$joinScript = @'
try {
$headers = @{ "Metadata" = "true" }
$response = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/instance/compute/tagsList?api-version=2021-02-01" -Headers $headers
$authkey = ($response | Where-Object { $_.name -eq "tailscale:authkey" }).value
if ($authkey) {
$stepKey = ($response | Where-Object { $_.name -eq "buildkite:step-key" }).value
$buildNumber = ($response | Where-Object { $_.name -eq "buildkite:build-number" }).value
if ($stepKey) {
$hostname = "azure-${stepKey}"
if ($buildNumber) { $hostname += "-${buildNumber}" }
} else {
$hostname = (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/instance/compute/name?api-version=2021-02-01&format=text" -Headers $headers)
}
& "C:\Program Files\Tailscale\tailscale.exe" up --authkey=$authkey --hostname=$hostname --unattended
}
} catch { }
'@
$scriptPath = "C:\ProgramData\tailscale-join.ps1"
Set-Content -Path $scriptPath -Value $joinScript -Force
$action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "TailscaleJoin" -Action $action -Trigger $trigger `
-Settings $settings -User "SYSTEM" -RunLevel Highest -Force
Write-Output "Registered TailscaleJoin startup task"
}
# Manual installs (not in Scoop or need special handling)
Install-Pwsh
Install-OpenSSH
#Install-Tailscale # Disabled — Tailscale adapter interferes with IPv6 multicast tests (node-dgram)
Install-Bun
Install-Ccache
Install-Rust
Install-Visual-Studio
Install-PdbAddr2line
if ($CI) {
Install-Buildkite
}
if ($Optimize) {
Optimize-System-Needs-Reboot
}
}

View File

@@ -14,14 +14,7 @@ import {
startGroup,
} from "./utils.mjs";
// Detect Windows ARM64 - bun may run under x64 emulation (WoW64), so check multiple indicators
const isWindowsARM64 =
isWindows &&
(process.env.PROCESSOR_ARCHITECTURE === "ARM64" ||
process.env.VSCMD_ARG_HOST_ARCH === "arm64" ||
process.env.MSYSTEM_CARCH === "aarch64" ||
(process.env.PROCESSOR_IDENTIFIER || "").includes("ARMv8") ||
process.arch === "arm64");
const isWindowsARM64 = isWindows && process.arch === "arm64";
if (globalThis.Bun) {
await import("./glob-sources.mjs");
@@ -57,11 +50,7 @@ async function build(args) {
if (process.platform === "win32" && !process.env["VSINSTALLDIR"]) {
const shellPath = join(import.meta.dirname, "vs-shell.ps1");
const scriptPath = import.meta.filename;
// When cross-compiling to ARM64, tell vs-shell.ps1 to set up the x64_arm64 VS environment
const toolchainIdx = args.indexOf("--toolchain");
const requestedVsArch = toolchainIdx !== -1 && args[toolchainIdx + 1] === "windows-aarch64" ? "arm64" : undefined;
const env = requestedVsArch ? { ...process.env, BUN_VS_ARCH: requestedVsArch } : undefined;
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args], { env });
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args]);
}
if (isCI) {

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env node
import { existsSync, mkdtempSync, readdirSync } from "node:fs";
import { chmodSync, existsSync, mkdtempSync, readdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, extname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { inspect, parseArgs } from "node:util";
import { azure } from "./azure.mjs";
import { docker } from "./docker.mjs";
import { tart } from "./tart.mjs";
import {
@@ -35,7 +37,6 @@ import {
spawnSshSafe,
spawnSyncSafe,
startGroup,
tmpdir,
waitForPort,
which,
writeFile,
@@ -1047,16 +1048,14 @@ function getRdpFile(hostname, username) {
* @property {(options: MachineOptions) => Promise<Machine>} createMachine
*/
/**
* @param {string} name
* @returns {Cloud}
*/
function getCloud(name) {
switch (name) {
case "docker":
return docker;
case "aws":
return aws;
case "azure":
return azure;
case "tart":
return tart;
}
@@ -1127,6 +1126,173 @@ function getCloud(name) {
* @property {SshKey[]} sshKeys
*/
async function getAzureToken(tenantId, clientId, clientSecret) {
const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${encodeURIComponent(clientSecret)}&scope=https://management.azure.com/.default`,
});
if (!response.ok) throw new Error(`Azure auth failed: ${response.status}`);
const data = await response.json();
return data.access_token;
}
/**
* Build a Windows image using Packer (Azure only).
* Packer handles VM creation, bootstrap, sysprep, and gallery capture via WinRM.
* This eliminates all the Azure Run Command issues (output truncation, x64 emulation,
* PATH not refreshing, stderr false positives, quote escaping).
*/
async function buildWindowsImageWithPacker({ os, arch, release, command, ci, agentPath, bootstrapPath }) {
const { getSecret } = await import("./utils.mjs");
// Determine Packer template
const templateName = arch === "aarch64" ? "windows-arm64" : "windows-x64";
const templateDir = resolve(import.meta.dirname, "packer");
const templateFile = join(templateDir, `${templateName}.pkr.hcl`);
if (!existsSync(templateFile)) {
throw new Error(`Packer template not found: ${templateFile}`);
}
// Get Azure credentials from Buildkite secrets
const clientId = await getSecret("AZURE_CLIENT_ID");
const clientSecret = await getSecret("AZURE_CLIENT_SECRET");
const subscriptionId = await getSecret("AZURE_SUBSCRIPTION_ID");
const tenantId = await getSecret("AZURE_TENANT_ID");
const resourceGroup = await getSecret("AZURE_RESOURCE_GROUP");
const location = (await getSecret("AZURE_LOCATION")) || "eastus2";
const galleryName = (await getSecret("AZURE_GALLERY_NAME")) || "bunCIGallery2";
// Image naming must match getImageName() in ci.mjs:
// [publish images] / normal CI: "windows-x64-2019-v13"
// [build images]: "windows-x64-2019-build-37194"
const imageKey = arch === "aarch64" ? "windows-aarch64-11" : "windows-x64-2019";
const imageDefName =
command === "publish-image"
? `${imageKey}-v${getBootstrapVersion(os)}`
: ci
? `${imageKey}-build-${getBuildNumber()}`
: `${imageKey}-build-draft-${Date.now()}`;
const galleryArch = arch === "aarch64" ? "Arm64" : "x64";
console.log(`[packer] Ensuring gallery image definition: ${imageDefName}`);
const galleryPath = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Compute/galleries/${galleryName}/images/${imageDefName}`;
const token = await getAzureToken(tenantId, clientId, clientSecret);
const defResponse = await fetch(`https://management.azure.com${galleryPath}?api-version=2024-03-03`, {
method: "PUT",
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({
location: location,
properties: {
osType: "Windows",
osState: "Generalized",
hyperVGeneration: "V2",
architecture: galleryArch,
identifier: { publisher: "bun", offer: `${os}-${arch}-ci`, sku: imageDefName },
features: [
{ name: "DiskControllerTypes", value: "SCSI, NVMe" },
{ name: "SecurityType", value: "TrustedLaunch" },
],
},
}),
});
if (!defResponse.ok && defResponse.status !== 409) {
throw new Error(`Failed to create gallery image definition: ${defResponse.status} ${await defResponse.text()}`);
}
// Install Packer if not available
const packerBin = await ensurePacker();
// Initialize plugins
console.log("[packer] Initializing plugins...");
await spawnSafe([packerBin, "init", templateDir], { stdio: "inherit" });
// Build the image
console.log(`[packer] Building ${templateName} image: ${imageDefName}`);
const packerArgs = [
packerBin,
"build",
"-only",
`azure-arm.${templateName}`,
"-var",
`client_id=${clientId}`,
"-var",
`client_secret=${clientSecret}`,
"-var",
`subscription_id=${subscriptionId}`,
"-var",
`tenant_id=${tenantId}`,
"-var",
`resource_group=${resourceGroup}-EASTUS2`,
"-var",
`gallery_resource_group=${resourceGroup}`,
"-var",
`location=${location}`,
"-var",
`gallery_name=${galleryName}`,
"-var",
`image_name=${imageDefName}`,
"-var",
`bootstrap_script=${bootstrapPath}`,
"-var",
`agent_script=${agentPath}`,
templateDir,
];
await spawnSafe(packerArgs, {
stdio: "inherit",
env: {
...process.env,
// Packer also reads these env vars
ARM_CLIENT_ID: clientId,
ARM_CLIENT_SECRET: clientSecret,
ARM_SUBSCRIPTION_ID: subscriptionId,
ARM_TENANT_ID: tenantId,
},
});
console.log(`[packer] Image built successfully: ${imageDefName}`);
}
/**
* Download and install Packer if not already available.
*/
async function ensurePacker() {
// Check if packer is already in PATH
const packerPath = which("packer");
if (packerPath) {
console.log("[packer] Found:", packerPath);
return packerPath;
}
// Check if we have a local copy
const localPacker = join(tmpdir(), "packer");
if (existsSync(localPacker)) {
return localPacker;
}
// Download Packer
const version = "1.15.0";
const platform = process.platform === "win32" ? "windows" : process.platform;
const packerArch = process.arch === "arm64" ? "arm64" : "amd64";
const url = `https://releases.hashicorp.com/packer/${version}/packer_${version}_${platform}_${packerArch}.zip`;
console.log(`[packer] Downloading Packer ${version}...`);
const zipPath = join(tmpdir(), "packer.zip");
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to download Packer: ${response.status}`);
const buffer = Buffer.from(await response.arrayBuffer());
writeFileSync(zipPath, buffer);
// Extract
await spawnSafe(["unzip", "-o", zipPath, "-d", tmpdir()], { stdio: "inherit" });
chmodSync(localPacker, 0o755);
console.log(`[packer] Installed Packer ${version}`);
return localPacker;
}
async function main() {
const { positionals } = parseArgs({
allowPositionals: true,
@@ -1269,6 +1435,13 @@ async function main() {
}
}
// Use Packer for Windows Azure image builds — it handles VM creation,
// bootstrap, sysprep, and gallery capture via WinRM (no Run Command hacks).
if (args["cloud"] === "azure" && os === "windows" && (command === "create-image" || command === "publish-image")) {
await buildWindowsImageWithPacker({ os, arch, release, command, ci, agentPath, bootstrapPath });
return;
}
/** @type {Machine} */
const machine = await startGroup("Creating machine...", async () => {
console.log("Creating machine:");
@@ -1342,7 +1515,7 @@ async function main() {
});
}
await startGroup("Connecting with SSH...", async () => {
await startGroup(`Connecting${options.cloud === "azure" ? "" : " with SSH"}...`, async () => {
const command = os === "windows" ? ["cmd", "/c", "ver"] : ["uname", "-a"];
await machine.spawnSafe(command, { stdio: "inherit" });
});
@@ -1392,7 +1565,12 @@ async function main() {
if (cloud.name === "docker" || features?.includes("docker")) {
return;
}
await machine.spawnSafe(["node", remotePath, "install"], { stdio: "inherit" });
// Refresh PATH from registry before running agent.mjs — bootstrap added
// buildkite-agent to PATH but Azure Run Command sessions have stale PATH.
const cmd = `$env:PATH = [Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('PATH', 'User'); C:\\Scoop\\apps\\nodejs\\current\\node.exe ${remotePath} install`;
await machine.spawnSafe(["powershell", "-NoProfile", "-Command", cmd], {
stdio: "inherit",
});
});
} else {
const tmpPath = "/tmp/agent.mjs";

View File

@@ -0,0 +1,74 @@
packer {
required_plugins {
azure = {
source = "github.com/hashicorp/azure"
version = "= 2.5.0"
}
}
}
// Shared variables for all Windows image builds
variable "client_id" {
type = string
default = env("AZURE_CLIENT_ID")
}
variable "client_secret" {
type = string
sensitive = true
default = env("AZURE_CLIENT_SECRET")
}
variable "subscription_id" {
type = string
default = env("AZURE_SUBSCRIPTION_ID")
}
variable "tenant_id" {
type = string
default = env("AZURE_TENANT_ID")
}
variable "resource_group" {
type = string
default = env("AZURE_RESOURCE_GROUP")
}
variable "location" {
type = string
default = "eastus2"
}
variable "gallery_name" {
type = string
default = "bunCIGallery2"
}
variable "build_number" {
type = string
default = "0"
}
variable "image_name" {
type = string
default = ""
description = "Gallery image definition name. If empty, derived from build_number."
}
variable "bootstrap_script" {
type = string
default = "scripts/bootstrap.ps1"
}
variable "agent_script" {
type = string
default = ""
description = "Path to bundled agent.mjs. If empty, agent install is skipped."
}
variable "gallery_resource_group" {
type = string
default = "BUN-CI"
description = "Resource group containing the Compute Gallery (may differ from build RG)"
}

View File

@@ -0,0 +1,143 @@
source "azure-arm" "windows-arm64" {
// Authentication
client_id = var.client_id
client_secret = var.client_secret
subscription_id = var.subscription_id
tenant_id = var.tenant_id
// Source image Windows 11 ARM64 (no Windows Server ARM64 exists)
os_type = "Windows"
image_publisher = "MicrosoftWindowsDesktop"
image_offer = "windows11preview-arm64"
image_sku = "win11-24h2-pro"
image_version = "latest"
// Build VM only used during image creation, not for CI runners.
// CI runner VM sizes are set in ci.mjs (azureVmSizes).
vm_size = "Standard_D4ps_v6"
// Use existing resource group instead of creating a temp one
build_resource_group_name = var.resource_group
os_disk_size_gb = 150
// Security
security_type = "TrustedLaunch"
secure_boot_enabled = true
vtpm_enabled = true
// Networking Packer creates a temp VNet + public IP + NSG automatically.
// WinRM communicator
communicator = "winrm"
winrm_use_ssl = true
winrm_insecure = true
winrm_timeout = "15m"
winrm_username = "packer"
// CRITICAL: No managed_image_name ARM64 doesn't support Managed Images.
// Packer publishes directly from the VM to the gallery (PR #242 feature).
shared_image_gallery_destination {
subscription = var.subscription_id
resource_group = var.gallery_resource_group
gallery_name = var.gallery_name
image_name = var.image_name != "" ? var.image_name : "windows-aarch64-11-build-${var.build_number}"
image_version = "1.0.0"
storage_account_type = "Standard_LRS"
target_region { name = var.location }
target_region { name = "australiaeast" }
target_region { name = "brazilsouth" }
target_region { name = "canadacentral" }
target_region { name = "canadaeast" }
target_region { name = "centralindia" }
target_region { name = "centralus" }
target_region { name = "francecentral" }
target_region { name = "germanywestcentral" }
target_region { name = "italynorth" }
target_region { name = "japaneast" }
target_region { name = "japanwest" }
target_region { name = "koreacentral" }
target_region { name = "mexicocentral" }
target_region { name = "northcentralus" }
target_region { name = "northeurope" }
target_region { name = "southcentralus" }
target_region { name = "southeastasia" }
target_region { name = "spaincentral" }
target_region { name = "swedencentral" }
target_region { name = "switzerlandnorth" }
target_region { name = "uaenorth" }
target_region { name = "ukwest" }
target_region { name = "westeurope" }
target_region { name = "westus" }
target_region { name = "westus2" }
target_region { name = "westus3" }
}
azure_tags = {
os = "windows"
arch = "aarch64"
build = var.build_number
}
}
build {
sources = ["source.azure-arm.windows-arm64"]
// Step 1: Run bootstrap installs all build dependencies
provisioner "powershell" {
script = var.bootstrap_script
valid_exit_codes = [0, 3010]
environment_vars = ["CI=true"]
}
// Step 2: Upload agent.mjs
provisioner "file" {
source = var.agent_script
destination = "C:\\buildkite-agent\\agent.mjs"
}
// Step 3: Install agent service via nssm
provisioner "powershell" {
inline = [
"C:\\Scoop\\apps\\nodejs\\current\\node.exe C:\\buildkite-agent\\agent.mjs install"
]
valid_exit_codes = [0]
}
// Step 4: Reboot to clear pending updates (VS Build Tools, Windows Updates)
provisioner "windows-restart" {
restart_timeout = "10m"
}
// Step 5: Sysprep MUST be last provisioner
provisioner "powershell" {
inline = [
"Remove-Item -Recurse -Force C:\\Windows\\Panther -ErrorAction SilentlyContinue",
"Write-Output '>>> Clearing pending reboot flags...'",
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -Recurse -Force -ErrorAction SilentlyContinue",
"Remove-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update' -Name 'RebootRequired' -Force -ErrorAction SilentlyContinue",
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -Recurse -Force -ErrorAction SilentlyContinue",
"Remove-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager' -Name 'PendingFileRenameOperations' -Force -ErrorAction SilentlyContinue",
"Write-Output '>>> Waiting for Azure Guest Agent...'",
"while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
"while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
"Write-Output '>>> Running Sysprep...'",
"$global:LASTEXITCODE = 0",
"& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm",
"$timeout = 300; $elapsed = 0",
"while ($true) {",
" $imageState = (Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State).ImageState",
" Write-Output \"ImageState: $imageState ($${elapsed}s)\"",
" if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break }",
" if ($elapsed -ge $timeout) {",
" Write-Error \"Timed out after $${timeout}s -- stuck at $imageState\"",
" Get-Content \"$env:SystemRoot\\System32\\Sysprep\\Panther\\setupact.log\" -Tail 100 -ErrorAction SilentlyContinue",
" exit 1",
" }",
" Start-Sleep -s 10",
" $elapsed += 10",
"}",
"Write-Output '>>> Sysprep complete.'"
]
}
}

View File

@@ -0,0 +1,144 @@
source "azure-arm" "windows-x64" {
// Authentication (from env vars or -var flags)
client_id = var.client_id
client_secret = var.client_secret
subscription_id = var.subscription_id
tenant_id = var.tenant_id
// Source image Windows Server 2019 Gen2
os_type = "Windows"
image_publisher = "MicrosoftWindowsServer"
image_offer = "WindowsServer"
image_sku = "2019-datacenter-gensecond"
image_version = "latest"
// Build VM only used during image creation, not for CI runners.
// CI runner VM sizes are set in ci.mjs (azureVmSizes).
vm_size = "Standard_D4ds_v6"
// Use existing resource group instead of creating a temp one
build_resource_group_name = var.resource_group
os_disk_size_gb = 150
// Security
security_type = "TrustedLaunch"
secure_boot_enabled = true
vtpm_enabled = true
// Networking Packer creates a temp VNet + public IP + NSG automatically.
// WinRM needs the public IP to connect from CI runners.
// WinRM communicator Packer auto-configures via temp Key Vault
communicator = "winrm"
winrm_use_ssl = true
winrm_insecure = true
winrm_timeout = "15m"
winrm_username = "packer"
// Output Managed Image (x64 supports this)
// Also publish to Compute Gallery
shared_image_gallery_destination {
subscription = var.subscription_id
resource_group = var.gallery_resource_group
gallery_name = var.gallery_name
image_name = var.image_name != "" ? var.image_name : "windows-x64-2019-build-${var.build_number}"
image_version = "1.0.0"
storage_account_type = "Standard_LRS"
target_region { name = var.location }
target_region { name = "australiaeast" }
target_region { name = "brazilsouth" }
target_region { name = "canadacentral" }
target_region { name = "canadaeast" }
target_region { name = "centralindia" }
target_region { name = "centralus" }
target_region { name = "francecentral" }
target_region { name = "germanywestcentral" }
target_region { name = "italynorth" }
target_region { name = "japaneast" }
target_region { name = "japanwest" }
target_region { name = "koreacentral" }
target_region { name = "mexicocentral" }
target_region { name = "northcentralus" }
target_region { name = "northeurope" }
target_region { name = "southcentralus" }
target_region { name = "southeastasia" }
target_region { name = "spaincentral" }
target_region { name = "swedencentral" }
target_region { name = "switzerlandnorth" }
target_region { name = "uaenorth" }
target_region { name = "ukwest" }
target_region { name = "westeurope" }
target_region { name = "westus" }
target_region { name = "westus2" }
target_region { name = "westus3" }
}
azure_tags = {
os = "windows"
arch = "x64"
build = var.build_number
}
}
build {
sources = ["source.azure-arm.windows-x64"]
// Step 1: Run bootstrap installs all build dependencies
provisioner "powershell" {
script = var.bootstrap_script
valid_exit_codes = [0, 3010]
environment_vars = ["CI=true"]
}
// Step 2: Upload agent.mjs
provisioner "file" {
source = var.agent_script
destination = "C:\\buildkite-agent\\agent.mjs"
}
// Step 3: Install agent service via nssm
provisioner "powershell" {
inline = [
"C:\\Scoop\\apps\\nodejs\\current\\node.exe C:\\buildkite-agent\\agent.mjs install"
]
valid_exit_codes = [0]
}
// Step 4: Reboot to clear pending updates (VS Build Tools, Windows Updates)
provisioner "windows-restart" {
restart_timeout = "10m"
}
// Step 5: Sysprep MUST be last provisioner
provisioner "powershell" {
inline = [
"Remove-Item -Recurse -Force C:\\Windows\\Panther -ErrorAction SilentlyContinue",
"Write-Output '>>> Clearing pending reboot flags...'",
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -Recurse -Force -ErrorAction SilentlyContinue",
"Remove-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update' -Name 'RebootRequired' -Force -ErrorAction SilentlyContinue",
"Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -Recurse -Force -ErrorAction SilentlyContinue",
"Remove-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager' -Name 'PendingFileRenameOperations' -Force -ErrorAction SilentlyContinue",
"Write-Output '>>> Waiting for Azure Guest Agent...'",
"while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
"while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
"Write-Output '>>> Running Sysprep...'",
"$global:LASTEXITCODE = 0",
"& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm",
"$timeout = 300; $elapsed = 0",
"while ($true) {",
" $imageState = (Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State).ImageState",
" Write-Output \"ImageState: $imageState ($${elapsed}s)\"",
" if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break }",
" if ($elapsed -ge $timeout) {",
" Write-Error \"Timed out after $${timeout}s -- stuck at $imageState\"",
" Get-Content \"$env:SystemRoot\\System32\\Sysprep\\Panther\\setupact.log\" -Tail 100 -ErrorAction SilentlyContinue",
" exit 1",
" }",
" Start-Sleep -s 10",
" $elapsed += 10",
"}",
"Write-Output '>>> Sysprep complete.'"
]
}
}

View File

@@ -1837,6 +1837,13 @@ export function getTailscale() {
}
}
if (isWindows) {
const tailscaleExe = "C:\\Program Files\\Tailscale\\tailscale.exe";
if (existsSync(tailscaleExe)) {
return tailscaleExe;
}
}
return "tailscale";
}
@@ -2043,7 +2050,7 @@ export function getShell() {
}
/**
* @typedef {"aws" | "google"} Cloud
* @typedef {"aws" | "google" | "azure"} Cloud
*/
/** @type {Cloud | undefined} */
@@ -2136,6 +2143,37 @@ export async function isGoogleCloud() {
}
}
/**
* @returns {Promise<boolean | undefined>}
*/
export async function isAzure() {
if (typeof detectedCloud === "string") {
return detectedCloud === "azure";
}
async function detectAzure() {
// Azure IMDS (Instance Metadata Service) — the official way to detect Azure VMs.
// https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
const { error, body } = await curl("http://169.254.169.254/metadata/instance?api-version=2021-02-01", {
headers: { "Metadata": "true" },
retries: 1,
});
if (!error && body) {
try {
const metadata = JSON.parse(body);
if (metadata?.compute?.azEnvironment) {
return true;
}
} catch {}
}
}
if (await detectAzure()) {
detectedCloud = "azure";
return true;
}
}
/**
* @returns {Promise<Cloud | undefined>}
*/
@@ -2151,6 +2189,10 @@ export async function getCloud() {
if (await isGoogleCloud()) {
return "google";
}
if (await isAzure()) {
return "azure";
}
}
/**
@@ -2175,6 +2217,10 @@ export async function getCloudMetadata(name, cloud) {
} else if (cloud === "google") {
url = new URL(name, "http://metadata.google.internal/computeMetadata/v1/instance/");
headers = { "Metadata-Flavor": "Google" };
} else if (cloud === "azure") {
// Azure IMDS uses a single JSON endpoint; individual fields are extracted by the caller.
url = new URL("http://169.254.169.254/metadata/instance?api-version=2021-02-01");
headers = { "Metadata": "true" };
} else {
throw new Error(`Unsupported cloud: ${inspect(cloud)}`);
}
@@ -2193,7 +2239,25 @@ export async function getCloudMetadata(name, cloud) {
* @param {Cloud} [cloud]
* @returns {Promise<string | undefined>}
*/
export function getCloudMetadataTag(tag, cloud) {
export async function getCloudMetadataTag(tag, cloud) {
cloud ??= await getCloud();
if (cloud === "azure") {
// Azure IMDS returns all tags in a single JSON response.
// Tags are in compute.tagsList as [{name, value}, ...].
const body = await getCloudMetadata("", cloud);
if (!body) return;
try {
const metadata = JSON.parse(body);
const tags = metadata?.compute?.tagsList;
if (Array.isArray(tags)) {
const entry = tags.find(t => t.name === tag);
return entry?.value;
}
} catch {}
return;
}
const metadata = {
"aws": `tags/instance/${tag}`,
"google": `labels/${tag.replace(":", "-")}`,

View File

@@ -5,22 +5,7 @@ $ErrorActionPreference = "Stop"
# Detect system architecture
$script:IsARM64 = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64
# Allow overriding the target arch (useful for cross-compiling on x64 -> ARM64)
$script:VsArch = $null
if ($env:BUN_VS_ARCH) {
switch ($env:BUN_VS_ARCH.ToLowerInvariant()) {
"arm64" { $script:VsArch = "arm64" }
"aarch64" { $script:VsArch = "arm64" }
"amd64" { $script:VsArch = "amd64" }
"x64" { $script:VsArch = "amd64" }
default { throw "Invalid BUN_VS_ARCH: $env:BUN_VS_ARCH (expected arm64|amd64)" }
}
}
if (-not $script:VsArch) {
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
}
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
if($env:VSINSTALLDIR -eq $null) {
Write-Host "Loading Visual Studio environment, this may take a second..."
@@ -51,10 +36,15 @@ if($env:VSINSTALLDIR -eq $null) {
Push-Location $vsDir
try {
$vsShell = (Join-Path -Path $vsDir -ChildPath "Common7\Tools\Launch-VsDevShell.ps1")
# Visual Studio's Launch-VsDevShell.ps1 only supports x86/amd64 for HostArch
# For ARM64 builds, use amd64 as HostArch since it can cross-compile to ARM64
# -HostArch only accepts "x86" or "amd64" — even on native ARM64, use "amd64"
$hostArch = if ($script:VsArch -eq "arm64") { "amd64" } else { $script:VsArch }
. $vsShell -Arch $script:VsArch -HostArch $hostArch
# VS dev shell with -HostArch amd64 sets PROCESSOR_ARCHITECTURE=AMD64,
# which causes CMake to misdetect the system as x64. Restore it on ARM64.
if ($script:IsARM64) {
$env:PROCESSOR_ARCHITECTURE = "ARM64"
}
} finally {
Pop-Location
}

View File

@@ -1088,7 +1088,7 @@ pub const WindowsSpawnOptions = struct {
pub fn deinit(this: *const Stdio) void {
if (this.* == .buffer) {
bun.default_allocator.destroy(this.buffer);
this.buffer.closeAndDestroy();
}
}
};

View File

@@ -1,5 +1,6 @@
const WindowsNamedPipeContext = @This();
ref_count: RefCount,
named_pipe: uws.WindowsNamedPipe,
socket: SocketType,
@@ -10,6 +11,14 @@ task: jsc.AnyTask,
task_event: EventState = .none,
is_open: bool = false,
const RefCount = bun.ptr.RefCount(@This(), "ref_count", scheduleDeinit, .{});
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
fn scheduleDeinit(this: *WindowsNamedPipeContext) void {
this.deinitInNextTick();
}
pub const EventState = enum(u8) {
deinit,
none,
@@ -148,7 +157,7 @@ fn onClose(this: *WindowsNamedPipeContext) void {
.none => {},
}
this.deinitInNextTick();
this.deref();
}
fn runEvent(this: *WindowsNamedPipeContext) void {
@@ -169,6 +178,7 @@ fn deinitInNextTick(this: *WindowsNamedPipeContext) void {
pub fn create(globalThis: *jsc.JSGlobalObject, socket: SocketType) *WindowsNamedPipeContext {
const vm = globalThis.bunVM();
const this = WindowsNamedPipeContext.new(.{
.ref_count = .init(),
.vm = vm,
.globalThis = globalThis,
.task = undefined,
@@ -179,6 +189,8 @@ pub fn create(globalThis: *jsc.JSGlobalObject, socket: SocketType) *WindowsNamed
// named_pipe owns the pipe (PipeWriter owns the pipe and will close and deinit it)
this.named_pipe = uws.WindowsNamedPipe.from(bun.handleOom(bun.default_allocator.create(uv.Pipe)), .{
.ctx = this,
.ref_ctx = @ptrCast(&WindowsNamedPipeContext.ref),
.deref_ctx = @ptrCast(&WindowsNamedPipeContext.deref),
.onOpen = @ptrCast(&WindowsNamedPipeContext.onOpen),
.onData = @ptrCast(&WindowsNamedPipeContext.onData),
.onHandshake = @ptrCast(&WindowsNamedPipeContext.onHandshake),
@@ -218,7 +230,7 @@ pub fn open(globalThis: *jsc.JSGlobalObject, fd: bun.FileDescriptor, ssl_config:
},
.none => {},
}
this.deinitInNextTick();
this.deref();
}
try this.named_pipe.open(fd, ssl_config).unwrap();
return &this.named_pipe;
@@ -238,7 +250,7 @@ pub fn connect(globalThis: *jsc.JSGlobalObject, path: []const u8, ssl_config: ?j
},
.none => {},
}
this.deinitInNextTick();
this.deref();
}
if (path[path.len - 1] == 0) {

View File

@@ -186,6 +186,47 @@ pub const HTMLRewriter = struct {
return out;
}
// Handle Blob and BunFile inputs by wrapping in a Response and transforming.
// BunFile (file-backed Blob) requires async I/O so we return a Response directly.
// In-memory Blobs return a Blob.
if (response_value.as(jsc.WebCore.Blob)) |blob| {
const body_value = try jsc.WebCore.Body.extract(global, response_value);
const resp = bun.new(Response, Response.init(
.{
.status_code = 200,
},
body_value,
bun.String.empty,
false,
));
if (blob.needsToReadFile()) {
// BunFile requires async I/O via ValueBufferer; return Response directly.
const out = try this.beginTransform(global, resp);
if (out.toError()) |err| {
return global.throwValue(err);
}
return out;
}
defer resp.finalize();
const out_response_value = try this.beginTransform(global, resp);
if (out_response_value.toError()) |err| {
return global.throwValue(err);
}
out_response_value.ensureStillAlive();
var out_response = out_response_value.as(Response) orelse return out_response_value;
var any_blob = out_response.getBodyValue().useAsAnyBlobAllowNonUTF8String();
defer {
_ = Response.js.dangerouslySetPtr(out_response_value, null);
out_response.finalize();
}
const result_blob = jsc.WebCore.Blob.new(any_blob.toBlob(global));
return result_blob.toJS(global);
}
const ResponseKind = enum { string, array_buffer, other };
const kind: ResponseKind = brk: {
if (response_value.isString())
@@ -235,7 +276,7 @@ pub const HTMLRewriter = struct {
}
}
return global.throwInvalidArguments("Expected Response or Body", .{});
return global.throwInvalidArguments("Expected Response, Blob, String, or ArrayBuffer", .{});
}
pub const on = host_fn.wrapInstanceMethod(HTMLRewriter, "on_", false);

View File

@@ -2450,13 +2450,20 @@ void JSC__JSObject__putRecord(JSC::JSObject* object, JSC::JSGlobalObject* global
descriptor.setValue(JSC::jsString(global->vm(), Zig::toStringCopy(values[0])));
} else {
// Pre-convert all strings to JSValues before entering ObjectInitializationScope,
// since jsString() allocates GC cells which is not allowed inside the scope.
MarkedArgumentBuffer strings;
for (size_t i = 0; i < valuesLen; ++i) {
strings.append(JSC::jsString(global->vm(), Zig::toStringCopy(values[i])));
}
JSC::JSArray* array = nullptr;
{
JSC::ObjectInitializationScope initializationScope(global->vm());
if ((array = JSC::JSArray::tryCreateUninitializedRestricted(initializationScope, nullptr, global->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), valuesLen))) {
for (size_t i = 0; i < valuesLen; ++i) {
array->initializeIndexWithoutBarrier(initializationScope, i, JSC::jsString(global->vm(), Zig::toStringCopy(values[i])));
array->initializeIndexWithoutBarrier(initializationScope, i, strings.at(i));
}
}
}
@@ -2490,6 +2497,13 @@ void JSC__JSValue__putRecord(JSC::EncodedJSValue objectValue, JSC::JSGlobalObjec
descriptor.setValue(JSC::jsString(global->vm(), Zig::toString(values[0])));
} else {
// Pre-convert all strings to JSValues before entering ObjectInitializationScope,
// since jsString() allocates GC cells which is not allowed inside the scope.
MarkedArgumentBuffer strings;
for (size_t i = 0; i < valuesLen; ++i) {
strings.append(JSC::jsString(global->vm(), Zig::toString(values[i])));
}
JSC::JSArray* array = nullptr;
{
JSC::ObjectInitializationScope initializationScope(global->vm());
@@ -2500,7 +2514,7 @@ void JSC__JSValue__putRecord(JSC::EncodedJSValue objectValue, JSC::JSGlobalObjec
for (size_t i = 0; i < valuesLen; ++i) {
array->initializeIndexWithoutBarrier(
initializationScope, i, JSC::jsString(global->vm(), Zig::toString(values[i])));
initializationScope, i, strings.at(i));
}
}
}

View File

@@ -759,8 +759,6 @@ static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQ
// Slow path:
JSC::ObjectInitializationScope initializationScope(vm);
// 64 is the maximum we can preallocate here
// see https://github.com/oven-sh/bun/issues/987
JSObject* prototype = castedThis->userPrototype ? castedThis->userPrototype.get() : lexicalGlobalObject->objectPrototype();

View File

@@ -929,7 +929,7 @@ pub const SendQueue = struct {
return err;
};
ipc_pipe.open(pipe_fd).unwrap() catch |err| {
bun.default_allocator.destroy(ipc_pipe);
ipc_pipe.closeAndDestroy();
return err;
};
ipc_pipe.unref();

View File

@@ -587,12 +587,12 @@ pub const PathLike = union(enum) {
if (std.fs.path.isAbsolute(sliced)) {
if (sliced.len > 2 and bun.path.isDriveLetter(sliced[0]) and sliced[1] == ':' and bun.path.isSepAny(sliced[2])) {
// Add the long path syntax. This affects most of node:fs
const drive_resolve_buf = bun.path_buffer_pool.get();
defer bun.path_buffer_pool.put(drive_resolve_buf);
const rest = path_handler.PosixToWinNormalizer.resolveCWDWithExternalBufZ(drive_resolve_buf, sliced) catch @panic("Error while resolving path.");
// Normalize the path directly into buf without an intermediate
// buffer. The input (sliced) already has a drive letter, so
// resolveCWDWithExternalBufZ would just memcpy it, making the
// temporary allocation unnecessary.
buf[0..4].* = bun.windows.long_path_prefix_u8;
// When long path syntax is used, the entire string should be normalized
const n = bun.path.normalizeBuf(rest, buf[4..], .windows).len;
const n = bun.path.normalizeBuf(sliced, buf[4..], .windows).len;
buf[4 + n] = 0;
return buf[0 .. 4 + n :0];
}

View File

@@ -815,16 +815,10 @@ pub const Value = union(Tag) {
}
pub fn tryUseAsAnyBlob(this: *Value) ?AnyBlob {
if (this.* == .WTFStringImpl) {
if (this.WTFStringImpl.canUseAsUTF8()) {
return AnyBlob{ .WTFStringImpl = this.WTFStringImpl };
}
}
const any_blob: AnyBlob = switch (this.*) {
.Blob => AnyBlob{ .Blob = this.Blob },
.InternalBlob => AnyBlob{ .InternalBlob = this.InternalBlob },
// .InlineBlob => AnyBlob{ .InlineBlob = this.InlineBlob },
.Blob => .{ .Blob = this.Blob },
.InternalBlob => .{ .InternalBlob = this.InternalBlob },
.WTFStringImpl => |str| if (str.canUseAsUTF8()) .{ .WTFStringImpl = str } else return null,
.Locked => this.Locked.toAnyBlobAllowPromise() orelse return null,
else => return null,
};

View File

@@ -14,10 +14,12 @@ param(
[Switch]$DownloadWithoutCurl = $false
);
# filter out 32 bit + ARM
if (-not ((Get-CimInstance Win32_ComputerSystem)).SystemType -match "x64-based") {
# Detect real CPU architecture from registry — works even under x64 emulation on ARM64.
# Win32_ComputerSystem.SystemType can be unreliable under WoW64.
$Arch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE
if (-not ($Arch -eq "AMD64" -or $Arch -eq "ARM64")) {
Write-Output "Install Failed:"
Write-Output "Bun for Windows is currently only available for x86 64-bit Windows.`n"
Write-Output "Bun for Windows is only available for x86 64-bit and ARM64 Windows.`n"
return 1
}
@@ -103,13 +105,17 @@ function Install-Bun {
$Version = "bun-$Version"
}
$Arch = "x64"
$IsBaseline = $ForceBaseline
if (!$IsBaseline) {
$IsBaseline = !( `
Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' `
-Name 'Kernel32' -Namespace 'Win32' -PassThru `
)::IsProcessorFeaturePresent(40);
$IsARM64 = $Arch -eq "ARM64"
$BunArch = if ($IsARM64) { "aarch64" } else { "x64" }
$IsBaseline = $false
if (-not $IsARM64) {
$IsBaseline = $ForceBaseline
if (!$IsBaseline) {
$IsBaseline = !( `
Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' `
-Name 'Kernel32' -Namespace 'Win32' -PassThru `
)::IsProcessorFeaturePresent(40);
}
}
$BunRoot = if ($env:BUN_INSTALL) { $env:BUN_INSTALL } else { "${Home}\.bun" }
@@ -134,9 +140,9 @@ function Install-Bun {
return 1
}
$Target = "bun-windows-$Arch"
$Target = "bun-windows-$BunArch"
if ($IsBaseline) {
$Target = "bun-windows-$Arch-baseline"
$Target = "bun-windows-$BunArch-baseline"
}
$BaseURL = "https://github.com/oven-sh/bun/releases"
$URL = "$BaseURL/$(if ($Version -eq "latest") { "latest/download" } else { "download/$Version" })/$Target.zip"
@@ -219,7 +225,8 @@ function Install-Bun {
# I want to keep this error message in for a few months to ensure that
# if someone somehow runs into this, it can be reported.
Write-Output "Install Failed - You are missing a DLL required to run bun.exe"
Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> https://aka.ms/vs/17/release/vc_redist.x64.exe`n`n"
$VCRedistUrl = if ($IsARM64) { "https://aka.ms/vs/17/release/vc_redist.arm64.exe" } else { "https://aka.ms/vs/17/release/vc_redist.x64.exe" }
Write-Output "This can be solved by installing the Visual C++ Redistributable from Microsoft:`nSee https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist`nDirect Download -> ${VCRedistUrl}`n`n"
Write-Output "The error above should be unreachable as Bun does not depend on this library. Please comment in https://github.com/oven-sh/bun/issues/8598 or open a new issue.`n`n"
Write-Output "The command '${BunBin}\bun.exe --revision' exited with code ${LASTEXITCODE}`n"
return 1

View File

@@ -70,6 +70,9 @@ case $platform in
'Linux aarch64' | 'Linux arm64')
target=linux-aarch64
;;
'MINGW64'*'ARM64'* | 'MINGW64'*'aarch64'*)
target=windows-aarch64
;;
'MINGW64'*)
target=windows-x64
;;

View File

@@ -1414,6 +1414,17 @@ pub const Pipe = extern struct {
pub fn asStream(this: *@This()) *uv_stream_t {
return @ptrCast(this);
}
/// Close the pipe handle and then free it in the close callback.
/// Use this when a pipe has been init'd but needs to be destroyed
/// (e.g. when open() fails after init() succeeded).
pub fn closeAndDestroy(this: *@This()) void {
this.close(&onCloseDestroy);
}
fn onCloseDestroy(handle: *@This()) callconv(.c) void {
bun.default_allocator.destroy(handle);
}
};
const union_unnamed_416 = extern union {
fd: c_int,

View File

@@ -51,6 +51,8 @@ pub const Flags = packed struct(u8) {
};
pub const Handlers = struct {
ctx: *anyopaque,
ref_ctx: *const fn (*anyopaque) void,
deref_ctx: *const fn (*anyopaque) void,
onOpen: *const fn (*anyopaque) void,
onHandshake: *const fn (*anyopaque, bool, uws.us_bun_verify_error_t) void,
onData: *const fn (*anyopaque, []const u8) void,
@@ -271,7 +273,16 @@ pub fn from(
.handlers = handlers,
};
}
pub fn ref(this: *WindowsNamedPipe) void {
this.handlers.ref_ctx(this.handlers.ctx);
}
pub fn deref(this: *WindowsNamedPipe) void {
this.handlers.deref_ctx(this.handlers.ctx);
}
fn onConnect(this: *WindowsNamedPipe, status: uv.ReturnCode) void {
defer this.deref();
if (this.pipe) |pipe| {
_ = pipe.unref();
}
@@ -376,6 +387,7 @@ pub fn open(this: *WindowsNamedPipe, fd: bun.FileDescriptor, ssl_options: ?jsc.A
return openResult;
}
this.ref();
onConnect(this, uv.ReturnCode.zero);
return .success;
}
@@ -410,7 +422,12 @@ pub fn connect(this: *WindowsNamedPipe, path: []const u8, ssl_options: ?jsc.API.
}
this.connect_req.data = this;
return this.pipe.?.connect(&this.connect_req, path, this, onConnect);
const result = this.pipe.?.connect(&this.connect_req, path, this, onConnect);
if (result.asErr() != null) {
return result;
}
this.ref();
return result;
}
pub fn startTLS(this: *WindowsNamedPipe, ssl_options: jsc.API.ServerConfig.SSLConfig, is_client: bool) !void {
this.flags.is_ssl = true;

View File

@@ -348,7 +348,7 @@ pub const LifecycleScriptSubprocess = struct {
if (this.optional) {
if (this.ctx) |ctx| {
ctx.installer.store.entries.items(.step)[ctx.entry_id.get()].store(.done, .release);
ctx.installer.onTaskComplete(ctx.entry_id, .fail);
ctx.installer.onTaskComplete(ctx.entry_id, .skipped);
}
this.decrementPendingScriptTasks();
this.deinitAndDeletePackage();
@@ -454,7 +454,7 @@ pub const LifecycleScriptSubprocess = struct {
if (this.optional) {
if (this.ctx) |ctx| {
ctx.installer.store.entries.items(.step)[ctx.entry_id.get()].store(.done, .release);
ctx.installer.onTaskComplete(ctx.entry_id, .fail);
ctx.installer.onTaskComplete(ctx.entry_id, .skipped);
}
this.decrementPendingScriptTasks();
this.deinitAndDeletePackage();

View File

@@ -694,8 +694,8 @@ pub fn PosixStreamingWriter(comptime Parent: type, comptime function_table: anyt
}
pub fn deinit(this: *PosixWriter) void {
this.outgoing.deinit();
this.closeWithoutReporting();
this.outgoing.deinit();
}
pub fn hasRef(this: *PosixWriter) bool {
@@ -815,29 +815,39 @@ fn BaseWindowsPipeWriter(
pub fn close(this: *WindowsPipeWriter) void {
this.is_done = true;
if (this.source) |source| {
switch (source) {
.sync_file, .file => |file| {
// Use state machine to handle close after operation completes
if (this.owns_fd) {
file.detach();
} else {
// Don't own fd, just stop operations and detach parent
file.stop();
file.fs.data = null;
}
},
.pipe => |pipe| {
pipe.data = pipe;
pipe.close(onPipeClose);
},
.tty => |tty| {
tty.data = tty;
tty.close(onTTYClose);
},
}
this.source = null;
this.onCloseSource();
const source = this.source orelse return;
// Check for in-flight file write before detaching. detach()
// nulls fs.data so onFsWriteComplete can't recover the writer
// to call deref(). We must balance processSend's ref() here.
const has_inflight_write = if (@hasField(WindowsPipeWriter, "current_payload")) switch (source) {
.sync_file, .file => |file| file.state == .operating or file.state == .canceling,
else => false,
} else false;
switch (source) {
.sync_file, .file => |file| {
// Use state machine to handle close after operation completes
if (this.owns_fd) {
file.detach();
} else {
// Don't own fd, just stop operations and detach parent
file.stop();
file.fs.data = null;
}
},
.pipe => |pipe| {
pipe.data = pipe;
pipe.close(onPipeClose);
},
.tty => |tty| {
tty.data = tty;
tty.close(onTTYClose);
},
}
this.source = null;
this.onCloseSource();
// Deref last — this may free the parent and `this`.
if (has_inflight_write) {
this.parent.deref();
}
}
@@ -1298,6 +1308,10 @@ pub fn WindowsStreamingWriter(comptime Parent: type, function_table: anytype) ty
}
fn onWriteComplete(this: *WindowsWriter, status: uv.ReturnCode) void {
// Deref the parent at the end to balance the ref taken in
// processSend before submitting the async write request.
defer this.parent.deref();
if (status.toError(.write)) |err| {
this.last_write_result = .{ .err = err };
log("onWrite() = {s}", .{err.name()});
@@ -1347,7 +1361,8 @@ pub fn WindowsStreamingWriter(comptime Parent: type, function_table: anytype) ty
// ALWAYS complete first
file.complete(was_canceled);
// If detached, file may be closing (owned fd) or just stopped (non-owned fd)
// If detached, file may be closing (owned fd) or just stopped (non-owned fd).
// The deref to balance processSend's ref was already done in close().
if (parent_ptr == null) {
return;
}
@@ -1355,17 +1370,21 @@ pub fn WindowsStreamingWriter(comptime Parent: type, function_table: anytype) ty
const this = bun.cast(*WindowsWriter, parent_ptr);
if (was_canceled) {
// Canceled write - reset buffers
// Canceled write - reset buffers and deref to balance processSend ref
this.current_payload.reset();
this.parent.deref();
return;
}
if (result.toError(.write)) |err| {
// deref to balance processSend ref
defer this.parent.deref();
this.close();
onError(this.parent, err);
return;
}
// onWriteComplete handles the deref
this.onWriteComplete(.zero);
}
@@ -1428,6 +1447,10 @@ pub fn WindowsStreamingWriter(comptime Parent: type, function_table: anytype) ty
}
},
}
// Ref the parent to prevent it from being freed while the async
// write is in flight. The matching deref is in onWriteComplete
// or onFsWriteComplete.
this.parent.ref();
this.last_write_result = .{ .pending = 0 };
}
@@ -1442,10 +1465,11 @@ pub fn WindowsStreamingWriter(comptime Parent: type, function_table: anytype) ty
}
pub fn deinit(this: *WindowsWriter) void {
// clean both buffers if needed
// Close the pipe first to cancel any in-flight writes before
// freeing the buffers they reference.
this.closeWithoutReporting();
this.outgoing.deinit();
this.current_payload.deinit();
this.closeWithoutReporting();
}
fn writeInternal(this: *WindowsWriter, buffer: anytype, comptime writeFn: anytype) WriteResult {

View File

@@ -222,7 +222,7 @@ pub const Source = union(enum) {
switch (pipe.open(fd)) {
.err => |err| {
bun.default_allocator.destroy(pipe);
pipe.closeAndDestroy();
return .{
.err = err,
};

View File

@@ -633,12 +633,12 @@ var access = function access(path, mode, callback) {
const { defineCustomPromisifyArgs } = require("internal/promisify");
var kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom");
{
const existsCb = exists;
exists[kCustomPromisifiedSymbol] = function exists(path) {
const existsCb = exists;
exists[kCustomPromisifiedSymbol] = {
exists(path) {
return new Promise(resolve => existsCb(path, resolve));
};
}
},
}.exists;
defineCustomPromisifyArgs(read, ["bytesRead", "buffer"]);
defineCustomPromisifyArgs(readv, ["bytesRead", "buffers"]);
defineCustomPromisifyArgs(write, ["bytesWritten", "buffer"]);

View File

@@ -2226,8 +2226,8 @@ Interface.prototype.question = function question(query, options, cb) {
}
};
{
Interface.prototype.question[promisify.custom] = function question(query, options) {
Interface.prototype.question[promisify.custom] = {
question(query, options) {
if (options === null || typeof options !== "object") {
options = kEmptyObject;
}
@@ -2252,8 +2252,8 @@ Interface.prototype.question = function question(query, options, cb) {
}
this.question(query, options, cb);
});
};
}
},
}.question;
/**
* Creates a new `readline.Interface` instance.

View File

@@ -10,13 +10,12 @@
"@bufbuild/protobuf": "2.10.2",
"@connectrpc/connect": "2.1.1",
"@connectrpc/connect-node": "2.0.0",
"@duckdb/node-api": "1.1.3-alpha.7",
"@electric-sql/pglite": "0.2.17",
"@fastify/websocket": "11.0.2",
"@grpc/grpc-js": "1.12.0",
"@grpc/proto-loader": "0.7.10",
"@happy-dom/global-registrator": "17.0.3",
"@napi-rs/canvas": "0.1.65",
"@napi-rs/canvas": "0.1.91",
"@nestjs/common": "11.0.3",
"@nestjs/core": "11.0.3",
"@prisma/client": "5.8.0",
@@ -40,7 +39,6 @@
"commander": "12.1.0",
"detect-libc": "2.0.3",
"devalue": "5.1.1",
"duckdb": "1.3.1",
"es-module-lexer": "1.3.0",
"esbuild": "0.18.6",
"express": "4.18.2",
@@ -61,7 +59,6 @@
"jws": "4.0.0",
"lodash": "4.17.21",
"mongodb": "6.0.0",
"msgpackr-extract": "3.0.2",
"msw": "2.3.0",
"mysql2": "3.7.0",
"node-gyp": "10.0.1",
@@ -82,7 +79,7 @@
"reflect-metadata": "0.2.2",
"rollup": "4.4.1",
"sass": "1.79.4",
"sharp": "0.33.0",
"sharp": "0.34.5",
"sinon": "6.0.0",
"socket.io": "4.7.1",
"socket.io-adapter": "2.5.5",
@@ -120,6 +117,11 @@
"@types/utf-8-validate": "5.0.0",
"@types/ws": "8.5.10",
},
"optionalDependencies": {
"@duckdb/node-api": "1.1.3-alpha.7",
"duckdb": "1.3.1",
"msgpackr-extract": "3.0.2",
},
},
},
"overrides": {
@@ -227,7 +229,7 @@
"@electric-sql/pglite": ["@electric-sql/pglite@0.2.17", "", {}, "sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw=="],
"@emnapi/runtime": ["@emnapi/runtime@0.44.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="],
@@ -305,43 +307,55 @@
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@17.0.3", "", { "dependencies": { "happy-dom": "^17.0.3" } }, "sha512-isCCWywZq8XPE3A5y7pRyFIsAgij+3eVXgQNCbexGRP00/+nctmf4SfQxC3vV3MmEaOXaNj7IiiSC0BtSHQZgg=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-070tEheekI1LJWTGPC9WlQEa5UoKTXzzlORBHMX4TbfUxMiL336YHR8vBEUNsjse0RJCX8dZ4ZXwT595aEF1ug=="],
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.0" }, "os": "darwin", "cpu": "x64" }, "sha512-pu/nvn152F3qbPeUkr+4e9zVvEhD3jhwzF473veQfMPkOYo9aoWXSfdZH/E6F+nYC3qvFjbxbvdDbUtEbghLqw=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.0", "", { "os": "linux", "cpu": "arm" }, "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.0" }, "os": "linux", "cpu": "arm" }, "sha512-4horD3wMFd5a0ddbDY8/dXU9CaOgHjEHALAddXgafoR5oWq5s8X61PDgsSeh4Qupsdo6ycfPPSSNBrfVQnwwrg=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.0" }, "os": "linux", "cpu": "arm64" }, "sha512-dcomVSrtgF70SyOr8RCOCQ8XGVThXwe71A1d8MGA+mXEVRJ/J6/TrCbBEJh9ddcEIIsrnrkolaEvYSHqVhswQw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.0" }, "os": "linux", "cpu": "s390x" }, "sha512-TiVJbx38J2rNVfA309ffSOB+3/7wOsZYQEOlKqOUdWD/nqkjNGrX+YQGz7nzcf5oy2lC+d37+w183iNXRZNngQ=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.0" }, "os": "linux", "cpu": "x64" }, "sha512-PaZM4Zi7/Ek71WgTdvR+KzTZpBqrQOFcPe7/8ZoPRlTYYRe43k6TWsf4GVH6XKRLMYeSp8J89RfAhBrSP4itNA=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" }, "os": "linux", "cpu": "arm64" }, "sha512-1QLbbN0zt+32eVrg7bb1lwtvEaZwlhEsY1OrijroMkwAqlHqFj6R33Y47s2XUv7P6Ie1PwCxK/uFnNqMnkd5kg=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.0", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.0" }, "os": "linux", "cpu": "x64" }, "sha512-CecqgB/CnkvCWFhmfN9ZhPGMLXaEBXl4o7WtA6U3Ztrlh/s7FUKX4vNxpMSYLIrWuuzjiaYdfU3+Tdqh1xaHfw=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.0", "", { "dependencies": { "@emnapi/runtime": "^0.44.0" }, "cpu": "none" }, "sha512-Hn4js32gUX9qkISlemZBUPuMs0k/xNJebUNl/L6djnU07B/HAA2KaxRVb3HvbU5fL242hLOcp0+tR+M8dvJUFw=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-5HfcsCZi3l5nPRF2q3bllMVMDXBqEWI3Q8KQONfzl0TferFE5lnsIG0A1YrntMAGqvkzdW6y1Ci1A2uTvxhfzg=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-i3DtP/2ce1yKFj4OzOnOYltOEL/+dp4dc4dJXJBv6god1AFTcmkaA99H/7SwOmkCOBQkbVvA3lCGm3/5nDtf9Q=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@inquirer/confirm": ["@inquirer/confirm@3.1.9", "", { "dependencies": { "@inquirer/core": "^8.2.2", "@inquirer/type": "^1.3.3" } }, "sha512-UF09aejxCi4Xqm6N/jJAiFXArXfi9al52AFaSD+2uIHnhZGtd1d6lIGTRMPouVSJxbGEi+HkOWSYaiEY/+szUw=="],
@@ -449,27 +463,29 @@
"@mswjs/interceptors": ["@mswjs/interceptors@0.29.1", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.2.1", "strict-event-emitter": "^0.5.1" } }, "sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw=="],
"@napi-rs/canvas": ["@napi-rs/canvas@0.1.65", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.65", "@napi-rs/canvas-darwin-arm64": "0.1.65", "@napi-rs/canvas-darwin-x64": "0.1.65", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.65", "@napi-rs/canvas-linux-arm64-gnu": "0.1.65", "@napi-rs/canvas-linux-arm64-musl": "0.1.65", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.65", "@napi-rs/canvas-linux-x64-gnu": "0.1.65", "@napi-rs/canvas-linux-x64-musl": "0.1.65", "@napi-rs/canvas-win32-x64-msvc": "0.1.65" } }, "sha512-YcFhXQcp+b2d38zFOJNbpyPHnIL7KAEkhJQ+UeeKI5IpE9B8Cpf/M6RiHPQXSsSqnYbrfFylnW49dyh2oeSblQ=="],
"@napi-rs/canvas": ["@napi-rs/canvas@0.1.91", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.91", "@napi-rs/canvas-darwin-arm64": "0.1.91", "@napi-rs/canvas-darwin-x64": "0.1.91", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.91", "@napi-rs/canvas-linux-arm64-gnu": "0.1.91", "@napi-rs/canvas-linux-arm64-musl": "0.1.91", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.91", "@napi-rs/canvas-linux-x64-gnu": "0.1.91", "@napi-rs/canvas-linux-x64-musl": "0.1.91", "@napi-rs/canvas-win32-arm64-msvc": "0.1.91", "@napi-rs/canvas-win32-x64-msvc": "0.1.91" } }, "sha512-eeIe1GoB74P1B0Nkw6pV8BCQ3hfCfvyYr4BntzlCsnFXzVJiPMDnLeIx3gVB0xQMblHYnjK/0nCLvirEhOjr5g=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.65", "", { "os": "android", "cpu": "arm64" }, "sha512-ZYwqFYEKcT5Zr8lbiaJNJj/poLaeK2TncolY914r+gD2TJNeP7ZqvE7A2SX/1C9MB4E3DQEwm3YhL3WEf0x3MQ=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.91", "", { "os": "android", "cpu": "arm64" }, "sha512-SLLzXXgSnfct4zy/BVAfweZQkYkPJsNsJ2e5DOE8DFEHC6PufyUrwb12yqeu2So2IOIDpWJJaDAxKY/xpy6MYQ=="],
"@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.65", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Pg1pfiJEyDIsX+V0QaJPRWvXbw5zmWAk3bivFCvt/5pwZb37/sT6E/RqPHT9NnqpDyKW6SriwY9ypjljysUA1Q=="],
"@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.91", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzdbCjIjw3iRuVFL+uxdSoMra/l09ydGNX9gsBxO/zg+5nlppscIpj6gg+nL6VNG85zwUarDleIrUJ+FWHvmuA=="],
"@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.65", "", { "os": "darwin", "cpu": "x64" }, "sha512-3Tr+/HjdJN7Z/VKIcsxV2DvDIibZCExgfYTgljCkUSFuoI7iNkOE6Dc1Q6j212EB9PeO8KmfrViBqHYT6IwWkA=="],
"@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.91", "", { "os": "darwin", "cpu": "x64" }, "sha512-q3qpkpw0IsG9fAS/dmcGIhCVoNxj8ojbexZKWwz3HwxlEWsLncEQRl4arnxrwbpLc2nTNTyj4WwDn7QR5NDAaA=="],
"@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.65", "", { "os": "linux", "cpu": "arm" }, "sha512-3KP+dYObH7CVkZMZWwk1WX9jRjL+EKdQtD43H8MOI+illf+dwqLlecdQ4d9bQRIxELKJ8dyPWY4fOp/Ngufrdg=="],
"@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.91", "", { "os": "linux", "cpu": "arm" }, "sha512-Io3g8wJZVhK8G+Fpg1363BE90pIPqg+ZbeehYNxPWDSzbgwU3xV0l8r/JBzODwC7XHi1RpFEk+xyUTMa2POj6w=="],
"@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.65", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ka3StKz7Dq7kjTF3nNJCq43UN/VlANS7qGE3dWkn1d+tQNsCRy/wRmyt1TUFzIjRqcTFMQNRbgYq84+53UBA0A=="],
"@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.91", "", { "os": "linux", "cpu": "arm64" }, "sha512-HBnto+0rxx1bQSl8bCWA9PyBKtlk2z/AI32r3cu4kcNO+M/5SD4b0v1MWBWZyqMQyxFjWgy3ECyDjDKMC6tY1A=="],
"@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.65", "", { "os": "linux", "cpu": "arm64" }, "sha512-O4xMASm2JrmqYoiDyxVWi+z5C14H+oVEag2rZ5iIA67dhWqYZB+iO7wCFpBYRj31JPBR29FOsu6X9zL+DwBFdw=="],
"@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.91", "", { "os": "linux", "cpu": "arm64" }, "sha512-/eJtVe2Xw9A86I4kwXpxxoNagdGclu12/NSMsfoL8q05QmeRCbfjhg1PJS7ENAuAvaiUiALGrbVfeY1KU1gztQ=="],
"@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.65", "", { "os": "linux", "cpu": "none" }, "sha512-dblWDaA59ZU8bPbkfM+riSke7sFbNZ70LEevUdI5rgiFEUzYUQlU34gSBzemTACj5rCWt1BYeu0GfkLSjNMBSw=="],
"@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.91", "", { "os": "linux", "cpu": "none" }, "sha512-floNK9wQuRWevUhhXRcuis7h0zirdytVxPgkonWO+kQlbvxV7gEUHGUFQyq4n55UHYFwgck1SAfJ1HuXv/+ppQ=="],
"@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.65", "", { "os": "linux", "cpu": "x64" }, "sha512-wsp+atutw13OJXGU3DDkdngtBDoEg01IuK5xMe0L6VFPV8maGkh17CXze078OD5QJOc6kFyw3DDscMLOPF8+oA=="],
"@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.91", "", { "os": "linux", "cpu": "x64" }, "sha512-c3YDqBdf7KETuZy2AxsHFMsBBX1dWT43yFfWUq+j1IELdgesWtxf/6N7csi3VPf6VA3PmnT9EhMyb+M1wfGtqw=="],
"@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.65", "", { "os": "linux", "cpu": "x64" }, "sha512-odX+nN+IozWzhdj31INcHz3Iy9+EckNw+VqsZcaUxZOTu7/3FmktRNI6aC1qe5minZNv1m05YOS1FVf7fvmjlA=="],
"@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.91", "", { "os": "linux", "cpu": "x64" }, "sha512-RpZ3RPIwgEcNBHSHSX98adm+4VP8SMT5FN6250s5jQbWpX/XNUX5aLMfAVJS/YnDjS1QlsCgQxFOPU0aCCWgag=="],
"@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.65", "", { "os": "win32", "cpu": "x64" }, "sha512-RZQX3luWnlNWgdMnLMQ1hyfQraeAn9lnxWWVCHuUM4tAWEV8UDdeb7cMwmJW7eyt8kAosmjeHt3cylQMHOxGFg=="],
"@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.91", "", { "os": "win32", "cpu": "arm64" }, "sha512-gF8MBp4X134AgVurxqlCdDA2qO0WaDdi9o6Sd5rWRVXRhWhYQ6wkdEzXNLIrmmros0Tsp2J0hQzx4ej/9O8trQ=="],
"@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.91", "", { "os": "win32", "cpu": "x64" }, "sha512-++gtW9EV/neKI8TshD8WFxzBYALSPag2kFRahIJV+LYsyt5kBn21b1dBhEUDHf7O+wiZmuFCeUa7QKGHnYRZBA=="],
"@nestjs/common": ["@nestjs/common@11.0.3", "", { "dependencies": { "iterare": "1.2.1", "tslib": "2.8.1", "uid": "2.0.2" }, "peerDependencies": { "class-transformer": "*", "class-validator": "*", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "optionalPeers": ["class-transformer", "class-validator"] }, "sha512-fTkJWQ20+jvPKfrv3A+T3wsPwwYSJyoJ+pcBzyKtv5fCpK/yX/rJalFUIpw1CDmarfqIaMX9SdkplNyxtvH6RA=="],
@@ -1575,7 +1591,7 @@
"is-arguments": ["is-arguments@1.1.1", "", { "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" } }, "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
@@ -2339,7 +2355,7 @@
"shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="],
"sharp": ["sharp@0.33.0", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "semver": "^7.5.4" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.0", "@img/sharp-darwin-x64": "0.33.0", "@img/sharp-libvips-darwin-arm64": "1.0.0", "@img/sharp-libvips-darwin-x64": "1.0.0", "@img/sharp-libvips-linux-arm": "1.0.0", "@img/sharp-libvips-linux-arm64": "1.0.0", "@img/sharp-libvips-linux-s390x": "1.0.0", "@img/sharp-libvips-linux-x64": "1.0.0", "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", "@img/sharp-libvips-linuxmusl-x64": "1.0.0", "@img/sharp-linux-arm": "0.33.0", "@img/sharp-linux-arm64": "0.33.0", "@img/sharp-linux-s390x": "0.33.0", "@img/sharp-linux-x64": "0.33.0", "@img/sharp-linuxmusl-arm64": "0.33.0", "@img/sharp-linuxmusl-x64": "0.33.0", "@img/sharp-wasm32": "0.33.0", "@img/sharp-win32-ia32": "0.33.0", "@img/sharp-win32-x64": "0.33.0" } }, "sha512-99DZKudjm/Rmz+M0/26t4DKpXyywAOJaayGS9boEn7FvgtG0RYBi46uPE2c+obcJRtA3AZa0QwJot63gJQ1F0Q=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -2781,6 +2797,8 @@
"@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@fastify/ajv-compiler/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
@@ -3079,8 +3097,6 @@
"engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"escodegen/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -3299,7 +3315,11 @@
"serve-static/send": ["send@0.18.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg=="],
"sharp/semver": ["semver@7.6.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="],
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"sinon/diff": ["diff@3.5.0", "", {}, "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="],

View File

@@ -203,13 +203,14 @@ console.log(utils());`,
});
expect(await exited).toBe(0);
const { stdout } = Bun.spawn({
await using proc = Bun.spawn({
cmd: [path.join(baseDir, "exe.exe")],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const text = await stdout.text();
const text = await proc.stdout.text();
await proc.exited;
expect(text).toContain(path.join(baseDir, "我") + "\n");
expect(text).toContain(path.join(baseDir, "我", "我.ts") + "\n");

View File

@@ -178,7 +178,7 @@ describe.skipIf(!isWindows).concurrent("Windows compile metadata", () => {
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: "bun-windows-x64",
target: process.arch === "arm64" ? "bun-windows-aarch64" : "bun-windows-x64",
outfile: "app-api.exe",
windows: {
title: "API App",
@@ -225,7 +225,7 @@ describe.skipIf(!isWindows).concurrent("Windows compile metadata", () => {
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: "bun-windows-x64",
target: process.arch === "arm64" ? "bun-windows-aarch64" : "bun-windows-x64",
outfile: "partial-api.exe",
windows: {
title: "Partial App",
@@ -262,7 +262,7 @@ describe.skipIf(!isWindows).concurrent("Windows compile metadata", () => {
entrypoints: [join(String(dir), "app.js")],
outdir: "./out",
compile: {
target: "bun-windows-x64",
target: process.arch === "arm64" ? "bun-windows-aarch64" : "bun-windows-x64",
outfile: "relative.exe",
windows: {
title: "Relative Path App",

View File

@@ -5081,7 +5081,7 @@ describe.concurrent("bun-install", () => {
stdout: "pipe",
stdin: "ignore",
stderr: "pipe",
env: { ...env, "GIT_ASKPASS": "echo" },
env: { ...env, "GIT_ASKPASS": "echo", "GIT_CONFIG_NOSYSTEM": "1" },
});
const err = await stderr.text();
expect(err.split(/\r?\n/)).toContain('error: "git clone" for "private-install" failed');

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isBroken, isCI, isIntelMacOS, isMacOS, isWindows, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, isArm64, isBroken, isCI, isIntelMacOS, isMacOS, isWindows, tempDirWithFiles } from "harness";
import { join } from "path";
describe.concurrent("require.cache", () => {
@@ -24,7 +24,8 @@ describe.concurrent("require.cache", () => {
});
// https://github.com/oven-sh/bun/issues/5188
test("require.cache does not include unevaluated modules", async () => {
// msgpackr-extract has no prebuilt binary for win32-arm64, so it's unavailable there
test.skipIf(isWindows && isArm64)("require.cache does not include unevaluated modules", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "run", join(import.meta.dir, "require-cache-bug-5188.js")],
env: bunEnv,

View File

@@ -3,6 +3,7 @@
# Tests that are broken
test/cli/create/create-jsx.test.ts [ FAIL ] # false > react spa (no tailwind) > build
[ WINDOWS-AARCH64 ] test/js/node/test/parallel/test-repl-close.js [ FAIL ] # EPIPE on stdin.write to closed child process
test/bundler/native-plugin.test.ts [ FAIL ] # prints name when plugin crashes
test/cli/run/run-crash-handler.test.ts [ FAIL ] # automatic crash reporter > segfault should report

View File

@@ -1,9 +1,12 @@
import { spawn } from "bun";
import { beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
import { cp, rm, writeFile } from "fs/promises";
import { bunExe, bunEnv as env, tempDir } from "harness";
import { bunExe, bunEnv as env, isArm64, isWindows, tempDir } from "harness";
import { join } from "path";
// esbuild@0.19.8 does not support win32-arm64 at runtime
const isWindowsArm64 = isWindows && isArm64;
beforeAll(() => {
setDefaultTimeout(1000 * 60 * 5);
});
@@ -49,7 +52,7 @@ describe.concurrent("esbuild integration test", () => {
expect(await exited).toBe(0);
});
test("install and use estrella", async () => {
test.skipIf(isWindowsArm64)("install and use estrella", async () => {
using dir = tempDir("esbuild-estrella-test", {
"package.json": JSON.stringify({
name: "bun-esbuild-estrella-test",

View File

@@ -1,13 +1,16 @@
import { cc, CString, ptr, type FFIFunction, type Library } from "bun:ffi";
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { promises as fs } from "fs";
import { bunEnv, bunExe, isASAN, isWindows, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, isArm64, isASAN, isWindows, tempDirWithFiles } from "harness";
import path from "path";
// TinyCC (and all of bun:ffi) is disabled on Windows ARM64
const isFFIUnavailable = isWindows && isArm64;
// TODO: we need to install build-essential and Apple SDK in CI.
// It can't find includes. It can on machines with that enabled.
// TinyCC's setjmp/longjmp error handling conflicts with ASan.
it.todoIf(isWindows || isASAN)("can run a .c file", () => {
it.todoIf(isWindows || isASAN || isFFIUnavailable)("can run a .c file", () => {
const result = Bun.spawnSync({
cmd: [bunExe(), path.join(__dirname, "cc-fixture.js")],
cwd: __dirname,
@@ -19,7 +22,8 @@ it.todoIf(isWindows || isASAN)("can run a .c file", () => {
});
// TinyCC's setjmp/longjmp error handling conflicts with ASan.
describe.skipIf(isASAN)("given an add(a, b) function", () => {
// TinyCC is disabled on Windows ARM64.
describe.skipIf(isASAN || isFFIUnavailable)("given an add(a, b) function", () => {
const source = /* c */ `
int add(int a, int b) {
return a + b;

View File

@@ -1,8 +1,11 @@
import { dlopen, linkSymbols } from "bun:ffi";
import { describe, expect, test } from "bun:test";
import { isMusl } from "harness";
import { isArm64, isMusl, isWindows } from "harness";
describe("FFI error messages", () => {
// TinyCC (and all of bun:ffi) is disabled on Windows ARM64
const isFFIUnavailable = isWindows && isArm64;
describe.skipIf(isFFIUnavailable)("FFI error messages", () => {
test("dlopen shows library name when library cannot be opened", () => {
// Try to open a non-existent library
try {

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
// Regression test for use-after-poison in builtin OutputTask callbacks
// inside command substitution $().
@@ -14,7 +14,7 @@ import { bunEnv, bunExe, tempDir } from "harness";
// alongside non-existent paths reliably triggers the ASAN
// use-after-poison.
describe("builtins in command substitution with errors should not crash", () => {
describe.skipIf(isWindows)("builtins in command substitution with errors should not crash", () => {
test("ls with errors in command substitution", async () => {
// Create a temp directory with many files to produce output,
// and include non-existent paths to produce errors.

View File

@@ -14,7 +14,7 @@ async function spawn100() {
test("does not leak", async () => {
const before = process.memoryUsage().rss;
console.log("before", (before / 1024 / 1024).toFixed(3), "MB");
for (let index = 0; index < 200; index++) {
for (let index = 0; index < 30; index++) {
await spawn100();
Bun.gc(true);
}

View File

@@ -11,6 +11,14 @@ function repeat(fn: any) {
const interval = setInterval(fn, 20).unref();
return interval;
}
// Write to a temp file then rename, so stat never sees a 0-byte intermediate
// state (writeFileSync uses O_TRUNC which briefly truncates the file to 0
// bytes, visible to concurrent stat on Windows).
function updateFile(filepath: string, data: string) {
const tmp = filepath + ".tmp";
fs.writeFileSync(tmp, data);
fs.renameSync(tmp, filepath);
}
const encodingFileName = `新建文夹件.txt`;
let testDir = "";
beforeEach(() => {
@@ -54,7 +62,7 @@ describe("fs.watchFile", () => {
let increment = 0;
const interval = repeat(() => {
increment++;
fs.writeFileSync(path.join(testDir, "watch.txt"), "hello" + increment);
updateFile(path.join(testDir, "watch.txt"), "hello" + increment);
});
await promise;
clearInterval(interval);
@@ -79,7 +87,7 @@ describe("fs.watchFile", () => {
let increment = 0;
const interval = repeat(() => {
increment++;
fs.writeFileSync(path.join(testDir, encodingFileName), "hello" + increment);
updateFile(path.join(testDir, encodingFileName), "hello" + increment);
});
await promise;
clearInterval(interval);
@@ -105,7 +113,7 @@ describe("fs.watchFile", () => {
let increment = 0;
const interval = repeat(() => {
increment++;
fs.writeFileSync(path.join(testDir, encodingFileName), "hello" + "a".repeat(increment));
updateFile(path.join(testDir, encodingFileName), "hello" + "a".repeat(increment));
});
await promise;
clearInterval(interval);
@@ -144,7 +152,7 @@ describe("fs.watchFile", () => {
let increment = 0;
const interval = repeat(() => {
increment++;
fs.writeFileSync(filepath, "hello" + increment);
updateFile(filepath, "hello" + increment);
});
await promise;
clearInterval(interval);

View File

@@ -14,6 +14,10 @@ if (libcFamily == "musl") {
// @duckdb/node-bindings does not distribute musl binaries, so we skip this test on musl to avoid CI noise
process.exit(0);
}
if (process.platform === "win32" && process.arch === "arm64") {
// @duckdb/node-bindings does not distribute win32-arm64 binaries
process.exit(0);
}
import { describe, test } from "bun:test";
import assert from "node:assert";

View File

@@ -3,6 +3,10 @@ if (libcFamily == "musl") {
// duckdb does not distribute musl binaries, so we skip this test on musl to avoid CI noise
process.exit(0);
}
if (process.platform === "win32" && process.arch === "arm64") {
// duckdb does not distribute win32-arm64 binaries
process.exit(0);
}
import { describe, expect, test } from "bun:test";
// Must be CJS require so that the above code can exit before we attempt to import DuckDB

View File

@@ -1,11 +1,14 @@
import { spawnSync } from "bun";
import { cc, dlopen } from "bun:ffi";
import { beforeAll, describe, expect, it } from "bun:test";
import { bunEnv, bunExe, isASAN, isWindows } from "harness";
import { bunEnv, bunExe, isArm64, isASAN, isWindows } from "harness";
import { join } from "path";
import source from "./napi-app/ffi_addon_1.c" with { type: "file" };
// TinyCC (and all of bun:ffi) is disabled on Windows ARM64
const isFFIUnavailable = isWindows && isArm64;
const symbols = {
set_instance_data: {
args: ["napi_env", "int"],
@@ -24,6 +27,8 @@ const symbols = {
let addon1, addon2, cc1, cc2;
beforeAll(() => {
if (isFFIUnavailable) return;
// build gyp
const install = spawnSync({
cmd: [bunExe(), "install", "--verbose"],
@@ -59,7 +64,7 @@ beforeAll(() => {
}
});
describe("ffi napi integration", () => {
describe.skipIf(isFFIUnavailable)("ffi napi integration", () => {
it("has a different napi_env for each ffi library", () => {
addon1.set_instance_data(undefined, 5);
addon2.set_instance_data(undefined, 6);
@@ -75,7 +80,7 @@ describe("ffi napi integration", () => {
});
});
describe("cc napi integration", () => {
describe.skipIf(isFFIUnavailable)("cc napi integration", () => {
// fails on windows as TCC can't link the napi_ functions
// TinyCC's setjmp/longjmp error handling conflicts with ASan.
it.todoIf(isWindows || isASAN)("has a different napi_env for each cc invocation", () => {

View File

@@ -14,13 +14,12 @@
"@bufbuild/protobuf": "2.10.2",
"@connectrpc/connect": "2.1.1",
"@connectrpc/connect-node": "2.0.0",
"@duckdb/node-api": "1.1.3-alpha.7",
"@electric-sql/pglite": "0.2.17",
"@fastify/websocket": "11.0.2",
"@grpc/grpc-js": "1.12.0",
"@grpc/proto-loader": "0.7.10",
"@happy-dom/global-registrator": "17.0.3",
"@napi-rs/canvas": "0.1.65",
"@napi-rs/canvas": "0.1.91",
"@nestjs/common": "11.0.3",
"@nestjs/core": "11.0.3",
"@prisma/client": "5.8.0",
@@ -44,7 +43,6 @@
"commander": "12.1.0",
"detect-libc": "2.0.3",
"devalue": "5.1.1",
"duckdb": "1.3.1",
"es-module-lexer": "1.3.0",
"esbuild": "0.18.6",
"express": "4.18.2",
@@ -65,7 +63,6 @@
"jws": "4.0.0",
"lodash": "4.17.21",
"mongodb": "6.0.0",
"msgpackr-extract": "3.0.2",
"msw": "2.3.0",
"mysql2": "3.7.0",
"node-gyp": "10.0.1",
@@ -86,7 +83,7 @@
"reflect-metadata": "0.2.2",
"rollup": "4.4.1",
"sass": "1.79.4",
"sharp": "0.33.0",
"sharp": "0.34.5",
"sinon": "6.0.0",
"socket.io": "4.7.1",
"socket.io-adapter": "2.5.5",
@@ -122,6 +119,11 @@
"bd:v": "(bun run --silent --cwd=../ 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"
},
"optionalDependencies": {
"duckdb": "1.3.1",
"@duckdb/node-api": "1.1.3-alpha.7",
"msgpackr-extract": "3.0.2"
},
"resolutions": {
"react": "../node_modules/react",
"@types/node": "25.0.0"

View File

@@ -0,0 +1,74 @@
import { expect, test } from "bun:test";
import { tempDir } from "harness";
test("HTMLRewriter.transform supports Blob input", () => {
const html = '<div class="hello">Hello</div><script src="/main.js"></script>';
const blob = new Blob([html], { type: "text/html" });
const tags: string[] = [];
const result = new HTMLRewriter()
.on("*", {
element(element) {
tags.push(element.tagName);
},
})
.transform(blob);
expect(result).toBeInstanceOf(Blob);
expect(tags).toEqual(["div", "script"]);
});
test("HTMLRewriter.transform supports Blob input and modifies content", async () => {
const html = '<div class="old">content</div>';
const blob = new Blob([html], { type: "text/html" });
const result = new HTMLRewriter()
.on("div", {
element(element) {
element.setAttribute("class", "new");
},
})
.transform(blob);
expect(result).toBeInstanceOf(Blob);
const text = await result.text();
expect(text).toBe('<div class="new">content</div>');
});
test("HTMLRewriter.transform supports Bun.file() input", async () => {
using dir = tempDir("html-rewriter-bunfile", {
"index.html": '<h1>Hello</h1><p class="old">World</p>',
});
const file = Bun.file(`${dir}/index.html`);
const result = new HTMLRewriter()
.on("p", {
element(element) {
element.setAttribute("class", "new");
},
})
.transform(file);
// BunFile requires async I/O, so transform returns a Response
expect(result).toBeInstanceOf(Response);
const text = await result.text();
expect(text).toBe('<h1>Hello</h1><p class="new">World</p>');
});
test("HTMLRewriter.transform Blob with element handler reading attributes", () => {
const html = '<script src="/app.js"></script><script src="/vendor.js"></script>';
const blob = new Blob([html]);
const srcs: string[] = [];
new HTMLRewriter()
.on("script", {
element(element) {
const src = element.getAttribute("src");
if (src) srcs.push(src);
},
})
.transform(blob);
expect(srcs).toEqual(["/app.js", "/vendor.js"]);
});

View File

@@ -0,0 +1,28 @@
import { test } from "bun:test";
test("issue #27099", async () => {
// Run it twice to trigger ASAN.
await run();
await run();
});
async function run() {
const fileOps = Array.from({ length: 10 }, () => Bun.file("/tmp/nope").exists());
const outer = Bun.spawn(["bash", "-c", 'for j in $(seq 1 100); do echo "padding padding padding"; done'], {
stdout: "pipe",
stderr: "pipe",
});
const outerText = new Response(outer.stdout as ReadableStream).text();
const inner = Bun.spawn(["cat"], {
stdin: new Response(Buffer.allocUnsafe(20000).fill("a").toString()),
stdout: "pipe",
});
await new Response(inner.stdout as ReadableStream).text();
await inner.exited;
await outerText;
await outer.exited;
await Promise.all(fileOps);
}

View File

@@ -1,6 +1,6 @@
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { readFileSync, unlinkSync } from "fs";
import { bunEnv, bunExe, isWindows, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, isArm64, isWindows, tempDirWithFiles } from "harness";
import { join } from "path";
describe.if(isWindows)("PE codesigning integrity", () => {
@@ -215,7 +215,7 @@ console.log("Test data:", JSON.stringify(data));
// Validate PE header
expect(validation.pe.signature).toBe(0x00004550); // "PE\0\0"
expect(validation.pe.machine).toBe(0x8664); // x64
expect(validation.pe.machine).toBe(isArm64 ? 0xaa64 : 0x8664); // arm64 or x64
expect(validation.pe.numberOfSections).toBeGreaterThan(0);
// Validate optional header