aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/install/install.zig154
-rw-r--r--src/install/lockfile.zig248
-rw-r--r--test/cli/install/bun-install.test.ts87
-rw-r--r--test/cli/install/bun-remove.test.ts29
4 files changed, 368 insertions, 150 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index 6ff76d421..ab3bb3eef 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -574,17 +574,16 @@ const Task = struct {
}
this.err = err;
this.status = Status.fail;
+ this.data = .{ .package_manifest = .{} };
this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
};
- this.data = .{ .package_manifest = .{} };
-
switch (package_manifest) {
.cached => unreachable,
.fresh => |manifest| {
- this.data = .{ .package_manifest = manifest };
this.status = Status.success;
+ this.data = .{ .package_manifest = manifest };
this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
},
@@ -593,6 +592,7 @@ const Task = struct {
this.request.package_manifest.name.slice(),
}) catch unreachable;
this.status = Status.fail;
+ this.data = .{ .package_manifest = .{} };
this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
},
@@ -6649,6 +6649,63 @@ pub const PackageManager = struct {
}
}
}
+
+ var scripts = this.lockfile.packages.items(.scripts)[package_id];
+ if (scripts.hasAny()) {
+ var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const path_str = Path.joinAbsString(
+ bun.getFdPath(this.node_modules_folder.dir.fd, &path_buf) catch unreachable,
+ &[_]string{destination_dir_subpath},
+ .posix,
+ );
+
+ scripts.enqueue(this.lockfile, buf, path_str);
+ } else if (!scripts.filled and switch (resolution.tag) {
+ .folder => Features.folder.scripts,
+ .npm => Features.npm.scripts,
+ .git, .github, .gitlab, .local_tarball, .remote_tarball => Features.tarball.scripts,
+ .symlink => Features.link.scripts,
+ .workspace => Features.workspace.scripts,
+ else => false,
+ }) {
+ var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ const path_str = Path.joinAbsString(
+ bun.getFdPath(this.node_modules_folder.dir.fd, &path_buf) catch unreachable,
+ &[_]string{destination_dir_subpath},
+ .posix,
+ );
+
+ scripts.enqueueFromPackageJSON(
+ this.manager.log,
+ this.lockfile,
+ this.node_modules_folder.dir,
+ destination_dir_subpath,
+ path_str,
+ ) catch |err| {
+ if (comptime log_level != .silent) {
+ const fmt = "\n<r><red>error:<r> failed to parse life-cycle scripts for <b>{s}<r>: {s}\n";
+ const args = .{ name, @errorName(err) };
+
+ if (comptime log_level.showProgress()) {
+ if (Output.enable_ansi_colors) {
+ this.progress.log(comptime Output.prettyFmt(fmt, true), args);
+ } else {
+ this.progress.log(comptime Output.prettyFmt(fmt, false), args);
+ }
+ } else {
+ Output.prettyErrorln(fmt, args);
+ }
+ }
+
+ if (this.manager.options.enable.fail_early) {
+ Global.exit(1);
+ }
+
+ Output.flush();
+ this.summary.fail += 1;
+ return;
+ };
+ }
},
.fail => |cause| {
if (cause.isPackageMissingFromCache()) {
@@ -7522,6 +7579,22 @@ pub const PackageManager = struct {
}
}
+ if (root.scripts.hasAny()) {
+ root.scripts.enqueue(
+ manager.lockfile,
+ manager.lockfile.buffers.string_bytes.items,
+ strings.withoutTrailingSlash(Fs.FileSystem.instance.top_level_dir),
+ );
+ }
+
+ var install_summary = PackageInstall.Summary{};
+ if (manager.options.do.install_packages) {
+ install_summary = try manager.installPackages(
+ manager.lockfile,
+ log_level,
+ );
+ }
+
// Install script order for npm 8.3.0:
// 1. preinstall
// 2. install
@@ -7529,40 +7602,27 @@ pub const PackageManager = struct {
// 4. preprepare
// 5. prepare
// 6. postprepare
-
const run_lifecycle_scripts = manager.options.do.run_scripts and manager.lockfile.scripts.hasAny() and manager.options.do.install_packages;
- const has_pre_lifecycle_scripts = manager.lockfile.scripts.preinstall.items.len > 0;
- const needs_configure_bundler_for_run = run_lifecycle_scripts and !has_pre_lifecycle_scripts;
-
- if (run_lifecycle_scripts and has_pre_lifecycle_scripts) {
+ if (run_lifecycle_scripts) {
// We need to figure out the PATH and other environment variables
// to do that, we re-use the code from bun run
// this is expensive, it traverses the entire directory tree going up to the root
// so we really only want to do it when strictly necessary
- {
- var this_bundler: bundler.Bundler = undefined;
- var ORIGINAL_PATH: string = "";
- _ = try RunCommand.configureEnvForRun(
- ctx,
- &this_bundler,
- manager.env,
- &ORIGINAL_PATH,
- log_level != .silent,
- false,
- );
- }
+ var this_bundler: bundler.Bundler = undefined;
+ var ORIGINAL_PATH: string = "";
+ _ = try RunCommand.configureEnvForRun(
+ ctx,
+ &this_bundler,
+ manager.env,
+ &ORIGINAL_PATH,
+ log_level != .silent,
+ false,
+ );
+ // 1. preinstall
try manager.lockfile.scripts.run(manager.allocator, manager.env, log_level != .silent, "preinstall");
}
- var install_summary = PackageInstall.Summary{};
- if (manager.options.do.install_packages) {
- install_summary = try manager.installPackages(
- manager.lockfile,
- log_level,
- );
- }
-
if (needs_new_lockfile) {
manager.summary.add = @truncate(u32, manager.lockfile.packages.len);
}
@@ -7666,44 +7726,6 @@ pub const PackageManager = struct {
}
if (run_lifecycle_scripts and install_summary.fail == 0) {
- // We need to figure out the PATH and other environment variables
- // to do that, we re-use the code from bun run
- // this is expensive, it traverses the entire directory tree going up to the root
- // so we really only want to do it when strictly necessary
- if (needs_configure_bundler_for_run) {
- var this_bundler: bundler.Bundler = undefined;
- var ORIGINAL_PATH: string = "";
- _ = try RunCommand.configureEnvForRun(
- ctx,
- &this_bundler,
- manager.env,
- &ORIGINAL_PATH,
- log_level != .silent,
- false,
- );
- } else {
- // bun install may have installed new bins, so we need to update the PATH
- // this can happen if node_modules/.bin didn't previously exist
- // note: it is harmless to have the same directory in the PATH multiple times
- const current_path = manager.env.map.get("PATH") orelse "";
-
- // TODO: windows
- const cwd_without_trailing_slash = if (Fs.FileSystem.instance.top_level_dir.len > 1 and Fs.FileSystem.instance.top_level_dir[Fs.FileSystem.instance.top_level_dir.len - 1] == '/')
- Fs.FileSystem.instance.top_level_dir[0 .. Fs.FileSystem.instance.top_level_dir.len - 1]
- else
- Fs.FileSystem.instance.top_level_dir;
-
- try manager.env.map.put("PATH", try std.fmt.allocPrint(
- ctx.allocator,
- "{s}:{s}/node_modules/.bin",
- .{
- current_path,
- cwd_without_trailing_slash,
- },
- ));
- }
-
- // 1. preinstall
// 2. install
// 3. postinstall
try manager.lockfile.scripts.run(manager.allocator, manager.env, log_level != .silent, "install");
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index bef058bf1..f5ad3681b 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -63,22 +63,22 @@ const Dependency = @import("./dependency.zig");
const Behavior = Dependency.Behavior;
const FolderResolution = @import("./resolvers/folder_resolver.zig").FolderResolution;
const Install = @import("./install.zig");
+const Aligner = Install.Aligner;
+const alignment_bytes_to_repeat_buffer = Install.alignment_bytes_to_repeat_buffer;
const PackageManager = Install.PackageManager;
+const DependencyID = Install.DependencyID;
const ExternalSlice = Install.ExternalSlice;
const ExternalSliceAligned = Install.ExternalSliceAligned;
-const PackageID = Install.PackageID;
-const DependencyID = Install.DependencyID;
-const Features = Install.Features;
-const PackageInstall = Install.PackageInstall;
-const PackageNameHash = Install.PackageNameHash;
-const Aligner = Install.Aligner;
+const ExternalStringList = Install.ExternalStringList;
const ExternalStringMap = Install.ExternalStringMap;
-const alignment_bytes_to_repeat_buffer = Install.alignment_bytes_to_repeat_buffer;
+const Features = Install.Features;
const initializeStore = Install.initializeStore;
const invalid_package_id = Install.invalid_package_id;
-const ExternalStringList = Install.ExternalStringList;
-const Resolution = @import("./resolution.zig").Resolution;
const Origin = Install.Origin;
+const PackageID = Install.PackageID;
+const PackageInstall = Install.PackageInstall;
+const PackageNameHash = Install.PackageNameHash;
+const Resolution = @import("./resolution.zig").Resolution;
const Crypto = @import("../sha.zig").Hashers;
const PackageJSON = @import("../resolver/package_json.zig").PackageJSON;
@@ -124,35 +124,28 @@ pub const Scripts = struct {
postprepare: StringArrayList = .{},
pub fn hasAny(this: *Scripts) bool {
- return (this.preinstall.items.len +
- this.install.items.len +
- this.postinstall.items.len +
- this.preprepare.items.len +
- this.prepare.items.len +
- this.postprepare.items.len) > 0;
+ inline for (Package.Scripts.Hooks) |hook| {
+ if (@field(this, hook).items.len > 0) return true;
+ }
+ return false;
}
pub fn run(this: *Scripts, allocator: Allocator, env: *DotEnv.Loader, silent: bool, comptime hook: []const u8) !void {
for (@field(this, hook).items) |entry| {
if (comptime Environment.allow_assert) std.debug.assert(Fs.FileSystem.instance_loaded);
- const cwd = Path.joinAbsString(
- FileSystem.instance.top_level_dir,
- &[_]string{
- entry.cwd,
- },
- .posix,
- );
- _ = try RunCommand.runPackageScript(allocator, entry.script, hook, cwd, env, &.{}, silent);
+ _ = try RunCommand.runPackageScript(allocator, entry.script, hook, entry.cwd, env, &.{}, silent);
}
}
pub fn deinit(this: *Scripts, allocator: Allocator) void {
- this.preinstall.deinit(allocator);
- this.install.deinit(allocator);
- this.postinstall.deinit(allocator);
- this.preprepare.deinit(allocator);
- this.prepare.deinit(allocator);
- this.postprepare.deinit(allocator);
+ inline for (Package.Scripts.Hooks) |hook| {
+ const list = &@field(this, hook);
+ for (list.items) |entry| {
+ allocator.free(entry.cwd);
+ allocator.free(entry.script);
+ }
+ list.deinit(allocator);
+ }
}
};
@@ -1737,6 +1730,124 @@ pub const Package = extern struct {
meta: Meta = .{},
bin: Bin = .{},
+ scripts: Package.Scripts = .{},
+
+ pub const Scripts = extern struct {
+ preinstall: String = .{},
+ install: String = .{},
+ postinstall: String = .{},
+ preprepare: String = .{},
+ prepare: String = .{},
+ postprepare: String = .{},
+ filled: bool = false,
+
+ pub const Hooks = .{
+ "preinstall",
+ "install",
+ "postinstall",
+ "preprepare",
+ "prepare",
+ "postprepare",
+ };
+
+ pub fn clone(this: *const Package.Scripts, buf: []const u8, comptime Builder: type, builder: Builder) Package.Scripts {
+ if (!this.filled) return .{};
+ var scripts = Package.Scripts{
+ .filled = true,
+ };
+ inline for (Package.Scripts.Hooks) |hook| {
+ @field(scripts, hook) = builder.append(String, @field(this, hook).slice(buf));
+ }
+ return scripts;
+ }
+
+ pub fn count(this: *const Package.Scripts, buf: []const u8, comptime Builder: type, builder: Builder) void {
+ inline for (Package.Scripts.Hooks) |hook| {
+ builder.count(@field(this, hook).slice(buf));
+ }
+ }
+
+ pub fn hasAny(this: *const Package.Scripts) bool {
+ inline for (Package.Scripts.Hooks) |hook| {
+ if (!@field(this, hook).isEmpty()) return true;
+ }
+ return false;
+ }
+
+ pub fn enqueue(this: *const Package.Scripts, lockfile: *Lockfile, buf: []const u8, cwd: string) void {
+ inline for (Package.Scripts.Hooks) |hook| {
+ const script = @field(this, hook);
+ if (!script.isEmpty()) {
+ @field(lockfile.scripts, hook).append(lockfile.allocator, .{
+ .cwd = lockfile.allocator.dupe(u8, cwd) catch unreachable,
+ .script = lockfile.allocator.dupe(u8, script.slice(buf)) catch unreachable,
+ }) catch unreachable;
+ }
+ }
+ }
+
+ pub fn parseCount(allocator: Allocator, builder: *Lockfile.StringBuilder, json: Expr) void {
+ if (json.asProperty("scripts")) |scripts_prop| {
+ if (scripts_prop.expr.data == .e_object) {
+ inline for (Package.Scripts.Hooks) |script_name| {
+ if (scripts_prop.expr.get(script_name)) |script| {
+ if (script.asString(allocator)) |input| {
+ builder.count(input);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub fn parseAlloc(this: *Package.Scripts, allocator: Allocator, builder: *Lockfile.StringBuilder, json: Expr) void {
+ if (json.asProperty("scripts")) |scripts_prop| {
+ if (scripts_prop.expr.data == .e_object) {
+ inline for (Package.Scripts.Hooks) |script_name| {
+ if (scripts_prop.expr.get(script_name)) |script| {
+ if (script.asString(allocator)) |input| {
+ @field(this, script_name) = builder.append(String, input);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub fn enqueueFromPackageJSON(
+ this: *Package.Scripts,
+ log: *logger.Log,
+ lockfile: *Lockfile,
+ node_modules: std.fs.Dir,
+ subpath: [:0]const u8,
+ cwd: string,
+ ) !void {
+ var pkg_dir = try bun.openDir(node_modules, subpath);
+ defer pkg_dir.close();
+ const json_file = try pkg_dir.dir.openFileZ("package.json", .{ .mode = .read_only });
+ defer json_file.close();
+ const json_stat = try json_file.stat();
+ const json_buf = try lockfile.allocator.alloc(u8, json_stat.size + 64);
+ const json_len = try json_file.preadAll(json_buf, 0);
+ const json_src = logger.Source.initPathString(cwd, json_buf[0..json_len]);
+ initializeStore();
+ const json = try json_parser.ParseJSONUTF8(
+ &json_src,
+ log,
+ lockfile.allocator,
+ );
+
+ var tmp: Lockfile = undefined;
+ try tmp.initEmpty(lockfile.allocator);
+ defer tmp.deinit();
+ var builder = tmp.stringBuilder();
+ Lockfile.Package.Scripts.parseCount(lockfile.allocator, &builder, json);
+ try builder.allocate();
+ this.parseAlloc(lockfile.allocator, &builder, json);
+
+ this.enqueue(lockfile, tmp.buffers.string_bytes.items, cwd);
+ }
+ };
pub fn verify(this: *const Package, externs: []const ExternalString) void {
if (comptime !Environment.allow_assert)
@@ -1796,6 +1907,7 @@ pub const Package = extern struct {
builder.count(this.name.slice(old_string_buf));
this.resolution.count(old_string_buf, *Lockfile.StringBuilder, builder);
this.meta.count(old_string_buf, *Lockfile.StringBuilder, builder);
+ this.scripts.count(old_string_buf, *Lockfile.StringBuilder, builder);
const new_extern_string_count = this.bin.count(old_string_buf, old_extern_string_buf, *Lockfile.StringBuilder, builder);
const old_dependencies: []const Dependency = this.dependencies.get(old.buffers.dependencies.items);
const old_resolutions: []const PackageID = this.resolutions.get(old.buffers.resolutions.items);
@@ -1832,7 +1944,14 @@ pub const Package = extern struct {
this.name.slice(old_string_buf),
this.name_hash,
),
- .bin = this.bin.clone(old_string_buf, old_extern_string_buf, new.buffers.extern_strings.items, new_extern_strings, *Lockfile.StringBuilder, builder),
+ .bin = this.bin.clone(
+ old_string_buf,
+ old_extern_string_buf,
+ new.buffers.extern_strings.items,
+ new_extern_strings,
+ *Lockfile.StringBuilder,
+ builder,
+ ),
.name_hash = this.name_hash,
.meta = this.meta.clone(
id,
@@ -1845,6 +1964,11 @@ pub const Package = extern struct {
*Lockfile.StringBuilder,
builder,
),
+ .scripts = this.scripts.clone(
+ old_string_buf,
+ *Lockfile.StringBuilder,
+ builder,
+ ),
.dependencies = .{ .off = prev_len, .len = end - prev_len },
.resolutions = .{ .off = prev_len, .len = end - prev_len },
},
@@ -2762,18 +2886,11 @@ pub const Package = extern struct {
if (json.asProperty("bin")) |bin| {
switch (bin.expr.data) {
.e_object => |obj| {
- switch (obj.properties.len) {
- 0 => {
- break :bin;
- },
- 1 => {},
- else => {},
- }
-
for (obj.properties.slice()) |bin_prop| {
string_builder.count(bin_prop.key.?.asString(allocator) orelse break :bin);
string_builder.count(bin_prop.value.?.asString(allocator) orelse break :bin);
}
+ break :bin;
},
.e_string => {
if (bin.expr.asString(allocator)) |str_| {
@@ -2796,33 +2913,7 @@ pub const Package = extern struct {
}
if (comptime features.scripts) {
- if (json.asProperty("scripts")) |scripts_prop| {
- if (scripts_prop.expr.data == .e_object) {
- const scripts = .{
- "install",
- "postinstall",
- "postprepare",
- "preinstall",
- "prepare",
- "preprepare",
- };
- var cwd: string = "";
-
- inline for (scripts) |script_name| {
- if (scripts_prop.expr.get(script_name)) |script| {
- if (script.asString(allocator)) |input| {
- if (cwd.len == 0 and source.path.name.dir.len > 0) {
- cwd = try allocator.dupe(u8, source.path.name.dir);
- }
- try @field(lockfile.scripts, script_name).append(allocator, .{
- .cwd = cwd,
- .script = input,
- });
- }
- }
- }
- }
- }
+ Package.Scripts.parseCount(allocator, &string_builder, json);
}
if (comptime ResolverContext != void) {
@@ -3098,6 +3189,11 @@ pub const Package = extern struct {
}
}
+ if (comptime features.scripts) {
+ package.scripts.parseAlloc(allocator, &string_builder, json);
+ }
+ package.scripts.filled = true;
+
// It is allowed for duplicate dependencies to exist in optionalDependencies and regular dependencies
if (comptime features.check_for_duplicate_dependencies) {
lockfile.scratch.duplicate_checker_map.clearRetainingCapacity();
@@ -3328,9 +3424,14 @@ pub const Package = extern struct {
}
const field_count = try reader.readIntLittle(u64);
-
- if (field_count != sizes.Types.len) {
- return error.@"Lockfile validation failed: unexpected number of package fields";
+ switch (field_count) {
+ sizes.Types.len => {},
+ // "scripts" field is absent before v0.6.8
+ // we will back-fill from each package.json
+ sizes.Types.len - 1 => {},
+ else => {
+ return error.@"Lockfile validation failed: unexpected number of package fields";
+ },
}
const begin_at = try reader.readIntLittle(u64);
@@ -3345,8 +3446,15 @@ pub const Package = extern struct {
inline for (FieldsEnum.fields) |field| {
var bytes = std.mem.sliceAsBytes(sliced.items(@field(Lockfile.Package.List.Field, field.name)));
- @memcpy(bytes.ptr, stream.buffer[stream.pos..].ptr, bytes.len);
- stream.pos += bytes.len;
+ const end_pos = stream.pos + bytes.len;
+ if (end_pos <= end_at) {
+ @memcpy(bytes.ptr, stream.buffer[stream.pos..].ptr, bytes.len);
+ stream.pos = end_pos;
+ } else if (comptime strings.eqlComptime(field.name, "scripts")) {
+ @memset(bytes.ptr, 0, bytes.len);
+ } else {
+ return error.@"Lockfile validation failed: invalid package list range";
+ }
}
return list;
diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts
index 8dd3d3cba..f44dc5a7e 100644
--- a/test/cli/install/bun-install.test.ts
+++ b/test/cli/install/bun-install.test.ts
@@ -610,6 +610,93 @@ it("should handle life-cycle scripts within workspaces", async () => {
await access(join(package_dir, "bun.lockb"));
});
+it("should handle life-cycle scripts during re-installation", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ scripts: {
+ install: [bunExe(), "index.js"].join(" "),
+ },
+ workspaces: ["bar"],
+ }),
+ );
+ await writeFile(join(package_dir, "index.js"), 'console.log("[scripts:run] Foo");');
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ scripts: {
+ preinstall: [bunExe(), "index.js"].join(" "),
+ },
+ }),
+ );
+ await writeFile(join(package_dir, "bar", "index.js"), 'console.log("[scripts:run] Bar");');
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1).toContain("Saved lockfile");
+ expect(stdout1).toBeDefined();
+ const out1 = await new Response(stdout1).text();
+ expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ "[scripts:run] Bar",
+ " + Bar@workspace:bar",
+ "[scripts:run] Foo",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ await access(join(package_dir, "bun.lockb"));
+ // Perform `bun install` again but with lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("Saved lockfile");
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ "[scripts:run] Bar",
+ " + Bar@workspace:bar",
+ "[scripts:run] Foo",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual(["Bar"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
it("should ignore workspaces within workspaces", async () => {
await writeFile(
join(package_dir, "package.json"),
diff --git a/test/cli/install/bun-remove.test.ts b/test/cli/install/bun-remove.test.ts
index 5a6f612c4..bdf0873e9 100644
--- a/test/cli/install/bun-remove.test.ts
+++ b/test/cli/install/bun-remove.test.ts
@@ -1,21 +1,10 @@
import { bunExe, bunEnv as env } from "harness";
-import { access, mkdir, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises";
+import { mkdir, mkdtemp, realpath, rm, writeFile } from "fs/promises";
import { join, relative } from "path";
import { tmpdir } from "os";
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
-import {
- dummyAfterAll,
- dummyAfterEach,
- dummyBeforeAll,
- dummyBeforeEach,
- dummyRegistry,
- package_dir,
- readdirSorted,
- requested,
- root_url,
- setHandler,
-} from "./dummy.registry";
-import { spawn, write } from "bun";
+import { dummyAfterAll, dummyAfterEach, dummyBeforeAll, dummyBeforeEach, package_dir } from "./dummy.registry";
+import { spawn } from "bun";
import { file } from "bun";
beforeAll(dummyBeforeAll);
@@ -65,12 +54,18 @@ it("should remove existing package", async () => {
const { exited: exited1 } = spawn({
cmd: [bunExe(), "add", `file:${pkg1_path}`],
cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
env,
});
expect(await exited1).toBe(0);
const { exited: exited2 } = spawn({
cmd: [bunExe(), "add", `file:${pkg2_path}`],
cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
env,
});
expect(await exited2).toBe(0);
@@ -182,6 +177,9 @@ it("should reject missing package", async () => {
const { exited: addExited } = spawn({
cmd: [bunExe(), "add", `file:${pkg_path}`],
cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
env,
});
expect(await addExited).toBe(0);
@@ -257,6 +255,9 @@ it("should retain a new line in the end of package.json", async () => {
const { exited: addExited } = spawn({
cmd: [bunExe(), "add", `file:${pkg_path}`],
cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
env,
});
expect(await addExited).toBe(0);