mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
46 Commits
claude/nod
...
nektro-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
418c83e072 | ||
|
|
57b79151d3 | ||
|
|
65dcdff8cf | ||
|
|
4302a43374 | ||
|
|
d71846757c | ||
|
|
1916a68cfd | ||
|
|
b475a769eb | ||
|
|
3a8691fe65 | ||
|
|
e4ecd46bd6 | ||
|
|
d0cab7c108 | ||
|
|
8817c77124 | ||
|
|
868c694157 | ||
|
|
51a7a01c7e | ||
|
|
30c5f39d4c | ||
|
|
3438f9b5ab | ||
|
|
a2a8f95d41 | ||
|
|
709afb9c71 | ||
|
|
02db0e43df | ||
|
|
935146e9ab | ||
|
|
b67739b7db | ||
|
|
632bdee900 | ||
|
|
7f6394e761 | ||
|
|
1936940043 | ||
|
|
9168a64f61 | ||
|
|
53ed865927 | ||
|
|
65889dc803 | ||
|
|
1590631cf9 | ||
|
|
a6f15e4277 | ||
|
|
1fc547fdd9 | ||
|
|
49ee19cb41 | ||
|
|
40dcadf0da | ||
|
|
e8115529d3 | ||
|
|
7c4616b2a1 | ||
|
|
b8b8b26f85 | ||
|
|
714331909f | ||
|
|
b3e53e1eb5 | ||
|
|
5332a17551 | ||
|
|
203022e04c | ||
|
|
c0e84c9076 | ||
|
|
c861a6fcda | ||
|
|
f1a61ceb8e | ||
|
|
d2ff2cc1dc | ||
|
|
0b11991811 | ||
|
|
186456788e | ||
|
|
d6b815268d | ||
|
|
131bea538d |
2
.github/workflows/bun-linux-build.yml
vendored
2
.github/workflows/bun-linux-build.yml
vendored
@@ -253,6 +253,8 @@ jobs:
|
||||
# # Core filenames will be of the form executable.pid.timestamp:
|
||||
# sudo bash -c 'echo "/cores/%e.%p.%t" > /proc/sys/kernel/core_pattern'
|
||||
|
||||
- id: verdaccio
|
||||
run: bun run verdaccio &
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
env:
|
||||
|
||||
2
.github/workflows/bun-mac-aarch64.yml
vendored
2
.github/workflows/bun-mac-aarch64.yml
vendored
@@ -422,6 +422,8 @@ jobs:
|
||||
bun install --verbose
|
||||
bun install --cwd=test --verbose
|
||||
bun install --cwd=packages/bun-internal-test --verbose
|
||||
- id: verdaccio
|
||||
run: bun run verdaccio &
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
env:
|
||||
|
||||
2
.github/workflows/bun-mac-x64-baseline.yml
vendored
2
.github/workflows/bun-mac-x64-baseline.yml
vendored
@@ -409,6 +409,8 @@ jobs:
|
||||
bun install --verbose
|
||||
bun install --cwd=test --verbose
|
||||
bun install --cwd=packages/bun-internal-test --verbose
|
||||
- id: verdaccio
|
||||
run: bun run verdaccio &
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
env:
|
||||
|
||||
2
.github/workflows/bun-mac-x64.yml
vendored
2
.github/workflows/bun-mac-x64.yml
vendored
@@ -406,6 +406,8 @@ jobs:
|
||||
bun install --verbose
|
||||
bun install --cwd=test --verbose
|
||||
bun install --cwd=packages/bun-internal-test --verbose
|
||||
- id: verdaccio
|
||||
run: bun run verdaccio &
|
||||
- id: test
|
||||
name: Test (node runner)
|
||||
env:
|
||||
|
||||
2
.github/workflows/bun-windows.yml
vendored
2
.github/workflows/bun-windows.yml
vendored
@@ -430,6 +430,8 @@ jobs:
|
||||
cd test && npm install
|
||||
cd ../packages/bun-internal-test && npm install
|
||||
cd ../..
|
||||
- id: verdaccio
|
||||
run: bun run verdaccio &
|
||||
- id: test
|
||||
name: Run tests
|
||||
env:
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"lint:fix": "eslint './**/*.d.ts' --cache --fix",
|
||||
"test": "node packages/bun-internal-test/src/runner.node.mjs ./build/bun-debug",
|
||||
"test:release": "node packages/bun-internal-test/src/runner.node.mjs ./build-release/bun",
|
||||
"verdaccio": "verdaccio -c test/cli/install/registry/verdaccio.yaml",
|
||||
"update-known-failures": "node packages/bun-internal-test/src/update-known-windows-failures.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,21 +277,18 @@ pub const StandaloneModuleGraph = struct {
|
||||
}.toClean;
|
||||
|
||||
const cloned_executable_fd: bun.FileDescriptor = brk: {
|
||||
var self_buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
|
||||
const self_exe = std.fs.selfExePath(&self_buf) catch |err| {
|
||||
const self_exeZ = bun.selfExePath() catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get self executable path: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
};
|
||||
self_buf[self_exe.len] = 0;
|
||||
const self_exeZ = self_buf[0..self_exe.len :0];
|
||||
|
||||
if (comptime Environment.isWindows) {
|
||||
// copy self and then open it for writing
|
||||
|
||||
var in_buf: bun.WPathBuffer = undefined;
|
||||
strings.copyU8IntoU16(&in_buf, self_exeZ);
|
||||
in_buf[self_exe.len] = 0;
|
||||
const in = in_buf[0..self_exe.len :0];
|
||||
in_buf[self_exeZ.len] = 0;
|
||||
const in = in_buf[0..self_exeZ.len :0];
|
||||
var out_buf: bun.WPathBuffer = undefined;
|
||||
strings.copyU8IntoU16(&out_buf, zname);
|
||||
out_buf[zname.len] = 0;
|
||||
@@ -733,10 +730,8 @@ pub const StandaloneModuleGraph = struct {
|
||||
.mac => {
|
||||
// Use of MAX_PATH_BYTES here is valid as the resulting path is immediately
|
||||
// opened with no modification.
|
||||
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
const self_exe_path = try std.fs.selfExePath(&buf);
|
||||
buf[self_exe_path.len] = 0;
|
||||
const file = try std.fs.openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, .{});
|
||||
const self_exe_path = try bun.selfExePath();
|
||||
const file = try std.fs.openFileAbsoluteZ(self_exe_path.ptr, .{});
|
||||
return bun.toFD(file.handle);
|
||||
},
|
||||
.windows => {
|
||||
|
||||
@@ -1261,9 +1261,10 @@ pub const Subprocess = struct {
|
||||
} else {
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
if (@intFromPtr(pipe.signal.ptr) == @intFromPtr(subprocess)) {
|
||||
if (pipe.signal.ptr == @as(?*anyopaque, @ptrCast(this))) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
|
||||
return pipe.toJSWithDestructor(
|
||||
globalThis,
|
||||
JSC.WebCore.SinkDestructor.Ptr.init(subprocess),
|
||||
@@ -1283,8 +1284,11 @@ pub const Subprocess = struct {
|
||||
|
||||
return switch (this.*) {
|
||||
.pipe => |pipe| {
|
||||
pipe.deref();
|
||||
if (pipe.signal.ptr == @as(?*anyopaque, @ptrCast(this))) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
|
||||
pipe.deref();
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.buffer => {
|
||||
|
||||
@@ -5757,13 +5757,8 @@ pub const NodeFS = struct {
|
||||
const target: [:0]u8 = args.old_path.sliceZWithForceCopy(&this.sync_error_buf, true);
|
||||
// UV does not normalize slashes in symlink targets, but Node does
|
||||
// See https://github.com/oven-sh/bun/issues/8273
|
||||
//
|
||||
// TODO: investigate if simd can be easily used here
|
||||
for (target) |*c| {
|
||||
if (c.* == '/') {
|
||||
c.* = '\\';
|
||||
}
|
||||
}
|
||||
bun.path.posixToPlatformInPlace(u8, target);
|
||||
|
||||
return Syscall.symlinkUV(
|
||||
target,
|
||||
args.new_path.sliceZ(&to_buf),
|
||||
|
||||
@@ -4852,12 +4852,10 @@ pub const Process = struct {
|
||||
}
|
||||
|
||||
pub fn getExecPath(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
const out = std.fs.selfExePath(&buf) catch {
|
||||
const out = bun.selfExePath() catch {
|
||||
// if for any reason we are unable to get the executable path, we just return argv[0]
|
||||
return getArgv0(globalObject);
|
||||
};
|
||||
|
||||
return JSC.ZigString.fromUTF8(out).toValueGC(globalObject);
|
||||
}
|
||||
|
||||
@@ -4935,7 +4933,7 @@ pub const Process = struct {
|
||||
bun.String.static("bun"),
|
||||
);
|
||||
} else {
|
||||
const exe_path = std.fs.selfExePathAlloc(allocator) catch null;
|
||||
const exe_path = bun.selfExePath() catch null;
|
||||
args_list.appendAssumeCapacity(
|
||||
if (exe_path) |str| bun.String.fromUTF8(str) else bun.String.static("bun"),
|
||||
);
|
||||
|
||||
17
src/bun.zig
17
src/bun.zig
@@ -1555,7 +1555,7 @@ pub fn reloadProcess(
|
||||
}
|
||||
|
||||
// we must clone selfExePath incase the argv[0] was not an absolute path (what appears in the terminal)
|
||||
const exec_path = (allocator.dupeZ(u8, std.fs.selfExePathAlloc(allocator) catch unreachable) catch unreachable).ptr;
|
||||
const exec_path = (allocator.dupeZ(u8, bun.selfExePath() catch unreachable) catch unreachable).ptr;
|
||||
|
||||
// we clone argv so that the memory address isn't the same as the libc one
|
||||
const newargv = @as([*:null]?[*:0]const u8, @ptrCast(dupe_argv.ptr));
|
||||
@@ -2803,6 +2803,21 @@ pub fn linuxKernelVersion() Semver.Version {
|
||||
return @import("./analytics.zig").GenerateHeader.GeneratePlatform.kernelVersion();
|
||||
}
|
||||
|
||||
pub fn selfExePath() ![:0]u8 {
|
||||
const memo = struct {
|
||||
var set = false;
|
||||
// this is lame; open issues to fix std soon
|
||||
var value: [4096]u8 = undefined;
|
||||
var len: usize = 0;
|
||||
};
|
||||
if (memo.set) return memo.value[0..memo.len :0];
|
||||
const init = try std.fs.selfExePath(&memo.value);
|
||||
memo.len = init.len;
|
||||
memo.value[memo.len] = 0;
|
||||
memo.set = true;
|
||||
return memo.value[0..memo.len :0];
|
||||
}
|
||||
|
||||
pub const WindowsSpawnWorkaround = @import("./child_process_windows.zig");
|
||||
|
||||
pub const exe_suffix = if (Environment.isWindows) ".exe" else "";
|
||||
|
||||
@@ -1218,11 +1218,11 @@ pub const Command = struct {
|
||||
};
|
||||
|
||||
pub fn isBunX(argv0: []const u8) bool {
|
||||
return strings.endsWithComptime(argv0, "bunx") or (Environment.isDebug and strings.endsWithComptime(argv0, "bunx-debug"));
|
||||
return strings.endsWithComptime(argv0, "bunx" ++ bun.exe_suffix);
|
||||
}
|
||||
|
||||
pub fn isNode(argv0: []const u8) bool {
|
||||
return strings.endsWithComptime(argv0, "node");
|
||||
return strings.endsWithComptime(argv0, "node" ++ bun.exe_suffix);
|
||||
}
|
||||
|
||||
pub fn which() Tag {
|
||||
|
||||
@@ -3,6 +3,6 @@ const PackageManager = @import("../install/install.zig").PackageManager;
|
||||
|
||||
pub const AddCommand = struct {
|
||||
pub fn exec(ctx: Command.Context) !void {
|
||||
try PackageManager.add(ctx);
|
||||
try PackageManager.exec(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -423,18 +423,11 @@ pub const BunxCommand = struct {
|
||||
|
||||
debug("bunx_cache_dir: {s}", .{bunx_cache_dir});
|
||||
|
||||
const bin_extension = switch (Environment.os) {
|
||||
.windows => ".exe",
|
||||
.mac => "",
|
||||
.linux => "",
|
||||
.wasm => "",
|
||||
};
|
||||
|
||||
var absolute_in_cache_dir_buf: bun.PathBuffer = undefined;
|
||||
var absolute_in_cache_dir = std.fmt.bufPrint(
|
||||
&absolute_in_cache_dir_buf,
|
||||
bun.pathLiteral("{s}/node_modules/.bin/{s}{s}"),
|
||||
.{ bunx_cache_dir, initial_bin_name, bin_extension },
|
||||
.{ bunx_cache_dir, initial_bin_name, bun.exe_suffix },
|
||||
) catch return error.PathTooLong;
|
||||
|
||||
const passthrough = passthrough_list.items;
|
||||
@@ -523,7 +516,7 @@ pub const BunxCommand = struct {
|
||||
if (getBinName(&this_bundler, root_dir_fd, bunx_cache_dir, initial_bin_name)) |package_name_for_bin| {
|
||||
// if we check the bin name and its actually the same, we don't need to check $PATH here again
|
||||
if (!strings.eqlLong(package_name_for_bin, initial_bin_name, true)) {
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, "{s}/node_modules/.bin/{s}{s}", .{ bunx_cache_dir, package_name_for_bin, bin_extension }) catch unreachable;
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, "{s}/node_modules/.bin/{s}{s}", .{ bunx_cache_dir, package_name_for_bin, bun.exe_suffix }) catch unreachable;
|
||||
|
||||
// Only use the system-installed version if there is no version specified
|
||||
if (update_request.version.literal.isEmpty()) {
|
||||
@@ -572,7 +565,7 @@ pub const BunxCommand = struct {
|
||||
}
|
||||
|
||||
var args = std.BoundedArray([]const u8, 7).fromSlice(&.{
|
||||
try std.fs.selfExePathAlloc(ctx.allocator),
|
||||
try bun.selfExePath(),
|
||||
"add",
|
||||
install_param,
|
||||
"--no-summary",
|
||||
@@ -635,7 +628,7 @@ pub const BunxCommand = struct {
|
||||
},
|
||||
}
|
||||
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, bun.pathLiteral("{s}/node_modules/.bin/{s}{s}"), .{ bunx_cache_dir, initial_bin_name, bin_extension }) catch unreachable;
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, bun.pathLiteral("{s}/node_modules/.bin/{s}{s}"), .{ bunx_cache_dir, initial_bin_name, bun.exe_suffix }) catch unreachable;
|
||||
|
||||
// Similar to "npx":
|
||||
//
|
||||
@@ -663,7 +656,7 @@ pub const BunxCommand = struct {
|
||||
// 2. The "bin" is possibly not the same as the package name, so we load the package.json to figure out what "bin" to use
|
||||
if (getBinNameFromTempDirectory(&this_bundler, bunx_cache_dir, result_package_name)) |package_name_for_bin| {
|
||||
if (!strings.eqlLong(package_name_for_bin, initial_bin_name, true)) {
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, "{s}/node_modules/.bin/{s}{s}", .{ bunx_cache_dir, package_name_for_bin, bin_extension }) catch unreachable;
|
||||
absolute_in_cache_dir = std.fmt.bufPrint(&absolute_in_cache_dir_buf, "{s}/node_modules/.bin/{s}{s}", .{ bunx_cache_dir, package_name_for_bin, bun.exe_suffix }) catch unreachable;
|
||||
|
||||
if (bun.which(
|
||||
&path_buf,
|
||||
|
||||
@@ -1402,7 +1402,7 @@ pub const CreateCommand = struct {
|
||||
if (!create_options.skip_install) {
|
||||
npm_client_ = NPMClient{
|
||||
.tag = .bun,
|
||||
.bin = try std.fs.selfExePathAlloc(ctx.allocator),
|
||||
.bin = try bun.selfExePath(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -427,7 +427,7 @@ pub const InitCommand = struct {
|
||||
if (exists("package.json")) {
|
||||
var process = std.ChildProcess.init(
|
||||
&.{
|
||||
try std.fs.selfExePathAlloc(alloc),
|
||||
try bun.selfExePath(),
|
||||
"install",
|
||||
},
|
||||
alloc,
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
const Command = @import("../cli.zig").Command;
|
||||
const bun = @import("root").bun;
|
||||
const PackageManager = @import("../install/install.zig").PackageManager;
|
||||
|
||||
pub const InstallCommand = struct {
|
||||
pub fn exec(ctx: Command.Context) !void {
|
||||
PackageManager.install(ctx) catch |err| switch (err) {
|
||||
error.InstallFailed,
|
||||
error.InvalidPackageJSON,
|
||||
=> {
|
||||
const log = &bun.CLI.Cli.log_;
|
||||
log.printForLogLevel(bun.Output.errorWriter()) catch {};
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
try PackageManager.exec(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
const bunx_name = if (Environment.isDebug) "bunx-debug" else "bunx";
|
||||
|
||||
fn installBunxSymlinkPosix(allocator: std.mem.Allocator, cwd: []const u8) !void {
|
||||
fn installBunxSymlinkPosix(cwd: []const u8) !void {
|
||||
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
|
||||
// don't install it if it's already there
|
||||
@@ -54,7 +54,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
return;
|
||||
|
||||
// first try installing the symlink into the same directory as the bun executable
|
||||
const exe = try std.fs.selfExePathAlloc(allocator);
|
||||
const exe = try bun.selfExePath();
|
||||
var target_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
var target = std.fmt.bufPrint(&target_buf, "{s}/" ++ bunx_name, .{std.fs.path.dirname(exe).?}) catch unreachable;
|
||||
std.os.symlink(exe, target) catch {
|
||||
@@ -89,7 +89,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn installBunxSymlinkWindows(_: std.mem.Allocator, _: []const u8) !void {
|
||||
fn installBunxSymlinkWindows(_: []const u8) !void {
|
||||
// Because symlinks are not always allowed on windows,
|
||||
// `bunx.exe` on windows is a hardlink to `bun.exe`
|
||||
// for this to work, we need to delete and recreate the hardlink every time
|
||||
@@ -130,11 +130,11 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn installBunxSymlink(allocator: std.mem.Allocator, cwd: []const u8) !void {
|
||||
fn installBunxSymlink(cwd: []const u8) !void {
|
||||
if (Environment.isWindows) {
|
||||
try installBunxSymlinkWindows(allocator, cwd);
|
||||
try installBunxSymlinkWindows(cwd);
|
||||
} else {
|
||||
try installBunxSymlinkPosix(allocator, cwd);
|
||||
try installBunxSymlinkPosix(cwd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
Global.exit(fail_exit_code);
|
||||
};
|
||||
|
||||
installBunxSymlink(allocator, cwd) catch {};
|
||||
installBunxSymlink(cwd) catch {};
|
||||
|
||||
if (Environment.isWindows) {
|
||||
installUninstallerWindows() catch {};
|
||||
|
||||
@@ -636,7 +636,7 @@ pub const RunCommand = struct {
|
||||
argv0 = bun.argv()[0];
|
||||
} else if (optional_bun_path.len == 0) {
|
||||
// otherwise, ask the OS for the absolute path
|
||||
var self = try std.fs.selfExePath(&self_exe_bin_path_buf);
|
||||
var self = try bun.selfExePath();
|
||||
if (self.len > 0) {
|
||||
self.ptr[self.len] = 0;
|
||||
argv0 = @as([*:0]const u8, @ptrCast(self.ptr));
|
||||
@@ -731,6 +731,9 @@ pub const RunCommand = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PATH.items.len > 0 and PATH.items[PATH.items.len - 1] != std.fs.path.delimiter) {
|
||||
try PATH.append(std.fs.path.delimiter);
|
||||
}
|
||||
|
||||
// The reason for the extra delim is because we are going to append the system PATH
|
||||
// later on. this is done by the caller, and explains why we are adding bun_node_dir
|
||||
@@ -827,7 +830,7 @@ pub const RunCommand = struct {
|
||||
|
||||
if (this_bundler.env.get("npm_execpath") == null) {
|
||||
// we don't care if this fails
|
||||
if (std.fs.selfExePathAlloc(ctx.allocator)) |self_exe_path| {
|
||||
if (bun.selfExePath()) |self_exe_path| {
|
||||
this_bundler.env.map.putDefault("npm_execpath", self_exe_path) catch unreachable;
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
@@ -756,7 +756,7 @@ pub const UpgradeCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const destination_executable = std.fs.selfExePath(¤t_executable_buf) catch return error.UpgradeFailedMissingExecutable;
|
||||
const destination_executable = bun.selfExePath() catch return error.UpgradeFailedMissingExecutable;
|
||||
current_executable_buf[destination_executable.len] = 0;
|
||||
|
||||
const target_filename_ = std.fs.path.basename(destination_executable);
|
||||
|
||||
@@ -388,7 +388,7 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD
|
||||
.npm => folder_name[name.len + 1 ..],
|
||||
else => folder_name,
|
||||
},
|
||||
.{},
|
||||
.{ .is_directory = true },
|
||||
) catch break :create_index;
|
||||
}
|
||||
|
||||
|
||||
@@ -1361,19 +1361,13 @@ pub const PackageInstall = struct {
|
||||
if (comptime Environment.isWindows) {
|
||||
var fd_path_buf: bun.PathBuffer = undefined;
|
||||
|
||||
// buf[0] = '\\';
|
||||
// buf[1] = '\\';
|
||||
// buf[2] = '?';
|
||||
// buf[3] = '\\';
|
||||
// buf[0..4].* = bun.windows.nt_maxpath_prefix;
|
||||
// const dest_path = try bun.getFdPath(subdir.fd, &fd_path_buf);
|
||||
// strings.copyU8IntoU16(buf[4..], dest_path);
|
||||
// buf[dest_path.len + 4] = '\\';
|
||||
// to_copy_buf = buf[dest_path.len + 5 ..];
|
||||
|
||||
// buf2[0] = '\\';
|
||||
// buf2[1] = '\\';
|
||||
// buf2[2] = '?';
|
||||
// buf2[3] = '\\';
|
||||
// buf2[0..4].* = bun.windows.nt_maxpath_prefix;
|
||||
// const cache_path = try bun.getFdPath(cached_package_dir.fd, &fd_path_buf);
|
||||
// strings.copyU8IntoU16(buf2[4..], cache_path);
|
||||
// buf2[cache_path.len + 4] = '\\';
|
||||
@@ -2554,9 +2548,6 @@ pub const PackageManager = struct {
|
||||
}
|
||||
|
||||
pub fn ensureTempNodeGypScript(this: *PackageManager) !void {
|
||||
if (Environment.isWindows) {
|
||||
Output.debug("TODO: VERIFY ensureTempNodeGypScript WORKS!!", .{});
|
||||
}
|
||||
if (this.node_gyp_tempdir_name.len > 0) return;
|
||||
|
||||
const tempdir = this.getTemporaryDirectory();
|
||||
@@ -2592,26 +2583,34 @@ pub const PackageManager = struct {
|
||||
};
|
||||
defer node_gyp_file.close();
|
||||
|
||||
var bytes: string = switch (Environment.os) {
|
||||
else => "#!/usr/bin/env node\nrequire(\"child_process\").spawnSync(\"bun\",[\"x\",\"node-gyp\",...process.argv.slice(2)],{stdio:\"inherit\"})",
|
||||
.windows => "@node -e \"require('child_process').spawnSync('bun',['x','node-gyp',...process.argv.slice(2)],{stdio:'inherit'})\"",
|
||||
const shebang = switch (Environment.os) {
|
||||
.windows =>
|
||||
\\0</* :{
|
||||
\\ @echo off
|
||||
\\ node %~f0 %*
|
||||
\\ exit /b %errorlevel%
|
||||
\\:} */0;
|
||||
\\
|
||||
,
|
||||
else =>
|
||||
\\#!/usr/bin/env node
|
||||
\\
|
||||
,
|
||||
};
|
||||
const content =
|
||||
\\const child_process = require("child_process");
|
||||
\\child_process.spawnSync("bun", ["x", "node-gyp", ...process.argv.slice(2)], { stdio: "inherit" });
|
||||
\\
|
||||
;
|
||||
|
||||
node_gyp_file.writeAll(shebang ++ content) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r>: <b><red>{s}<r> writing to " ++ file_name ++ " file", .{@errorName(err)});
|
||||
Global.crash();
|
||||
};
|
||||
var index: usize = 0;
|
||||
while (index < bytes.len) {
|
||||
switch (bun.sys.write(bun.toFD(node_gyp_file.handle), bytes[index..])) {
|
||||
.result => |written| {
|
||||
index += written;
|
||||
},
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("<r><red>error<r>: <b><red>{s}<r> writing to " ++ file_name ++ " file", .{@tagName(err.getErrno())});
|
||||
Global.crash();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Add our node-gyp tempdir to the path
|
||||
const existing_path = this.env.get("PATH") orelse "";
|
||||
var PATH = try std.ArrayList(u8).initCapacity(bun.default_allocator, existing_path.len + 1 + this.node_gyp_tempdir_name.len);
|
||||
var PATH = try std.ArrayList(u8).initCapacity(bun.default_allocator, existing_path.len + 1 + this.temp_dir_name.len + 1 + this.node_gyp_tempdir_name.len);
|
||||
try PATH.appendSlice(existing_path);
|
||||
if (existing_path.len > 0 and existing_path[existing_path.len - 1] != std.fs.path.delimiter)
|
||||
try PATH.append(std.fs.path.delimiter);
|
||||
@@ -2620,15 +2619,16 @@ pub const PackageManager = struct {
|
||||
try PATH.appendSlice(this.node_gyp_tempdir_name);
|
||||
try this.env.map.put("PATH", PATH.items);
|
||||
|
||||
const node_gyp_abs_dir = try bun.fmt.bufPrint(&path_buf, "{s}" ++ .{std.fs.path.sep} ++ "{s}", .{
|
||||
const npm_config_node_gyp = try bun.fmt.bufPrint(&path_buf, "{s}{s}{s}{s}{s}", .{
|
||||
strings.withoutTrailingSlash(this.temp_dir_name),
|
||||
std.fs.path.sep_str,
|
||||
strings.withoutTrailingSlash(this.node_gyp_tempdir_name),
|
||||
std.fs.path.sep_str,
|
||||
file_name,
|
||||
});
|
||||
try this.env.map.putAllocKeyAndValue(this.allocator, "BUN_WHICH_IGNORE_CWD", node_gyp_abs_dir);
|
||||
|
||||
path_buf[node_gyp_abs_dir.len] = std.fs.path.sep;
|
||||
@memcpy(path_buf[node_gyp_abs_dir.len + 1 ..][0.."node-gyp".len], "node-gyp");
|
||||
const npm_config_node_gyp = path_buf[0 .. node_gyp_abs_dir.len + 1 + "node-gyp".len];
|
||||
const node_gyp_abs_dir = std.fs.path.dirname(npm_config_node_gyp).?;
|
||||
try this.env.map.putAllocKeyAndValue(this.allocator, "BUN_WHICH_IGNORE_CWD", node_gyp_abs_dir);
|
||||
try this.env.map.putAllocKeyAndValue(this.allocator, "npm_config_node_gyp", npm_config_node_gyp);
|
||||
}
|
||||
|
||||
@@ -7016,7 +7016,7 @@ pub const PackageManager = struct {
|
||||
switch (bun.sys.sys_uv.symlinkUV(
|
||||
link_path,
|
||||
dest_path,
|
||||
bun.windows.libuv.UV_FS_SYMLINK_JUNCTION,
|
||||
bun.windows.libuv.UV_FS_SYMLINK_DIR,
|
||||
)) {
|
||||
.err => |err| {
|
||||
Output.prettyErrorln("<r><red>error:<r> failed to create junction to node_modules in global dir due to error {}", .{err});
|
||||
@@ -8128,6 +8128,19 @@ pub const PackageManager = struct {
|
||||
var package_json_cwd_buf: bun.PathBuffer = undefined;
|
||||
pub var package_json_cwd: string = "";
|
||||
|
||||
pub fn exec(ctx: Command.Context) !void {
|
||||
install(ctx) catch |err| switch (err) {
|
||||
error.InstallFailed,
|
||||
error.InvalidPackageJSON,
|
||||
=> {
|
||||
const log = &bun.CLI.Cli.log_;
|
||||
log.printForLogLevel(bun.Output.errorWriter()) catch {};
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn install(ctx: Command.Context) !void {
|
||||
var manager = try init(ctx, .install);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
this.current_script_index = next_script_index;
|
||||
this.has_called_process_exit = false;
|
||||
|
||||
const shell_bin = bun.CLI.RunCommand.findShell(env.get("PATH") orelse "", cwd) orelse return error.MissingShell;
|
||||
const shell_bin = bun.CLI.RunCommand.findShell(env.get("PATH") orelse "", cwd) orelse null;
|
||||
|
||||
var copy_script = try std.ArrayList(u8).initCapacity(manager.allocator, original_script.script.len + 1);
|
||||
defer copy_script.deinit();
|
||||
@@ -131,13 +131,40 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
|
||||
const combined_script: [:0]u8 = copy_script.items[0 .. copy_script.items.len - 1 :0];
|
||||
|
||||
var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
var path_buf2: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
const lifecycle_script_path, var lifecycle_script_file = if (Environment.isWindows or shell_bin == null) blk: {
|
||||
const tempdir = this.manager.getTemporaryDirectory();
|
||||
var lifecycle_script_name = bun.span(try bun.fs.FileSystem.instance.tmpname("lifecycle-script", &path_buf, 12345));
|
||||
@memcpy(path_buf[lifecycle_script_name.len..].ptr, ".bun.sh\x00");
|
||||
lifecycle_script_name.len += 7;
|
||||
|
||||
const lifecycle_script_file = try tempdir.createFile(lifecycle_script_name, .{});
|
||||
errdefer lifecycle_script_file.close();
|
||||
bun.path.platformToPosixInPlace(u8, combined_script);
|
||||
try lifecycle_script_file.writeAll(combined_script);
|
||||
const lifecycle_script_path = try bun.getFdPath(lifecycle_script_file.handle, &path_buf2);
|
||||
path_buf2[lifecycle_script_path.len] = 0;
|
||||
|
||||
break :blk .{
|
||||
path_buf2[0..lifecycle_script_path.len :0],
|
||||
@as(?std.fs.File, lifecycle_script_file),
|
||||
};
|
||||
} else .{ "", null };
|
||||
if (lifecycle_script_file) |*f| f.close();
|
||||
|
||||
log("{s} - {s} $ {s}", .{ this.package_name, this.scriptName(), combined_script });
|
||||
|
||||
var argv = [_]?[*:0]const u8{
|
||||
shell_bin,
|
||||
if (Environment.isWindows) "/c" else "-c",
|
||||
var argv = if (shell_bin != null and !Environment.isWindows) [_]?[*:0]const u8{
|
||||
shell_bin.?,
|
||||
"-c",
|
||||
combined_script,
|
||||
null,
|
||||
} else [_]?[*:0]const u8{
|
||||
try bun.selfExePath(),
|
||||
"run",
|
||||
lifecycle_script_path,
|
||||
null,
|
||||
};
|
||||
if (Environment.isWindows) {
|
||||
this.stdout.source = .{ .pipe = bun.default_allocator.create(uv.Pipe) catch bun.outOfMemory() };
|
||||
@@ -166,6 +193,7 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
.windows = if (Environment.isWindows)
|
||||
.{
|
||||
.loop = JSC.EventLoopHandle.init(&manager.event_loop),
|
||||
.verbatim_arguments = true,
|
||||
}
|
||||
else {},
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//! use undefined memory.
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const string = []const u8;
|
||||
const bun = @import("root").bun;
|
||||
const os = std.os;
|
||||
const Arena = std.heap.ArenaAllocator;
|
||||
@@ -487,16 +488,23 @@ pub const CowEnvMap = Cow(EnvMap, struct {
|
||||
|
||||
pub const EnvMap = struct {
|
||||
map: MapType,
|
||||
|
||||
pub const Iterator = MapType.Iterator;
|
||||
|
||||
const MapType = std.ArrayHashMap(EnvStr, EnvStr, struct {
|
||||
pub fn hash(self: @This(), s: EnvStr) u32 {
|
||||
_ = self;
|
||||
if (bun.Environment.isWindows) {
|
||||
return bun.CaseInsensitiveASCIIStringContext.hash(undefined, s.slice());
|
||||
}
|
||||
return std.array_hash_map.hashString(s.slice());
|
||||
}
|
||||
pub fn eql(self: @This(), a: EnvStr, b: EnvStr, b_index: usize) bool {
|
||||
_ = self;
|
||||
_ = b_index;
|
||||
if (bun.Environment.isWindows) {
|
||||
return bun.CaseInsensitiveASCIIStringContext.eql(undefined, a.slice(), b.slice(), undefined);
|
||||
}
|
||||
return std.array_hash_map.eqlString(a.slice(), b.slice());
|
||||
}
|
||||
}, true);
|
||||
@@ -1148,18 +1156,21 @@ pub const Interpreter = struct {
|
||||
|
||||
pub fn initAndRunFromFile(mini: *JSC.MiniEventLoop, path: []const u8) !bun.shell.ExitCode {
|
||||
var arena = bun.ArenaAllocator.init(bun.default_allocator);
|
||||
defer arena.deinit();
|
||||
const src = src: {
|
||||
var file = try std.fs.cwd().openFile(path, .{});
|
||||
defer file.close();
|
||||
break :src try file.reader().readAllAlloc(arena.allocator(), std.math.maxInt(u32));
|
||||
};
|
||||
defer arena.deinit();
|
||||
return initAndRunFromFileSource(mini, &arena, path, src);
|
||||
}
|
||||
|
||||
pub fn initAndRunFromFileSource(mini: *JSC.MiniEventLoop, arena: *bun.ArenaAllocator, path: string, src: string) !bun.shell.ExitCode {
|
||||
const jsobjs: []JSValue = &[_]JSValue{};
|
||||
var out_parser: ?bun.shell.Parser = null;
|
||||
var out_lex_result: ?bun.shell.LexResult = null;
|
||||
const script = ThisInterpreter.parse(
|
||||
&arena,
|
||||
arena,
|
||||
src,
|
||||
jsobjs,
|
||||
&[_]bun.String{},
|
||||
@@ -1183,7 +1194,7 @@ pub const Interpreter = struct {
|
||||
};
|
||||
const script_heap = try arena.allocator().create(ast.Script);
|
||||
script_heap.* = script;
|
||||
var interp = switch (ThisInterpreter.init(.{ .mini = mini }, bun.default_allocator, &arena, script_heap, jsobjs)) {
|
||||
var interp = switch (ThisInterpreter.init(.{ .mini = mini }, bun.default_allocator, arena, script_heap, jsobjs)) {
|
||||
.err => |*e| {
|
||||
throwShellErr(e, .{ .mini = mini });
|
||||
return 1;
|
||||
@@ -3784,7 +3795,9 @@ pub const Interpreter = struct {
|
||||
args_slice: ?[]const [:0]const u8 = null,
|
||||
cwd: bun.FileDescriptor,
|
||||
|
||||
impl: union(Kind) {
|
||||
impl: RealImpl,
|
||||
|
||||
const RealImpl = union(Kind) {
|
||||
cat: Cat,
|
||||
touch: Touch,
|
||||
mkdir: Mkdir,
|
||||
@@ -3796,7 +3809,8 @@ pub const Interpreter = struct {
|
||||
rm: Rm,
|
||||
mv: Mv,
|
||||
ls: Ls,
|
||||
},
|
||||
exit: Exit,
|
||||
};
|
||||
|
||||
const Result = @import("../result.zig").Result;
|
||||
|
||||
@@ -3812,6 +3826,7 @@ pub const Interpreter = struct {
|
||||
rm,
|
||||
mv,
|
||||
ls,
|
||||
exit,
|
||||
|
||||
pub fn parentType(this: Kind) type {
|
||||
_ = this;
|
||||
@@ -3830,6 +3845,7 @@ pub const Interpreter = struct {
|
||||
.rm => "usage: rm [-f | -i] [-dIPRrvWx] file ...\n unlink [--] file\n",
|
||||
.mv => "usage: mv [-f | -i | -n] [-hv] source target\n mv [-f | -i | -n] [-v] source ... directory\n",
|
||||
.ls => "usage: ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]\n",
|
||||
.exit => "usage: exit [n]\n",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3846,6 +3862,7 @@ pub const Interpreter = struct {
|
||||
.rm => "rm",
|
||||
.mv => "mv",
|
||||
.ls => "ls",
|
||||
.exit => "exit",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4007,6 +4024,7 @@ pub const Interpreter = struct {
|
||||
.pwd => this.callImplWithType(Pwd, Ret, "pwd", field, args_),
|
||||
.mv => this.callImplWithType(Mv, Ret, "mv", field, args_),
|
||||
.ls => this.callImplWithType(Ls, Ret, "ls", field, args_),
|
||||
.exit => this.callImplWithType(Exit, Ret, "exit", field, args_),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4076,26 +4094,6 @@ pub const Interpreter = struct {
|
||||
};
|
||||
|
||||
switch (kind) {
|
||||
.cat => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.cat = Cat{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.touch => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.touch = Touch{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.mkdir => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.mkdir = Mkdir{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.@"export" => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.@"export" = Export{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.rm => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.rm = Rm{
|
||||
@@ -4112,36 +4110,10 @@ pub const Interpreter = struct {
|
||||
},
|
||||
};
|
||||
},
|
||||
.cd => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.cd = Cd{
|
||||
.bltn = &cmd.exec.bltn,
|
||||
},
|
||||
};
|
||||
},
|
||||
.which => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.which = Which{
|
||||
.bltn = &cmd.exec.bltn,
|
||||
},
|
||||
};
|
||||
},
|
||||
.pwd => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.pwd = Pwd{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.mv => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.mv = Mv{ .bltn = &cmd.exec.bltn },
|
||||
};
|
||||
},
|
||||
.ls => {
|
||||
cmd.exec.bltn.impl = .{
|
||||
.ls = Ls{
|
||||
.bltn = &cmd.exec.bltn,
|
||||
},
|
||||
};
|
||||
inline else => |tag| {
|
||||
cmd.exec.bltn.impl = @unionInit(RealImpl, @tagName(tag), .{
|
||||
.bltn = &cmd.exec.bltn,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8521,6 +8493,84 @@ pub const Interpreter = struct {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const Exit = struct {
|
||||
bltn: *Builtin,
|
||||
state: enum {
|
||||
idle,
|
||||
waiting_io,
|
||||
err,
|
||||
done,
|
||||
} = .idle,
|
||||
|
||||
pub fn start(this: *Exit) Maybe(void) {
|
||||
const args = this.bltn.argsSlice();
|
||||
switch (args.len) {
|
||||
0 => {
|
||||
this.bltn.done(0);
|
||||
return Maybe(void).success;
|
||||
},
|
||||
1 => {
|
||||
const first_arg = args[0][0..std.mem.len(args[0]) :0];
|
||||
const exit_code: ExitCode = std.fmt.parseInt(u8, first_arg, 10) catch |err| switch (err) {
|
||||
error.Overflow => @intCast((std.fmt.parseInt(usize, first_arg, 10) catch return this.fail("exit: numeric argument required")) % 256),
|
||||
error.InvalidCharacter => return this.fail("exit: numeric argument required"),
|
||||
};
|
||||
this.bltn.done(exit_code);
|
||||
return Maybe(void).success;
|
||||
},
|
||||
else => {
|
||||
return this.fail("exit: too many arguments");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fail(this: *Exit, msg: string) Maybe(void) {
|
||||
if (this.bltn.stderr.needsIO()) {
|
||||
this.state = .waiting_io;
|
||||
this.bltn.stderr.enqueue(this, msg);
|
||||
return Maybe(void).success;
|
||||
}
|
||||
_ = this.bltn.writeNoIO(.stderr, msg);
|
||||
this.bltn.done(1);
|
||||
return Maybe(void).success;
|
||||
}
|
||||
|
||||
pub fn next(this: *Exit) void {
|
||||
while (!(this.state == .err or this.state == .done)) {
|
||||
switch (this.state) {
|
||||
.waiting_io => return,
|
||||
.idle, .done, .err => unreachable,
|
||||
}
|
||||
}
|
||||
if (this.state == .done) {
|
||||
this.bltn.done(1);
|
||||
return;
|
||||
}
|
||||
if (this.state == .err) {
|
||||
this.bltn.done(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onIOWriterChunk(this: *Exit, _: usize, maybe_e: ?JSC.SystemError) void {
|
||||
if (comptime bun.Environment.allow_assert) {
|
||||
std.debug.assert(this.state == .waiting_io);
|
||||
}
|
||||
if (maybe_e) |e| {
|
||||
defer e.deref();
|
||||
this.state = .err;
|
||||
this.next();
|
||||
return;
|
||||
}
|
||||
this.state = .done;
|
||||
this.next();
|
||||
}
|
||||
|
||||
pub fn deinit(this: *Exit) void {
|
||||
_ = this;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// This type is reference counted, but deinitialization is queued onto the event loop
|
||||
@@ -9459,7 +9509,7 @@ inline fn fastMod(val: anytype, comptime rhs: comptime_int) @TypeOf(val) {
|
||||
return val & (rhs - 1);
|
||||
}
|
||||
|
||||
fn throwShellErr(e: *const bun.shell.ShellErr, event_loop: JSC.EventLoopHandle) void {
|
||||
pub fn throwShellErr(e: *const bun.shell.ShellErr, event_loop: JSC.EventLoopHandle) void {
|
||||
switch (event_loop) {
|
||||
.mini => e.throwMini(),
|
||||
.js => e.throwJS(event_loop.js.global),
|
||||
@@ -9515,6 +9565,7 @@ pub const IOWriterChildPtr = struct {
|
||||
Interpreter.Builtin.Touch,
|
||||
Interpreter.Builtin.Touch.ShellTouchOutputTask,
|
||||
Interpreter.Builtin.Cat,
|
||||
Interpreter.Builtin.Exit,
|
||||
shell.subproc.PipeReader.CapturedWriter,
|
||||
});
|
||||
|
||||
|
||||
@@ -108,23 +108,20 @@ pub const ShellErr = union(enum) {
|
||||
pub fn throwMini(this: @This()) void {
|
||||
defer this.deinit(bun.default_allocator);
|
||||
switch (this) {
|
||||
.sys => {
|
||||
const err = this.sys;
|
||||
const str = std.fmt.allocPrint(bun.default_allocator, "bunsh: {s}: {}", .{ err.message, err.path }) catch bun.outOfMemory();
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error <b>{s}<r>", .{str});
|
||||
.sys => |err| {
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error: <b>bunsh: {s}: {}<r>", .{ err.message, err.path });
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
.custom => {
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error <b>{s}<r>", .{this.custom});
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error: <b>{s}<r>", .{this.custom});
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
.invalid_arguments => {
|
||||
const str = std.fmt.allocPrint(bun.default_allocator, "bunsh: invalid arguments: {s}", .{this.invalid_arguments.val}) catch bun.outOfMemory();
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error <b>{s}<r>", .{str});
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error: <b>bunsh: invalid arguments: {s}<r>", .{this.invalid_arguments.val});
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
.todo => {
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error <b>TODO: {s}<r>", .{this.todo});
|
||||
bun.Output.prettyErrorln("<r><red>error<r>: Failed to due to error: <b>TODO: {s}<r>", .{this.todo});
|
||||
bun.Global.exit(1);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -698,6 +698,10 @@ pub inline fn endsWith(self: string, str: string) bool {
|
||||
return str.len == 0 or @call(bun.callmod_inline, std.mem.endsWith, .{ u8, self, str });
|
||||
}
|
||||
|
||||
pub inline fn endsWithGeneric(comptime T: type, self: []const T, str: []const T) bool {
|
||||
return str.len == 0 or @call(bun.callmod_inline, std.mem.endsWith, .{ T, self, str });
|
||||
}
|
||||
|
||||
pub inline fn endsWithComptime(self: string, comptime str: anytype) bool {
|
||||
return self.len >= str.len and eqlComptimeIgnoreLen(self[self.len - str.len .. self.len], comptime str);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,23 @@ fn isValid(buf: *bun.PathBuffer, segment: []const u8, bin: []const u8) ?u16 {
|
||||
// Like /usr/bin/which but without needing to exec a child process
|
||||
// Remember to resolve the symlink if necessary
|
||||
pub fn which(buf: *bun.PathBuffer, path: []const u8, cwd: []const u8, bin: []const u8) ?[:0]const u8 {
|
||||
|
||||
// handle absolute paths
|
||||
if (std.fs.path.isAbsolute(bin)) {
|
||||
bun.copy(u8, buf, bin);
|
||||
buf[bin.len] = 0;
|
||||
const binZ: [:0]u8 = buf[0..bin.len :0];
|
||||
if (bun.Environment.isWindows) {
|
||||
(std.fs.cwd().openFile(bin, .{}) catch return null).close();
|
||||
return binZ;
|
||||
}
|
||||
if (bun.sys.isExecutableFilePath(binZ)) return binZ;
|
||||
|
||||
// note that directories are often executable
|
||||
// TODO: should we return null here? What about the case where ytou have
|
||||
// /foo/bar/baz as a path and you're in /home/jarred?
|
||||
}
|
||||
|
||||
if (bun.Environment.os == .windows) {
|
||||
var convert_buf: bun.WPathBuffer = undefined;
|
||||
const result = whichWin(&convert_buf, path, cwd, bin) orelse return null;
|
||||
@@ -25,18 +42,6 @@ pub fn which(buf: *bun.PathBuffer, path: []const u8, cwd: []const u8, bin: []con
|
||||
}
|
||||
if (bin.len == 0) return null;
|
||||
|
||||
// handle absolute paths
|
||||
if (std.fs.path.isAbsolute(bin)) {
|
||||
bun.copy(u8, buf, bin);
|
||||
buf[bin.len] = 0;
|
||||
const binZ: [:0]u8 = buf[0..bin.len :0];
|
||||
if (bun.sys.isExecutableFilePath(binZ)) return binZ;
|
||||
|
||||
// note that directories are often executable
|
||||
// TODO: should we return null here? What about the case where ytou have
|
||||
// /foo/bar/baz as a path and you're in /home/jarred?
|
||||
}
|
||||
|
||||
if (cwd.len > 0) {
|
||||
if (isValid(buf, std.mem.trimRight(u8, cwd, std.fs.path.sep_str), bin)) |len| {
|
||||
return buf[0..len :0];
|
||||
@@ -53,7 +58,7 @@ pub fn which(buf: *bun.PathBuffer, path: []const u8, cwd: []const u8, bin: []con
|
||||
return null;
|
||||
}
|
||||
|
||||
const win_extensionsW = .{
|
||||
const win_extensionsW = [_][:0]const u16{
|
||||
bun.strings.w("exe"),
|
||||
bun.strings.w("cmd"),
|
||||
bun.strings.w("bat"),
|
||||
@@ -77,8 +82,18 @@ pub fn endsWithExtension(str: []const u8) bool {
|
||||
|
||||
/// Check if the WPathBuffer holds a existing file path, checking also for windows extensions variants like .exe, .cmd and .bat (internally used by whichWin)
|
||||
fn searchBin(buf: *bun.WPathBuffer, path_size: usize, check_windows_extensions: bool) ?[:0]const u16 {
|
||||
if (bun.sys.existsOSPath(buf[0..path_size :0], true))
|
||||
if (!bun.Environment.isWindows and bun.sys.existsOSPath(buf[0..path_size :0], true))
|
||||
return buf[0..path_size :0];
|
||||
if (bun.Environment.isWindows) {
|
||||
const haystack = buf[0..path_size :0];
|
||||
for (win_extensionsW) |ext| {
|
||||
if (path_size < 4) continue;
|
||||
if (!bun.strings.endsWithGeneric(u16, haystack, ext)) continue;
|
||||
if (haystack[haystack.len - 4] != '.') continue;
|
||||
if (!bun.sys.existsOSPath(buf[0..path_size :0], true)) continue;
|
||||
return haystack;
|
||||
}
|
||||
}
|
||||
|
||||
if (check_windows_extensions) {
|
||||
buf[path_size] = '.';
|
||||
@@ -122,14 +137,6 @@ pub fn whichWin(buf: *bun.WPathBuffer, path: []const u8, cwd: []const u8, bin: [
|
||||
|
||||
const check_windows_extensions = !endsWithExtension(bin);
|
||||
|
||||
// handle absolute paths
|
||||
if (std.fs.path.isAbsolute(bin)) {
|
||||
const normalized_bin = PosixToWinNormalizer.resolveCWDWithExternalBuf(&path_buf, bin) catch return null;
|
||||
const bin_utf16 = bun.strings.convertUTF8toUTF16InBuffer(buf, normalized_bin);
|
||||
buf[bin_utf16.len] = 0;
|
||||
return searchBin(buf, bin_utf16.len, check_windows_extensions);
|
||||
}
|
||||
|
||||
// check if bin is in cwd
|
||||
if (searchBinInPath(buf, &path_buf, cwd, bin, check_windows_extensions)) |bin_path| {
|
||||
return bin_path;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { realpathSync, chmodSync } from "fs";
|
||||
import { bunEnv, bunExe, isWindows, tempDirWithFiles, toTOMLString } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
const runCmd = cmd === "bun" ? ["-c=bunfig.toml", "run"] : ["-c=bunfig.toml"];
|
||||
@@ -17,14 +16,13 @@ describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
bun,
|
||||
},
|
||||
});
|
||||
const which = "which";
|
||||
|
||||
const cwd = tempDirWithFiles("run.where.node." + cmd2, {
|
||||
const cwd = tempDirWithFiles("run.where.node", {
|
||||
"bunfig.toml": bunfig,
|
||||
"package.json": JSON.stringify(
|
||||
{
|
||||
scripts: {
|
||||
"where-node": `${which} node`,
|
||||
"where-node": `which node`,
|
||||
},
|
||||
},
|
||||
null,
|
||||
@@ -51,11 +49,12 @@ describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
} else {
|
||||
expect(realpathSync(nodeBin)).toBe(realpathSync(node));
|
||||
}
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(["bun", "system", "default"])(`run.shell = "%s"`, shellStr => {
|
||||
if (isWindows && shellStr === "system") return; // windows always uses the bun shell now
|
||||
const shell = shellStr === "default" ? (isWindows ? "bun" : "system") : shellStr;
|
||||
const command_not_found =
|
||||
isWindows && shell === "system" ? "is not recognized as an internal or external command" : "command not found";
|
||||
@@ -85,7 +84,7 @@ describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
cmd: [bunExe(), ...runCmd, "startScript"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "ignore",
|
||||
stdout: "pipe",
|
||||
stdin: "ignore",
|
||||
cwd,
|
||||
});
|
||||
@@ -95,7 +94,7 @@ describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
} else {
|
||||
expect(result.stderr.toString().trim()).toContain("$ echo 1");
|
||||
}
|
||||
expect(result.success).toBeTrue();
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
test("command not found", async () => {
|
||||
const bunfig = toTOMLString({
|
||||
@@ -127,11 +126,6 @@ describe.each(["bun run", "bun"])(`%s`, cmd => {
|
||||
});
|
||||
|
||||
const err = result.stderr.toString().trim();
|
||||
if (shell === "bun") {
|
||||
expect(err).toStartWith("bun: ");
|
||||
} else {
|
||||
expect(err).not.toStartWith("bun: ");
|
||||
}
|
||||
expect(err).toContain(command_not_found);
|
||||
expect(err).toContain("this-should-start-with-bun-in-the-error-message");
|
||||
expect(result.success).toBeFalse();
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// @known-failing-on-windows: 1 failing
|
||||
import { file, spawn, spawnSync } from "bun";
|
||||
import { afterEach, beforeEach, expect, it, describe } from "bun:test";
|
||||
import { bunEnv, bunExe, bunEnv as env, isWindows } from "harness";
|
||||
import { mkdtemp, realpath, rm, writeFile, exists } from "fs/promises";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { join, sep } from "path";
|
||||
import { readdirSorted } from "./dummy.registry";
|
||||
|
||||
let run_dir: string;
|
||||
@@ -311,7 +310,7 @@ it("should download dependencies to run local file", async () => {
|
||||
import { file } from "bun";
|
||||
import decompress from "decompress@4.2.1";
|
||||
|
||||
const buffer = await file("${filePath}").arrayBuffer();
|
||||
const buffer = await file("${filePath.replaceAll(sep, "/")}").arrayBuffer();
|
||||
for (const entry of await decompress(Buffer.from(buffer))) {
|
||||
console.log(\`\${entry.type}: \${entry.path}\`);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { file, spawn } from "bun";
|
||||
import { bunExe, bunEnv as env, toBeValidBin, toHaveBins } from "harness";
|
||||
import { join } from "path";
|
||||
import { bunExe, bunEnv as env, isWindows, toBeValidBin, toHaveBins, writeShebangScript } from "harness";
|
||||
import { join, sep } from "path";
|
||||
import { mkdtempSync, realpathSync } from "fs";
|
||||
import { rm, writeFile, mkdir, exists, cp } from "fs/promises";
|
||||
import { readdirSorted } from "../dummy.registry";
|
||||
import { tmpdir } from "os";
|
||||
import { fork, ChildProcess } from "child_process";
|
||||
import { beforeAll, afterAll, beforeEach, afterEach, test, expect, describe } from "bun:test";
|
||||
|
||||
expect.extend({
|
||||
@@ -13,31 +12,10 @@ expect.extend({
|
||||
toHaveBins,
|
||||
});
|
||||
|
||||
var verdaccioServer: ChildProcess;
|
||||
var testCounter: number = 0;
|
||||
var port: number = 4873;
|
||||
var packageDir: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
verdaccioServer = fork(
|
||||
await import.meta.resolve("verdaccio/bin/verdaccio"),
|
||||
["-c", join(import.meta.dir, "verdaccio.yaml"), "-l", `${port}`],
|
||||
{ silent: true, execPath: "bun" },
|
||||
);
|
||||
|
||||
await new Promise<void>(done => {
|
||||
verdaccioServer.on("message", (msg: { verdaccio_started: boolean }) => {
|
||||
if (msg.verdaccio_started) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
verdaccioServer.kill();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
packageDir = mkdtempSync(join(realpathSync(tmpdir()), "bun-install-registry-" + testCounter++ + "-"));
|
||||
await writeFile(
|
||||
@@ -869,7 +847,7 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
|
||||
expect(err).not.toContain("panic:");
|
||||
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
||||
"",
|
||||
expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
|
||||
"Checked 19 installs across 23 packages (no changes)",
|
||||
]);
|
||||
|
||||
expect(await exited).toBe(0);
|
||||
@@ -2049,7 +2027,7 @@ test("it should re-populate .bin folder if package is reinstalled", async () =>
|
||||
"",
|
||||
" + what-bin@1.5.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(Bun.which("what-bin", { PATH: join(packageDir, "node_modules", ".bin") })).toBe(
|
||||
@@ -2182,7 +2160,10 @@ test("missing package on reinstall, some with binaries", async () => {
|
||||
).toBe(join(packageDir, "node_modules", "uses-what-bin", "node_modules", ".bin", bin));
|
||||
});
|
||||
|
||||
for (const forceWaiterThread of [false, true]) {
|
||||
for (const forceWaiterThread of [
|
||||
false,
|
||||
true,
|
||||
]) {
|
||||
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
||||
describe("lifecycle scripts" + (forceWaiterThread ? " (waiter thread)" : ""), async () => {
|
||||
test("root package with all lifecycle scripts", async () => {
|
||||
@@ -2297,7 +2278,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + all-lifecycle-scripts@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await file(join(packageDir, "preinstall.txt")).text()).toBe("preinstall exists!");
|
||||
@@ -2352,7 +2333,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + all-lifecycle-scripts@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
|
||||
expect(await file(join(packageDir, "preinstall.txt")).text()).toBe("preinstall!");
|
||||
@@ -2466,7 +2447,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(await exists(join(packageDir, "packages", "pkg2", "postprepare.txt"))).toBeFalse();
|
||||
});
|
||||
|
||||
test("dependency lifecycle scripts run before root lifecycle scripts", async () => {
|
||||
test.skipIf(isWindows)("dependency lifecycle scripts run before root lifecycle scripts", async () => {
|
||||
const script = '[[ -f "./node_modules/uses-what-bin-slow/what-bin.txt" ]]';
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
@@ -2545,7 +2526,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + all-lifecycle-scripts@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
expect.stringContaining(" 1 package installed"),
|
||||
"",
|
||||
" Blocked 3 postinstalls. Run `bun pm untrusted` for details.",
|
||||
"",
|
||||
@@ -2794,7 +2775,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
" + lifecycle-postinstall@1.0.0",
|
||||
"",
|
||||
// @ts-ignore
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(err).toContain("Saved lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
@@ -2819,7 +2800,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + lifecycle-postinstall@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(err).not.toContain("Saved lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
@@ -2879,12 +2860,13 @@ for (const forceWaiterThread of [false, true]) {
|
||||
" + another-init-cwd@1.0.0",
|
||||
" + lifecycle-init-cwd@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await file(join(packageDir, "test.txt")).text()).toBe(packageDir + "/");
|
||||
expect(await file(join(packageDir, "node_modules/lifecycle-init-cwd/test.txt")).text()).toBe(packageDir + "/");
|
||||
expect(await file(join(packageDir, "node_modules/another-init-cwd/test.txt")).text()).toBe(packageDir + "/");
|
||||
const packageDir_expected = packageDir.replaceAll(sep, "/") + "/";
|
||||
expect(await file(join(packageDir, "test.txt")).text()).toBe(packageDir_expected);
|
||||
expect(await file(join(packageDir, "node_modules/lifecycle-init-cwd/test.txt")).text()).toBe(packageDir_expected);
|
||||
expect(await file(join(packageDir, "node_modules/another-init-cwd/test.txt")).text()).toBe(packageDir_expected);
|
||||
});
|
||||
|
||||
test("failing lifecycle script should print output", async () => {
|
||||
@@ -2923,7 +2905,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
name: "fooooooooo",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
preinstall: `${bunExe().replace(/\\/g, "\\\\")} -e "throw new Error('Oops!')"`,
|
||||
preinstall: `${bunExe()} -e "throw new Error('Oops!')"`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -2943,6 +2925,43 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(err).toContain('error: preinstall script from "fooooooooo" exited with 1');
|
||||
});
|
||||
|
||||
test("exit 0 in lifecycle scripts works", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "foo",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
postinstall: "exit 0",
|
||||
prepare: "exit 0",
|
||||
postprepare: "exit 0",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
var { stdout, stderr, exited } = spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: packageDir,
|
||||
stdout: "pipe",
|
||||
stdin: "pipe",
|
||||
stderr: "pipe",
|
||||
env: testEnv,
|
||||
});
|
||||
|
||||
const err = await new Response(stderr).text();
|
||||
expect(err).toContain("No packages! Deleted empty lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
expect(err).not.toContain("error:");
|
||||
expect(err).not.toContain("panic:");
|
||||
const out = await new Response(stdout).text();
|
||||
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
||||
"",
|
||||
expect.stringContaining("done"),
|
||||
"",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
test("--ignore-scripts should skip lifecycle scripts", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
@@ -3111,7 +3130,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + node-gyp@1.5.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "build.node"))).toBeTrue();
|
||||
@@ -3169,7 +3188,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + node-gyp@1.5.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "build.node"))).toBeTrue();
|
||||
@@ -3209,7 +3228,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + node-gyp@1.5.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "build.node"))).toBeFalse();
|
||||
@@ -3297,7 +3316,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "postinstall.txt"))).toBeTrue();
|
||||
});
|
||||
|
||||
test("root lifecycle scripts should wait for dependency lifecycle scripts", async () => {
|
||||
test.skipIf(isWindows)("root lifecycle scripts should wait for dependency lifecycle scripts", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
@@ -3383,7 +3402,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
|
||||
test("reach max concurrent scripts", async () => {
|
||||
const scripts = {
|
||||
"preinstall": `${bunExe().replace(/\\/g, "\\\\")} -e "Bun.sleepSync(500)"`,
|
||||
"preinstall": `${bunExe()} -e "Bun.sleepSync(500)"`,
|
||||
};
|
||||
|
||||
const dependenciesList = await createPackagesWithScripts(4, scripts);
|
||||
@@ -3412,9 +3431,9 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
test("stress test", async () => {
|
||||
test.skip("stress test", async () => {
|
||||
const dependenciesList = await createPackagesWithScripts(500, {
|
||||
"postinstall": `${bunExe().replace(/\\/g, "\\\\")} --version`,
|
||||
"postinstall": `${bunExe()} --version`,
|
||||
});
|
||||
|
||||
// the script is quick, default number for max concurrent scripts
|
||||
@@ -3443,7 +3462,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
test("it should install and use correct binary version", async () => {
|
||||
test.skip("it should install and use correct binary version", async () => {
|
||||
// this should install `what-bin` in two places:
|
||||
//
|
||||
// - node_modules/.bin/what-bin@1.5.0
|
||||
@@ -3591,6 +3610,40 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
test("npm_config_node_gyp should be set and usable in lifecycle scripts: basic", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "foo",
|
||||
scripts: {
|
||||
install: "echo $npm_config_node_gyp > npm_config_node_gyp.txt",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const { stderr, exited } = spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: packageDir,
|
||||
stdout: "pipe",
|
||||
stdin: "pipe",
|
||||
stderr: "pipe",
|
||||
env: testEnv,
|
||||
});
|
||||
|
||||
const err = await new Response(stderr).text();
|
||||
expect(err).not.toContain("Saved lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
expect(err).not.toContain("error:");
|
||||
expect(err).not.toContain("panic:");
|
||||
expect(err).toContain("v");
|
||||
|
||||
expect(await exited).toBe(0);
|
||||
|
||||
expect(await exists(join(packageDir, "npm_config_node_gyp.txt"))).toBeTrue();
|
||||
const ext = isWindows ? ".cmd" : "";
|
||||
expect(await file(join(packageDir, "npm_config_node_gyp.txt")).text()).toEndWith(`${sep}node-gyp${ext}\n`);
|
||||
});
|
||||
|
||||
test("npm_config_node_gyp should be set and usable in lifecycle scripts", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
@@ -3611,13 +3664,13 @@ for (const forceWaiterThread of [false, true]) {
|
||||
env: testEnv,
|
||||
});
|
||||
|
||||
expect(await exited).toBe(0);
|
||||
const err = await new Response(stderr).text();
|
||||
expect(err).not.toContain("Saved lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
expect(err).not.toContain("error:");
|
||||
expect(err).not.toContain("panic:");
|
||||
expect(err).toContain("v");
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
// if this test fails, `electron` might be removed from the default list
|
||||
@@ -3651,7 +3704,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + electron@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(out).not.toContain("Blocked");
|
||||
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
||||
@@ -3820,7 +3873,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + electron@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeFalse();
|
||||
@@ -4014,7 +4067,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" installed no-deps@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "node_modules", "no-deps"))).toBeTrue();
|
||||
@@ -4052,7 +4105,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" installed no-deps@2.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "node_modules", "no-deps"))).toBeTrue();
|
||||
@@ -4288,7 +4341,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + electron@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
||||
@@ -4346,7 +4399,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
name: "foo",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
postinstall: 'node -p \'require("fs").writeFileSync("postinstall.txt", "postinstall")\'',
|
||||
postinstall: `node -p 'require("fs").writeFileSync("postinstall.txt","postinstall")'`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -4375,6 +4428,41 @@ for (const forceWaiterThread of [false, true]) {
|
||||
|
||||
expect(await exists(join(packageDir, "postinstall.txt"))).toBeTrue();
|
||||
});
|
||||
test("ensureTempNodeGypScript works", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "foo",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
preinstall: "node-gyp --version",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const originalPath = env.PATH;
|
||||
env.PATH = "";
|
||||
|
||||
let { stderr, exited } = spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
cwd: packageDir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "ignore",
|
||||
env,
|
||||
});
|
||||
|
||||
env.PATH = originalPath;
|
||||
|
||||
let err = await Bun.readableStreamToText(stderr);
|
||||
expect(err).toContain("No packages! Deleted empty lockfile");
|
||||
expect(err).not.toContain("not found");
|
||||
expect(err).not.toContain("error:");
|
||||
expect(err).not.toContain("warn:");
|
||||
expect(err).not.toContain("panic:");
|
||||
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
|
||||
test("bun pm trust and untrusted on missing package", async () => {
|
||||
await writeFile(
|
||||
@@ -4599,7 +4687,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
"",
|
||||
" + uses-what-bin@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
expect.stringContaining(" 1 package installed"),
|
||||
"",
|
||||
" Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
||||
"",
|
||||
@@ -4619,20 +4707,20 @@ for (const forceWaiterThread of [false, true]) {
|
||||
expect(err).not.toContain("error:");
|
||||
expect(err).not.toContain("warn:");
|
||||
out = await Bun.readableStreamToText(stdout);
|
||||
expect(out).toContain("./node_modules/uses-what-bin @1.0.0");
|
||||
expect(out).toContain("./node_modules/uses-what-bin @1.0.0".replaceAll("/", sep));
|
||||
expect(await exited).toBe(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe.if(!forceWaiterThread || process.platform === "linux")("does not use 100% cpu", async () => {
|
||||
test("install", async () => {
|
||||
test.skip("install", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "foo",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
preinstall: `${bunExe().replace(/\\/g, "\\\\")} -e "Bun.sleepSync(1000)"`,
|
||||
preinstall: `${bunExe()} -e "Bun.sleepSync(1000)"`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -4650,7 +4738,7 @@ for (const forceWaiterThread of [false, true]) {
|
||||
|
||||
expect(proc.resourceUsage()?.cpuTime.total).toBeLessThan(750_000);
|
||||
});
|
||||
test("bun pm trust", async () => {
|
||||
test.skip("bun pm trust", async () => {
|
||||
await writeFile(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
@@ -4822,12 +4910,10 @@ test("it should be able to find binary in node_modules/.bin from parent director
|
||||
|
||||
await cp(join(packageDir, "bunfig.toml"), join(packageDir, "morePackageDir", "bunfig.toml"));
|
||||
|
||||
await await writeFile(
|
||||
await writeShebangScript(
|
||||
join(packageDir, "node_modules", ".bin", "missing-bin"),
|
||||
`#!/usr/bin/env node
|
||||
require("fs").writeFileSync("missing-bin.txt", "missing-bin@WHAT");
|
||||
`,
|
||||
{ mode: 0o777 },
|
||||
"node",
|
||||
`require("fs").writeFileSync("missing-bin.txt", "missing-bin@WHAT");`,
|
||||
);
|
||||
|
||||
const { stdout, stderr, exited } = spawn({
|
||||
@@ -4849,7 +4935,7 @@ require("fs").writeFileSync("missing-bin.txt", "missing-bin@WHAT");
|
||||
"",
|
||||
" + what-bin@1.0.0",
|
||||
"",
|
||||
expect.stringContaining("1 package installed"),
|
||||
" 1 package installed",
|
||||
]);
|
||||
expect(await exited).toBe(0);
|
||||
expect(await file(join(packageDir, "morePackageDir", "missing-bin.txt")).text()).toBe("missing-bin@WHAT");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { gc as bunGC, unsafe, which } from "bun";
|
||||
import { describe, test, expect, afterAll, beforeAll } from "bun:test";
|
||||
import { readlink, readFile } from "fs/promises";
|
||||
import { isAbsolute } from "path";
|
||||
import { readlink, readFile, writeFile } from "fs/promises";
|
||||
import { isAbsolute, sep } from "path";
|
||||
import { openSync, closeSync } from "node:fs";
|
||||
|
||||
export const isMacOS = process.platform === "darwin";
|
||||
@@ -231,9 +231,8 @@ export async function runWithErrorPromise(cb: () => unknown): Promise<Error | un
|
||||
}
|
||||
|
||||
export function fakeNodeRun(dir: string, file: string | string[], env?: Record<string, string>) {
|
||||
var path = require("path");
|
||||
const result = Bun.spawnSync([bunExe(), "--bun", "node", ...(Array.isArray(file) ? file : [file])], {
|
||||
cwd: dir ?? path.dirname(file),
|
||||
cwd: dir,
|
||||
env: {
|
||||
...bunEnv,
|
||||
NODE_ENV: undefined,
|
||||
@@ -552,8 +551,26 @@ export function toTOMLString(opts: object) {
|
||||
const props = makeFlatPropertyMap(opts);
|
||||
let ret = "";
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (value === undefined) continue;
|
||||
ret += `${key} = ${JSON.stringify(value)}` + "\n";
|
||||
if (!value) continue;
|
||||
ret += `${key} = ${JSON.stringify(value)}\n`;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const shebang_posix = (program: string) => `#!/usr/bin/env ${program}
|
||||
`;
|
||||
|
||||
const shebang_windows = (program: string) => `0</* :{
|
||||
@echo off
|
||||
${program} %~f0 %*
|
||||
exit /b %errorlevel%
|
||||
:} */0;
|
||||
`;
|
||||
|
||||
export function writeShebangScript(path: string, program: string, data: string) {
|
||||
if (!isWindows) {
|
||||
return writeFile(path, shebang_posix(program) + "\n" + data, { mode: 0o777 });
|
||||
} else {
|
||||
return writeFile(path + ".cmd", shebang_windows(program) + "\n" + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { spawnSync, spawn } from "bun";
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { bunExe } from "harness";
|
||||
import { bunExe, bunEnv } from "harness";
|
||||
import { join } from "lodash";
|
||||
|
||||
describe("should work for static input", () => {
|
||||
const inputs = [
|
||||
@@ -11,6 +12,7 @@ describe("should work for static input", () => {
|
||||
"Hello\nWorld\n",
|
||||
"1",
|
||||
"💕 Red Heart ✨ Sparkles 🔥 Fire\n💕 Red Heart ✨ Sparkles\n💕 Red Heart\n💕\n\nnormal",
|
||||
"a\n§\nb",
|
||||
];
|
||||
|
||||
for (let input of inputs) {
|
||||
@@ -18,9 +20,7 @@ describe("should work for static input", () => {
|
||||
const { stdout } = spawnSync({
|
||||
cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.ts"],
|
||||
stdin: Buffer.from(input),
|
||||
env: {
|
||||
BUN_DEBUG_QUIET_LOGS: "1",
|
||||
},
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(stdout.toString()).toBe(input.replaceAll("\n", ""));
|
||||
});
|
||||
@@ -36,6 +36,7 @@ describe("should work for streaming input", () => {
|
||||
"Hello\nWorld\n",
|
||||
"1",
|
||||
"💕 Red Heart ✨ Sparkles 🔥 Fire\n 💕 Red Heart ✨ Sparkles\n 💕 Red Heart\n 💕 \n\nnormal",
|
||||
"a\n§\nb",
|
||||
];
|
||||
|
||||
for (let input of inputs) {
|
||||
@@ -44,9 +45,7 @@ describe("should work for streaming input", () => {
|
||||
cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.ts"],
|
||||
stdin: "pipe",
|
||||
stdout: "pipe",
|
||||
env: {
|
||||
BUN_DEBUG_QUIET_LOGS: "1",
|
||||
},
|
||||
env: bunEnv,
|
||||
});
|
||||
const { stdout, stdin } = proc;
|
||||
stdin.write(input.slice(0, (input.length / 2) | 0));
|
||||
@@ -68,9 +67,7 @@ it("can use the console iterator more than once", async () => {
|
||||
cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run-2.ts"],
|
||||
stdin: "pipe",
|
||||
stdout: "pipe",
|
||||
env: {
|
||||
BUN_DEBUG_QUIET_LOGS: "1",
|
||||
},
|
||||
env: bunEnv,
|
||||
});
|
||||
const { stdout, stdin } = proc;
|
||||
stdin.write("hello\nworld\nbreak\nanother\nbreak\n");
|
||||
|
||||
@@ -255,16 +255,18 @@ it("import.meta paths have the correct slash", () => {
|
||||
expect(import.meta.url).not.toInclude("\\");
|
||||
});
|
||||
|
||||
it("import.meta is correct in a module that was imported with a query param", async () => {
|
||||
it("import.meta is correct in a module that was imported with a query param esm", async () => {
|
||||
const esm = (await import("./other.js?foo=bar")).default;
|
||||
const cjs = require("./other-cjs.js?foo=bar").meta;
|
||||
|
||||
expect(esm.url).toBe(new URL("./other.js?foo=bar", import.meta.url).toString());
|
||||
expect(cjs.url).toBe(new URL("./other-cjs.js?foo=bar", import.meta.url).toString());
|
||||
expect(esm.path).toBe(join(import.meta.dir, "./other.js"));
|
||||
expect(cjs.path).toBe(join(import.meta.dir, "./other-cjs.js"));
|
||||
expect(esm.dir).toBe(import.meta.dir);
|
||||
expect(cjs.dir).toBe(import.meta.dir);
|
||||
expect(esm.file).toBe("other.js");
|
||||
});
|
||||
|
||||
it("import.meta is correct in a module that was imported with a query param cjs", async () => {
|
||||
const cjs = require("./other-cjs.js?foo=bar").meta;
|
||||
expect(cjs.url).toBe(new URL("./other-cjs.js?foo=bar", import.meta.url).toString());
|
||||
expect(cjs.path).toBe(join(import.meta.dir, "./other-cjs.js"));
|
||||
expect(cjs.dir).toBe(import.meta.dir);
|
||||
expect(cjs.file).toBe("other-cjs.js");
|
||||
});
|
||||
|
||||
22
test/js/bun/shell/commands/exit.test.ts
Normal file
22
test/js/bun/shell/commands/exit.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { $ } from "bun";
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { TestBuilder } from "../test_builder";
|
||||
import { sortedShellOutput } from "../util";
|
||||
import { join } from "path";
|
||||
|
||||
describe("exit", async () => {
|
||||
TestBuilder.command`exit`.exitCode(0).runAsTest("works");
|
||||
|
||||
describe("argument sets exit code", async () => {
|
||||
for (const arg of [0, 2, 11]) {
|
||||
TestBuilder.command`exit ${arg}`.exitCode(arg).runAsTest(`${arg}`);
|
||||
}
|
||||
});
|
||||
|
||||
TestBuilder.command`exit 3 5`.exitCode(1).stderr("exit: too many arguments").runAsTest("too many arguments");
|
||||
|
||||
TestBuilder.command`exit 62757836`.exitCode(204).runAsTest("exit code wraps u8");
|
||||
|
||||
// prettier-ignore
|
||||
TestBuilder.command`exit abc`.exitCode(1).stderr("exit: numeric argument required").runAsTest("numeric argument required");
|
||||
});
|
||||
Reference in New Issue
Block a user