Repository navigation
Conversation
Add support for offline package installation mode that skips all network requests and uses only cached packages. This is useful for CI/CD pipelines with restricted network access, air-gapped environments, and hermetic builds (Nix, Bazel). Usage: bun install --offline BUN_CONFIG_OFFLINE=1 bun install Fixes oven-sh#7956
…tion - Handle error.OfflineMode in PackageInstaller and isolated_install error switches - Add OfflineMode to PopulateManifestCache error type - Fix flake.nix for Darwin builds (use darwin.ICU for libicucore compatibility) - Fix offline tests to use mock registry with explicit --registry flag - Add comprehensive documentation for --offline flag including: - Usage examples and use cases - Cache requirements - Environment variable (BUN_CONFIG_OFFLINE) and bunfig.toml options
- Add step-by-step workflow for preparing cache on connected machine - Add instructions for transferring cache to air-gapped systems - Add Docker workflow examples for containerized air-gapped builds - Add CI/CD caching example with GitHub Actions - Add troubleshooting section for common offline install issues
- Add --prefer-offline CLI flag - Add BUN_CONFIG_PREFER_OFFLINE environment variable - Add preferOffline bunfig.toml option - Skip network requests when cached manifest available with prefer-offline - Add tests for --prefer-offline (4 test cases) - Update documentation
51087ba to
446bbac
Compare
WalkthroughAdds strict (--offline) and prefer-offline (--prefer-offline) install controls (CLI, bunfig, env), wires them through package-manager options and resolution (cache-first for prefer-offline), introduces OfflineMode error and offline guards, adds docs and tests, and adjusts macOS build/linker/toolchain behavior. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
test/cli/install/bun-install-registry.test.ts (2)
54-65: Scope the global registry override to avoid masking registry-change tests.Forcing
BUN_CONFIG_REGISTRYinbeforeEachmakes every install use the mock registry, which can invalidate tests that intentionally switch registries or rely on the default registry. Consider clearing the env var in those tests (or using a test-local env object) so registry-change behavior is actually exercised.
365-512: Also set USERPROFILE on Windows to fully isolate .npmrc.
HOMEis ignored on Windows for npm-style config resolution, so a global%USERPROFILE%\.npmrccan still leak into these tests. SetUSERPROFILE(optionallyAPPDATA) inwhoamiEnv()for consistent isolation.♻️ Proposed fix
function whoamiEnv() { const cleanEnv = { ...env }; delete cleanEnv.BUN_CONFIG_REGISTRY; cleanEnv.HOME = packageDir; + if (isWindows) { + cleanEnv.USERPROFILE = packageDir; + } return cleanEnv; }
🤖 Fix all issues with AI agents
In `@cmake/targets/BuildBun.cmake`:
- Around line 1066-1071: The current removal of -Wl,-ld_new from the Apple
target_link_options for target ${bun} should be gated so non‑Nix macOS builds
keep the previous linker behavior; add a CMake option (e.g.,
OPTION_USE_NIX_LINKER or BUILD_FOR_NIX) or detect a Nix build environment and
wrap the -Wl,-ld_new flag in a conditional so that target_link_options(${bun}
PUBLIC -Wl,-ld_new) is only applied when that option/flag is true (leave the
existing -Wl,-no_compact_unwind, -Wl,-stack_size,... lines unchanged for all
Apple builds).
In `@docs/pm/cli/install.mdx`:
- Around line 264-269: Update the docs in the [install] block to clarify that
the two settings are mutually exclusive by adding a short note referencing the
keys offline and preferOffline under the [install] section; state that only one
should be used and that if both are present, offline takes precedence (or
document the actual precedence behavior if different), so readers know to not
set both simultaneously.
In `@flake.nix`:
- Around line 151-165: The current snippet overwrites CMAKE_CXX_FLAGS; change it
to append the include flags instead so existing flags are preserved (use the
shell parameter expansion pattern already used for CXXFLAGS/CFLAGS, e.g.
preserve ${CMAKE_CXX_FLAGS:+$CMAKE_CXX_FLAGS } before adding
-I${pkgs.icu.dev}/include -I${pkgs.sqlite.dev}/include) and add a similar append
for CMAKE_C_FLAGS to mirror behavior for C-only builds; update the
CMAKE_CXX_FLAGS and CMAKE_C_FLAGS assignments accordingly and keep the existing
CXXFLAGS/CFLAGS handling consistent.
In `@src/install/PackageManager/CommandLineArguments.zig`:
- Around line 808-809: Add an explicit validation for mutually exclusive flags
cli.offline and cli.prefer_offline: after parsing flags (where cli.offline and
cli.prefer_offline are assigned) check if both are true and either return an
error or print a clear message and exit with non‑zero status; update the CLI
parsing/validation function that sets these fields to perform this guard so
ambiguous combinations are rejected (or, alternatively, document and implement
an explicit precedence rule if you prefer precedence instead of rejection).
In `@src/install/PackageManager/PackageManagerEnqueue.zig`:
- Around line 755-760: The current prefer_offline branch in the
retry_from_manifests_ptr loop short-circuits the network whenever any cached
manifest exists, which can mask cache misses; change the logic so that when
this.options.enable.prefer_offline is true (or manifest_cache_control succeeds
and not expired) you first attempt to resolve the requested range/dist-tag
against the cached manifest and only execute `_ =
this.network_dedupe_map.remove(task_id); continue :retry_from_manifests_ptr;` if
that in-cache resolution succeeds; if the cached manifest cannot satisfy the
request (no matching version/dist-tag) do not skip the network—leave the entry
in network_dedupe_map as needed and allow the network fetch path to run so
resolution can fall back to the registry. Ensure the check still respects
`expired` and `this.options.enable.manifest_cache_control`, and update any
control-flow around the retry_from_manifests_ptr label accordingly.
In `@test/cli/install/bun-install-offline.test.ts`:
- Around line 132-135: Replace the shell removal via Bun.spawn with Bun's
filesystem API: remove the node_modules directory using Bun.rm (or Bun.remove)
on the path constructed from ctx.package_dir + "/node_modules" with options {
recursive: true, force: true } instead of calling Bun.spawn; update any imports
if you choose to build the path with path.join and ensure the code targets the
same variable names (ctx.package_dir and the node_modules subpath).
- Around line 31-34: Remove the explicit timeout: in the beforeAll block delete
the setDefaultTimeout(1000 * 60 * 5) call and leave only dummyBeforeAll() so
tests rely on Bun's built-in timeouts; also update the import on the top (remove
or stop importing setDefaultTimeout) so only dummyBeforeAll is imported/used.
In `@test/cli/install/bun-install-prefer-offline.test.ts`:
- Around line 93-96: Replace the shell invocation that deletes node_modules via
Bun.spawn({ cmd: ["rm","-rf","node_modules"], ... }) with a call to an fs.rm
promise; e.g., import Node's fs.promises (or use Bun.fs.promises) at the top and
call await fs.rm(ctx.package_dir + "/node_modules", { recursive: true, force:
true }) where the Bun.spawn removal appears (references: the Bun.spawn call in
this test file). Apply the same replacement for the other occurrences noted
(around lines 200-203 and 266-269).
- Around line 30-32: Remove the explicit timeout call by deleting the
setDefaultTimeout(1000 * 60 * 5) invocation and keep the dummyBeforeAll() call
as-is; also update the module import at the top of the file to stop importing
setDefaultTimeout (remove it from the import list) so there are no unused
imports.
In `@test/harness.ts`:
- Around line 1284-1289: The warning-detection logic is inverted: replace the
current compound check with a direct line-level search so hasRealWarning becomes
true if any line contains "warn:" but not "debug warn:". Concretely, use
err.split("\n").some(line => line.includes("warn:") && !line.includes("debug
warn:")) to set hasRealWarning (keeping the existing options?.allowWarnings
guard and the expect(hasRealWarning).toBe(false) assertion semantics).
| if(APPLE) | ||
| target_link_options(${bun} PUBLIC | ||
| -Wl,-ld_new | ||
| # -Wl,-ld_new # Disabled for Nix compatibility - uses default linker | ||
| -Wl,-no_compact_unwind | ||
| -Wl,-stack_size,0x1200000 | ||
| -fno-keep-static-consts |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Gate -Wl,-ld_new removal to avoid changing default macOS linker behavior unintentionally.
If this is only required for Nix builds, consider an option/conditional so non‑Nix macOS builds retain the prior linker.
🔧 Possible gating option
+option(BUN_USE_LD_NEW "Use ld_new linker on macOS" ON)
if(APPLE)
target_link_options(${bun} PUBLIC
- # -Wl,-ld_new # Disabled for Nix compatibility - uses default linker
+ $<$<BOOL:${BUN_USE_LD_NEW}>:-Wl,-ld_new>
-Wl,-no_compact_unwind
-Wl,-stack_size,0x1200000
-fno-keep-static-consts
)
endif()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if(APPLE) | |
| target_link_options(${bun} PUBLIC | |
| -Wl,-ld_new | |
| # -Wl,-ld_new # Disabled for Nix compatibility - uses default linker | |
| -Wl,-no_compact_unwind | |
| -Wl,-stack_size,0x1200000 | |
| -fno-keep-static-consts | |
| option(BUN_USE_LD_NEW "Use ld_new linker on macOS" ON) | |
| if(APPLE) | |
| target_link_options(${bun} PUBLIC | |
| $<$<BOOL:${BUN_USE_LD_NEW}>:-Wl,-ld_new> | |
| -Wl,-no_compact_unwind | |
| -Wl,-stack_size,0x1200000 | |
| -fno-keep-static-consts |
🤖 Prompt for AI Agents
In `@cmake/targets/BuildBun.cmake` around lines 1066 - 1071, The current removal
of -Wl,-ld_new from the Apple target_link_options for target ${bun} should be
gated so non‑Nix macOS builds keep the previous linker behavior; add a CMake
option (e.g., OPTION_USE_NIX_LINKER or BUILD_FOR_NIX) or detect a Nix build
environment and wrap the -Wl,-ld_new flag in a conditional so that
target_link_options(${bun} PUBLIC -Wl,-ld_new) is only applied when that
option/flag is true (leave the existing -Wl,-no_compact_unwind,
-Wl,-stack_size,... lines unchanged for all Apple builds).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@test/cli/install/bun-install-offline.test.ts`:
- Around line 9-18: createCleanEnv currently doesn't remove BUN_CONFIG_OFFLINE
and BUN_CONFIG_PREFER_OFFLINE, which can make the cache-priming install run in
offline mode; update the createCleanEnv function to delete
cleanEnv.BUN_CONFIG_OFFLINE and cleanEnv.BUN_CONFIG_PREFER_OFFLINE (alongside
the existing deletions like npm_config_registry and BUN_CONFIG_REGISTRY) so the
test environment is not influenced by a developer's offline prefs and remains
deterministic.
- Around line 211-238: The network counter isn't bound to the test registry;
update the spawn invocation so bun install uses the test registry URL by adding
the registry flag (use ctx.registry_url) to the command array passed to spawn
(the same call that currently builds cmd: [bunExe(), "install", "--offline"]).
Ensure the install process launched by spawn (in this test that uses
withContext, setContextHandler, bunExe, and ctx.package_dir) includes
"--registry" and ctx.registry_url so any outgoing requests hit the dummy
registry and increment networkRequestCount.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@test/cli/install/bun-install-offline.test.ts`:
- Around line 40-51: withContext uses a non-null assertion on opts.linker which
can pass undefined to createTestContext when opts is an empty object; change the
call to createTestContext to only pass { linker } when opts.linker is actually
defined (e.g. extract const linker = opts?.linker and call
createTestContext(linker ? { linker } : undefined) or use opts?.linker in a
conditional), keeping the function name withContext and createTestContext to
locate the change.
In `@test/cli/install/bun-install-prefer-offline.test.ts`:
- Around line 38-50: The withContext helper uses a non-null assertion on
opts.linker which can fail when opts is provided but linker is undefined; update
withContext to pass a safe value to createTestContext (e.g., use opts?.linker or
provide defaultOpts.linker) instead of opts.linker!, and consider moving
withContext to a shared test helper module (reusing the same helper across
bun-install-offline.test.ts and bun-install-prefer-offline.test.ts) to eliminate
duplication; locate the function named withContext and the call to
createTestContext to apply the change.
- Gate -Wl,-ld_new linker flag with BUN_USE_LD_NEW option for Nix builds - Clarify mutual exclusivity of offline modes in docs - Append CMAKE_CXX_FLAGS instead of overwriting in flake.nix - Add validation to reject --offline + --prefer-offline together - Fix prefer-offline to only skip network when cached manifest can satisfy request - Remove explicit test timeouts (use Bun's built-in timeouts) - Replace shell rm -rf commands with fs.rm API in tests - Fix inverted warning detection logic in test harness
- Configure bunfig.toml with mock registry and fresh cache directory - Update assertions to match actual error messages (regex pattern) - Ensure tests run in isolated environment without global cache interference
bbdd09a to
17b050e
Compare
- Clear BUN_CONFIG_OFFLINE and BUN_CONFIG_PREFER_OFFLINE env vars in test setup to ensure deterministic behavior - Bind network counter test to mock registry via bunfig.toml to properly verify offline mode prevents network requests
bd87f99 to
20a6359
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/install/PackageManager/PackageManagerEnqueue.zig`:
- Around line 766-770: The switch that sets can_resolve has an unexplained else
=> true branch; update the switch in the can_resolve calculation (the block
referencing version.tag, manifest.findBestMatchingVersion, and
manifest.findByDistTag/this.lockfile.str) to include a brief inline comment on
the else branch explaining that folder and other local/non-network tags are
allowed to proceed with cache because they don't require remote resolution
(e.g., .folder is resolved locally), so returning true is intentional for those
cases.
In `@test/cli/install/bun-install-offline.test.ts`:
- Around line 61-70: The TOML string uses cacheDir directly which on Windows
produces backslashes that break double-quoted TOML strings; update the
bunfigContent construction to use the toml-escaped path helper
(tomlPath(cacheDir)) instead of cacheDir wherever you build bunfigContent (the
cache = "..." line) so the cache path is properly escaped; apply the same change
to all other bunfigContent blocks in this test file (the other occurrences that
set cache = "...") to ensure Windows paths are escaped consistently.
In `@test/cli/install/bun-install-prefer-offline.test.ts`:
- Around line 58-66: The TOML string uses cacheDir (built with path.join) which
produces backslashes on Windows and can break bunfig.toml; wrap the path with
tomlPath(cacheDir) wherever cacheDir is interpolated into bunfigContent (e.g.,
the bunfigContent variable near the top and the other bunfigContent blocks in
this test file) so the path is properly escaped for TOML; update every
occurrence of "${cacheDir}" in bunfigContent to "${tomlPath(cacheDir)}" (and
import/ensure tomlPath is available) including the other bunfigContent blocks in
this file.
- Add comment explaining else => true case for non-network version types - Use tomlPath helper to escape Windows backslashes in bunfig.toml paths
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@cmake/targets/BuildBun.cmake`:
- Around line 1094-1100: The option BUN_USE_LD_NEW in BuildBun.cmake is declared
with help text saying "disable for Nix builds" but defaults to ON and Nix files
(flake.nix/shell.nix) do not override it; either wire the CMake option into the
Nix build or change the help text: update flake.nix and/or shell.nix to pass
-DBUN_USE_LD_NEW=OFF when invoking CMake to actually disable ld_new for Nix, or
edit the option(...) call in BuildBun.cmake to remove the "disable for Nix
builds" clause so the comment reflects current behavior (references: symbol
BUN_USE_LD_NEW and file BuildBun.cmake).
| option(BUN_USE_LD_NEW "Use ld_new linker on macOS (disable for Nix builds)" ON) | ||
| if(APPLE) | ||
| target_link_options(${bun} PUBLIC | ||
| -Wl,-ld_new | ||
| $<$<BOOL:${BUN_USE_LD_NEW}>:-Wl,-ld_new> | ||
| -Wl,-no_compact_unwind | ||
| -Wl,-stack_size,0x1200000 | ||
| -fno-keep-static-consts |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "BUN_USE_LD_NEW" -C 2Repository: oven-sh/bun
Length of output: 563
🏁 Script executed:
# Search for files that might configure or pass CMAKE options
find . -type f \( -name "CMakeLists.txt" -o -name "*.nix" -o -name "*.github*" -o -name "Makefile" \) | head -20Repository: oven-sh/bun
Length of output: 214
🏁 Script executed:
# Also search for patterns that pass CMAKE options
rg -n "BUN_USE_LD_NEW|add_subdirectory|target_link_options" --type cmake | grep -i "bun_use\|ld_new\|subdirectory" | head -30Repository: oven-sh/bun
Length of output: 247
🏁 Script executed:
# Check Nix build files for BUN_USE_LD_NEW or macOS-specific options
fd -e nix | head -20Repository: oven-sh/bun
Length of output: 74
🏁 Script executed:
# Check if CMakeLists.txt passes this option when including BuildBun.cmake
rg -n "BUN_USE_LD_NEW|BuildBun" CMakeLists.txt -C 3Repository: oven-sh/bun
Length of output: 134
🏁 Script executed:
# Check Nix files for BUN_USE_LD_NEW or CMAKE option passing
rg -n "BUN_USE_LD_NEW|cmake.*-D|build.*flags" flake.nix shell.nix -C 2Repository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Search for any cmake invocation that might set this option
rg -n "\-DBUN_USE_LD_NEW" --type cmakeRepository: oven-sh/bun
Length of output: 37
🏁 Script executed:
# Check how nix files configure cmake
cat flake.nix | head -100Repository: oven-sh/bun
Length of output: 2792
🏁 Script executed:
# Check shell.nix as well
cat shell.nix | head -50Repository: oven-sh/bun
Length of output: 1007
The option's help text contradicts the actual configuration: it says "disable for Nix builds" but Nix builds do not disable it.
The BUN_USE_LD_NEW option is defined and used correctly in BuildBun.cmake with a proper generator expression. However, it only appears in that file—neither flake.nix nor shell.nix pass -DBUN_USE_LD_NEW=OFF or any override. The option defaults to ON, meaning Nix builds will use the ld_new linker despite the help text suggesting otherwise.
Either wire the option into the Nix build by passing -DBUN_USE_LD_NEW=OFF, or remove the "disable for Nix builds" clause from the help text to reflect actual behavior.
🤖 Prompt for AI Agents
In `@cmake/targets/BuildBun.cmake` around lines 1094 - 1100, The option
BUN_USE_LD_NEW in BuildBun.cmake is declared with help text saying "disable for
Nix builds" but defaults to ON and Nix files (flake.nix/shell.nix) do not
override it; either wire the CMake option into the Nix build or change the help
text: update flake.nix and/or shell.nix to pass -DBUN_USE_LD_NEW=OFF when
invoking CMake to actually disable ld_new for Nix, or edit the option(...) call
in BuildBun.cmake to remove the "disable for Nix builds" clause so the comment
reflects current behavior (references: symbol BUN_USE_LD_NEW and file
BuildBun.cmake).
The Flatpak ecosystem doesn't support Bun very well, and Bun doesn't support pure offline installation yet (oven-sh/bun#26227) - Updated all test files to import from 'vitest' instead of 'bun:test'. - Replaced mock implementations using Bun's mock module with Vitest's vi.mock. - Adjusted test setup and teardown processes to align with Vitest's API. - Removed caching from dynamic imports in tests to ensure fresh module loading. - Added Vitest configuration file for test environment setup and coverage reporting. - Introduced Vite configuration for GUI development with React. - Updated TypeScript configuration to include type roots for better type resolution.
|
Closing as stale: this PR predates the Rust rewrite. Every If the underlying change is still wanted, it will need to be redone against the current Rust/C++ tree. Apologies for the churn, and thank you for the contribution. |
Summary
Add support for offline and cache-first package installation modes:
--offline: Skip all network requests, use only cached packages. Fails if packages aren't cached.--prefer-offline: Try cache first, fall back to network only when needed.Motivation
This addresses common use cases where network access is limited or undesirable:
Fixes #7956
Changes
Core Features
--offlinemodeerror.OfflineModehandling inPackageInstaller.zigandisolated_install.zig--prefer-offlinemodeConfiguration Options
--offline--prefer-offlineBUN_CONFIG_OFFLINE=1BUN_CONFIG_PREFER_OFFLINE=1offline = truepreferOffline = trueError Handling Improvements
OfflineModetoPopulateManifestCacheerror typePackageInstallerandisolated_installerror switchesBuild System
flake.nixfor Darwin builds (usedarwin.ICUfor libicucore compatibility)cmake/targets/BuildBun.cmakeadjustmentsDocumentation (
docs/pm/cli/install.mdx)Comprehensive documentation additions (~460 lines):
Test Plan
bun-install-offline.test.ts- Tests for--offlinemodebun-install-prefer-offline.test.ts- Tests for--prefer-offlinemode--registryflagtest/harness.tswith offline testing utilitiesFiles Changed