aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cli.zig20
-rw-r--r--src/cli/run_command.zig264
-rw-r--r--src/env_loader.zig14
-rw-r--r--src/install/install.zig2
4 files changed, 222 insertions, 78 deletions
diff --git a/src/cli.zig b/src/cli.zig
index 2a9b31091..fac996b5a 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -163,6 +163,7 @@ pub const Arguments = struct {
const public_params = [_]ParamType{
clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable,
+ clap.parseParam("-b, --bun Force a script or package to use Bun.js instead of Node.js (via symlinking node)") catch unreachable,
clap.parseParam("--bunfile <STR> Use a .bun file (default: node_modules.bun)") catch unreachable,
clap.parseParam("--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun)") catch unreachable,
clap.parseParam("--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable,
@@ -178,7 +179,7 @@ pub const Arguments = struct {
clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable,
clap.parseParam("--no-summary Don't print a summary (when generating .bun") catch unreachable,
clap.parseParam("-v, --version Print version and exit") catch unreachable,
- clap.parseParam("--platform <STR> \"browser\" or \"node\". Defaults to \"browser\"") catch unreachable,
+ clap.parseParam("--platform <STR> \"bun\" or \"browser\" or \"node\", used when building or bundling") catch unreachable,
// clap.parseParam("--production [not implemented] generate production code") catch unreachable,
clap.parseParam("--public-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable,
clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable,
@@ -618,8 +619,12 @@ pub const Arguments = struct {
PlatformMatcher.case("bun") => Api.Platform.bun,
else => invalidPlatform(&diag, _platform),
};
+
+ ctx.debug.run_in_bun = opts.platform.? == .bun;
}
+ ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun;
+
if (jsx_factory != null or
jsx_fragment != null or
jsx_import_source != null or
@@ -815,6 +820,7 @@ pub const Command = struct {
hot_reload: bool = false,
global_cache: options.GlobalCache = .auto,
offline_mode_setting: ?Bunfig.OfflineMode = null,
+ run_in_bun: bool = false,
// technical debt
macros: ?MacroMap = null,
@@ -1207,12 +1213,21 @@ pub const Command = struct {
break :brk null;
};
+ const force_using_bun = ctx.debug.run_in_bun;
+ var did_check = false;
if (default_loader) |loader| {
if (loader.isJavaScriptLike()) {
was_js_like = true;
if (maybeOpenWithBunJS(&ctx)) {
return;
}
+ did_check = true;
+ }
+ }
+
+ if (force_using_bun and !did_check) {
+ if (maybeOpenWithBunJS(&ctx)) {
+ return;
}
}
@@ -1243,6 +1258,9 @@ pub const Command = struct {
}
fn maybeOpenWithBunJS(ctx: *const Command.Context) bool {
+ if (ctx.args.entry_points.len == 0)
+ return false;
+
const script_name_to_search = ctx.args.entry_points[0];
var file_path = script_name_to_search;
diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig
index bbeed40a2..86404c315 100644
--- a/src/cli/run_command.zig
+++ b/src/cli/run_command.zig
@@ -84,6 +84,8 @@ pub const RunCommand = struct {
const BUN_BIN_NAME = if (Environment.isDebug) "bun-debug" else "bun";
const BUN_RUN = std.fmt.comptimePrint("{s} run", .{BUN_BIN_NAME});
+ const BUN_RUN_USING_BUN = std.fmt.comptimePrint("{s} --bun run", .{BUN_BIN_NAME});
+
// Look for invocations of any:
// - yarn run
// - yarn $cmdName
@@ -91,7 +93,10 @@ pub const RunCommand = struct {
// - npm run
// Replace them with "bun run"
- pub inline fn replacePackageManagerRun(copy_script: *std.ArrayList(u8), script: string) !void {
+ pub inline fn replacePackageManagerRun(
+ copy_script: *std.ArrayList(u8),
+ script: string,
+ ) !void {
var entry_i: usize = 0;
var delimiter: u8 = ' ';
@@ -110,7 +115,7 @@ pub const RunCommand = struct {
if (strings.indexOfChar(next, ' ')) |space| {
const yarn_cmd = next[0..space];
if (strings.eqlComptime(yarn_cmd, "run")) {
- try copy_script.appendSlice("bun run");
+ try copy_script.appendSlice(BUN_RUN);
entry_i += "yarn run".len;
continue;
}
@@ -132,7 +137,7 @@ pub const RunCommand = struct {
// implicit yarn commands
if (std.mem.indexOfScalar(u64, yarn_commands, std.hash.Wyhash.hash(0, yarn_cmd)) == null) {
- try copy_script.appendSlice("bun run");
+ try copy_script.appendSlice(BUN_RUN);
try copy_script.append(' ');
try copy_script.appendSlice(yarn_cmd);
entry_i += "yarn ".len + yarn_cmd.len;
@@ -166,7 +171,7 @@ pub const RunCommand = struct {
if (base.len > "npm run ".len) {
const remainder = base[0.."npm run ".len];
if (strings.eqlComptimeIgnoreLen(remainder, "npm run ")) {
- try copy_script.appendSlice("bun run ");
+ try copy_script.appendSlice(BUN_RUN ++ " ");
entry_i += remainder.len;
delimiter = 0;
continue;
@@ -183,7 +188,7 @@ pub const RunCommand = struct {
if (npm_i < script.len) {
const remainder = script[start .. npm_i + 1];
if (remainder.len > npm_i and strings.eqlComptimeIgnoreLen(remainder, "pnpm run") and remainder[remainder.len - 1] == delimiter) {
- try copy_script.appendSlice("bun run ");
+ try copy_script.appendSlice(BUN_RUN ++ " ");
entry_i += remainder.len;
delimiter = 0;
continue;
@@ -411,6 +416,68 @@ pub const RunCommand = struct {
this_bundler.configureLinker();
}
+ const bun_node_dir = switch (@import("builtin").target.os.tag) {
+ // TODO:
+ .windows => "TMPDIR",
+
+ .macos => "/private/tmp",
+ else => "/tmp",
+ } ++ if (!Environment.isDebug)
+ "/bun-node"
+ else
+ "/bun-debug-node";
+
+ var self_exe_bin_path_buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
+ fn createFakeTemporaryNodeExecutable(PATH: *std.ArrayList(u8), optional_bun_path: *string) !void {
+ var retried = false;
+
+ if (!strings.endsWithComptime(std.mem.span(std.os.argv[0]), "node")) {
+ var argv0 = std.meta.assumeSentinel(optional_bun_path.*, 0).ptr;
+
+ // if we are already an absolute path, use that
+ // if the user started the application via a shebang, it's likely that the path is absolute already
+ if (std.os.argv[0][0] == '/') {
+ optional_bun_path.* = bun.span(std.os.argv[0]);
+ argv0 = std.os.argv[0];
+ } else if (optional_bun_path.len == 0) {
+ // otherwise, ask the OS for the absolute path
+ var self = std.fs.selfExePath(&self_exe_bin_path_buf) catch "";
+ if (self.len > 0) {
+ self.ptr[self.len] = 0;
+ argv0 = std.meta.assumeSentinel(self, 0).ptr;
+ optional_bun_path.* = self;
+ }
+ }
+
+ if (optional_bun_path.len == 0) {
+ argv0 = std.os.argv[0];
+ }
+
+ while (true) {
+ inner: {
+ std.os.symlinkZ(argv0, bun_node_dir ++ "/node") catch |err| {
+ if (err == error.PathAlreadyExists) break :inner;
+ if (retried)
+ return;
+
+ std.fs.makeDirAbsoluteZ(bun_node_dir) catch {};
+
+ retried = true;
+ continue;
+ };
+ }
+ _ = bun.C.chmod(bun_node_dir ++ "/node", 0o777);
+ break;
+ }
+ }
+
+ if (PATH.items.len > 0) {
+ try PATH.append(':');
+ }
+
+ try PATH.appendSlice(bun_node_dir ++ ":");
+ }
+
pub const Filter = enum { script, bin, all, bun_js, all_plus_bun_js, script_and_descriptions, script_exclude };
const DirInfo = @import("../resolver/dir_info.zig");
pub fn configureEnvForRun(
@@ -419,6 +486,7 @@ pub const RunCommand = struct {
env: ?*DotEnv.Loader,
ORIGINAL_PATH: *string,
log_errors: bool,
+ force_using_bun: bool,
) !*DirInfo {
var args = ctx.args;
args.node_modules_bundle_path = null;
@@ -493,6 +561,14 @@ pub const RunCommand = struct {
var PATH = this_bundler.env.map.get("PATH") orelse "";
ORIGINAL_PATH.* = PATH;
+ const found_node = this_bundler.env.loadNodeJSConfig(
+ this_bundler.fs,
+ if (force_using_bun) bun_node_dir ++ "/node" else "",
+ ) catch false;
+
+ var needs_to_force_bun = force_using_bun or !found_node;
+ var optional_bun_self_path: string = "";
+
if (bin_dirs.len > 0 or package_json_dir.len > 0) {
var new_path_len: usize = PATH.len + 2;
for (bin_dirs) |bin| {
@@ -503,8 +579,21 @@ pub const RunCommand = struct {
new_path_len += package_json_dir.len + 1;
}
+ new_path_len += if (needs_to_force_bun) bun_node_dir.len + 1 else 0;
+
var new_path = try std.ArrayList(u8).initCapacity(ctx.allocator, new_path_len);
+ if (needs_to_force_bun) {
+ createFakeTemporaryNodeExecutable(&new_path, &optional_bun_self_path) catch unreachable;
+ if (!force_using_bun) {
+ this_bundler.env.map.put("NODE", bun_node_dir ++ "/node") catch unreachable;
+ this_bundler.env.map.put("npm_node_execpath", bun_node_dir ++ "/node") catch unreachable;
+ this_bundler.env.map.put("npm_execpath", optional_bun_self_path) catch unreachable;
+ }
+
+ needs_to_force_bun = false;
+ }
+
{
var needs_colon = false;
if (package_json_dir.len > 0) {
@@ -537,7 +626,25 @@ pub const RunCommand = struct {
PATH = new_path.items;
}
- this_bundler.env.loadNodeJSConfig(this_bundler.fs) catch {};
+ if (needs_to_force_bun) {
+ needs_to_force_bun = false;
+
+ var new_path = try std.ArrayList(u8).initCapacity(ctx.allocator, PATH.len);
+ createFakeTemporaryNodeExecutable(&new_path, &optional_bun_self_path) catch unreachable;
+ if (new_path.items.len > 0)
+ try new_path.append(':');
+ try new_path.appendSlice(PATH);
+
+ this_bundler.env.map.put("PATH", new_path.items) catch unreachable;
+
+ if (!force_using_bun) {
+ this_bundler.env.map.put("NODE", bun_node_dir ++ "/node") catch unreachable;
+ this_bundler.env.map.put("npm_node_execpath", bun_node_dir ++ "/node") catch unreachable;
+ this_bundler.env.map.put("npm_execpath", optional_bun_self_path) catch unreachable;
+ }
+ PATH = new_path.items;
+ }
+
this_bundler.env.map.putDefault("npm_config_local_prefix", this_bundler.fs.top_level_dir) catch unreachable;
// we have no way of knowing what version they're expecting without running the node executable
@@ -793,75 +900,84 @@ pub const RunCommand = struct {
}
const passthrough = ctx.passthrough;
+ const force_using_bun = ctx.debug.run_in_bun;
- if (comptime log_errors) {
+ if (log_errors or force_using_bun) {
if (script_name_to_search.len > 0) {
possibly_open_with_bun_js: {
- if (options.defaultLoaders.get(std.fs.path.extension(script_name_to_search))) |loader| {
- if (loader.isJavaScriptLike()) {
- var file_path = script_name_to_search;
- const file_: std.fs.File.OpenError!std.fs.File = brk: {
- if (script_name_to_search[0] == std.fs.path.sep) {
- break :brk std.fs.openFileAbsolute(script_name_to_search, .{ .mode = .read_only });
- } else {
- const cwd = std.os.getcwd(&path_buf) catch break :possibly_open_with_bun_js;
- path_buf[cwd.len] = std.fs.path.sep;
- var parts = [_]string{script_name_to_search};
- file_path = resolve_path.joinAbsStringBuf(
- path_buf[0 .. cwd.len + 1],
- &path_buf2,
- &parts,
- .auto,
- );
- if (file_path.len == 0) break :possibly_open_with_bun_js;
- path_buf2[file_path.len] = 0;
- var file_pathZ = path_buf2[0..file_path.len :0];
- break :brk std.fs.openFileAbsoluteZ(file_pathZ, .{ .mode = .read_only });
- }
- };
-
- const file = file_ catch break :possibly_open_with_bun_js;
- // "White space after #! is optional."
- var shebang_buf: [64]u8 = undefined;
- const shebang_size = file.pread(&shebang_buf, 0) catch |err| {
- Output.prettyErrorln("<r><red>error<r>: Failed to read file <b>{s}<r> due to error <b>{s}<r>", .{ file_path, @errorName(err) });
- Global.exit(1);
- };
-
- var shebang: string = shebang_buf[0..shebang_size];
- shebang = std.mem.trim(u8, shebang, " \r\n\t");
- if (shebang.len == 0) break :possibly_open_with_bun_js;
-
- if (shebang.len > 2 and strings.eqlComptimeIgnoreLen(shebang[0..2], "#!")) {
- const first_arg: string = if (std.os.argv.len > 0) bun.span(std.os.argv[0]) else "";
- const filename = std.fs.path.basename(first_arg);
- // are we attempting to run the script with bun?
- if (!strings.contains(shebang, filename)) {
- break :possibly_open_with_bun_js;
- }
- }
- Global.configureAllocator(.{ .long_running = true });
-
- Run.boot(ctx, file, ctx.allocator.dupe(u8, file_path) catch unreachable) catch |err| {
- if (Output.enable_ansi_colors) {
- ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
- } else {
- ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
- }
+ if (!force_using_bun) {
+ if (options.defaultLoaders.get(std.fs.path.extension(script_name_to_search))) |load| {
+ if (!load.isJavaScriptLike())
+ break :possibly_open_with_bun_js;
+ } else {
+ break :possibly_open_with_bun_js;
+ }
+ }
- Output.prettyErrorln("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{
- std.fs.path.basename(file_path),
- @errorName(err),
- });
- Global.exit(1);
- };
+ var file_path = script_name_to_search;
- return true;
+ const file_: std.fs.File.OpenError!std.fs.File = brk: {
+ if (script_name_to_search[0] == std.fs.path.sep) {
+ break :brk std.fs.openFileAbsolute(script_name_to_search, .{ .mode = .read_only });
+ } else {
+ const cwd = std.os.getcwd(&path_buf) catch break :possibly_open_with_bun_js;
+ path_buf[cwd.len] = std.fs.path.sep;
+ var parts = [_]string{script_name_to_search};
+ file_path = resolve_path.joinAbsStringBuf(
+ path_buf[0 .. cwd.len + 1],
+ &path_buf2,
+ &parts,
+ .auto,
+ );
+ if (file_path.len == 0) break :possibly_open_with_bun_js;
+ path_buf2[file_path.len] = 0;
+ var file_pathZ = path_buf2[0..file_path.len :0];
+ break :brk std.fs.openFileAbsoluteZ(file_pathZ, .{ .mode = .read_only });
+ }
+ };
- // If we get here, then we run it with bun.js
+ const file = file_ catch break :possibly_open_with_bun_js;
+ // ignore the shebang if they explicitly passed `--bun`
+ if (!force_using_bun) {
+ // "White space after #! is optional."
+ var shebang_buf: [64]u8 = undefined;
+ const shebang_size = file.pread(&shebang_buf, 0) catch |err| {
+ Output.prettyErrorln("<r><red>error<r>: Failed to read file <b>{s}<r> due to error <b>{s}<r>", .{ file_path, @errorName(err) });
+ Global.exit(1);
+ };
+
+ var shebang: string = shebang_buf[0..shebang_size];
+ shebang = std.mem.trim(u8, shebang, " \r\n\t");
+ if (shebang.len == 0) break :possibly_open_with_bun_js;
+
+ if (shebang.len > 2 and strings.eqlComptimeIgnoreLen(shebang[0..2], "#!")) {
+ const first_arg: string = if (std.os.argv.len > 0) bun.span(std.os.argv[0]) else "";
+ const filename = std.fs.path.basename(first_arg);
+ // are we attempting to run the script with bun?
+ if (!strings.contains(shebang, filename)) {
+ break :possibly_open_with_bun_js;
+ }
}
}
+
+ Global.configureAllocator(.{ .long_running = true });
+
+ Run.boot(ctx, file, ctx.allocator.dupe(u8, file_path) catch unreachable) catch |err| {
+ if (Output.enable_ansi_colors) {
+ ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {};
+ } else {
+ ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
+ }
+
+ Output.prettyErrorln("<r><red>error<r>: Failed to run <b>{s}<r> due to error <b>{s}<r>", .{
+ std.fs.path.basename(file_path),
+ @errorName(err),
+ });
+ Global.exit(1);
+ };
+
+ return true;
}
}
}
@@ -871,7 +987,7 @@ pub const RunCommand = struct {
var did_print = false;
var ORIGINAL_PATH: string = "";
var this_bundler: bundler.Bundler = undefined;
- var root_dir_info = try configureEnvForRun(ctx, &this_bundler, null, &ORIGINAL_PATH, log_errors);
+ var root_dir_info = try configureEnvForRun(ctx, &this_bundler, null, &ORIGINAL_PATH, log_errors, force_using_bun);
if (root_dir_info.enclosing_package_json) |package_json| {
if (package_json.scripts) |scripts| {
switch (script_name_to_search.len) {
@@ -908,6 +1024,7 @@ pub const RunCommand = struct {
else => {
if (scripts.get(script_name_to_search)) |script_content| {
// allocate enough to hold "post${scriptname}"
+
var temp_script_buffer = try std.fmt.allocPrint(ctx.allocator, "ppre{s}", .{script_name_to_search});
if (scripts.get(temp_script_buffer[1..])) |prescript| {
@@ -924,7 +1041,10 @@ pub const RunCommand = struct {
}
}
- if (!try runPackageScript(
+ std.mem.copy(u8, temp_script_buffer, "post");
+ const postscript_ = scripts.get(temp_script_buffer);
+
+ if (!try runPackageScriptWithIsLast(
ctx.allocator,
script_content,
script_name_to_search,
@@ -932,12 +1052,11 @@ pub const RunCommand = struct {
this_bundler.env,
passthrough,
ctx.debug.silent,
+ postscript_ == null,
)) return false;
- std.mem.copy(u8, temp_script_buffer, "post");
-
- if (scripts.get(temp_script_buffer)) |postscript| {
- if (!try runPackageScript(
+ if (postscript_) |postscript| {
+ if (!try runPackageScriptWithIsLast(
ctx.allocator,
postscript,
temp_script_buffer,
@@ -945,6 +1064,7 @@ pub const RunCommand = struct {
this_bundler.env,
passthrough,
ctx.debug.silent,
+ true,
)) {
return false;
}
diff --git a/src/env_loader.zig b/src/env_loader.zig
index 222d98910..b2ee6815a 100644
--- a/src/env_loader.zig
+++ b/src/env_loader.zig
@@ -431,13 +431,17 @@ pub const Loader = struct {
this.map.get("bamboo.buildKey")) != null;
}
- pub fn loadNodeJSConfig(this: *Loader, fs: *Fs.FileSystem) !void {
+ pub fn loadNodeJSConfig(this: *Loader, fs: *Fs.FileSystem, override_node: []const u8) !bool {
var buf: Fs.PathBuffer = undefined;
- var node = this.getNodePath(fs, &buf) orelse return;
- var cloned = try fs.dirname_store.append([]const u8, std.mem.span(node));
- try this.map.put("NODE", cloned);
- try this.map.put("npm_node_execpath", cloned);
+ var node_path_to_use = override_node;
+ if (node_path_to_use.len == 0) {
+ var node = this.getNodePath(fs, &buf) orelse return false;
+ node_path_to_use = try fs.dirname_store.append([]const u8, std.mem.span(node));
+ }
+ try this.map.put("NODE", node_path_to_use);
+ try this.map.put("npm_node_execpath", node_path_to_use);
+ return true;
}
pub fn get(this: *const Loader, key: string) ?string {
diff --git a/src/install/install.zig b/src/install/install.zig
index 592eaf14b..758585015 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -6549,6 +6549,7 @@ pub const PackageManager = struct {
manager.env,
&ORIGINAL_PATH,
log_level != .silent,
+ false,
);
}
@@ -6676,6 +6677,7 @@ pub const PackageManager = struct {
manager.env,
&ORIGINAL_PATH,
log_level != .silent,
+ false,
);
} else {
// bun install may have installed new bins, so we need to update the PATH