Date: Tue, 28 Apr 2026 00:18:02 +0000 From: Yuri Victorovich <yuri@FreeBSD.org> To: ports-committers@FreeBSD.org, dev-commits-ports-all@FreeBSD.org, dev-commits-ports-main@FreeBSD.org Subject: git: 8407c9414efb - main - lang/bun: New port: JavaScript runtime, bundler, test runner, and package manager Message-ID: <69effcba.3002c.79c3c5c0@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by yuri: URL: https://cgit.FreeBSD.org/ports/commit/?id=8407c9414efb4948e63c15ec66c0cb182c1f30cd commit 8407c9414efb4948e63c15ec66c0cb182c1f30cd Author: Yuri Victorovich <yuri@FreeBSD.org> AuthorDate: 2026-04-28 00:17:47 +0000 Commit: Yuri Victorovich <yuri@FreeBSD.org> CommitDate: 2026-04-28 00:17:47 +0000 lang/bun: New port: JavaScript runtime, bundler, test runner, and package manager --- lang/Makefile | 1 + lang/bun/Makefile | 246 +++++++++++++++++++++ lang/bun/distinfo | 11 + lang/bun/files/patch-build.zig | 42 ++++ lang/bun/files/patch-scripts_build_codegen.ts | 17 ++ lang/bun/files/patch-scripts_build_config.ts | 38 ++++ lang/bun/files/patch-scripts_build_tools.ts | 36 +++ lang/bun/files/patch-src_codegen_bake-codegen.ts | 48 ++++ lang/bun/files/patch-src_codegen_bundle-modules.ts | 32 +++ ...tion_next-pages_test_dev-server-ssr-100.test.ts | 39 ++++ ...h-test_js_web_console_console-log-utf16.test.ts | 18 ++ .../patch-test_regression_issue_10132.test.ts | 32 +++ .../patch-test_regression_issue_18547.test.ts | 13 ++ ...patch-test_regression_issue_20144_20144.test.ts | 13 ++ .../patch-test_regression_issue_24314.test.ts | 37 ++++ lang/bun/pkg-descr | 11 + 16 files changed, 634 insertions(+) diff --git a/lang/Makefile b/lang/Makefile index e540ed9d1b06..afd36345ae4c 100644 --- a/lang/Makefile +++ b/lang/Makefile @@ -16,6 +16,7 @@ SUBDIR += basic256 SUBDIR += bend SUBDIR += bsh + SUBDIR += bun SUBDIR += bwbasic SUBDIR += c SUBDIR += cairo diff --git a/lang/bun/Makefile b/lang/bun/Makefile new file mode 100644 index 000000000000..5d90e89980f5 --- /dev/null +++ b/lang/bun/Makefile @@ -0,0 +1,246 @@ +PORTNAME= bun +DISTVERSIONPREFIX= bun-v +DISTVERSION= 1.3.13-93 +DISTVERSIONSUFFIX= -g146a674cf6 +CATEGORIES= lang +MASTER_SITES= GH \ + https://github.com/oven-sh/zig/releases/download/autobuild-04e7f6ac1e009525bc00934f20199c68f04e0a24/:ovenzig \ + https://github.com/oven-sh/WebKit/releases/download/autobuild-${WEBKIT_VERSION}/:webkit \ + https://nodejs.org/dist/v${NODE_VERSION}/:nodejshdr \ + LOCAL/yuri/:bootstrap +DISTFILES= bootstrap-x86_64-linux-musl.zip:ovenzig \ + bun-webkit-freebsd-amd64.tar.gz:webkit \ + node-v${NODE_VERSION}-headers.tar.gz:nodejshdr \ + bun-1.3.13-93-bootstrap:bootstrap +DIST_SUBDIR= ${PORTNAME} +# Only auto-extract the GitHub source tarball; everything else is handled in post-extract. +EXTRACT_ONLY= ${DISTNAME}${EXTRACT_SUFX} + +MAINTAINER= yuri@FreeBSD.org +COMMENT= JavaScript runtime, bundler, test runner, and package manager +WWW= https://bun.com/ \ + https://github.com/oven-sh/bun + +LICENSE= LGPL20 + +ONLY_FOR_ARCHS= amd64 +ONLY_FOR_ARCHS_REASON= bootstrap binary is only available for amd64 for now + +# do-fetch requires npm (node_modules build) and cargo (lolhtml vendor) +FETCH_DEPENDS= ${LOCALBASE}/bin/npm:www/npm-node24 \ + git:devel/git \ + cargo:lang/rust \ + ${LOCALBASE}/llvm21/bin/clang++:devel/llvm21 \ + cmake:devel/cmake-core \ + ninja:devel/ninja \ + ${LOCALBASE}/bin/node:www/node24 +BUILD_DEPENDS= ${LOCALBASE}/llvm21/bin/clang++:devel/llvm21 \ + cmake:devel/cmake-core \ + git:devel/git \ + ninja:devel/ninja \ + cargo:lang/rust \ + ${LOCALBASE}/bin/node:www/node24 \ + ${LOCALBASE}/bin/npm:www/npm-node24 +TEST_DEPENDS= bash:shells/bash + +USES= shebangfix + +USE_GITHUB= yes +GH_ACCOUNT= oven-sh + +SHEBANG_FILES= test/regression/issue/18239/data-generator.sh + +# WebKit/JavaScriptCore FreeBSD prebuilt +WEBKIT_VERSION= bdf6aab38a9c6f99df3fd1486406ab6b74180fbb +WEBKIT_SHORT= bdf6aab38a9c6f99 +# Node.js headers (official tarball from nodejs.org; do-fetch trims unused dirs) +NODE_VERSION= 24.3.0 + +# Custom tarballs are built by do-fetch and stored in DD. +# They are NOT listed in DISTFILES so checksum phase does not verify them. + +# Dep tarball cache names and their upstream GitHub URLs. +# Names are {depname}-{sha256(url)[0:16]}.tar.gz (computed by bun's fetch-cli.ts). +# Used by do-fetch to pre-populate the bun dep tarballs cache. +_BUN_DEP_TARBALLS= \ + boringssl-5e15ff9594809574.tar.gz:https://github.com/oven-sh/boringssl/archive/0c5fce43b7ed5eb6001487ee48ac65766f5ddcd1.tar.gz \ + brotli-723494d4c3a9902a.tar.gz:https://github.com/google/brotli/archive/v1.1.0.tar.gz \ + cares-4e43539b43c0f4ae.tar.gz:https://github.com/c-ares/c-ares/archive/3ac47ee46edd8ea40370222f91613fc16c434853.tar.gz \ + hdrhistogram-97084f213075a65e.tar.gz:https://github.com/HdrHistogram/HdrHistogram_c/archive/be60a9987ee48d0abf0d7b6a175bad8d6c1585d1.tar.gz \ + highway-a10c8937e1b920ad.tar.gz:https://github.com/google/highway/archive/ac0d5d297b13ab1b89f48484fc7911082d76a93f.tar.gz \ + libarchive-4296b191210d6b1b.tar.gz:https://github.com/libarchive/libarchive/archive/ded82291ab41d5e355831b96b0e1ff49e24d8939.tar.gz \ + libdeflate-ce0e2d9805b30dcc.tar.gz:https://github.com/ebiggers/libdeflate/archive/c8c56a20f8f621e6a966b716b31f1dedab6a41e3.tar.gz \ + lolhtml-929339b1d898e66b.tar.gz:https://github.com/cloudflare/lol-html/archive/77127cd2b8545998756e8d64e36ee2313c4bb312.tar.gz \ + lshpack-73e0c55d12ea4fc2.tar.gz:https://github.com/litespeedtech/ls-hpack/archive/8905c024b6d052f083a3d11d0a169b3c2735c8a1.tar.gz \ + mimalloc-9e61bdd994e99463.tar.gz:https://github.com/oven-sh/mimalloc/archive/dbbb9a0819d00d521f9f58ad4d0a336efa4ce53a.tar.gz \ + picohttpparser-fad59b16ad4752cc.tar.gz:https://github.com/h2o/picohttpparser/archive/066d2b1e9ab820703db0837a7255d92d30f0c9f5.tar.gz \ + zlib-655c6ecdb6fc9cd5.tar.gz:https://github.com/zlib-ng/zlib-ng/archive/12731092979c6d07f42da27da673a9f6c7b13586.tar.gz \ + zstd-e010993a24072468.tar.gz:https://github.com/facebook/zstd/archive/f8745da6ff1ad1e7bab384bd1f9d742439278e99.tar.gz + +DD= ${DISTDIR}/${DIST_SUBDIR} +BUN_CACHE_DIR= ${WRKDIR}/bun-cache +BUN_ZIG_DIR= ${WRKDIR}/oven-zig +BUN_BUILD_DIR= ${WRKSRC}/build/release + +PLIST_FILES= bin/${PORTNAME} + +# do-fetch: download the standard distfiles, then build the 3 custom archives +# (node_modules, dep tarballs, cargo vendor) if they are not already cached. +# All files land in ${DD} so make checksum and poudriere can reuse them. +do-fetch: + @${MKDIR} ${DD} + # Fetch directly-downloadable distfiles (GH0 uses DEFAULT master site, no group suffix) + @${SETENV} ${_DO_FETCH_ENV} ${_MASTER_SITES_ENV} dp_SITE_FLAVOR=MASTER \ + ${SH} ${SCRIPTSDIR}/do-fetch.sh \ + ${DISTNAME}${EXTRACT_SUFX} \ + ${DISTFILES:M*\:ovenzig} \ + ${DISTFILES:M*\:webkit} \ + ${DISTFILES:M*\:nodejshdr} \ + ${DISTFILES:M*\:bootstrap} + # Build bun-node-modules tarball (npm install with workspace: → file: fixes). + # Uses deterministic tar so the checksum is reproducible across builds. + @if ! [ -f "${DD}/bun-node-modules-${DISTVERSION}.tar.gz" ]; then \ + ${ECHO_CMD} "===> Building bun-node-modules-${DISTVERSION}.tar.gz" && \ + _NTmp=$$(mktemp -d /tmp/bun-nmod.XXXXXX) && \ + bsdtar xzf ${DD}/${DISTNAME}${EXTRACT_SUFX} -C $$_NTmp && \ + _NSrc=$$_NTmp/${WRKSRC:T} && \ + ${SED} -i '' \ + 's|"@types/bun": "workspace:\*"|"@types/bun": "file:packages/@types/bun"|' \ + $$_NSrc/package.json && \ + ${SED} -i '' \ + 's|"bun-types": "workspace:"|"bun-types": "file:../bun-types"|' \ + $$_NSrc/packages/@types/bun/package.json && \ + (cd $$_NSrc && ${LOCALBASE}/bin/npm install --ignore-scripts) && \ + (cd $$_NSrc/packages/bun-error && \ + ${LOCALBASE}/bin/npm install --ignore-scripts) && \ + (cd $$_NSrc/src/node-fallbacks && \ + ${LOCALBASE}/bin/npm install --ignore-scripts) && \ + ${FIND} $$_NSrc/node_modules \ + $$_NSrc/packages/bun-error/node_modules \ + $$_NSrc/src/node-fallbacks/node_modules \ + -exec ${TOUCH} -h -d 1970-01-01T00:00:00Z {} \; && \ + (cd $$_NSrc && \ + ${FIND} node_modules packages/bun-error/node_modules \ + src/node-fallbacks/node_modules -print0 \ + | LC_ALL=C ${SORT} -z \ + | ${SETENV} -i ${TAR} czf ${DD}/bun-node-modules-${DISTVERSION}.tar.gz \ + --format=cpio --gid 0 --uid 0 --options gzip:!timestamp \ + --no-recursion --null -T -) && \ + ${RM} -rf $$_NTmp; \ + fi + # Build bun-dep-tarballs + bun-cargo-vendor-lolhtml tarballs. + # Uses deterministic tar so checksums are reproducible across builds. + @if ! [ -f "${DD}/bun-dep-tarballs-${DISTVERSION}.tar.gz" ] || \ + ! [ -f "${DD}/bun-cargo-vendor-lolhtml-${DISTVERSION}.tar.gz" ]; then \ + ${ECHO_CMD} "===> Downloading bun dep tarballs" && \ + _DTmp=$$(mktemp -d /tmp/bun-dtp.XXXXXX) && \ + ${MKDIR} $$_DTmp/tarballs && \ + for _tu in ${_BUN_DEP_TARBALLS}; do \ + _tc=$${_tu%%:*}; _url=$${_tu#*:}; \ + if ! [ -f "$$_DTmp/tarballs/$$_tc" ]; then \ + ${FETCH_CMD} ${FETCH_BEFORE_ARGS} $$_url ${FETCH_AFTER_ARGS} \ + -o $$_DTmp/tarballs/$$_tc || exit 1; \ + fi; \ + done && \ + if ! [ -f "${DD}/bun-dep-tarballs-${DISTVERSION}.tar.gz" ]; then \ + ${ECHO_CMD} "===> Packaging bun-dep-tarballs-${DISTVERSION}.tar.gz" && \ + ( cd $$_DTmp && \ + ${FIND} tarballs -exec ${TOUCH} -h -d 1970-01-01T00:00:00Z {} \; && \ + ${FIND} tarballs -print0 \ + | LC_ALL=C ${SORT} -z \ + | ${SETENV} -i ${TAR} czf ${DD}/bun-dep-tarballs-${DISTVERSION}.tar.gz \ + --format=cpio --gid 0 --uid 0 --options gzip:!timestamp \ + --no-recursion --null -T - ); \ + fi && \ + if ! [ -f "${DD}/bun-cargo-vendor-lolhtml-${DISTVERSION}.tar.gz" ]; then \ + ${ECHO_CMD} "===> Vendoring lolhtml Rust crates" && \ + _LTmp=$$(mktemp -d /tmp/bun-lol.XXXXXX) && \ + bsdtar xzf $$_DTmp/tarballs/lolhtml-929339b1d898e66b.tar.gz \ + -C $$_LTmp --strip-components 1 && \ + (cd $$_LTmp/c-api && \ + ${SETENV} HOME=$$_LTmp \ + ${LOCALBASE}/bin/cargo vendor vendor) && \ + ${FIND} $$_LTmp/c-api/vendor -exec ${TOUCH} -h -d 1970-01-01T00:00:00Z {} \; && \ + ( cd $$_LTmp/c-api && \ + ${FIND} vendor -print0 \ + | LC_ALL=C ${SORT} -z \ + | ${SETENV} -i ${TAR} czf ${DD}/bun-cargo-vendor-lolhtml-${DISTVERSION}.tar.gz \ + --format=cpio --gid 0 --uid 0 --options gzip:!timestamp \ + --no-recursion --null -T - ) && \ + ${RM} -rf $$_LTmp; \ + fi && \ + ${RM} -rf $$_DTmp; \ + fi + +post-extract: + # Set up bun cache directory structure + @${MKDIR} ${BUN_CACHE_DIR}/tarballs + # Extract oven-sh/zig fork (Linux binary, via linux compat) + @${MKDIR} ${BUN_ZIG_DIR} + @cd ${BUN_ZIG_DIR} && bsdtar -xf ${DD}/bootstrap-x86_64-linux-musl.zip + @${CHMOD} +x ${BUN_ZIG_DIR}/bootstrap-x86_64-linux-musl/zig + # Install pre-built native FreeBSD bun bootstrap binary + @${MKDIR} ${WRKDIR}/bun-bootstrap + @${CP} ${DD}/bun-1.3.13-93-bootstrap ${WRKDIR}/bun-bootstrap/bun + @${CHMOD} +x ${WRKDIR}/bun-bootstrap/bun + # Extract WebKit prebuilt into cache + @${MKDIR} ${BUN_CACHE_DIR}/webkit-${WEBKIT_SHORT}-freebsd + @tar xzf ${DD}/bun-webkit-freebsd-amd64.tar.gz \ + -C ${BUN_CACHE_DIR}/webkit-${WEBKIT_SHORT}-freebsd \ + --strip-components 1 + @${ECHO_CMD} "${WEBKIT_VERSION}" > \ + ${BUN_CACHE_DIR}/webkit-${WEBKIT_SHORT}-freebsd/.identity + # Extract dep tarballs into cache + @tar xzf ${DD}/bun-dep-tarballs-${DISTVERSION}.tar.gz \ + -C ${BUN_CACHE_DIR} + # Extract vendored Rust crates for lolhtml + @${MKDIR} ${WRKDIR}/cargo-vendor-lolhtml + @tar xzf ${DD}/bun-cargo-vendor-lolhtml-${DISTVERSION}.tar.gz \ + -C ${WRKDIR}/cargo-vendor-lolhtml + # Configure cargo to use vendored sources (via CARGO_HOME config) + @${MKDIR} ${WRKDIR}/.cargo + @${PRINTF} '[source.crates-io]\nreplace-with = "vendored-sources"\n\n[source.vendored-sources]\ndirectory = "%s/vendor"\n' \ + ${WRKDIR}/cargo-vendor-lolhtml > ${WRKDIR}/.cargo/config.toml + # Extract nodejs headers into bun cache and remove dirs bun doesn't need + @${MKDIR} ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION} + @tar xzf ${DD}/node-v${NODE_VERSION}-headers.tar.gz \ + -C ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION} \ + --strip-components 1 + @${RM} -rf \ + ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION}/include/node/openssl \ + ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION}/include/node/uv \ + ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION}/include/node/uv.h + @${ECHO_CMD} "${NODE_VERSION}" > \ + ${BUN_CACHE_DIR}/nodejs-headers-${NODE_VERSION}/.identity + # Extract node_modules into WRKSRC + @tar xzf ${DD}/bun-node-modules-${DISTVERSION}.tar.gz \ + -C ${WRKSRC} + +do-build: + @cd ${WRKSRC} && \ + ${SETENV} PATH=${LOCALBASE}/llvm21/bin:${LOCALBASE}/bin:/usr/bin:/bin \ + BUN_ZIG_PATH=${BUN_ZIG_DIR}/bootstrap-x86_64-linux-musl \ + BUN_HOST_OS=freebsd \ + FREEBSD_SYSROOT=/ \ + CARGO_HOME=${WRKDIR}/.cargo \ + GIT_SHA=${DISTVERSIONSUFFIX:S/-g//} \ + ${WRKDIR}/bun-bootstrap/bun scripts/build.ts \ + --os=freebsd \ + --cacheDir=${BUN_CACHE_DIR} \ + --buildDir=${BUN_BUILD_DIR} \ + --profile=release \ + -j${MAKE_JOBS_NUMBER} + +do-install: + ${INSTALL_PROGRAM} ${BUN_BUILD_DIR}/bun ${STAGEDIR}${PREFIX}/bin/ + +do-test: + @${ECHO_CMD} "==> Running Bun version check..." + ${STAGEDIR}${PREFIX}/bin/bun --version + @${ECHO_CMD} "==> Installing test dependencies..." + cd ${WRKSRC}/test && ${STAGEDIR}${PREFIX}/bin/bun install \ + --frozen-lockfile --ignore-scripts + @${ECHO_CMD} "==> Running Bun regression tests..." + cd ${WRKSRC} && ${STAGEDIR}${PREFIX}/bin/bun test --timeout 120000 --parallel 1 + +.include <bsd.port.mk> diff --git a/lang/bun/distinfo b/lang/bun/distinfo new file mode 100644 index 000000000000..3a7d3a4aafe4 --- /dev/null +++ b/lang/bun/distinfo @@ -0,0 +1,11 @@ +TIMESTAMP = 1777332350 +SHA256 (bun/bootstrap-x86_64-linux-musl.zip) = 0c09b44c0b2432d7ad8a759b685ba8ae1f883902851ac52e282ad60a89bebe42 +SIZE (bun/bootstrap-x86_64-linux-musl.zip) = 100112252 +SHA256 (bun/bun-webkit-freebsd-amd64.tar.gz) = 2bbab3f44e3b3d7961408c703271285ca3cc4d128d7ff9fc54dc5ab112e89fbf +SIZE (bun/bun-webkit-freebsd-amd64.tar.gz) = 37660405 +SHA256 (bun/node-v24.3.0-headers.tar.gz) = 045e9bf477cd5db0ec67f8c1a63ba7f784dedfe2c581e3d0ed09b88e9115dd07 +SIZE (bun/node-v24.3.0-headers.tar.gz) = 8747815 +SHA256 (bun/bun-1.3.13-93-bootstrap) = aea7f1c221f812d726aad220ecc879914bbe49b05e5bd4e76c3155ec0a133f54 +SIZE (bun/bun-1.3.13-93-bootstrap) = 105685712 +SHA256 (bun/oven-sh-bun-bun-v1.3.13-93-g146a674cf6_GH0.tar.gz) = 12bec710dd6717f83ccaaec7fb7970828177f173639018042ca3031b851b24ff +SIZE (bun/oven-sh-bun-bun-v1.3.13-93-g146a674cf6_GH0.tar.gz) = 54226399 diff --git a/lang/bun/files/patch-build.zig b/lang/bun/files/patch-build.zig new file mode 100644 index 000000000000..e9da8d2ea74a --- /dev/null +++ b/lang/bun/files/patch-build.zig @@ -0,0 +1,42 @@ +-- Fix Zig 0.15.x API compatibility issues when building bun for FreeBSD: +-- 1. FreeBSD sysroot string literal: use @as([]const u8, "/") instead of bare "/" +-- because *const [1:0]u8 cannot coerce to ?[]const u8 in oven-sh/zig fork +-- 2. SemanticVersion format: use {d}.{d}.{d} instead of {f} for compatibility +-- 3. validateGeneratedPath: skip panic when generated files are not yet present + +--- build.zig.orig 2026-04-27 07:25:40 UTC ++++ build.zig +@@ -90,7 +90,7 @@ const BunBuildOptions = struct { + opts.addOption(bool, "enable_valgrind", this.enable_valgrind); + opts.addOption(bool, "enable_tinycc", this.enable_tinycc); + opts.addOption(bool, "use_mimalloc", this.use_mimalloc); +- opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{f}", .{this.reported_nodejs_version})); ++ opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{d}.{d}.{d}", .{this.reported_nodejs_version.major, this.reported_nodejs_version.minor, this.reported_nodejs_version.patch})); + opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm); + opts.addOption(bool, "override_no_export_cpp_apis", this.override_no_export_cpp_apis); + +@@ -226,7 +226,7 @@ pub fn build(b: *Build) !void { + // linkLibC() gets FreeBSD libc via `zig build --libc <file>`. On a + // native FreeBSD host the system root is the sysroot. + const freebsd_sysroot = b.option([]const u8, "freebsd_sysroot", "FreeBSD sysroot (extracted base.txz) for translate-c headers") orelse +- if (os == .freebsd and builtin.os.tag == .freebsd) "/" else null; ++ if (os == .freebsd and builtin.os.tag == .freebsd) @as([]const u8, "/") else null; + if (os == .freebsd and freebsd_sysroot == null) { + std.debug.panic("-Dfreebsd_sysroot is required when cross-compiling to FreeBSD (zig does not bundle FreeBSD libc headers)", .{}); + } +@@ -1086,13 +1086,8 @@ fn validateGeneratedPath(path: []const u8) void { + } + + fn validateGeneratedPath(path: []const u8) void { +- if (!exists(path)) { +- std.debug.panic( +- \\Generated file '{s}' is missing! +- \\ +- \\Make sure to use CMake and Ninja, or pass a manual codegen folder with '-Dgenerated-code=...' +- , .{path}); +- } ++ // Skip validation - generated files may not exist before codegen runs ++ _ = path; + } + + const WindowsShim = struct { diff --git a/lang/bun/files/patch-scripts_build_codegen.ts b/lang/bun/files/patch-scripts_build_codegen.ts new file mode 100644 index 000000000000..2caa71445091 --- /dev/null +++ b/lang/bun/files/patch-scripts_build_codegen.ts @@ -0,0 +1,17 @@ +-- Skip bun install when node_modules already exists. +-- On FreeBSD, bun install fails due to a linux compat layer limitation +-- with /proc/self/fd (fchdir returns ENOTDIR for symlink-based FDs). +-- By checking for existing node_modules, the build uses pre-populated +-- node_modules from the port's distfile instead. + + 2026-04-27 07:19:58 UTC ++++ scripts/build/codegen.ts +@@ -167,7 +167,7 @@ export function registerCodegenRules(n: Ninja, cfg: Co + n.rule("bun_install", { + command: hostWin + ? `cmd /c "cd /d $dir && ${bun} install --frozen-lockfile && ${touch} $stamp"` +- : `cd $dir && ${bun} install --frozen-lockfile && ${touch} $stamp`, ++ : `cd $dir && ([ -d node_modules ] || ${bun} install --frozen-lockfile) && ${touch} $stamp`, + description: "install $dir", + restat: true, + // bun install can be memory-hungry and grabs a lockfile; serialize. diff --git a/lang/bun/files/patch-scripts_build_config.ts b/lang/bun/files/patch-scripts_build_config.ts new file mode 100644 index 000000000000..97bf1d0421df --- /dev/null +++ b/lang/bun/files/patch-scripts_build_config.ts @@ -0,0 +1,38 @@ +-- Add BUN_HOST_OS environment variable support to override host OS detection. +-- When building with a Linux bootstrap bun binary on FreeBSD, bun reports +-- host.os = "linux" which triggers cross-compilation mode (dep_cargo_cross, +-- rustup target add, cross-compile clang flags). Setting BUN_HOST_OS=freebsd +-- makes the build treat FreeBSD as native, using dep_cargo (no rustup needed) +-- and native LLVM/clang without cross-compile sysroot flags. +-- +-- Also inline WEBKIT_VERSION to break a circular ES module dependency: +-- deps/index.ts → deps/webkit.ts → source.ts → config.ts → deps/webkit.ts +-- Bun 1.3.14 enforces strict TDZ for circular ES modules; inlining breaks cycle. + +--- scripts/build/config.ts.orig 2026-04-27 07:22:36 UTC ++++ scripts/build/config.ts +@@ -11,7 +11,9 @@ + import { homedir, arch as hostArch, platform as hostPlatform } from "node:os"; + import { isAbsolute, join, relative, resolve, sep } from "node:path"; + import { NODEJS_ABI_VERSION, NODEJS_VERSION } from "./deps/nodejs-headers.ts"; +-import { WEBKIT_VERSION } from "./deps/webkit.ts"; ++// WEBKIT_VERSION inlined here (was imported from deps/webkit.ts) to break circular module dep: ++// deps/index.ts → deps/webkit.ts → source.ts → config.ts → deps/webkit.ts ++const WEBKIT_VERSION = "bdf6aab38a9c6f99df3fd1486406ab6b74180fbb"; + import { assert, BuildError } from "./error.ts"; + import { clangTargetArch } from "./tools.ts"; + import { cyan, dim, green } from "./tty.ts"; +@@ -341,9 +343,12 @@ + + /** + * Host platform detection. Only used for picking defaults. ++ * The BUN_HOST_OS environment variable can override the detected OS. ++ * This is needed when using a Linux bun bootstrap binary on FreeBSD. + */ + export function detectHost(): Host { +- const plat = hostPlatform(); ++ const envOs = process.env.BUN_HOST_OS; ++ const plat = envOs !== undefined ? envOs : hostPlatform(); + const os: OS = + plat === "linux" + ? "linux" diff --git a/lang/bun/files/patch-scripts_build_tools.ts b/lang/bun/files/patch-scripts_build_tools.ts new file mode 100644 index 000000000000..2ef0f33dbd64 --- /dev/null +++ b/lang/bun/files/patch-scripts_build_tools.ts @@ -0,0 +1,36 @@ +-- Add FreeBSD support to LLVM toolchain resolution: +-- 1. llvmSearchPaths: add /usr/local/llvmNN/bin (FreeBSD ports layout) +-- 2. resolveLlvmToolchain: use ld.lld for FreeBSD (same as Linux) +-- 3. llvmInstallHint: add FreeBSD install hint +--- scripts/build/tools.ts.orig 2026-04-27 08:09:39 UTC ++++ scripts/build/tools.ts +@@ -312,6 +312,12 @@ function llvmSearchPaths(os: OS, arch: Arch): string[] + paths.push(`/usr/lib/llvm${LLVM_MAJOR}/bin`); + } + ++ if (os === "freebsd") { ++ // FreeBSD ports install LLVM under /usr/local/llvm${MAJOR}/bin ++ paths.push(`/usr/local/llvm${LLVM_MAJOR}/bin`); ++ paths.push(`/usr/local/llvm-${LLVM_MAJOR}/bin`); ++ } ++ + return paths; + } + +@@ -332,6 +338,7 @@ function llvmInstallHint(os: OS): string { + if (os === "darwin") return `Install with: brew install llvm@${LLVM_MAJOR}`; + if (os === "linux") + return `Install with: apt install clang-${LLVM_MAJOR} lld-${LLVM_MAJOR} (or equivalent for your distro)`; ++ if (os === "freebsd") return `Install with: pkg install llvm${LLVM_MAJOR}`; + if (os === "windows") return `Install LLVM ${LLVM_VERSION} from https://github.com/llvm/llvm-project/releases`; + return ""; + } +@@ -419,7 +426,7 @@ export function resolveLlvmToolchain( + let ld: string; + if (os === "windows") { + ld = findLlvmTool("lld-link", paths, os, { checkVersion: false, required: true })?.path ?? ""; +- } else if (os === "linux") { ++ } else if (os === "linux" || os === "freebsd") { + ld = findLlvmTool("ld.lld", paths, os, { checkVersion: true, required: true })?.path ?? ""; + } else { + ld = ""; // darwin: unused diff --git a/lang/bun/files/patch-src_codegen_bake-codegen.ts b/lang/bun/files/patch-src_codegen_bake-codegen.ts new file mode 100644 index 000000000000..b7c95c65c911 --- /dev/null +++ b/lang/bun/files/patch-src_codegen_bake-codegen.ts @@ -0,0 +1,48 @@ +-- Replace Bun.spawnSync with async Bun.spawn in bake-codegen. +-- On FreeBSD linux compat layer, Bun.spawnSync with stdio:"pipe" fails +-- with EINVAL because pread() is not valid on pipes. Using async Bun.spawn +-- with Response.arrayBuffer() works correctly on FreeBSD linux compat. + + 2026-04-27 07:15:02 UTC ++++ src/codegen/bake-codegen.ts +@@ -30,20 +30,27 @@ function convertZigEnum(zig: string, names: string[]) + return output; + } + +-function css(file: string, is_development: boolean): string { +- const { success, stdout, stderr } = Bun.spawnSync({ ++async function css(file: string, is_development: boolean): Promise<string> { ++ const proc = Bun.spawn({ + cmd: [process.execPath, "build", file, "--minify"], + cwd: import.meta.dir, + stdio: ["ignore", "pipe", "pipe"], + }); +- if (!success) throw new Error(stderr.toString("utf-8")); +- return stdout.toString("utf-8"); ++ const [stdoutBuf, stderrBuf] = await Promise.all([ ++ new Response(proc.stdout).arrayBuffer(), ++ new Response(proc.stderr).arrayBuffer(), ++ ]); ++ const exitCode = await proc.exited; ++ if (exitCode !== 0) throw new Error(Buffer.from(stderrBuf).toString("utf-8")); ++ return Buffer.from(stdoutBuf).toString("utf-8"); + } + + async function run() { + const devServerZig = readFileSync(join(base_dir, "DevServer.zig"), "utf-8"); + writeIfNotChanged(join(base_dir, "generated.ts"), convertZigEnum(devServerZig, ["IncomingMessageId", "MessageId"])); + ++ const overlayCSS = await css("../bake/client/overlay.css", !!debug); ++ + const results = await Promise.allSettled( + ["client", "server", "error"].map(async file => { + const side = file === "error" ? "client" : file; +@@ -53,7 +60,7 @@ async function run() { + side: JSON.stringify(side), + IS_ERROR_RUNTIME: String(file === "error"), + IS_BUN_DEVELOPMENT: String(!!debug), +- OVERLAY_CSS: css("../bake/client/overlay.css", !!debug), ++ OVERLAY_CSS: overlayCSS, + }, + minify: { + syntax: !debug, diff --git a/lang/bun/files/patch-src_codegen_bundle-modules.ts b/lang/bun/files/patch-src_codegen_bundle-modules.ts new file mode 100644 index 000000000000..c5a3bd64989b --- /dev/null +++ b/lang/bun/files/patch-src_codegen_bundle-modules.ts @@ -0,0 +1,32 @@ +-- Replace Bun.spawnSync with async Bun.spawn in bundle-modules. +-- On FreeBSD linux compat layer, Bun.spawnSync with stdio:"pipe" fails +-- with EINVAL because pread() is not valid on pipes. Using async Bun.spawn +-- with Response.arrayBuffer() works correctly on FreeBSD linux compat. + + 2026-04-27 07:15:02 UTC ++++ src/codegen/bundle-modules.ts +@@ -221,12 +221,22 @@ verbose("running: ", config_cli); + path.join(TMP_DIR, "modules_out"), + ]; + verbose("running: ", config_cli); +-const out = Bun.spawnSync({ ++const _spawnProc = Bun.spawn({ + cmd: config_cli, + cwd: process.cwd(), + env: process.env, +- stdio: ["pipe", "pipe", "pipe"], ++ stdio: ["ignore", "pipe", "pipe"], + }); ++const [_spawnStdout, _spawnStderr] = await Promise.all([ ++ new Response(_spawnProc.stdout).arrayBuffer(), ++ new Response(_spawnProc.stderr).arrayBuffer(), ++]); ++const _spawnExit = await _spawnProc.exited; ++const out = { ++ exitCode: _spawnExit, ++ stdout: Buffer.from(_spawnStdout), ++ stderr: Buffer.from(_spawnStderr), ++}; + if (out.exitCode !== 0) { + console.error(out.stderr.toString()); + process.exit(out.exitCode); diff --git a/lang/bun/files/patch-test_integration_next-pages_test_dev-server-ssr-100.test.ts b/lang/bun/files/patch-test_integration_next-pages_test_dev-server-ssr-100.test.ts new file mode 100644 index 000000000000..b60b8b94e0bd --- /dev/null +++ b/lang/bun/files/patch-test_integration_next-pages_test_dev-server-ssr-100.test.ts @@ -0,0 +1,39 @@ +-- Skip gracefully when bun:internal-for-testing is unavailable (release builds). +-- bun:internal-for-testing is only compiled into debug/development builds of bun. +-- FreeBSD ports always build a release binary, so this module is never available. +-- Without this patch, the test file crashes at module load time with ENOENT, +-- causing "Unhandled error between tests" in the bun test output. +--- test/integration/next-pages/test/dev-server-ssr-100.test.ts.orig2026-04-27 17:51:47 UTC ++++ test/integration/next-pages/test/dev-server-ssr-100.test.ts +@@ -1,12 +1,24 @@ + import { Subprocess } from "bun"; +-import { install_test_helpers } from "bun:internal-for-testing"; +-import { afterAll, beforeAll, expect, test } from "bun:test"; ++// bun:internal-for-testing is only available in debug builds; skip gracefully in release builds. ++let install_test_helpers: any; ++try { ++ ({ install_test_helpers } = require("bun:internal-for-testing") as any); ++} catch { ++ install_test_helpers = null; ++} ++import { afterAll, beforeAll, describe, expect, test } from "bun:test"; + import { copyFileSync } from "fs"; + import { cp, rm } from "fs/promises"; + import PQueue from "p-queue"; + import { join } from "path"; + import { StringDecoder } from "string_decoder"; + import { bunEnv, bunExe, tmpdirSync, toMatchNodeModulesAt } from "../../../harness"; ++ ++if (!install_test_helpers) { ++ describe.skip("next-pages dev server (bun:internal-for-testing unavailable in release builds)", () => { ++ test("skipped", () => {}); ++ }); ++} else { + const { parseLockfile } = install_test_helpers; + + expect.extend({ toMatchNodeModulesAt }); +@@ -172,3 +184,4 @@ + }, + timeout, + ); ++} diff --git a/lang/bun/files/patch-test_js_web_console_console-log-utf16.test.ts b/lang/bun/files/patch-test_js_web_console_console-log-utf16.test.ts new file mode 100644 index 000000000000..85ca207155e1 --- /dev/null +++ b/lang/bun/files/patch-test_js_web_console_console-log-utf16.test.ts @@ -0,0 +1,18 @@ +-- Replace toBeEmpty() with toBe("") to avoid matcher pollution from test 16312. +-- Test 16312 calls expect.extend(@testing-library/jest-dom/matchers) globally, +-- which overrides bun's built-in toBeEmpty() with a DOM-only version that rejects +-- strings. When tests run in the same process (bun test --jobs 1), subsequent +-- tests calling toBeEmpty() on string values fail with "value must be an +-- HTMLElement or an SVGElement". Using toBe("") is the correct, unambiguous way +-- to assert an empty string. +--- test/js/web/console/console-log-utf16.test.ts.orig 2026-04-27 17:38:48 UTC ++++ test/js/web/console/console-log-utf16.test.ts +@@ -13,7 +13,7 @@ it("works with large utf-16 strings", async () => { + const exitCode = await proc.exited; + const stdout = await proc.stdout.text(); + const stderr = await proc.stderr.text(); +- expect(stderr).toBeEmpty(); ++ expect(stderr).toBe(""); + expect(exitCode).toBe(0); + + const expected = Array(10000).fill("肉醬意大利粉").join("\n"); diff --git a/lang/bun/files/patch-test_regression_issue_10132.test.ts b/lang/bun/files/patch-test_regression_issue_10132.test.ts new file mode 100644 index 000000000000..20726729faa6 --- /dev/null +++ b/lang/bun/files/patch-test_regression_issue_10132.test.ts @@ -0,0 +1,32 @@ +-- Fix $.cwd() global state leak from test 10132 into subsequent test files. +-- Test 10132 sets $.cwd() in a test body but never resets it via afterAll. +-- When tests run in the same process (bun test --jobs 1), this leaks the +-- deleted temp dir path as the shell cwd to following test files, causing +-- ENOENT failures in tests 17405 and 17294 that use the bun Shell ($). +--- test/regression/issue/10132.test.ts.orig 2026-04-27 17:36:19 UTC ++++ test/regression/issue/10132.test.ts +@@ -1,5 +1,5 @@ import { $ } from "bun"; + import { $ } from "bun"; +-import { beforeAll, expect, test } from "bun:test"; ++import { afterAll, beforeAll, expect, test } from "bun:test"; + import { chmodSync } from "fs"; + import { bunExe, isPosix, tempDirWithFiles } from "harness"; + import { join } from "path"; +@@ -50,6 +50,11 @@ echo My name is bun-hello2 + } + }); + ++const originalCwd = process.cwd(); ++afterAll(() => { ++ $.cwd(originalCwd); ++}); ++ + test("bun run sets cwd for script, matching npm", async () => { + $.cwd(dir); + const currentPwd = (await $`${bunExe()} run get-pwd`.text()).trim(); +@@ -80,4 +85,5 @@ test("issue #10132, bun run sets PATH", async () => { + join(dir, "subdir", "one", "two", "three"), + ].map(run), + ); ++ $.cwd(process.cwd()); + }); diff --git a/lang/bun/files/patch-test_regression_issue_18547.test.ts b/lang/bun/files/patch-test_regression_issue_18547.test.ts new file mode 100644 index 000000000000..0e8ec00cdf57 --- /dev/null +++ b/lang/bun/files/patch-test_regression_issue_18547.test.ts @@ -0,0 +1,13 @@ +-- Use port: 0 to let the OS assign a random available port. +-- Without this, Bun.serve defaults to port 3000, which causes EADDRINUSE +-- failures when tests run concurrently and another test is also using port 3000. +--- test/regression/issue/18547.test.ts.orig 2026-04-27 18:00:04 UTC ++++ test/regression/issue/18547.test.ts +@@ -2,6 +2,7 @@ test("18547", async () => { + + test("18547", async () => { + using serve = Bun.serve({ ++ port: 0, + routes: { + "/:foo": request => { + request.cookies.set("sessionToken", "123456"); diff --git a/lang/bun/files/patch-test_regression_issue_20144_20144.test.ts b/lang/bun/files/patch-test_regression_issue_20144_20144.test.ts new file mode 100644 index 000000000000..ddd9de97fbe7 --- /dev/null +++ b/lang/bun/files/patch-test_regression_issue_20144_20144.test.ts @@ -0,0 +1,13 @@ +-- Skip the TinyCC-based FFI test on FreeBSD; TinyCC is intentionally disabled +-- on FreeBSD (config.ts: tinycc = !(... || freebsd)). +--- test/regression/issue/20144/20144.test.ts.orig 2026-04-27 17:31:21 UTC ++++ test/regression/issue/20144/20144.test.ts +@@ -2,7 +2,7 @@ import { spawn } from "node:child_process"; + import assert from "node:assert"; + import { spawn } from "node:child_process"; + +-it.skipIf(process.platform === "win32")("should not time out", done => { ++it.skipIf(process.platform === "win32" || process.platform === "freebsd")("should not time out", done => { + const child = spawn(process.execPath, ["run", "./20144.fixture.ts"], { + cwd: __dirname, + stdio: [null, "inherit", "inherit", "ipc"], diff --git a/lang/bun/files/patch-test_regression_issue_24314.test.ts b/lang/bun/files/patch-test_regression_issue_24314.test.ts new file mode 100644 index 000000000000..b3591f686e36 --- /dev/null +++ b/lang/bun/files/patch-test_regression_issue_24314.test.ts @@ -0,0 +1,37 @@ +-- Skip gracefully when bun:internal-for-testing is unavailable (release builds). +-- bun:internal-for-testing is only compiled into debug/development builds of bun. +-- FreeBSD ports always build a release binary, so this module is never available. +-- Without this patch, the test file crashes at module load time with ENOENT, +-- causing "Unhandled error between tests" in the bun test output. +--- test/regression/issue/24314.test.ts.orig 2026-04-27 17:51:47 UTC ++++ test/regression/issue/24314.test.ts +@@ -1,4 +1,10 @@ +-import { readTarball } from "bun:internal-for-testing"; ++// bun:internal-for-testing is only available in debug builds; skip gracefully in release builds. ++let readTarball: ((path: string) => any) | undefined; ++try { ++ ({ readTarball } = require("bun:internal-for-testing") as any); ++} catch { ++ readTarball = undefined; ++} + import { expect, test } from "bun:test"; + import { bunEnv, bunExe, tempDir } from "harness"; + import path from "node:path"; +@@ -8,7 +14,7 @@ function normalizePath(p: string): string { + return p.replace(/\\/g, "/"); + } + +-test("bun pm pack respects changes to package.json from prepack scripts", async () => { ++test.skipIf(!readTarball)("bun pm pack respects changes to package.json from prepack scripts", async () => { + using dir = tempDir("pack-prepack", { + "package.json": JSON.stringify( + { +@@ -61,7 +67,7 @@ fs.writeFileSync('package.json', JSON.stringify(pkg, n + expect(extractedPkg.description).toBe("MODIFIED BY PREPACK"); + }); + +-test("bun pm pack respects changes to package.json from prepare scripts", async () => { ++test.skipIf(!readTarball)("bun pm pack respects changes to package.json from prepare scripts", async () => { + using dir = tempDir("pack-prepare", { + "package.json": JSON.stringify( + { diff --git a/lang/bun/pkg-descr b/lang/bun/pkg-descr new file mode 100644 index 000000000000..7385e861e1a5 --- /dev/null +++ b/lang/bun/pkg-descr @@ -0,0 +1,11 @@ +Bun is a fast JavaScript runtime, bundler, transpiler, and +package manager -- all in one. + +Key features: +- Up to 4x faster startup time than Node.js +- Native TypeScript and JSX support (no separate transpilation step) +- Built-in package manager (npm-compatible) with global caching +- Built-in test runner, bundler, and transpiler +- Web-standard APIs: fetch, WebSocket, ReadableStream, etc. +- Node.js compatibility: most Node.js built-in modules work out of the box +- Zig, C++, and Rust -- for maximum performancehome | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69effcba.3002c.79c3c5c0>
