rules_bun
Bazel rules for Bun. Hermetic 'bun test' + sandbox-escaping 'bun run' against prebuilt binaries from oven-sh/bun releases.
| Latest | 0.4.1 |
|---|---|
| Versions | 6 |
| Category | Bazel rules |
| Compat level | 1 |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_bun/ |
| Source | github.com/tomato-bazel/rules_bun |
bazel_dep(name = "rules_bun", version = "0.4.1")
View source & releases on GitHub ↗
Bazel rules for Bun. Fetches the prebuilt Bun binary,
wraps it as a Bazel toolchain, and provides hermetic bun test +
sandbox-escaping bun run runners, plus bun build bundling and
bun build --compile standalone-executable rules.
-
module extensions (see docs/extensions.md):
bun— auto-creates@bunwith the host-platform binary.bun_deps— Bun-nativenode_modulesstaging.bun_deps.install(...)runsbun install --frozen-lockfilefrom apackage.json+bun.lockand exposes the result as@<name>//:node_modules. The pure-Bun replacement for aspect_rules_js’snpm_translate_lock+npm_link_all_packages— no pnpm-lock, no aspect_rules_js.
-
toolchain:
bun_toolchain— wraps the binary; resolved via@rules_bun//bun:toolchain_type. See docs/toolchains.md. -
rules:
bun_test— runsbun testover listed source files as a Bazel test target (optionalnode_modulesfor dep resolution).bun_run—bazel run //path:targetmacro: invokesbun run <script>against the live workspace source.bun_bundle— bundle a JS/TS entry point into one self-contained file viabun build(Bun-nativenode_modulespath or legacy aspectdriverpath).bun_compile— compile a JS/TS entry point into a standalone native executable viabun build --compile.
See docs/defs.md.
Install
Add the registry to your .bazelrc:
common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/
In your MODULE.bazel:
bazel_dep(name = "rules_bun", version = "0.4.0")
bun = use_extension("@rules_bun//bun:extensions.bzl", "bun")
use_repo(bun, "bun")
register_toolchains("@bun//:bun_toolchain_def")
For the Bun-native dependency flow (recommended — no pnpm, no
aspect_rules_js) you only need a package.json + bun.lock:
bun_deps = use_extension("@rules_bun//bun:extensions.bzl", "bun_deps")
bun_deps.install(
name = "npm",
package_json = "//:package.json",
lock = "//:bun.lock",
)
use_repo(bun_deps, "npm")
bun_bundle / bun_compile then consume @npm//:node_modules directly
(see below). The legacy path instead drives bun build through an
aspect_rules_js js_binary, which needs aspect_rules_js (and a node
toolchain) plus a pnpm-lock:
bazel_dep(name = "aspect_rules_js", version = "3.1.2")
Pin a specific version:
bun.toolchain(version = "1.3.14")
Quick start
Hermetic tests:
load("@rules_bun//bun:defs.bzl", "bun_test")
bun_test(
name = "math_tests",
srcs = glob(["*.test.ts"]),
data = ["bunfig.toml"],
)
bazel test //:math_tests runs bun test <each src> with NO_COLOR=1 + DO_NOT_TRACK=1 set.
Dev runner:
load("@rules_bun//bun:defs.bzl", "bun_run")
bun_run(
name = "build",
script = "scripts/build.ts",
)
bazel run //:build -- --watch invokes bun run scripts/build.ts --watch against your live workspace source (not the Bazel sandbox). Useful for the dev loop where you want HMR / on-demand module resolution / filesystem watch outside the runfiles tree.
Bun-native flow (no aspect_rules_js, no pnpm-lock)
Stage node_modules with Bun and consume it from bun_test /
bun_bundle. Your repo needs only package.json + bun.lock (generate
the lock with bun install):
# MODULE.bazel
bun_deps = use_extension("@rules_bun//bun:extensions.bzl", "bun_deps")
bun_deps.install(
name = "npm",
package_json = "//:package.json",
lock = "//:bun.lock",
)
use_repo(bun_deps, "npm")
# BUILD.bazel
load("@rules_bun//bun:defs.bzl", "bun_bundle", "bun_test")
# bun test resolves deps from the staged closure (no bun install).
bun_test(
name = "unit",
srcs = glob(["*.test.ts"]),
node_modules = "@npm//:node_modules",
)
# bun build runs directly via the toolchain Bun — no js_binary driver.
bun_bundle(
name = "bundle",
srcs = ["index.ts"], # entry + local modules
entry = "pkg/index.ts", # workspace-relative entry path
out = "app.mjs",
node_modules = "@npm//:node_modules",
external = ["pg-native"],
)
bun_install fetches the sha-pinned host-platform Bun and runs bun install --frozen-lockfile; determinism comes from bun.lock. The build
rules symlink the staged node_modules next to a real copy of the entry
so Bun’s resolver walks up into it. See
examples/install/ for the runnable end-to-end smoke.
Legacy aspect_rules_js flow
Bundle a JS/TS entry into one file:
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@rules_bun//bun:defs.bzl", "bun_bundle")
# The driver js_binary stages the bundle entry + its full linked
# node_modules closure into runfiles; bun_bundle runs it as a build
# action so that closure materializes, then shells out to the hermetic
# Bun toolchain. `data` must list the entry's :lib + every npm-link dep.
js_binary(
name = "bundle_driver",
entry_point = "@rules_bun//bun:bun-build-driver",
data = [":lib", ":node_modules/pg", ":node_modules/source-map-support"],
)
bun_bundle(
name = "bundle",
driver = ":bundle_driver",
entry = "packages/api/index.js",
out = "api.mjs",
format = "esm",
# Keep native addons / runtime requires out of the bundle.
external = ["pg-native", "@aws-sdk/client-ssm", "encoding", "source-map-support"],
)
bazel build //:bundle emits a single self-contained api.mjs. Bun
resolves the import graph from the staged node_modules natively (no
bun install). The external modules are left as runtime requires
rather than inlined — provide them alongside the bundle.
Compile a JS/TS entry into a standalone native executable:
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@rules_bun//bun:defs.bzl", "bun_compile")
js_binary(
name = "cli_driver",
entry_point = "@rules_bun//bun:bun-build-driver",
data = [":lib", ":node_modules/pg"],
)
bun_compile(
name = "cli",
driver = ":cli_driver",
entry = "apps/cli/index.js",
out = "cli",
# Omit `target` to compile for the host; set it to cross-compile,
# e.g. "bun-linux-x64-modern" for a linux OCI image.
target = "bun-linux-x64-modern",
external = ["pg-native"],
)
The output is a runnable executable (bazel run //:cli, or drop it into
an OCI image). --compile bundles the Bun runtime + your JS into one
file. Native .node addons are NOT embedded — keep them external and
ship the .node files at runtime next to the binary.
Cross-target note: on a macOS dev host,
target = ""compiles a Mach-O binary; CI on linux compiles an ELF. For a linux OCI image, settarget = "bun-linux-x64-modern"(or thearm64/muslvariant) explicitly so the binary matches the image regardless of which host built it. A future enhancement could derivetargetfrom the Bazel--platformsvia a transition; for now pass the string.
See examples/ for runnable bun_bundle + bun_compile smoke tests.
How it works
The module extension fetches a sha-pinned Bun binary for the host platform from oven-sh/bun GitHub releases. The release zip extracts to bun-<platform>/bun; the repository rule strips the outer dir so the binary lands at @bun//:bun.
bun_toolchain wraps that binary as a Bazel toolchain. bun_test resolves the toolchain via @rules_bun//bun:toolchain_type and runs bun test over each src in a runfiles-staged sandbox. bun_run is a macro that emits a sh_binary escaping the sandbox to run against BUILD_WORKSPACE_DIRECTORY directly.
The bun_deps extension’s bun_install repo rule fetches the same sha-pinned host-platform Bun, copies your package.json + bun.lock, and runs bun install --frozen-lockfile to materialize node_modules. Like aspect’s npm extension (and http_archive), a repo rule is allowed network I/O — --frozen-lockfile makes the result a pure function of the committed bun.lock, so the only fetch is what the lock pins. Lifecycle scripts are skipped (--ignore-scripts) unless you --trust a package via trusted_dependencies.
Hermeticity + determinism
| Layer | Pinned by |
|---|---|
| Bun binary | sha256 in bun/private/known_versions.bzl per (version, platform) |
node_modules | bun.lock (consumed under bun install --frozen-lockfile; scripts off by default) |
| Test env | bun_test sets NO_COLOR=1, DO_NOT_TRACK=1, BUN_INSTALL_NO_TRACK=1 |
bun_run env | same, with NO_COLOR overridable for callers that want colored output |
bun_run is intentionally non-hermetic — Bun’s dev mode (HMR, watch, on-demand module resolution) needs filesystem access outside the runfiles tree. Counterpart to bun_test’s hermetic execution.
Compatibility
- Bazel: 7.4+, bzlmod required.
- Bun: 1.3.14 pinned by default. Bump via
known_versions.bzl. - Platforms:
darwin-aarch64,darwin-x64,linux-aarch64,linux-x64. Baseline + musl + Windows variants doable — add an entry to the table when needed.
Contributing
Reference docs (docs/{defs,extensions,toolchains}.md) are stardoc-generated. After editing rule docstrings:
bazel run //docs:update
CI gates this via bazel test //docs/....
License
MIT.
Usage#
Real usage, taken from the module’s examples/.
examples/BUILD.bazel
load("@examples_npm//:defs.bzl", "npm_link_all_packages")
package(default_visibility = ["//visibility:public"])
# Links the example pnpm workspace's node_modules at //examples, so Bun's
# resolver finds it by walking up from examples/bundle/ and examples/compile/.
npm_link_all_packages(name = "node_modules")
exports_files(["pnpm-lock.yaml"])
examples/bundle/BUILD.bazel
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@rules_bun//bun:defs.bzl", "bun_bundle", "bun_test")
package(default_visibility = ["//visibility:public"])
# The driver js_binary stages the bundle entry (the .ts sources, transpiled by
# Bun directly) next to the linked node_modules closure in its runfiles, so when
# `bun_bundle` runs it the whole import graph resolves from the staged tree.
js_binary(
name = "bundle_driver",
data = [
"greet.ts",
"index.ts",
"//examples:node_modules/is-number",
"//examples:node_modules/source-map-support",
],
entry_point = "@rules_bun//bun:bun-build-driver",
)
# Bundle examples/bundle/index.ts into one self-contained ESM file. is-number
# and ./greet are inlined; source-map-support stays external.
bun_bundle(
name = "bundle",
out = "app.mjs",
driver = ":bundle_driver",
entry = "examples/bundle/index.ts",
external = ["source-map-support"],
format = "esm",
target = "node",
)
# Smoke test: the bundle exists, runs, inlines the dep + local module, and keeps
# the external un-inlined. Run from the `_main` runfiles root with the bundle
# staged as data.
bun_test(
name = "bundle_test",
srcs = ["bundle.test.ts"],
data = [":bundle"],
)
examples/compile/BUILD.bazel
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@rules_bun//bun:defs.bzl", "bun_compile")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
package(default_visibility = ["//visibility:public"])
# Driver js_binary: stages the compile entry + its npm dep closure.
js_binary(
name = "compile_driver",
data = [
"main.ts",
"//examples:node_modules/is-number",
],
entry_point = "@rules_bun//bun:bun-build-driver",
)
# Compile examples/compile/main.ts into a standalone native executable for the
# HOST platform (no `target`), so CI builds for its own OS/arch without cross
# toolchains. The output is itself runnable: `bazel run //examples/compile:app`.
bun_compile(
name = "app",
out = "app_host",
driver = ":compile_driver",
entry = "examples/compile/main.ts",
)
# Smoke test: the produced file is executable and runs.
sh_test(
name = "app_test",
srcs = ["run_test.sh"],
args = ["$(rootpath :app)"],
data = [":app"],
)
examples/install/BUILD.bazel
load("@rules_bun//bun:defs.bzl", "bun_bundle", "bun_test")
package(default_visibility = ["//visibility:public"])
# Pure-Bun flow: NO aspect_rules_js, NO pnpm-lock. The node_modules closure is
# staged by `bun_deps.install` (see //MODULE.bazel) from this directory's
# package.json + bun.lock via `bun install --frozen-lockfile`, and consumed
# below as `@install_npm//:node_modules`.
# Bundle examples/install/index.ts into one self-contained ESM file. `is-number`
# (from the staged node_modules) and `./greet` are inlined — Bun runs directly
# via the toolchain (no js_binary driver). This is the `node_modules`/native
# `bun_bundle` path.
bun_bundle(
name = "bundle",
srcs = [
"greet.ts",
"index.ts",
],
out = "app.mjs",
entry = "examples/install/index.ts",
format = "esm",
node_modules = "@install_npm//:node_modules",
target = "node",
)
# Proof that `bun test` resolves a dep from the `bun_install` node_modules tree.
bun_test(
name = "resolve_test",
srcs = ["resolve.test.ts"],
node_modules = "@install_npm//:node_modules",
)
# Smoke test that the produced bundle exists, runs, and inlined the dep.
bun_test(
name = "bundle_test",
srcs = ["bundle.test.ts"],
data = [":bundle"],
)
exports_files([
"package.json",
"bun.lock",
])Rules & providers#
Generated with Stardoc from the module's .bzl sources.
from docs/defs.md
User-facing rules for rules_bun.
Four pieces:
-
bun_test— runsbun testas a hermetic Bazel test action with explicit srcs + deps. Returns aBunTestInfoprovider wrapping the test result file (for downstream consumers; the main consumer is the test framework, which only cares about exit codes). -
bun_run— sh_binary macro:bazel run //path:NAMEinvokesbun run <script>against the live workspace source. Intentionally non-hermetic (escapes the runfiles sandbox) for the dev loop. Counterpart tobun_test’s hermetic execution. -
bun_bundle— bundle a JS/TS entry point into one self-contained file withbun build. ReturnsBunBundleInfo. -
bun_compile— compile a JS/TS entry point into a standalone native executable withbun build --compile(Bun runtime + bundled JS). ReturnsBunBinaryInfoand isbazel run-nable.
All resolve the Bun binary via @rules_bun//bun:toolchain_type (set
up by register_toolchains("@bun//:bun_toolchain_def") in your
MODULE.bazel).
bun_bundle / bun_compile have two ways to provision node_modules:
-
Bun-native (recommended; no aspect_rules_js, no pnpm-lock): pass a
node_moduleslabel (a@<name>//:node_modulesfrom abun_deps.installtag — seeextensions.bzl) plussrcs(the entry- local modules).
bun buildruns directly via the toolchain Bun; a small shell driver stages the entry into a real tree and symlinks the closure so Bun resolves the import graph natively.
- local modules).
-
Legacy aspect_rules_js: pass a
driverjs_binary whose entry point is@rules_bun//bun:bun-build-driverand whosedatastages the build entry plus its full linked node_modules closure; aspect materializes that closure into the action runfiles.
driver and node_modules are mutually exclusive — set exactly one.
bun_test likewise takes an optional node_modules for dep resolution.
bun_bundle
load("@rules_bun//bun:defs.bzl", "bun_bundle")
bun_bundle(name, srcs, out, driver, entry, external, format, node_modules, target)
Bundle a JS/TS entry into one file via the hermetic Bun toolchain. Either Bun-native (node_modules from bun_deps.install, no aspect_rules_js) or the legacy aspect driver js_binary path.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| srcs | Bun-native path. The entry file + any local modules it imports, declared as action inputs. Ignored on the legacy driver path (that stages sources via the js_binary’s data). | List of labels | optional | [] |
| out | The single bundled output file (conventionally *.mjs). | Label | required | |
| driver | LEGACY aspect_rules_js path. A js_binary whose entry point is @rules_bun//bun:bun-build-driver and whose data stages the bundle entry + its full linked node_modules closure. Mutually exclusive with node_modules; set exactly one. | Label | optional | None |
| entry | Path of the entry point relative to the workspace root (e.g. packages/aion-cli/index.js). On the native path this is the execroot-relative path; on the legacy path it is relative to the driver’s _main runfiles root (same string in practice). | String | required | |
| external | Module names to exclude from the bundle (passed as --external <name>, repeatable). Use for native addons and runtime requires that must stay external, e.g. pg-native, @aws-sdk/*, encoding, source-map-support. | List of strings | optional | [] |
| format | Bun --format. Defaults to esm so import.meta in deps stays valid under Node. | String | optional | "esm" |
| node_modules | Bun-native path. A node_modules closure (typically @<name>//:node_modules from a bun_deps.install tag). When set, bun build runs directly via the toolchain Bun (no js_binary driver, no aspect_rules_js): the closure is symlinked to the execroot root so Bun resolves the import graph by walking up from entry. Mutually exclusive with driver. Pair with srcs (the entry + local modules). | Label | optional | None |
| target | Bun --target: the intended execution environment for the bundle. Defaults to node. | String | optional | "node" |
bun_compile
load("@rules_bun//bun:defs.bzl", "bun_compile")
bun_compile(name, srcs, out, driver, entry, external, node_modules, target)
Compile a JS/TS entry into a standalone native executable (Bun runtime + bundled JS) via bun build --compile. Either Bun-native (node_modules) or the legacy aspect driver path.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| srcs | Bun-native path. The entry file + any local modules it imports, declared as action inputs. Ignored on the legacy driver path. | List of labels | optional | [] |
| out | The standalone executable output. On --target bun-windows-* give it a .exe suffix. | Label | required | |
| driver | LEGACY aspect_rules_js path. A js_binary whose entry point is @rules_bun//bun:bun-build-driver and whose data stages the build entry + its full linked node_modules closure. Mutually exclusive with node_modules; set exactly one. | Label | optional | None |
| entry | Path of the entry point relative to the workspace root (e.g. apps/studio-cli/index.js). | String | required | |
| external | Module names to keep external (--external <name>, repeatable). NOTE: native .node addons are NOT embedded by --compile — list them here and provide the .node files at runtime alongside the produced binary. | List of strings | optional | [] |
| node_modules | Bun-native path. A node_modules closure (typically @<name>//:node_modules from a bun_deps.install tag). When set, bun build --compile runs directly via the toolchain Bun (no js_binary driver, no aspect_rules_js). Mutually exclusive with driver. Pair with srcs. | Label | optional | None |
| target | Bun compile target triple. Empty (the default) compiles for the host platform. Cross-compile values: bun-linux-x64, bun-linux-x64-modern, bun-linux-x64-baseline, bun-linux-arm64, bun-darwin-x64, bun-darwin-arm64, bun-windows-x64, and the *-musl libc variants (e.g. bun-linux-x64-musl). A future enhancement could derive this from the Bazel --platforms via a transition; for v1 pass the string. | String | optional | "" |
bun_test
load("@rules_bun//bun:defs.bzl", "bun_test")
bun_test(name, srcs, data, node_modules)
Run bun test over the listed source files as a Bazel test target.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| srcs | Test files (typically *.test.ts, *.test.js). Each is passed to bun test explicitly so Bazel tracks them as inputs. | List of labels | required | |
| data | Additional runtime inputs (fixtures, bunfig.toml, etc.). | List of labels | optional | [] |
| node_modules | Optional node_modules closure (typically @<name>//:node_modules from a bun_deps.install tag). Staged at the workspace runfiles root as node_modules/ so bun test resolves dependency imports without bun install. The Bun-native replacement for aspect_rules_js’s npm_link_all_packages. | Label | optional | None |
BunBinaryInfo
load("@rules_bun//bun:defs.bzl", "BunBinaryInfo")
BunBinaryInfo(binary, target)
A standalone native executable produced by bun build --compile.
FIELDS
| Name | Description |
|---|---|
| binary | File: the standalone executable. |
| target | string: the Bun compile target triple (empty = host). |
BunBundleInfo
load("@rules_bun//bun:defs.bzl", "BunBundleInfo")
BunBundleInfo(bundle, format)
A single-file bundle produced by bun build.
FIELDS
| Name | Description |
|---|---|
| bundle | File: the bundled output. |
| format | string: the Bun output format (esm/cjs/iife). |
BunTestInfo
load("@rules_bun//bun:defs.bzl", "BunTestInfo")
BunTestInfo(result)
Result metadata for a bun test run.
FIELDS
| Name | Description |
|---|---|
| result | File: the captured test output (stdout + stderr concatenated). |
bun_run
load("@rules_bun//bun:defs.bzl", "bun_run")
bun_run(name, script, args, **kwargs)
Invoke bun run <script> against the live workspace source.
Escapes the runfiles sandbox via BUILD_WORKSPACE_DIRECTORY so Bun
resolves modules + reads files from the user’s actual source tree.
Intentionally NOT hermetic — that’s bun_test’s job.
PARAMETERS
from docs/extensions.md
Module extensions for rules_bun.
Two extensions:
-
bun— auto-fetches a prebuilt Bun binary for the host platform. Versions are sha256-pinned inprivate/known_versions.bzl. Consumers can override via thetoolchaintag class.bun = use_extension("@rules_bun//bun:extensions.bzl", "bun") use_repo(bun, "bun") register_toolchains("@bun//:bun_toolchain_def")Pin a specific version:
bun.toolchain(version = "1.3.14") -
bun_deps— Bun-nativenode_modulesstaging. Eachinstalltag produces a repo@<name>whose:node_modulesfilegroup is abun install --frozen-lockfile-ed tree. The pure-Bun replacement for aspect_rules_js’snpm_translate_lock+npm_link_all_packages(no pnpm-lock, no aspect_rules_js):bun_deps = use_extension("@rules_bun//bun:extensions.bzl", "bun_deps") bun_deps.install( name = "npm", package_json = "//:package.json", lock = "//:bun.lock", ) use_repo(bun_deps, "npm")then
bun_test(node_modules = "@npm//:node_modules", ...)andbun_bundle(node_modules = "@npm//:node_modules", ...).
The actual release fetching is delegated to
@rules_github//github:repositories.bzl%github_binary_repository
so that the URL-shape + sha-pinning logic stays consistent across
all our rules_* repos.
bun
bun = use_extension("@rules_bun//bun:extensions.bzl", "bun")
bun.toolchain(version)
Sets up @bun as a Bazel-fetched prebuilt Bun binary.
TAG CLASSES
toolchain
Attributes
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| version | Override Bun version. Defaults to the value in known_versions.bzl. | String | optional | "" |
bun_deps
bun_deps = use_extension("@rules_bun//bun:extensions.bzl", "bun_deps")
bun_deps.install(name, bun_version, ignore_scripts, install_flags, lock, package_json,
trusted_dependencies)
Bun-native node_modules staging — @<name>//:node_modules from a bun install --frozen-lockfile. Replaces aspect_rules_js’s npm_translate_lock + npm_link_all_packages for pure-Bun repos.
TAG CLASSES
install
Stage a node_modules tree from a package.json + bun.lock.
Attributes
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | Name of the generated repo. Reference its node_modules as @<name>//:node_modules. | Name | required | |
| bun_version | Bun version to fetch for the install. Empty = the toolchain extension’s default. | String | optional | "" |
| ignore_scripts | Skip dependency lifecycle scripts (--ignore-scripts). Default True. | Boolean | optional | True |
| install_flags | Extra raw flags appended to bun install. | List of strings | optional | [] |
| lock | The bun.lock pinning the install (--frozen-lockfile). | Label | required | |
| package_json | The package.json to install from. | Label | required | |
| trusted_dependencies | Packages to --trust (run lifecycle scripts for) even when ignore_scripts is True. | List of strings | optional | [] |
from docs/toolchains.md
Toolchain rule for rules_bun.
bun_toolchain wraps a single Bun binary as a Bazel toolchain.
Consumers (the bun_test and bun_run rules) resolve Bun through
@rules_bun//bun:toolchain_type, so users can register custom Bun
binaries (locally-built fork, alternate version, baseline-CPU
variant) via register_toolchains(...) without modifying rule
attrs.
The module extension at @rules_bun//bun:extensions.bzl generates a
default toolchain (@bun//:bun_toolchain_def) wrapping the prebuilt
binary. Users register it from MODULE.bazel:
register_toolchains("@bun//:bun_toolchain_def")
bun_toolchain
load("@rules_bun//bun:toolchains.bzl", "bun_toolchain")
bun_toolchain(name, bun)
Declare a Bun binary as a Bazel toolchain.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| bun | Path to the Bun executable. | Label | required |
BunToolchainInfo
load("@rules_bun//bun:toolchains.bzl", "BunToolchainInfo")
BunToolchainInfo(bun)
The Bun binary, resolved via a toolchain.
FIELDS
| Name | Description |
|---|---|
| bun | File: the bun executable. |
Conformance#
No gate findings. 17 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
aspect_bazel_lib | 2.22.5 | 2.8.1 ×1 |
aspect_rules_js | 3.1.2 | 2.1.3 ×1 |
bazel_lib | 3.2.2 | 3.0.0 ×12 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
gawk | 5.3.2.bcr.3 | 5.3.2.bcr.1 ×12 |
jq.bzl | 0.4.0 | 0.1.0 ×12 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
package_metadata | 0.0.2 | 0.0.5 ×3 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_nodejs | 6.7.4 | 6.3.0 ×16.7.3 ×1 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
tar.bzl | 0.10.4 | 0.5.1 ×110.6.0 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
yq.bzl | 0.3.4 | 0.1.1 ×12 |
Dependencies#
Depends on
Used by (2 in the registry)
Versions#
6 published versions, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.4.1 latest | C4KdZFgzKbHzf91s… | tag archive ↗ |
0.4.0 | D0PyvOw9UVhiss2m… | tag archive ↗ |
0.3.0 | X0vFlHNWGLiDKYzU… | tag archive ↗ |
0.2.1 | GgO76UiD1AY1/TyV… | tag archive ↗ |
0.2.0 | 50pq4WmTCNFNK94K… | tag archive ↗ |
0.1.0 | IeRwaRUtshXq6NpX… | tag archive ↗ |
Changelog#
All notable changes to rules_bun. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.
0.4.0 — add bun_install (Bun-native node_modules; drop pnpm + aspect_rules_js)
- New
bun_depsmodule extension with aninstalltag — a Bun-native replacement for aspect_rules_js’snpm_translate_lock+npm_link_all_packages.bun_deps.install(name, package_json, lock)produces a repo@<name>whose:node_modulesfilegroup is an installednode_modulestree. The backing repo rule (bun_install) fetches a host-platform Bun (the same sha-pinned binary the toolchain extension uses, viaknown_versions.bzl), copies the consumer’spackage.json+bun.lockinto the repo root, and runsbun install --frozen-lockfile --no-progresswith a repo-pinnedBUN_INSTALL_CACHE_DIRand--ignore-scripts(opt back in per package viatrusted_dependencies).package.json+bun.lockare read as rule inputs so edits re-trigger the install; determinism comes from the lockfile (the only network I/O is the registry fetch the lock pins, exactly like aspect’s npm extension +http_archive). So a pure-Bun repo needs ONLYpackage.json+bun.lock— no pnpm-lock, no aspect_rules_js. bun_testgains an optionalnode_modulesattr (a@<name>//:node_moduleslabel). When set, the closure is staged sobun testresolves dependency imports with nobun install. Because Bazel stages the test files as symlinks into the read-only source tree and Bun’s resolver follows an entry’s realpath, the runner copies the test files into a real staging dir and symlinksnode_modulesat its root so resolution stays inside the staged tree.bun_bundle+bun_compilegain a Bun-native path: passnode_modules(+srcsfor the entry + local modules) instead of adriverjs_binary. On this pathbun buildruns directly via the toolchain Bun (no js_binary driver, no aspect_rules_js) — a small shell driver stages the entry into a real tree and symlinks the closure so Bun resolves the import graph.driveris now optional and mutually exclusive withnode_modules; the legacy aspect path is unchanged for back-compat.examples/install/: a pure-Bun end-to-end smoke (one npm depis-number, a local module) withpackage.json+bun.lock, abun_deps.install, and abun_bundle+ twobun_tests consuming@install_npm//:node_modules— NO aspect_rules_js, NO pnpm-lock. Proves the flow:bazel build //examples/install:bundle+bazel test //examples/install:resolve_test //examples/install:bundle_test.
0.3.0 — add bun_bundle + bun_compile
- New
bun_bundlerule: bundle a JS/TS entry point into one self-contained file viabun build. Takes adriverjs_binary (entry point@rules_bun//bun:bun-build-driver) whosedatastages the build entry plus its full linkednode_modulesclosure; aspect_rules_js materializes that closure into the action’s runfiles so Bun resolves the import graph natively (nobun install). Attrs:format(esm|cjs|iife, defaultesm),target(node|browser|bun, defaultnode), andexternal(a repeatable--external <name>list for native addons / runtime requires likepg-native,@aws-sdk/*,encoding,source-map-support). ReturnsBunBundleInfo. - New
bun_compilerule: compile a JS/TS entry point into a standalone native executable (Bun runtime + bundled JS) viabun build --compile. Shares the driver withbun_bundle(via a--compileflag). The output is itself runnable, sobazel run //pkg:targetworks. Attrs:target(a Bun compile target triple such asbun-linux-x64-modern/bun-darwin-arm64; empty = host platform) andexternal. ReturnsBunBinaryInfo. Native.nodeaddons are not embedded by--compile— keep themexternaland ship them at runtime alongside the binary. bun-build-driver.mjs: a single shared driver for both rules, wrapped in a publicjs_libraryat@rules_bun//bun:bun-build-driver. It re-anchors--bun/--outon$JS_BINARY__EXECROOT, chdirs into the_mainrunfiles root, and invokes the hermetic Bun toolchain.aspect_rules_jsis now a (non-dev)bazel_dep— consumers already bring it to declare the driver js_binary.examples/: abun_bundlesmoke test (npm dep + local module + oneexternal, asserts the bundle runs and the external is not inlined) and a host-targetbun_compilesmoke test (asserts the produced file is executable and runs). CI now runsbazel test //....
0.2.1 — fix bun_test toolchain runfiles path under bzlmod
bun_test’s generated runner failed to locate the hermetic Bun binary under bzlmod, exiting 127 (exec: : not found). Under bzlmod the toolchain Bun is an external repo file whoseshort_pathis../rules_bun++bun+bun/bun; the runner builtBUN_BINas${RUNFILES_DIR}/<short_path>, so the leading../escaped the runfiles tree. Prefix with_main/(${RUNFILES_DIR}/_main/<short_path>) so the embedded../resolves back out to the sibling external repo.- Make the
findfallback follow symlinks (find -L) so it can reach the symlinked Bun binary in the runfiles tree. - Resolve the
srcstest-filter paths from the same${RUNFILES_DIR}base asBUN_BIN(was$0.runfiles). Underbazel testBazel setsRUNFILES_DIRand$0is the already-staged in-runfiles script, so$0.runfilesdouble-appended.runfiles/_main, yielding a test filter with no matches.
0.2.0 — delegate release fetching to rules_github
- Replace the in-tree GitHub release download logic with a dependency
on
rules_github’sgithub_binary_repositoryso Bun binaries are fetched via the shared substrate alongside other fastverk rules.
0.1.0 — initial release
- First cut of Bazel rules for Bun: a
bunmodule extension that auto-creates@bunwith the host-platform binary, abun_toolchainresolved via@rules_bun//bun:toolchain_type, plusbun_test(hermetic) andbun_run(sandbox-escaping) rules.