Skip to content

Add postinstall optimizer with native binlink support and script skipping - #24283

Merged
Jarred-Sumner merged 14 commits into
mainfrom
jarred/postinstall-optimize
Nov 4, 2025
Merged

Jarred-Sumner merged 14 commits into
mainfrom
jarred/postinstall-optimize

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Nov 1, 2025 •

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces a new postinstall optimization system that significantly reduces the need to run lifecycle scripts for certain packages by intelligently handling their requirements at install time.

Key Features

1. Native Binlink Optimization

When packages like esbuild ship platform-specific binaries as optional dependencies, we now:

  • Detect the native binlink pattern (enabled by default for esbuild)
  • Find the matching platform-specific dependency based on target CPU/OS
  • Link binaries directly from the platform-specific package (e.g., @esbuild/darwin-arm64)
  • Fall back gracefully if the platform-specific package isn't found

Result: No postinstall scripts needed for esbuild and similar packages.

2. Lifecycle Script Skipping

For packages like sharp that run heavy postinstall scripts:

  • Skip lifecycle scripts entirely (enabled by default for sharp)
  • Prevents downloading large binaries or compiling native code unnecessarily
  • Reduces install time and potential failures in restricted environments

Configuration

Both features can be configured via package.json:

{
  "nativeDependencies": ["esbuild", "my-custom-package"],
  "ignoreScripts": ["sharp", "another-package"]
}

Set to empty arrays to disable defaults:

{
  "nativeDependencies": [],
  "ignoreScripts": []
}

Environment variable overrides:

  • BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER=1 - disable native binlink
  • BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1 - disable script ignoring

Implementation Details

Core Components

  • postinstall_optimizer.zig: New file containing the optimizer logic

    • PostinstallOptimizer enum with native_binlink and ignore variants
    • List type to track optimization strategies per package hash
    • Defaults for esbuild (native binlink) and sharp (ignore)
  • Bin.Linker changes: Extended to support separate target paths

    • target_node_modules_path: Where to find the actual binary
    • target_package_name: Name of the package containing the binary
    • Fallback logic when native binlink optimization fails

Modified Components

  • PackageInstaller.zig: Checks optimizer before:

    • Enqueueing lifecycle scripts
    • Linking binaries (with platform-specific package resolution)
  • isolated_install/Installer.zig: Similar checks for isolated linker mode

    • maybeReplaceNodeModulesPath() resolves platform-specific packages
    • Retry logic without optimization on failure
  • Lockfile: Added postinstall_optimizer field to persist configuration

Changes Included

  • Updated esbuild from 0.21.5 to 0.25.11 (testing with latest)
  • VS Code launch config updates for debugging install with new flags
  • New feature flags in env_var.zig

Test Plan

  • Existing install tests pass
  • Test esbuild install without postinstall scripts running
  • Test sharp install with scripts skipped
  • Test custom package.json configuration
  • Test fallback when platform-specific package not found
  • Test feature flag overrides

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Native binlink optimization: installs platform-specific binaries when available, with a safe retry fallback and verbose logging option.
    • Per-package postinstall controls to optionally skip lifecycle scripts.
    • New feature flags to disable native binlink optimization and to disable lifecycle-script ignoring.
  • Tests

    • End-to-end tests and test packages added to validate native binlink behavior across install scenarios and linker modes.
  • Documentation

    • Bench README and sample app migrated to a Next.js-based setup.

…ping

This introduces a new postinstall optimization system that allows packages to:
1. Skip running lifecycle scripts for specific dependencies
2. Use platform-specific optional dependencies for binaries instead of postinstall scripts

## Native Binlink Optimization

When a package like esbuild has platform-specific optional dependencies (@esbuild/darwin-arm64, @esbuild/linux-x64, etc.), we now:
- Detect when the package uses the native binlink pattern (enabled by default for esbuild)
- Find the matching platform-specific dependency based on target CPU/OS
- Link binaries directly from the platform-specific package
- Fall back to the main package if the platform-specific package isn't found or linking fails

This eliminates the need to run postinstall scripts that just copy binaries from optional dependencies.

## Script Ignoring

Packages can now skip lifecycle scripts for specific dependencies (enabled by default for sharp). This prevents running heavy postinstall scripts that may:
- Download large native binaries
- Compile native code unnecessarily
- Fail in environments where they're not needed

## Configuration

Both features support configuration via package.json:
- `nativeDependencies`: Array of package names to enable native binlink optimization
- `ignoreScripts`: Array of package names to skip lifecycle scripts

Set these to empty arrays to disable the defaults. Feature flags are also available:
- BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER
- BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS

## Implementation Details

- Added PostinstallOptimizer enum and List types to track optimization strategies per package
- Modified Bin.Linker to support separate target paths and package names for binlinks
- Updated PackageInstaller and isolated Installer to check optimizer before running scripts
- Fallback logic retries without optimization if linking fails
- Updated esbuild to 0.25.11 to test with latest version

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

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the claude label Nov 1, 2025
@coderabbitai

coderabbitai Bot commented Nov 1, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Threads a new PostinstallOptimizer through installer and linker flows to enable platform-aware native binlink replacement and lifecycle-script ignoring; Linker gains target path/name and a skipped flag; installers gain replacement helpers, retry/fallback linking, feature flags, tests, and registry fixtures.

Changes

Cohort / File(s) Summary
Linker struct & CLI plumbing
src/install/bin.zig, src/cli/link_command.zig, src/cli/unlink_command.zig
Added public target_node_modules_path and target_package_name to Linker, plus skipped_due_to_missing_bin; CLI link/unlink flows now initialize these fields.
Postinstall optimization module
src/install/postinstall_optimizer.zig, src/install/install.zig
New PostinstallOptimizer module (enum, List, Map, defaults, package.json parsing, platform-aware replacement lookup); exposed via pub const PostinstallOptimizer = ....
Installer & PackageInstaller plumbing
src/install/isolated_install/Installer.zig, src/install/PackageInstaller.zig
Added maybeReplaceNodeModulesPath helper; propagate target_node_modules_path/target_package_name into linker; implement retry/fallback when native-binlink is attempted then needs fallback; integrate lifecycle-script ignore checks using optimizer.
PackageManager & lockfile integration
src/install/PackageManager.zig, src/install/lockfile/Package.zig
Exported PostinstallOptimizer alias; added postinstall_optimizer: PostinstallOptimizer.List to PackageManager; Package.fromPackageJSON() initializes optimizer from package.json.
Feature flags
src/env_var.zig
Added feature flags BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER and BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS.
Linker behavior adjustments
src/install/bin.zig
Destination path construction now uses target_node_modules_path/target_package_name; missing-target cases set skipped_due_to_missing_bin = true.
CLI tests & registry fixtures
test/cli/install/bun-install-native-binlink.test.ts, test/cli/install/registry/packages/create-native-binlink-packages.ts, test/cli/install/registry/packages/test-native-binlink*/package.json
New integration test, package-generator script, and test package manifests to validate native binlink optimization and platform-specific target packages.
Test refactor (esbuild)
test/integration/esbuild/esbuild.test.ts
Refactored orchestration to Promise-based output collection, tempDir usage, and concurrent runs; behavior unchanged.
Bench: Remix → Next.js scaffold & removals
bench/install/** (e.g., bench/install/package.json, bench/install/next.config.js, bench/install/next-env.d.ts, removed bench/install/app/*, removed tsconfig/remix files, changed README)
Replaced Remix bench with Next.js scaffold and config; removed multiple Remix app/entry/route files and TS configs; added Next/TS declarations and package.json.

Sequence Diagram(s)

sequenceDiagram
    participant Installer as Installer / PackageInstaller
    participant Optimizer as PostinstallOptimizer
    participant Linker as Bin.Linker
    participant Scripts as Lifecycle Scripts

    Installer->>Optimizer: maybeReplaceNodeModulesPath(name_hash, resolutions, metas, target)
    Optimizer-->>Installer: ?replacementPackageID

    alt replacement found
        Installer->>Linker: set target_package_name & target_node_modules_path
        Linker-->>Installer: link result (success / skipped_due_to_missing_bin / error)
        alt skipped or error and retry allowed
            Installer->>Installer: reset target_* -> original
            Note right of Installer: verbose log "retrying without native binlink"
            Installer->>Linker: retry link (original package_name/path)
            Linker-->>Installer: final result
        end
    else no replacement
        Installer->>Linker: link with original package_name/path
        Linker-->>Installer: result
    end

    Installer->>Optimizer: shouldIgnoreLifecycleScripts(name_hash,resolutions,metas,target,tree_id)
    alt true
        Note right of Installer: skip enqueueing lifecycle scripts
    else false
        Installer->>Scripts: enqueueLifecycleScripts()
        Scripts-->>Installer: scripts enqueued / trusted deps updated
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay special attention to:
    • postinstall_optimizer parsing and platform-matching logic.
    • getNativeBinlinkReplacementPackageID selection correctness.
    • Retry/fallback state management in PackageInstaller and Installer (resetting target fields, logging).
    • Linker destination path construction and semantics of skipped_due_to_missing_bin.
    • New tests and package-generator script for registry integrity and platform constraints.

Possibly related PRs

Suggested reviewers

  • alii

Poem

🐇
I hopped through node_modules, sniffed for the right bin,
Chose a platform friend and nudged it safely in.
If native missed, I blinked and tried once more,
Then munched a carrot, and hopped out the door. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main change: adding a postinstall optimizer with native binlink support and script skipping, which is the core objective of this changeset.
Description check ✅ Passed The description covers the core sections: summary of the postinstall optimization system, key features (native binlink and script skipping), configuration options, implementation details, and test plan. It aligns well with the description template's implicit expectations.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jarred/postinstall-optimize

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 02d2163 and d8fed87.

📒 Files selected for processing (2)
  • src/install/isolated_install/Installer.zig (9 hunks)
  • src/install/postinstall_optimizer.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/postinstall_optimizer.zig
  • src/install/isolated_install/Installer.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/postinstall_optimizer.zig
  • src/install/isolated_install/Installer.zig
🧠 Learnings (19)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to src/**/*.zig : Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Implement the core feature in Zig under its own directory within src/<feature>/

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Place import statements at the bottom of the file (formatter will handle ordering)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.

Applied to files:

  • src/install/postinstall_optimizer.zig
  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/install/postinstall_optimizer.zig
  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-10-16T21:24:52.779Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/crash_handler.zig:1415-1423
Timestamp: 2025-10-16T21:24:52.779Z
Learning: When a boolean EnvVar in src/envvars.zig is defined with a default value (e.g., `.default = false`), the `get()` method returns `bool` instead of `?bool`. This means you cannot distinguish between "environment variable not set" and "environment variable explicitly set to the default value". For opt-out scenarios where detection of explicit setting is needed (like `BUN_ENABLE_CRASH_REPORTING` on platforms where crash reporting defaults to enabled), either: (1) don't provide a default value so `get()` returns `?bool`, or (2) use the returned boolean directly instead of only checking if it's true.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-09-02T17:09:17.391Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/allocators.zig:778-782
Timestamp: 2025-09-02T17:09:17.391Z
Learning: In Zig's bun codebase, the `isDefault` function in `src/allocators.zig` only needs to compare `allocator.vtable == c_allocator.vtable` rather than also checking the `ptr` field, because: (1) the codebase never creates multiple allocators that use `c_allocator.vtable` but have different `ptr`s, and (2) the default allocator vtable ignores the `ptr` field anyway, so any allocators sharing the same vtable would function identically.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/install/postinstall_optimizer.zig
  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to src/**/*.zig : In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-10-19T23:27:43.240Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23840
File: src/bun.js/node/node_fs_stat_watcher.zig:400-411
Timestamp: 2025-10-19T23:27:43.240Z
Learning: In Zig code (e.g., src/bun.js/node/node_fs_stat_watcher.zig), taking the address of a function return value (e.g., &this.getLastStat()) is valid when passed as a const pointer parameter (e.g., *const bun.sys.PosixStat) to another function in the same expression. The compiler materializes the temporary with appropriate lifetime for the call scope.

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-10-08T03:11:45.286Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-10-08T03:11:45.286Z
Learning: Applies to src/**/*.zig : When adding debug logs in Zig, create a scoped logger and log via Bun APIs: `const log = bun.Output.scoped(.${SCOPE}, .hidden);` then `log("...", .{})`

Applied to files:

  • src/install/isolated_install/Installer.zig
🔇 Additional comments (13)
src/install/isolated_install/Installer.zig (7)

438-439: LGTM!

The new per-package data accessors are correctly declared and will enable the optimizer to inspect resolutions and metadata during linking decisions.


930-941: LGTM!

The lifecycle script guard correctly consults the PostinstallOptimizer before enqueueing scripts. The snake_case naming follows Zig conventions.


1016-1070: LGTM!

The bin linker setup and retry flow correctly implements the simplified approach: one initial attempt with native binlink optimization, followed by a fallback attempt when the replacement is missing or fails. The conditional verbose logging is appropriate.


1298-1339: LGTM!

The helper now returns ?Store.Entry.Id directly, which is cleaner than modifying an out-parameter. The logic correctly searches for a replacement entry when native binlink optimization is enabled.


1388-1444: LGTM!

The dependency bin linking correctly implements the same simplified retry pattern: attempt native binlink optimization first, then fall back to the original package if the replacement is unavailable or linking fails. The logic is consistent with the per-entry linking flow.


1580-1580: LGTM!

The scoped logger follows Bun's conventions for debug logging in Zig.


1610-1610: LGTM!

The PostinstallOptimizer import is properly placed at the bottom with other imports, following Zig conventions.

src/install/postinstall_optimizer.zig (6)

5-11: LGTM!

The default hash arrays correctly specify esbuild for native binlink optimization and sharp for script ignoring, matching the PR objectives.


13-29: LGTM!

The parsing logic correctly handles both empty arrays (to disable defaults) and populated arrays (to specify custom packages).


31-38: LGTM!

The package.json parsing correctly handles both nativeDependencies and ignoreScripts fields, setting the appropriate disable flags when empty arrays are provided.


86-116: LGTM!

The lifecycle script logic correctly handles both modes: .ignore always skips scripts, while .native_binlink only skips when a platform-specific replacement is available, allowing the postinstall script to run as a fallback otherwise. The inline comment explains the rationale well.


118-130: LGTM!

The default lookup correctly searches both default arrays and returns the appropriate optimizer type.


132-155: LGTM!

The get method correctly implements the precedence: user-configured packages override defaults, and defaults are only returned when not explicitly disabled. The logic properly respects both disable_default_native_binlinks and disable_default_ignore flags.


Comment @coderabbitai help to get the list of available commands and usage tips.

@robobun

robobun commented Nov 1, 2025 •

Copy link
Copy Markdown
Collaborator
Updated 6:53 PM PT - Nov 3rd, 2025

❌ @dylan-conway, your commit d8fed87 has 3 failures in Build #30870 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 24283

That installs a local version of the PR into your bun-24283 executable, so you can run:

bun-24283 --bun

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0564b81 and ef18281.

📒 Files selected for processing (11)
  • src/cli/link_command.zig (1 hunks)
  • src/cli/unlink_command.zig (1 hunks)
  • src/env_var.zig (1 hunks)
  • src/install/PackageInstaller.zig (7 hunks)
  • src/install/PackageManager.zig (1 hunks)
  • src/install/bin.zig (3 hunks)
  • src/install/install.zig (1 hunks)
  • src/install/isolated_install/Installer.zig (6 hunks)
  • src/install/lockfile.zig (2 hunks)
  • src/install/lockfile/Package.zig (1 hunks)
  • src/install/postinstall_optimizer.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/lockfile.zig
  • src/env_var.zig
  • src/cli/link_command.zig
  • src/install/PackageManager.zig
  • src/install/bin.zig
  • src/install/PackageInstaller.zig
  • src/install/isolated_install/Installer.zig
  • src/install/postinstall_optimizer.zig
  • src/install/install.zig
  • src/cli/unlink_command.zig
  • src/install/lockfile/Package.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/lockfile.zig
  • src/env_var.zig
  • src/cli/link_command.zig
  • src/install/PackageManager.zig
  • src/install/bin.zig
  • src/install/PackageInstaller.zig
  • src/install/isolated_install/Installer.zig
  • src/install/postinstall_optimizer.zig
  • src/install/install.zig
  • src/cli/unlink_command.zig
  • src/install/lockfile/Package.zig
🧠 Learnings (20)
📚 Learning: 2025-10-16T02:17:35.237Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/analytics.zig:15-21
Timestamp: 2025-10-16T02:17:35.237Z
Learning: In src/analytics.zig and similar files using bun.EnvVar boolean environment variables: the new EnvVar API for boolean flags (e.g., bun.EnvVar.do_not_track.get(), bun.EnvVar.ci.get()) is designed to parse and return boolean values from environment variables, not just check for their presence. This is an intentional design change from the previous presence-based checks using bun.getenvZ().

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-10-15T22:03:50.832Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/envvars.zig:135-144
Timestamp: 2025-10-15T22:03:50.832Z
Learning: In src/envvars.zig, the boolean feature flag cache uses a single atomic enum and should remain monotonic. Only the string cache (which uses two atomics: ptr and len) requires acquire/release ordering to prevent torn reads.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/env_var.zig
  • src/cli/link_command.zig
  • src/install/PackageManager.zig
  • src/install/bin.zig
  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/env_var.zig
  • src/cli/link_command.zig
  • src/install/PackageManager.zig
  • src/install/bin.zig
  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-09-02T18:25:27.976Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/allocators/allocation_scope.zig:284-314
Timestamp: 2025-09-02T18:25:27.976Z
Learning: In bun's custom Zig implementation, the `#` prefix for private fields is valid syntax and should not be flagged as invalid. The syntax `#fieldname` creates private fields that cannot be accessed from outside the defining struct, and usage like `self.#fieldname` is correct within the same struct. This applies to fields like `#parent`, `#state`, `#allocator`, `#trace`, etc. throughout the codebase.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-09-02T17:14:46.924Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/safety/alloc.zig:93-95
Timestamp: 2025-09-02T17:14:46.924Z
Learning: In bun's Zig codebase, they use a custom extension of Zig that supports private field syntax with the `#` prefix (e.g., `#allocator`, `#trace`). This is not standard Zig syntax but is valid in their custom implementation. Fields prefixed with `#` are private fields that cannot be accessed from outside the defining struct.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-10-16T21:24:52.779Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/crash_handler.zig:1415-1423
Timestamp: 2025-10-16T21:24:52.779Z
Learning: When a boolean EnvVar in src/envvars.zig is defined with a default value (e.g., `.default = false`), the `get()` method returns `bool` instead of `?bool`. This means you cannot distinguish between "environment variable not set" and "environment variable explicitly set to the default value". For opt-out scenarios where detection of explicit setting is needed (like `BUN_ENABLE_CRASH_REPORTING` on platforms where crash reporting defaults to enabled), either: (1) don't provide a default value so `get()` returns `?bool`, or (2) use the returned boolean directly instead of only checking if it's true.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Implement the core feature in Zig under its own directory within src/<feature>/

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.

Applied to files:

  • src/env_var.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/install/PackageManager.zig
  • src/install/bin.zig
  • src/install/postinstall_optimizer.zig
  • src/install/install.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Declare the extern C symbol in Zig and export a Zig-friendly alias for use

Applied to files:

  • src/install/PackageManager.zig
  • src/install/install.zig
📚 Learning: 2025-08-30T09:09:18.384Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22231
File: src/bundler/bundle_v2.zig:48-48
Timestamp: 2025-08-30T09:09:18.384Z
Learning: In Zig, when a module exports a top-level struct, import("./Module.zig") directly returns that struct type and can be used as a type alias without needing to access a field within the module. This is a common pattern in the Bun codebase.

Applied to files:

  • src/install/PackageManager.zig
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • src/install/PackageManager.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to src/**/*.zig : Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Applied to files:

  • src/install/bin.zig
  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Place import statements at the bottom of the file (formatter will handle ordering)

Applied to files:

  • src/install/postinstall_optimizer.zig
  • src/install/install.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/install/postinstall_optimizer.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (6)
src/install/bin.zig (2)

848-865: LGTM: Target path redirection correctly implemented.

The buildTargetPackageDir method now correctly uses target_node_modules_path and target_package_name instead of the original fields, enabling the native binlink optimization to redirect package resolution to platform-specific dependencies.


507-513: All Bin.Linker instantiations properly initialized.

Verification confirms that all 5 instantiation sites across the codebase have been updated to initialize the new target_node_modules_path and target_package_name fields:

  • src/cli/link_command.zig:140
  • src/cli/unlink_command.zig:103
  • src/install/PackageInstaller.zig:322
  • src/install/isolated_install/Installer.zig:1021 and 1387

Initialization logic is appropriate, including conditional assignments in Installer.zig for native binlink optimization paths. The change is complete and consistent across the codebase.

src/install/PackageManager.zig (1)

1324-1324: LGTM: Public alias correctly exposes PostinstallOptimizer.

This alias enables external access to the PostinstallOptimizer type through the PackageManager module, consistent with the broader API exposure pattern across the install subsystem.

src/cli/unlink_command.zig (1)

104-105: LGTM: New Linker fields correctly initialized for unlink flow.

The target fields are appropriately set to the local values (node_modules_path and name) for the unlink operation, which doesn't require path redirection.

src/install/install.zig (1)

260-260: LGTM: PostinstallOptimizer correctly exported as public API.

The export follows the established pattern in this file for exposing install subsystem types. This enables external modules to reference install.PostinstallOptimizer for postinstall optimization configuration.

src/install/lockfile/Package.zig (1)

1586-1587: No action required—PostinstallOptimizer.fromPackageJSON already handles malformed or missing configuration gracefully.

The implementation correctly manages all edge cases without failing the install for configuration issues:

  • Missing fields (nativeDependencies, ignoreScripts) are silently ignored via optional expr.get()
  • Non-array field values gracefully return false via asArray() orelse return false
  • Empty arrays, non-string entries, and empty strings are all skipped without errors
  • The only error propagated is memory allocation failure in list.dynamic.put(), which is appropriate

The code meets the review concern—malformed or missing optimizer configuration is handled gracefully, allowing the install to proceed with default behavior rather than failing.

Comment thread src/install/lockfile.zig Outdated
Comment on lines 30 to 31
postinstall_optimizer: PostinstallOptimizer.List = .{},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve postinstall optimizer data in load/clone paths.

The new postinstall_optimizer list is introduced here, but the lifecycle of the list never gets wired up: loadFromBytes doesn’t reset it before repopulating, cleanWithLogger/Cloner never copy it into the new lockfile, and deinit never frees it. In practice that leaves stale entries hanging around between loads, drops optimizer decisions whenever we rebuild the lockfile, and leaks allocator state. Because this list governs which packages Bun skips or trusts for lifecycle scripts, losing it means the lockfile forgets those security/perf decisions the next time we save. (bun.com)

Please zero the list before loading (this.postinstall_optimizer = .{};), copy it when we create a trimmed lockfile (either clone or rebuild the list on new), and release it in deinit. Without those pieces the postinstall optimizer configuration can’t be persisted safely.

🤖 Prompt for AI Agents
In src/install/lockfile.zig around lines 30-31, the newly-added
postinstall_optimizer list is not initialized/reset when loading, not copied
when creating trimmed/clone lockfiles, and not freed on deinit; as a result
stale entries persist between loads, optimizer decisions are lost on
rebuild/clone, and allocator state leaks. Fix by (1) zeroing/resetting
this.postinstall_optimizer at the start of loadFromBytes (e.g.
this.postinstall_optimizer = .{} before repopulating), (2) ensuring
cleanWithLogger/Cloner (or the Lockfile::new used for trimmed copies) copies or
rebuilds the postinstall_optimizer contents into the new Lockfile instance so
optimizer decisions are preserved, and (3) releasing/freeing the list in deinit
so its allocator state is cleaned up.

Comment on lines +48 to +55
for (resolutions) |resolution| {
if (resolution > metas.len) continue;
const meta: *const Meta = &metas[resolution];
if (meta.arch == .all or meta.os == .all) continue;
if (meta.arch.isMatch(target_cpu) and meta.os.isMatch(target_os)) {
return resolution;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Prevent out-of-bounds access before reading meta.

Line 49 currently checks if (resolution > metas.len) and then immediately indexes metas[resolution]. When resolution == metas.len, this condition passes, but the subsequent index is still out of bounds and will panic. Please switch the comparison to >= so we never read past the end of metas.

Apply this diff:

-            if (resolution > metas.len) continue;
+            if (resolution >= metas.len) continue;
📝 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.

Suggested change
for (resolutions) |resolution| {
if (resolution > metas.len) continue;
const meta: *const Meta = &metas[resolution];
if (meta.arch == .all or meta.os == .all) continue;
if (meta.arch.isMatch(target_cpu) and meta.os.isMatch(target_os)) {
return resolution;
}
}
for (resolutions) |resolution| {
if (resolution >= metas.len) continue;
const meta: *const Meta = &metas[resolution];
if (meta.arch == .all or meta.os == .all) continue;
if (meta.arch.isMatch(target_cpu) and meta.os.isMatch(target_os)) {
return resolution;
}
}
🤖 Prompt for AI Agents
In src/install/postinstall_optimizer.zig around lines 48 to 55, the loop checks
`if (resolution > metas.len)` before indexing `metas[resolution]`, which still
allows an out-of-bounds access when `resolution == metas.len`; change the
comparison to `>=` so the guard prevents any attempt to read past the end of the
metas array, i.e., replace the `>` check with `>=` and keep the subsequent logic
unchanged.

Comment thread src/install/postinstall_optimizer.zig Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/install/isolated_install/Installer.zig (3)

1020-1052: Minor ordering inconsistency in retry logic.

The retry logic is correct and properly implements fallback behavior when native binlink optimization fails. However, there's a minor inconsistency: line 1040 sets bin_linker.err = null before the cleanup logic, while the equivalent code in linkDependencyBins (line 1411) does this after cleanup.

Consider making the ordering consistent between both retry blocks:

         if (bin_linker.err) |err| {
             if (can_retry_without_native_binlink_optimization) {
-                bin_linker.err = null;
                 debug("retrying without native bin link", .{});
                 can_retry_without_native_binlink_optimization = false;
                 target_node_modules_path.?.deinit();
                 target_node_modules_path = null;
                 target_package_name = strings.StringOrTinyString.init(dep_name);
+                bin_linker.err = null;
                 continue;
             }

Note: This is purely for consistency—both orderings work correctly since bin_linker is a local variable that's recreated each iteration.


1276-1321: Implementation looks correct.

The maybeReplaceNodeModulesPath function properly implements the native binlink replacement logic with correct memory management using the out-parameter pattern. The debug logging follows the coding guidelines (scoped logger).

Optional performance consideration: The linear search through entry_node_ids (lines 1305-1313) is O(n) relative to the number of entries. However, this is acceptable since it only executes when:

  1. Native binlink optimization is enabled
  2. A replacement package is configured for this dependency
  3. A platform-specific replacement is found

These conditions make the search path rare enough that the performance impact should be negligible.


1385-1419: Retry logic correctly mirrors the binaries step implementation.

The retry mechanism properly handles fallback when native binlink optimization fails. The logic is consistent with the binaries step (lines 1020-1052).

Optional refactor suggestion: There's substantial code duplication between this retry logic and the one in the binaries step (lines 1020-1052). Consider extracting the common retry pattern into a helper function. However, given that one executes in a task thread and the other on the main thread, keeping them separate may be intentional for clarity.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ef18281 and efa77d6.

📒 Files selected for processing (3)
  • src/install/isolated_install/Installer.zig (7 hunks)
  • src/install/lockfile.zig (2 hunks)
  • src/install/postinstall_optimizer.zig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/install/lockfile.zig
  • src/install/postinstall_optimizer.zig
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/isolated_install/Installer.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/isolated_install/Installer.zig
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes
📚 Learning: 2025-10-08T03:11:45.286Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-10-08T03:11:45.286Z
Learning: Applies to src/**/*.zig : When adding debug logs in Zig, create a scoped logger and log via Bun APIs: `const log = bun.Output.scoped(.${SCOPE}, .hidden);` then `log("...", .{})`

Applied to files:

  • src/install/isolated_install/Installer.zig
🔇 Additional comments (3)
src/install/isolated_install/Installer.zig (3)

438-438: LGTM!

The additional data gathering for pkg_resolutions_lists is correctly integrated and used by the new native binlink optimization logic.


1551-1551: LGTM!

The debug scope declaration and PostinstallOptimizer import follow the coding guidelines correctly:

  • Scoped logger created with .hidden default visibility
  • Import placed at the bottom of the file
  • Debug logging used appropriately throughout the new logic

Based on coding guidelines.

Also applies to: 1581-1581


1001-1052: Ensure comprehensive test coverage for the new optimization paths.

The implementation correctly handles the native binlink optimization with proper fallback behavior. According to the PR objectives, the following tests are pending:

  • esbuild without postinstall scripts
  • sharp with scripts skipped
  • custom package.json configuration
  • platform-specific fallback (the retry logic implemented here)
  • feature flag overrides

Please ensure these tests are added to verify both the optimization path and the fallback behavior, especially the retry logic when the platform-specific package is not found.

Also applies to: 1276-1321, 1323-1421

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/install/postinstall_optimizer.zig (2)

48-53: Fix the off-by-one before reading Meta.

resolution == metas.len still passes the guard, so the very next line dereferences past the end of metas, crashing the installer. Switch the comparison to >= so we never index out of bounds.

-        for (resolutions) |resolution| {
-            if (resolution > metas.len) continue;
+        for (resolutions) |resolution| {
+            if (resolution >= metas.len) continue;
             const meta: *const Meta = &metas[resolution];

67-79: Respect nativeDependencies opt-out.

When the user sets nativeDependencies: [], we mark disable_default_native_binlinks = true, but this function still returns true, so we keep attempting native binlinking against their explicit request. Flip the logic so the opt-out actually disables the feature unless there are explicit overrides and still honor the feature flag.

-        pub fn isNativeBinlinkEnabled(this: *const @This()) bool {
-            if (this.dynamic.count() == 0) {
-                if (this.disable_default_native_binlinks) {
-                    return true;
-                }
-            }
-
-            if (bun.env_var.feature_flag.BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER.get()) {
-                return false;
-            }
-
-            return true;
-        }
+        pub fn isNativeBinlinkEnabled(this: *const @This()) bool {
+            if (bun.env_var.feature_flag.BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER.get()) {
+                return false;
+            }
+
+            if (this.dynamic.count() == 0 and this.disable_default_native_binlinks) {
+                return false;
+            }
+
+            return true;
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between efa77d6 and b511a88.

📒 Files selected for processing (2)
  • src/install/isolated_install/Installer.zig (7 hunks)
  • src/install/postinstall_optimizer.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/postinstall_optimizer.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/postinstall_optimizer.zig
🧠 Learnings (16)
📚 Learning: 2025-10-08T03:11:45.286Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-10-08T03:11:45.286Z
Learning: Applies to src/**/*.zig : When adding debug logs in Zig, create a scoped logger and log via Bun APIs: `const log = bun.Output.scoped(.${SCOPE}, .hidden);` then `log("...", .{})`

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to src/**/*.zig : Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Place import statements at the bottom of the file (formatter will handle ordering)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Implement the core feature in Zig under its own directory within src/<feature>/

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Declare the extern C symbol in Zig and export a Zig-friendly alias for use

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-04T09:52:49.414Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-10-04T09:52:49.414Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use process.platform and process.arch for platform detection (rely on inlining/dead-code elimination)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T21:24:52.779Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/crash_handler.zig:1415-1423
Timestamp: 2025-10-16T21:24:52.779Z
Learning: When a boolean EnvVar in src/envvars.zig is defined with a default value (e.g., `.default = false`), the `get()` method returns `bool` instead of `?bool`. This means you cannot distinguish between "environment variable not set" and "environment variable explicitly set to the default value". For opt-out scenarios where detection of explicit setting is needed (like `BUN_ENABLE_CRASH_REPORTING` on platforms where crash reporting defaults to enabled), either: (1) don't provide a default value so `get()` returns `?bool`, or (2) use the returned boolean directly instead of only checking if it's true.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-09-02T17:09:17.391Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/allocators.zig:778-782
Timestamp: 2025-09-02T17:09:17.391Z
Learning: In Zig's bun codebase, the `isDefault` function in `src/allocators.zig` only needs to compare `allocator.vtable == c_allocator.vtable` rather than also checking the `ptr` field, because: (1) the codebase never creates multiple allocators that use `c_allocator.vtable` but have different `ptr`s, and (2) the default allocator vtable ignores the `ptr` field anyway, so any allocators sharing the same vtable would function identically.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/install/postinstall_optimizer.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format

Comment thread src/install/postinstall_optimizer.zig Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/install/bin.zig (1)

960-971: Signal “missing dir” as a skip to trigger fallback too

If bin points to a directory that doesn’t exist, we return early without setting skipped_due_to_missing_bin, so the retry path won’t trigger. Set the flag for consistency.

-                    var target_dir = bun.openDirAbsolute(abs_target_dir) catch |err| {
-                        if (err == error.ENOENT) {
-                            // avoid erroring when the directory does not exist
-                            return;
-                        }
+                    var target_dir = bun.openDirAbsolute(abs_target_dir) catch |err| {
+                        if (err == error.ENOENT) {
+                            // Directory missing; allow caller to retry without native binlink
+                            this.skipped_due_to_missing_bin = true;
+                            return;
+                        }
🧹 Nitpick comments (2)
src/install/isolated_install/Installer.zig (1)

1281-1327: Replacement lookup integrates well; add a quick short-circuit for no-op cases

The resolver scans entries to locate the replacement’s entry_id and computes its node_modules path. Consider early-returning if replacement_pkg_id == pkg_id to avoid redundant work.

-                    )) |replacement_pkg_id| {
+                    )) |replacement_pkg_id| {
+                        if (replacement_pkg_id == pkg_id) return null;
src/install/PackageInstaller.zig (1)

1157-1184: Lifecycle script skipping: gating is correct; add tests

The shouldIgnoreLifecycleScripts gate avoids enqueue; please add CLI tests to cover:

  • ignoreScripts default for sharp,
  • overrides via BUN_FEATURE_FLAG_DISABLE_IGNORE_SCRIPTS=1,
  • per‑project configuration in package.json.

I can draft data-driven tests under test/cli/install/ covering the three cases.

Also applies to: 1339-1369

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 772064b and 0845224.

📒 Files selected for processing (9)
  • src/install/PackageInstaller.zig (7 hunks)
  • src/install/PackageManager.zig (2 hunks)
  • src/install/bin.zig (5 hunks)
  • src/install/isolated_install/Installer.zig (7 hunks)
  • src/install/lockfile/Package.zig (1 hunks)
  • test/cli/install/bun-install-native-binlink.test.ts (1 hunks)
  • test/cli/install/registry/packages/create-native-binlink-packages.ts (1 hunks)
  • test/cli/install/registry/packages/test-native-binlink-target/package.json (1 hunks)
  • test/cli/install/registry/packages/test-native-binlink/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/install/lockfile/Package.zig
🧰 Additional context used
📓 Path-based instructions (9)
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
  • test/cli/install/registry/packages/test-native-binlink-target/package.json
  • test/cli/install/registry/packages/test-native-binlink/package.json
test/cli/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/cli/**/*.{js,ts}: Place CLI command tests (e.g., bun install, bun init) under test/cli/
When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
test/**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

For large/repetitive strings, use Buffer.alloc(count, fill).toString() instead of "A".repeat(count)

Files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid shell commands in tests (e.g., find, grep); use Bun's Glob and built-in tools

Files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test for files ending with *.test.{ts,js,jsx,tsx,mjs,cjs}
Prefer concurrent tests (test.concurrent/describe.concurrent) over sequential when feasible
Organize tests with describe blocks to group related tests
Use utilities like describe.each, toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach) and track resources for cleanup

Files:

  • test/cli/install/bun-install-native-binlink.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or invent random port functions
Prefer snapshot assertions and use normalizeBunSnapshot for snapshot output in tests
Never write tests that assert absence of crashes (e.g., no "panic" or "uncaught exception") in output
Use tempDir from "harness" for temporary directories; do not use tmpdirSync or fs.mkdtempSync in tests
When spawning processes in tests, assert on stdout before asserting exitCode
Do not use setTimeout in tests; await conditions instead to avoid flakiness

Files:

  • test/cli/install/bun-install-native-binlink.test.ts
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/bin.zig
  • src/install/PackageInstaller.zig
  • src/install/PackageManager.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/bin.zig
  • src/install/PackageInstaller.zig
  • src/install/PackageManager.zig
🧠 Learnings (26)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
  • test/cli/install/registry/packages/test-native-binlink/package.json
📚 Learning: 2025-10-04T09:52:49.414Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-10-04T09:52:49.414Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use process.platform and process.arch for platform detection (rely on inlining/dead-code elimination)

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/third_party/**/*.{js,ts} : Place third-party npm package tests under test/js/third_party/

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : Place CLI command tests (e.g., bun install, bun init) under test/cli/

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending with `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/js/node/test/{parallel,sequential}/*.js : For `test/js/node/test/{parallel,sequential}/*.js` (no .test extension), run with `bun bd <file>` instead of `bun bd test <file>`

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to test/**/*.{ts,tsx} : Avoid shell commands in tests (e.g., find, grep); use Bun's Glob and built-in tools

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/cli/install/registry/packages/create-native-binlink-packages.ts
  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/js/{bun,node}/** : Organize unit tests by module under `/test/js/bun/` and `/test/js/node/`

Applied to files:

  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js,test/**/*-fixture.ts} : Use `using`/`await using` for resource cleanup with Bun APIs (e.g., `Bun.spawn`, `Bun.listen`, `Bun.serve`)

Applied to files:

  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Import common utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, platform helpers, GC helpers)

Applied to files:

  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Prefer concurrent tests (`test.concurrent`/`describe.concurrent`) over sequential when feasible

Applied to files:

  • test/cli/install/bun-install-native-binlink.test.ts
📚 Learning: 2025-10-08T03:11:45.286Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-10-08T03:11:45.286Z
Learning: Applies to src/**/*.zig : When adding debug logs in Zig, create a scoped logger and log via Bun APIs: `const log = bun.Output.scoped(.${SCOPE}, .hidden);` then `log("...", .{})`

Applied to files:

  • src/install/isolated_install/Installer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/install/bin.zig
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.

Applied to files:

  • src/install/bin.zig
🧬 Code graph analysis (1)
test/cli/install/bun-install-native-binlink.test.ts (1)
test/harness.ts (2)
  • VerdaccioRegistry (1631-1767)
  • bunExe (102-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (9)
test/cli/install/registry/packages/test-native-binlink-target/package.json (1)

1-28: Clarify the test fixture design for platform-specific package resolution testing.

The test package declares support for multiple platforms (darwin, linux, win32) and architectures (arm64, x64) simultaneously. This is unusual for a platform-specific package fixture, which typically represents a single os+cpu combination (e.g., @esbuild/darwin-arm64).

Additionally, the package name doesn't follow the platform-specific naming convention used by real packages (e.g., @scope/package-os-arch).

Please clarify:

  1. How is this package used in the native binlink resolution test? Is it filtered by the installer based on the target platform, or are separate platform-specific variants expected?
  2. Should this be renamed or split into separate platform-specific packages to better mirror real-world scenarios (e.g., test-native-binlink-target-darwin-arm64, test-native-binlink-target-linux-x64)?
  3. Does the current fixture properly exercise the platform/architecture resolution logic in the optimizer?
src/install/bin.zig (2)

507-515: New Linker targeting metadata and skip flag look solid

target_node_modules_path/target_package_name enable source redirection; skipped_due_to_missing_bin is the right signal for fallback.

Also applies to: 533-535


643-699: Shebang normalization: preserve permissions and perform atomic swap correctly

The atomic replace logic and mode preservation look correct and guarded by best-effort I/O; no changes needed.

src/install/isolated_install/Installer.zig (3)

1005-1057: Retry logic is correct; ensure target_node_modules_path lifetime is managed

The loop retries without optimization if the target is missing or linking fails; and you deinit the optional AbsPath before nulling—good. No change needed.


1375-1427: Consistent fallback when linking dependency bins

Symmetric to task-time path; logs are gated by verbose flag. Looks good.


1559-1590: Add scoped logger: good practice

Using Output.scoped(.IsolatedInstaller, .hidden) aligns with repo logging guidelines.

src/install/PackageInstaller.zig (2)

259-306: Native binlink targeting and retry are integrated correctly

Reads metas/resolutions and swaps target_package_name when replacement is present; falls back cleanly.


338-365: Surface bin-link errors without aborting; respects fail_early flag

Good balance: logs when not silent, crashes only if configured.

src/install/PackageManager.zig (1)

92-93: PostinstallOptimizer field initialization verified

The field is correctly populated during init via fromPackageJSON at src/install/lockfile/Package.zig:1587, which processes package.json data and feature flags as expected. Usage throughout PackageInstaller.zig and Installer.zig confirms proper integration with methods like isNativeBinlinkEnabled() and shouldIgnoreLifecycleScripts(). No manual deinit required based on the List initialization pattern.

Comment on lines +9 to +13
beforeAll(async () => {
setDefaultTimeout(1000 * 60 * 5);
verdaccio = new VerdaccioRegistry();
await verdaccio.start();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Generate registry fixtures with the actual registry URL before starting tests

Without regenerating the package metadata/tarballs, the hardcoded 4873 tarball host may cause fetch failures. Generate fixtures up front using the current Verdaccio port.

@@
-import { spawn } from "bun";
+import { $, spawn } from "bun";
@@
 beforeAll(async () => {
   setDefaultTimeout(1000 * 60 * 5);
-  verdaccio = new VerdaccioRegistry();
-  await verdaccio.start();
+  verdaccio = new VerdaccioRegistry();
+  await verdaccio.start();
+  // Regenerate native-binlink test packages with this registry's URL
+  const genScript = join(import.meta.dir, "registry", "packages", "create-native-binlink-packages.ts");
+  process.env.VERDACCIO_REGISTRY_URL = verdaccio.registryUrl();
+  await $`bun ${genScript}`;
 });

Run the test locally after this change to confirm green:


🏁 Script executed:

#!/bin/bash
bun test test/cli/install/bun-install-native-binlink.test.ts

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Examine the test file
cat test/cli/install/bun-install-native-binlink.test.ts

Length of output: 4877


🏁 Script executed:

#!/bin/bash
# Check for VerdaccioRegistry implementation
find test -name "*.ts" -o -name "*.js" | xargs grep -l "class VerdaccioRegistry" | head -5

Length of output: 972


🏁 Script executed:

#!/bin/bash
# Search for the fixture generation script mentioned in the review
find test -path "*registry*" -name "*create-native-binlink*" -o -path "*registry*" -name "*packages*"

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Search for hardcoded port 4873 references in test fixtures
rg "4873" test/cli/install/ -C2

Length of output: 174762


🏁 Script executed:

#!/bin/bash
# Check VerdaccioRegistry implementation and createTestDir method
cat test/harness.ts | grep -A 50 "class VerdaccioRegistry"

Length of output: 1701


🏁 Script executed:

#!/bin/bash
# Check if createTestDir method regenerates packages
cat test/harness.ts | grep -A 30 "createTestDir"

Length of output: 1260


🏁 Script executed:

#!/bin/bash
# Look for registryUrl method implementation
cat test/harness.ts | grep -A 10 "registryUrl"

Length of output: 1313


Address hardcoded registry port in native-binlink test fixtures

The review comment is correct. Verdaccio starts on a random port via randomPort(), and registryUrl() returns the correct dynamic URL for bunfig.toml. However, the static fixture package.json files in registry/packages/ contain hardcoded http://localhost:4873/ URLs for tarballs. When Verdaccio runs on a different port, these tarball URLs won't match, causing fetch failures.

The test should regenerate fixtures before startup to embed the actual registry URL:

@@
-import { spawn } from "bun";
+import { $, spawn } from "bun";
@@
 beforeAll(async () => {
   setDefaultTimeout(1000 * 60 * 5);
   verdaccio = new VerdaccioRegistry();
   await verdaccio.start();
+  // Regenerate native-binlink test packages with this registry's URL
+  const genScript = join(import.meta.dir, "registry", "packages", "create-native-binlink-packages.ts");
+  process.env.VERDACCIO_REGISTRY_URL = verdaccio.registryUrl();
+  await $`bun ${genScript}`;
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test/cli/install/bun-install-native-binlink.test.ts around lines 9-13, the
fixture package.json files in registry/packages contain hardcoded tarball URLs
(http://localhost:4873) that don't match Verdaccio's dynamic port; after
starting Verdaccio, call a fixture-regeneration step (or implement a small
script) that reads all registry/packages/*.json and rewrites tarball URLs to use
verdaccio.registryUrl() (or the returned host+port), then save the files before
running the rest of the tests so tarball URLs match the running registry.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/install/postinstall_optimizer.zig (2)

48-55: Fix the out-of-bounds check.

Line 49 checks if (resolution > metas.len) before accessing metas[resolution] on line 50. When resolution == metas.len, the condition passes but the array access is out of bounds. Change the comparison to >=.

Apply this diff:

-            if (resolution > metas.len) continue;
+            if (resolution >= metas.len) continue;

67-79: Fix the inverted logic when defaults are disabled.

When disable_default_native_binlinks is true (set by nativeDependencies: [] in package.json), the function returns true on line 70, which enables native binlink despite the user's explicit request to disable it. The logic is inverted.

Apply this diff:

 pub fn isNativeBinlinkEnabled(this: *const @This()) bool {
-    if (this.dynamic.count() == 0) {
-        if (this.disable_default_native_binlinks) {
-            return true;
-        }
-    }
-
     if (bun.env_var.feature_flag.BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER.get()) {
         return false;
     }
 
+    if (this.dynamic.count() == 0 and this.disable_default_native_binlinks) {
+        return false;
+    }
+
     return true;
 }
🧹 Nitpick comments (2)
test/integration/esbuild/esbuild.test.ts (2)

13-19: Consider simplifying the tempDir usage pattern.

The pattern using dir = tempDir(...); const packageDir = dir + "" works correctly but the empty string concatenation is unnecessary. Since tempDir already returns a string (that's also disposable), you can use it directly:

-using dir = tempDir("esbuild-test", {
+using packageDir = tempDir("esbuild-test", {
   "package.json": JSON.stringify({
     name: "bun-esbuild-test",
     version: "1.0.0",
   }),
 });
-const packageDir = dir + "";

The same applies to the second test at lines 53-59.

Also applies to: 53-59


21-21: Use consistent variable declaration keywords.

Line 21 uses var while line 61 uses let for the same pattern. Prefer let consistently for reassignable bindings.

Apply this diff:

-var { stdout, stderr, exited } = spawn({
+let { stdout, stderr, exited } = spawn({

Also applies to: 61-61

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0845224 and 19b010f.

📒 Files selected for processing (3)
  • src/install/PackageInstaller.zig (7 hunks)
  • src/install/postinstall_optimizer.zig (1 hunks)
  • test/integration/esbuild/esbuild.test.ts (9 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/postinstall_optimizer.zig
  • src/install/PackageInstaller.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/postinstall_optimizer.zig
  • src/install/PackageInstaller.zig
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/integration/esbuild/esbuild.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/integration/esbuild/esbuild.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Use bun:test for files ending with *.test.{ts,js,jsx,tsx,mjs,cjs}
Prefer concurrent tests (test.concurrent/describe.concurrent) over sequential when feasible
Organize tests with describe blocks to group related tests
Use utilities like describe.each, toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach) and track resources for cleanup

Files:

  • test/integration/esbuild/esbuild.test.ts
test/**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (test/CLAUDE.md)

For large/repetitive strings, use Buffer.alloc(count, fill).toString() instead of "A".repeat(count)

Files:

  • test/integration/esbuild/esbuild.test.ts
test/integration/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Place integration tests under /test/integration/

Files:

  • test/integration/esbuild/esbuild.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must live under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or invent random port functions
Prefer snapshot assertions and use normalizeBunSnapshot for snapshot output in tests
Never write tests that assert absence of crashes (e.g., no "panic" or "uncaught exception") in output
Use tempDir from "harness" for temporary directories; do not use tmpdirSync or fs.mkdtempSync in tests
When spawning processes in tests, assert on stdout before asserting exitCode
Do not use setTimeout in tests; await conditions instead to avoid flakiness

Files:

  • test/integration/esbuild/esbuild.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid shell commands in tests (e.g., find, grep); use Bun's Glob and built-in tools

Files:

  • test/integration/esbuild/esbuild.test.ts
🧠 Learnings (40)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to src/**/*.zig : Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Run bun run zig:check-all for cross-platform Zig checks when making platform-specific changes

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-04T09:52:49.414Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-10-04T09:52:49.414Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use process.platform and process.arch for platform detection (rely on inlining/dead-code elimination)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Place import statements at the bottom of the file (formatter will handle ordering)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Implement the core feature in Zig under its own directory within src/<feature>/

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-25T22:53:31.261Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-10-25T22:53:31.261Z
Learning: Applies to src/**/*.zig : Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-09-06T03:37:41.154Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22229
File: src/bundler/LinkerGraph.zig:0-0
Timestamp: 2025-09-06T03:37:41.154Z
Learning: In Bun's codebase, when checking import record source indices in src/bundler/LinkerGraph.zig, prefer using `if (import_index >= self.import_records.len)` bounds checking over `isValid()` checks, as the bounds check is more robust and `isValid()` is a strict subset of this condition.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T21:24:52.779Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/crash_handler.zig:1415-1423
Timestamp: 2025-10-16T21:24:52.779Z
Learning: When a boolean EnvVar in src/envvars.zig is defined with a default value (e.g., `.default = false`), the `get()` method returns `bool` instead of `?bool`. This means you cannot distinguish between "environment variable not set" and "environment variable explicitly set to the default value". For opt-out scenarios where detection of explicit setting is needed (like `BUN_ENABLE_CRASH_REPORTING` on platforms where crash reporting defaults to enabled), either: (1) don't provide a default value so `get()` returns `?bool`, or (2) use the returned boolean directly instead of only checking if it's true.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-09-02T17:09:17.391Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/allocators.zig:778-782
Timestamp: 2025-09-02T17:09:17.391Z
Learning: In Zig's bun codebase, the `isDefault` function in `src/allocators.zig` only needs to compare `allocator.vtable == c_allocator.vtable` rather than also checking the `ptr` field, because: (1) the codebase never creates multiple allocators that use `c_allocator.vtable` but have different `ptr`s, and (2) the default allocator vtable ignores the `ptr` field anyway, so any allocators sharing the same vtable would function identically.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.

Applied to files:

  • src/install/postinstall_optimizer.zig
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Use `tempDirWithFiles` (or `tempDir`) from `harness` for temporary directories/files

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to test/**/*.test.{ts,tsx} : Use tempDir from "harness" for temporary directories; do not use tmpdirSync or fs.mkdtempSync in tests

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/esm.test.ts : esm.test.ts should cover ESM feature behavior in development mode

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Prefer concurrent tests (`test.concurrent`/`describe.concurrent`) over sequential when feasible

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/{dev/*.test.ts,dev-and-prod.ts} : Do not use node:fs APIs in tests; mutate files via dev.write, dev.patch, and dev.delete

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/ecosystem.test.ts : ecosystem.test.ts should focus on concrete library integration bugs rather than whole-package coverage

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/third_party/**/*.{js,ts} : Place third-party npm package tests under test/js/third_party/

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Always check exit codes and error scenarios in tests (e.g., spawned processes should assert non-zero on failure)

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use utilities like `describe.each`, `toMatchSnapshot`, and lifecycle hooks (`beforeAll`, `beforeEach`, `afterEach`) and track resources for cleanup

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to test/**/*.{ts,tsx} : Avoid shell commands in tests (e.g., find, grep); use Bun's Glob and built-in tools

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js,test/**/*-fixture.ts} : Use `using`/`await using` for resource cleanup with Bun APIs (e.g., `Bun.spawn`, `Bun.listen`, `Bun.serve`)

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Import common utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, platform helpers, GC helpers)

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending with `*.test.{ts,js,jsx,tsx,mjs,cjs}`

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Do not set explicit test timeouts; Bun already has timeouts

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to test/**/*.test.{ts,tsx} : When spawning processes in tests, assert on stdout before asserting exitCode

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-12T02:22:34.373Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : Prefer async/await; for single callbacks, use `Promise.withResolvers()`

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-26T05:04:50.692Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-26T05:04:50.692Z
Learning: Applies to test/bundler/**/*.test.{ts,tsx} : Bundler/transpiler tests must use the itBundled helper

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/{dev/*.test.ts,dev-and-prod.ts} : Explicitly assert client console logs with c.expectMessage; unasserted logs should not appear

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.

Applied to files:

  • test/integration/esbuild/esbuild.test.ts
🧬 Code graph analysis (1)
test/integration/esbuild/esbuild.test.ts (1)
test/harness.ts (2)
  • tempDir (277-284)
  • bunExe (102-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (14)
test/integration/esbuild/esbuild.test.ts (2)

73-76: LGTM: Promise.all pattern is efficient and correctly ordered.

The refactor to use Promise.all for collecting process outputs in parallel is clean and efficient. The assertion order (checking stdout/stderr before exitCode) correctly follows the testing guidelines.

Also applies to: 87-90, 103-104, 130-134, 145-148, 159-162, 173-176, 187-190


11-191: Consider adding explicit test coverage for postinstall optimizer behavior.

While these tests verify that esbuild installation and execution work correctly, they don't explicitly validate the new postinstall optimizer feature introduced in this PR. Based on the PR summary, tests for "esbuild install without postinstall scripts" are pending.

Consider adding test cases that verify:

  1. Postinstall scripts are skipped for configured packages (esbuild)
  2. Native binlinks work correctly for platform-specific packages
  3. Fallback behavior when platform-specific package is not found
  4. Feature flag overrides (BUN_FEATURE_FLAG_DISABLE_NATIVE_DEPENDENCY_LINKER)

The existing tests provide a baseline that the optimizer doesn't break normal esbuild usage, which is valuable. However, explicit coverage of the new optimization paths would increase confidence in the feature.

src/install/postinstall_optimizer.zig (7)

1-11: LGTM!

The enum definition and default package hashes are well-structured and align with the PR objectives (esbuild for native binlink, sharp for ignore).


13-29: LGTM!

The string array parsing logic correctly handles empty arrays (which disable defaults) and filters/hashes valid package names into the dynamic map.


31-38: LGTM!

The package.json parsing correctly sets disable flags when empty arrays are provided, aligning with the PR requirement that empty arrays disable defaults.


81-106: LGTM!

The lifecycle script skipping logic is correct. For native_binlink packages, it only skips scripts when a platform-specific replacement package is found. For ignore packages, it always skips scripts. This ensures fallback postinstall scripts can run when native binlink replacement fails.


108-120: LGTM!

The default package lookup correctly maps esbuild to native_binlink and sharp to ignore as specified in the PR objectives.


122-141: LGTM!

The get method correctly prioritizes user-configured packages over defaults and respects the disable flags when returning default optimizers.


145-156: LGTM!

Imports follow the Zig coding guideline of placing them at the bottom of the file, and all imported types are used in the implementation.

src/install/PackageInstaller.zig (5)

259-305: LGTM!

The native binlink optimization logic correctly captures package metadata, checks for platform-specific replacements, and sets up retry state when a replacement is found. The .ignore case appropriately does nothing in the linking phase since it only affects lifecycle scripts.


322-368: LGTM!

The retry loop correctly implements a one-time fallback when native binlink replacement fails. The loop prevents infinite retries by resetting the flag, logs the retry when verbose mode is enabled, and properly handles errors with fail-early support.


1156-1190: LGTM!

The lifecycle script handling correctly consults the postinstall optimizer before enqueueing scripts. It passes platform-specific parameters to ensure native binlink packages only skip scripts when a replacement exists, and properly logs when scripts are ignored.


1345-1381: LGTM!

The lifecycle script handling for cached packages mirrors the fresh install logic with appropriate differences in trusted dependency handling. While there is some code duplication with the earlier block (lines 1156-1190), the different trust propagation logic justifies the separate implementations.


1514-1514: LGTM!

The PostinstallOptimizer import follows the existing pattern for importing types from the install module.


var err = await stderr.text();
var out = await stdout.text();
[err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unnecessary Response wrapper for consistency.

Line 73 wraps stderr and stdout with new Response(...) before calling .text(), but all other instances in this file (lines 87, 103, 130, 145, 159, 173, 187) call .text() directly on the streams. The wrapper is unnecessary since Bun's spawn streams already have a .text() method.

Apply this diff:

-[err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
+[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
📝 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.

Suggested change
[err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
🤖 Prompt for AI Agents
In test/integration/esbuild/esbuild.test.ts around line 73, the code wraps
stderr and stdout with new Response(...) before calling .text(), which is
inconsistent with other instances and unnecessary because Bun's spawn streams
already expose .text(); remove the Response wrapper and call .text() directly on
stderr and stdout (i.e., replace new Response(stderr).text() and new
Response(stdout).text() with stderr.text() and stdout.text()) so the Promise.all
call matches the rest of the file.

@Jarred-Sumner
Jarred-Sumner requested a review from alii as a code owner November 1, 2025 11:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
bench/install/README.md (1)

23-23: Replace informal language for consistency with documentation standards.

The phrase "Check out these awesome tutorials" uses informal language. Consider replacing "awesome" with a more professional alternative such as "recommended," "excellent," or "comprehensive."

- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
+ [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these recommended tutorials
bench/install/next.config.js (1)

7-10: Consider adding Next.js optimizations for the benchmark.

The empty configuration uses Next.js defaults, which is valid. However, for a benchmark/install testing setup, consider adding configurations to optimize build performance or enable experimental features relevant to the PR's postinstall optimization work.

Examples of useful additions:

/** @type {import("next").NextConfig} */
const config = {
  // Enable static exports if this is just for testing
  // output: 'export',
  
  // Optimize for faster builds in CI/benchmark environments
  swcMinify: true,
  
  // Reduce bundle size for testing
  reactStrictMode: true,
};
bench/install/package.json (1)

6-19: Consider adding a test script.

The scripts section is well-organized for Next.js development, but lacks a test script. For a benchmark/install testing package, adding a test script would be beneficial for automated verification.

Example:

{
  "scripts": {
    "build": "next build",
    "test": "bun test",
    ...
  }
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 19b010f and eaf98ab.

⛔ Files ignored due to path filters (2)
  • bench/install/bun.lock is excluded by !**/*.lock
  • bench/install/public/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (13)
  • bench/install/README.md (1 hunks)
  • bench/install/app/entry.client.tsx (0 hunks)
  • bench/install/app/entry.server.tsx (0 hunks)
  • bench/install/app/root.tsx (0 hunks)
  • bench/install/app/routes/_index.tsx (0 hunks)
  • bench/install/next-env.d.ts (1 hunks)
  • bench/install/next.config.js (1 hunks)
  • bench/install/package.json (1 hunks)
  • bench/install/remix.config.js (0 hunks)
  • bench/install/remix.env.d.ts (0 hunks)
  • bench/install/tsconfig.json (0 hunks)
  • src/install/PackageInstaller.zig (7 hunks)
  • src/install/isolated_install/Installer.zig (8 hunks)
💤 Files with no reviewable changes (7)
  • bench/install/app/entry.client.tsx
  • bench/install/remix.config.js
  • bench/install/app/entry.server.tsx
  • bench/install/app/root.tsx
  • bench/install/tsconfig.json
  • bench/install/remix.env.d.ts
  • bench/install/app/routes/_index.tsx
✅ Files skipped from review due to trivial changes (1)
  • bench/install/next-env.d.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/PackageInstaller.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

When adding debug logs in Zig, create a scoped logger and log via Bun APIs: const log = bun.Output.scoped(.${SCOPE}, .hidden); then log("...", .{})

src/**/*.zig: Use Zig private fields with the # prefix for encapsulation (e.g., struct { #foo: u32 })
Prefer Decl literals for initialization (e.g., const decl: Decl = .{ .binding = 0, .value = 0 };)
Place @import statements at the bottom of the file (formatter will handle ordering)

src/**/*.zig: In Zig code, manage memory carefully: use appropriate allocators and defer for cleanup
Cache JavaScriptCore class structures in ZigGlobalObject when adding new classes

Files:

  • src/install/isolated_install/Installer.zig
  • src/install/PackageInstaller.zig
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-12T02:22:34.373Z
Learning: Applies to test/{test/**/*.test.{ts,js,jsx,tsx,mjs,cjs},test/js/node/test/{parallel,sequential}/*.js} : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-10-04T09:52:49.414Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use process.platform and process.arch for platform detection (rely on inlining/dead-code elimination)
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.

Applied to files:

  • bench/install/README.md
📚 Learning: 2025-10-08T03:11:45.286Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-10-08T03:11:45.286Z
Learning: Applies to src/**/*.zig : When adding debug logs in Zig, create a scoped logger and log via Bun APIs: `const log = bun.Output.scoped(.${SCOPE}, .hidden);` then `log("...", .{})`

Applied to files:

  • src/install/isolated_install/Installer.zig
🪛 LanguageTool
bench/install/README.md

[style] ~23-~23: Consider using a more formal and expressive alternative to ‘awesome’.
Context: ...-currently-available) — Check out these awesome tutorials You can check out the [creat...

(AWESOME)

🔇 Additional comments (6)
bench/install/README.md (1)

1-29: Verify: Complete content replacement appears misaligned with file purpose and PR objectives.

This file in the bench/install/ directory has been entirely replaced with generic T3 Stack boilerplate documentation. However:

  1. The original content (per AI summary) contained specific benchmarking instructions (hyperfine usage, online/offline modes, sample commands), which are now completely removed.
  2. The new content focuses on Next.js, Prisma, Drizzle, etc.—unrelated to the postinstall optimizer feature described in the PR objectives.
  3. The AI summary mentions "Next.js-based bench setup," but this is ambiguous without seeing the broader context.

This appears to be either:

  • An unintended file modification (wrong file updated during a refactor)
  • A merge conflict resolved incorrectly
  • Part of a larger architectural change to the benchmarking infrastructure (in which case the original instructions should be migrated elsewhere)

Please clarify:

  • Is this change intentional as part of a restructuring of the bench/install directory?
  • Where should developers now find the original benchmarking instructions?
  • Does this align with the PR's goal of adding the postinstall optimizer feature?

If this is indeed intentional, ensure the original benchmarking documentation is available elsewhere or is updated in a companion PR.

bench/install/package.json (5)

2-5: LGTM!

The package metadata is correctly configured with ESM support ("type": "module"), which aligns with Next.js 15 and modern tooling requirements.


49-51: LGTM!

The T3 stack metadata correctly tracks the initialization version for future migrations and upgrades.


47-47: No action required.

TypeScript 5.8.2 was released February 28, 2025, making it a stable, established version as of this PR's date. Next.js 15 officially supports TypeScript and is generally compatible with recent TypeScript releases. The specified @types packages (react 19.0.0, react-dom 19.0.0) are appropriately aligned. No compatibility issues exist.


45-46: PostCSS and Tailwind CSS v4 compatibility verified—no issues found.

The package.json correctly includes both required dependencies: @tailwindcss/postcss (^4.0.15) and postcss (^8.5.3). PostCSS 8.5.3 satisfies the 8.x requirement for Tailwind v4, and the separate PostCSS plugin package is present. The configuration is compatible.


30-30: next-auth beta version may introduce instability if benchmark is completed.

The package uses next-auth@5.0.0-beta.25 with Next.js 15.2.3, which fall within a documented problematic compatibility range. Web searches confirm real issues: route handler export failures, middleware auth() null returns, client bundle bloat, and login state bugs affecting Next.js 15.2.x + next-auth 5.0.0-beta.25.

However, bench/install is a T3 Stack template scaffold with no application code (no src/ directory, no route handlers, no middleware, no client-side auth hooks). It's not actively executed as a benchmark (no install script in parent package.json). The documented bugs won't trigger in the current state, but if this template is completed and deployed, these issues could surface.

Comment thread bench/install/next.config.js Resolved
Comment thread bench/install/package.json Resolved
can_retry_without_native_binlink_optimization = false;
target_node_modules_path.?.deinit();
target_node_modules_path = null;
if (PackageManager.verbose_install) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (PackageManager.verbose_install) {
if (this.manager.options.log_level.isVerbose()) {

Comment on lines +1405 to +1437
.global_bin_path = this.manager.options.bin_path,
.package_name = package_name,
.string_buf = string_buf,
.extern_string_buf = extern_string_buf,
.seen = &seen,
.node_modules_path = &node_modules_path,
.target_node_modules_path = if (target_node_modules_path) |*path| path else &node_modules_path,
.target_package_name = if (target_node_modules_path != null) target_package_name else package_name,
.abs_target_buf = link_target_buf,
.abs_dest_buf = link_dest_buf,
.rel_buf = link_rel_buf,
};

bin_linker.link(false);
bin_linker.link(false);

if (can_retry_without_native_binlink_optimization and (bin_linker.skipped_due_to_missing_bin or bin_linker.err != null)) {
can_retry_without_native_binlink_optimization = false;
target_node_modules_path.?.deinit();
target_node_modules_path = null;
if (PackageManager.verbose_install) {
Output.prettyErrorln("<d>[Bin Linker]<r> {s} -> {s} retrying without native bin link", .{
package_name.slice(),
target_package_name.slice(),
});
}
continue;
}

if (bin_linker.err) |err| {
return err;
if (bin_linker.err) |err| {
return err;
}

break;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be more simple. i'd delete can_retry_without_native_binlink_optimization and the while(true). after the first link do the second link:

if (target_node_modules_path != null and (bin_linker.skipped_due_to_missing_bin or bin_linker.err != null)) {
    target_node_modules_path.?.deinit();
    target_node_modules_path = null;

    bin_linker.target_node_modules_path = &node_modules_path;
    bin_linker.target_package_name = package_name;

    if (PackageManager.verbose_install) {
        Output.prettyErrorln("<d>[Bin Linker]<r> {s} -> {s} retrying without native bin link", .{
            package_name.slice(),
            target_package_name.slice(),
        });
    }

    bin_linker.link(false);
}

pkg_resolutions_buffer: []const PackageID,
pkg_metas: []const Package.Meta,
pkg_id: PackageID,
target_node_modules_path: *?bun.AbsPath(.{}),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
target_node_modules_path: *?bun.AbsPath(.{}),

it doesn't make sense to have this and the return value optional. in my opinion this function should return the ?Entry.Id and if non-null callers would use it to assign target_node_modules_path and create the package name string

) orelse strings.StringOrTinyString.init(dep_name);
var can_retry_without_native_binlink_optimization = target_node_modules_path != null;

while (true) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here: while(true) should be deleted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and can_retry_without_native_binlink_optimization

Comment thread src/install/postinstall_optimizer.zig Resolved
Comment on lines +5 to +11
const default_native_binlinks_name_hashes = &[_]PackageNameHash{
bun.Semver.String.Builder.stringHash("esbuild"),
};

const default_ignore_name_hashes = &[_]PackageNameHash{
bun.Semver.String.Builder.stringHash("sharp"),
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should add a bun pm command to list these like we do for the default trusted list

@Jarred-Sumner
Jarred-Sumner merged commit 528620e into main Nov 4, 2025
59 of 63 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the jarred/postinstall-optimize branch November 4, 2025 04:36
@dylan-conway dylan-conway mentioned this pull request Nov 14, 2025
Jarred-Sumner pushed a commit that referenced this pull request Nov 17, 2025
### What does this PR do?
Fixes a regression introduced in Bun v1.3.2 with #24283.

We are not able to skip `sharp` lifecycle scripts before v0.33.0 because
previous versions did not use optional dependencies with prebuilds.

Fixes #24550
Fixes ENG-21519
### How did you verify your code works?
Manually

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants