Compare commits

...

6 Commits

Author SHA1 Message Date
autofix-ci[bot]
eb3013565a [autofix.ci] apply automated fixes 2025-08-15 20:01:37 +00:00
Claude Bot
ef78ff1b12 fix: resolve git merge conflict markers in bun-install.test.ts 2025-08-15 19:59:28 +00:00
autofix-ci[bot]
bfc098410e [autofix.ci] apply automated fixes 2025-08-15 17:42:47 +00:00
Claude Bot
2287ec045c implement --bin-links flag for bun install
Add support for controlling symlink creation for package executables:
- CLI flags: --bin-links (default) and --no-bin-links
- bunfig.toml: binLinks setting (camelCase only)
- Default: true (maintains existing behavior)
- Works with both hoisted and isolated installs

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 17:41:02 +00:00
autofix-ci[bot]
343292ef5e [autofix.ci] apply automated fixes 2025-08-15 08:55:17 +00:00
Claude Bot
c3091a6395 Implement --bin-links flag for bun install
This implements the --bin-links flag for bun install to control whether
symlinks (or .cmd shims on Windows) are created for package executables.

Features implemented:
- --bin-links and --no-bin-links CLI flags
- bin-links / binLinks setting in bunfig.toml
- BUN_CONFIG_BIN_LINKS, NPM_CONFIG_BIN_LINKS, npm_config_bin_links env vars
- Works with both hoisted and isolated install strategies
- Default value is true (existing behavior)

The flag follows npm's bin-links behavior and can be used to work around
file systems that don't support symlinks.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 08:53:29 +00:00
10 changed files with 153 additions and 5 deletions

View File

@@ -592,6 +592,7 @@ message BunInstall {
bool frozen_lockfile = 19;
bool exact = 20;
uint32 concurrent_scripts = 21;
bool bin_links = 22;
}
struct ClientServerModule {

View File

@@ -3041,6 +3041,8 @@ pub const api = struct {
node_linker: ?bun.install.PackageManager.Options.NodeLinker = null,
bin_links: ?bool = null,
pub fn decode(reader: anytype) anyerror!BunInstall {
var this = std.mem.zeroes(BunInstall);
@@ -3113,6 +3115,9 @@ pub const api = struct {
21 => {
this.concurrent_scripts = try reader.readValue(u32);
},
22 => {
this.bin_links = try reader.readValue(bool);
},
else => {
return error.InvalidMessage;
},
@@ -3206,6 +3211,10 @@ pub const api = struct {
try writer.writeFieldID(21);
try writer.writeInt(concurrent_scripts);
}
if (this.bin_links) |bin_links| {
try writer.writeFieldID(22);
try writer.writeInt(@as(u8, @intFromBool(bin_links)));
}
try writer.endMessage();
}
};

View File

@@ -609,6 +609,12 @@ pub const Bunfig = struct {
install.link_workspace_packages = value;
}
}
if (install_obj.get("binLinks")) |bin_links| {
if (bin_links.asBool()) |value| {
install.bin_links = value;
}
}
}
if (json.get("run")) |run_expr| {

View File

@@ -151,7 +151,9 @@ fn link(ctx: Command.Context) !void {
.abs_dest_buf = &link_dest_buf,
.rel_buf = &link_rel_buf,
};
bin_linker.link(true);
if (manager.options.bin_links) {
bin_linker.link(true);
}
if (bin_linker.err) |err| {
if (manager.options.log_level != .silent)

View File

@@ -112,7 +112,9 @@ fn unlink(ctx: Command.Context) !void {
.abs_dest_buf = &link_dest_buf,
.rel_buf = &link_rel_buf,
};
bin_linker.unlink(true);
if (manager.options.bin_links) {
bin_linker.unlink(true);
}
}
// delete it if it exists

View File

@@ -301,7 +301,9 @@ pub const PackageInstaller = struct {
break :global false;
};
bin_linker.link(global);
if (this.manager.options.bin_links) {
bin_linker.link(global);
}
if (bin_linker.err) |err| {
if (log_level != .silent) {

View File

@@ -50,6 +50,8 @@ const shared_params = [_]ParamType{
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 <STR> Linker strategy (one of \"isolated\" or \"hoisted\")") catch unreachable,
clap.parseParam("--bin-links Create symlinks (or .cmd shims on Windows) for package executables (default true)") catch unreachable,
clap.parseParam("--no-bin-links Don't create symlinks for package executables") catch unreachable,
clap.parseParam("-h, --help Print this help menu") catch unreachable,
};
@@ -227,6 +229,8 @@ lockfile_only: bool = false,
node_linker: ?Options.NodeLinker = null,
bin_links: ?bool = null,
// `bun pm version` options
git_tag_version: bool = true,
allow_same_version: bool = false,
@@ -766,6 +770,12 @@ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !Com
cli.node_linker = .fromStr(linker);
}
if (args.flag("--bin-links")) {
cli.bin_links = true;
} else if (args.flag("--no-bin-links")) {
cli.bin_links = false;
}
if (args.option("--cache-dir")) |cache_dir| {
cli.cache_dir = cache_dir;
}

View File

@@ -71,6 +71,9 @@ depth: ?usize = null,
/// isolated installs (pnpm-like) or hoisted installs (yarn-like, original)
node_linker: NodeLinker = .auto,
/// whether to create symlinks (or .cmd shims on Windows) for package executables
bin_links: bool = true,
pub const PublishConfig = struct {
access: ?Access = null,
tag: string = "",
@@ -279,6 +282,10 @@ pub fn load(
this.node_linker = node_linker;
}
if (config.bin_links) |bin_links| {
this.bin_links = bin_links;
}
if (config.cafile) |cafile| {
this.ca_file_name = cafile;
}
@@ -543,6 +550,10 @@ pub fn load(
this.node_linker = node_linker;
}
if (cli.bin_links) |bin_links| {
this.bin_links = bin_links;
}
const disable_progress_bar = default_disable_progress_bar or cli.no_progress;
if (cli.verbose) {

View File

@@ -950,7 +950,9 @@ pub const Installer = struct {
.rel_buf = rel_buf,
};
bin_linker.link(false);
if (this.installer.manager.options.bin_links) {
bin_linker.link(false);
}
if (bin_linker.err) |err| {
return .failure(.{ .binaries = err });
@@ -1183,7 +1185,9 @@ pub const Installer = struct {
.rel_buf = link_rel_buf,
};
bin_linker.link(false);
if (this.manager.options.bin_links) {
bin_linker.link(false);
}
if (bin_linker.err) |err| {
return err;

View File

@@ -8595,3 +8595,104 @@ test("non-optional dependencies need to be resolvable in text lockfile", async (
expect(await exited).toBe(1);
});
it("should respect --bin-links and --no-bin-links flags", async () => {
const package_json = JSON.stringify({
name: "test-bin-links",
dependencies: {
"prettier": "^2.0.0",
},
});
await writeFile(join(package_dir, "package.json"), package_json);
// Test with --no-bin-links
{
await rm(join(package_dir, "node_modules"), { recursive: true, force: true });
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "--no-bin-links", "--registry", "https://registry.npmjs.org/"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await exited;
const stderrText = await stderr.text();
const stdoutText = await stdout.text();
if (exitCode !== 0) {
console.log("STDERR:", stderrText);
console.log("STDOUT:", stdoutText);
}
expect(exitCode).toBe(0);
expect(await exists(join(package_dir, "node_modules", ".bin"))).toBe(false);
}
// Test with --bin-links (default behavior)
{
await rm(join(package_dir, "node_modules"), { recursive: true, force: true });
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "--bin-links", "--registry", "https://registry.npmjs.org/"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await exited;
expect(exitCode).toBe(0);
expect(await exists(join(package_dir, "node_modules", ".bin"))).toBe(true);
expect(await exists(join(package_dir, "node_modules", ".bin", "prettier"))).toBe(true);
}
});
it("should respect binLinks setting from bunfig.toml", async () => {
const package_json = JSON.stringify({
name: "test-bin-links-config",
dependencies: {
"prettier": "^2.0.0",
},
});
await writeFile(join(package_dir, "package.json"), package_json);
// Test with binLinks = false in bunfig.toml
{
await writeFile(join(package_dir, "bunfig.toml"), "[install]\nbinLinks = false");
await rm(join(package_dir, "node_modules"), { recursive: true, force: true });
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "--registry", "https://registry.npmjs.org/"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
expect(await exited).toBe(0);
expect(await exists(join(package_dir, "node_modules", ".bin"))).toBe(false);
}
// Test with binLinks = true in bunfig.toml
{
await writeFile(join(package_dir, "bunfig.toml"), "[install]\nbinLinks = true");
await rm(join(package_dir, "node_modules"), { recursive: true, force: true });
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "--registry", "https://registry.npmjs.org/"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
expect(await exited).toBe(0);
expect(await exists(join(package_dir, "node_modules", ".bin"))).toBe(true);
expect(await exists(join(package_dir, "node_modules", ".bin", "prettier"))).toBe(true);
}
// Clean up bunfig.toml
await rm(join(package_dir, "bunfig.toml"), { force: true });
});