Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
37bde0b8b2 [autofix.ci] apply automated fixes 2025-07-16 13:21:45 +00:00
Claude Bot
94740e0fb0 Implement --linker flag for bun install
Add support for --linker flag to set node_linker strategy:
- `bun install --linker isolated` forces isolated node linker
- `bun install --linker hoisted` forces hoisted node linker

Changes:
- Add --linker CLI flag parsing with validation
- Store CLI node_linker override in PackageManagerOptions
- Apply CLI override in two locations where lockfile is initialized
- Add comprehensive tests for flag validation and functionality
- Update help text with examples

The CLI flag takes precedence over package.json and lockfile settings,
ensuring consistent behavior across different project configurations.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 13:15:48 +00:00
6 changed files with 207 additions and 0 deletions

View File

@@ -1102,6 +1102,11 @@ pub fn initWithRuntimeOnce(
} else {
manager.lockfile.initEmpty(allocator);
}
// Apply CLI node_linker override if specified
if (cli.node_linker) |cli_node_linker| {
manager.lockfile.node_linker = cli_node_linker;
}
}
var cwd_buf: bun.PathBuffer = undefined;
pub var package_json_cwd_buf: bun.PathBuffer = undefined;

View File

@@ -47,6 +47,7 @@ const shared_params = [_]ParamType{
clap.parseParam("--save-text-lockfile Save a text-based lockfile") catch unreachable,
clap.parseParam("--omit <dev|optional|peer>... Exclude 'dev', 'optional', or 'peer' dependencies from install") catch unreachable,
clap.parseParam("--lockfile-only Generate a lockfile without installing dependencies") catch unreachable,
clap.parseParam("--linker <isolated|hoisted> Set the node linker strategy") catch unreachable,
clap.parseParam("-h, --help Print this help menu") catch unreachable,
};
@@ -205,6 +206,8 @@ save_text_lockfile: ?bool = null,
lockfile_only: bool = false,
node_linker: ?NodeLinker = null,
// `bun pm version` options
git_tag_version: bool = true,
allow_same_version: bool = false,
@@ -254,6 +257,12 @@ pub fn printHelp(subcommand: Subcommand) void {
\\ <d>Skip devDependencies<r>
\\ <b><green>bun install<r> <cyan>--production<r>
\\
\\ <d>Use isolated node linker<r>
\\ <b><green>bun install<r> <cyan>--linker isolated<r>
\\
\\ <d>Use hoisted node linker<r>
\\ <b><green>bun install<r> <cyan>--linker hoisted<r>
\\
\\Full documentation is available at <magenta>https://bun.com/docs/cli/install<r>.
\\
;
@@ -864,6 +873,13 @@ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !Com
cli.registry = registry;
}
if (args.option("--linker")) |linker| {
cli.node_linker = NodeLinker.fromStr(linker) orelse {
Output.errGeneric("Invalid --linker value: '{}'. Expected 'isolated' or 'hoisted'.\n", .{bun.fmt.quote(linker)});
Global.crash();
};
}
cli.positionals = args.positionals();
if (subcommand == .patch and cli.positionals.len < 2) {
@@ -925,6 +941,7 @@ const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const std = @import("std");
const NodeLinker = @import("../lockfile.zig").NodeLinker;
const JSON = bun.JSON;

View File

@@ -47,6 +47,8 @@ min_simultaneous_requests: usize = 4,
max_concurrent_lifecycle_scripts: usize,
cli_node_linker: ?NodeLinker = null,
publish_config: PublishConfig = .{},
ca: []const string = &.{},
@@ -592,6 +594,9 @@ pub fn load(
this.ca_file_name = cli.ca_file_name;
}
// Store CLI node_linker override for use in lockfile initialization
this.cli_node_linker = cli.node_linker;
// `bun pm version` command options
this.git_tag_version = cli.git_tag_version;
this.allow_same_version = cli.allow_same_version;
@@ -671,3 +676,4 @@ const CommandLineArguments = @import("./CommandLineArguments.zig");
const Subcommand = bun.install.PackageManager.Subcommand;
const PackageManager = bun.install.PackageManager;
const PackageInstall = bun.install.PackageInstall;
const NodeLinker = @import("../lockfile.zig").NodeLinker;

View File

@@ -398,6 +398,11 @@ pub fn installWithManager(
root = .{};
manager.lockfile.initEmpty(manager.allocator);
// Apply CLI node_linker override if specified
if (manager.options.cli_node_linker) |cli_node_linker| {
manager.lockfile.node_linker = cli_node_linker;
}
if (manager.options.enable.frozen_lockfile and load_result != .not_found) {
if (log_level != .silent) {
Output.prettyErrorln("<r><red>error<r>: lockfile had changes, but lockfile is frozen", .{});

View File

@@ -0,0 +1,84 @@
import { spawn } from "bun";
import { expect, test } from "bun:test";
import { existsSync, readFileSync } from "fs";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("bun install --linker isolated creates lockfile with correct node_linker", async () => {
const dir = tempDirWithFiles("linker-isolated-lockfile", {
"package.json": JSON.stringify({
name: "test-linker",
version: "1.0.0",
dependencies: {
"is-number": "^7.0.0",
},
}),
});
await using proc = spawn({
cmd: [bunExe(), "install", "--linker", "isolated"],
cwd: dir,
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
const output = stdout + stderr;
expect(output).not.toContain("Invalid --linker value");
// Check that lockfile was created
const lockbExists = existsSync(`${dir}/bun.lockb`);
const lockExists = existsSync(`${dir}/bun.lock`);
expect(lockbExists || lockExists).toBe(true);
// If text lockfile exists, check it contains the node_linker setting
if (lockExists) {
const lockContent = readFileSync(`${dir}/bun.lock`, "utf8");
expect(lockContent).toContain('"nodeLinker": "isolated"');
}
});
test("bun install --linker hoisted creates lockfile with correct node_linker", async () => {
const dir = tempDirWithFiles("linker-hoisted-lockfile", {
"package.json": JSON.stringify({
name: "test-linker",
version: "1.0.0",
dependencies: {
"is-number": "^7.0.0",
},
}),
});
await using proc = spawn({
cmd: [bunExe(), "install", "--linker", "hoisted"],
cwd: dir,
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
const output = stdout + stderr;
expect(output).not.toContain("Invalid --linker value");
// Check that lockfile was created
const lockbExists = existsSync(`${dir}/bun.lockb`);
const lockExists = existsSync(`${dir}/bun.lock`);
expect(lockbExists || lockExists).toBe(true);
// If text lockfile exists, check it contains the node_linker setting
if (lockExists) {
const lockContent = readFileSync(`${dir}/bun.lock`, "utf8");
expect(lockContent).toContain('"nodeLinker": "hoisted"');
}
});

View File

@@ -0,0 +1,90 @@
import { spawn } from "bun";
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("bun install --linker isolated", async () => {
const dir = tempDirWithFiles("linker-isolated", {
"package.json": JSON.stringify({
name: "test-linker",
version: "1.0.0",
dependencies: {
"is-number": "^7.0.0",
},
}),
});
await using proc = spawn({
cmd: [bunExe(), "install", "--linker", "isolated"],
cwd: dir,
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// Should not show validation errors for valid linker values
const output = stdout + stderr;
expect(output).not.toContain("Invalid --linker value");
});
test("bun install --linker hoisted", async () => {
const dir = tempDirWithFiles("linker-hoisted", {
"package.json": JSON.stringify({
name: "test-linker",
version: "1.0.0",
dependencies: {
"is-number": "^7.0.0",
},
}),
});
await using proc = spawn({
cmd: [bunExe(), "install", "--linker", "hoisted"],
cwd: dir,
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// Should not show validation errors for valid linker values
const output = stdout + stderr;
expect(output).not.toContain("Invalid --linker value");
});
test("bun install --linker invalid should fail", async () => {
const dir = tempDirWithFiles("linker-invalid", {
"package.json": JSON.stringify({
name: "test-linker",
version: "1.0.0",
}),
});
await using proc = spawn({
cmd: [bunExe(), "install", "--linker", "invalid"],
cwd: dir,
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(1);
// Check both stdout and stderr for the error message
const output = stdout + stderr;
expect(output).toContain("Invalid --linker value");
expect(output).toContain("Expected 'isolated' or 'hoisted'");
});