Skip site navigation (1)Skip section navigation (2)
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 performance


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69effcba.3002c.79c3c5c0>