aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alex Lam S.L <alexlamsl@gmail.com> 2023-08-08 06:11:32 +0300
committerGravatar GitHub <noreply@github.com> 2023-08-07 20:11:32 -0700
commita32097aa9f53f7c8ea1331c61dc2658bc1b11208 (patch)
treeefd62b1e3471923c92cbe2c2fb312ad9cabfefb4
parent1239c9460ac1d10376e27653b4c34d789f2a8f43 (diff)
downloadbun-a32097aa9f53f7c8ea1331c61dc2658bc1b11208.tar.gz
bun-a32097aa9f53f7c8ea1331c61dc2658bc1b11208.tar.zst
bun-a32097aa9f53f7c8ea1331c61dc2658bc1b11208.zip
implement `bun update` (#4046)
- analogous to `npm update` - `bun update <name>` to refresh specified package under `package.json` - `bun update` to refresh all package to latest versions
-rw-r--r--src/cli.zig24
-rw-r--r--src/cli/update_command.zig8
-rw-r--r--src/install/install.zig172
-rw-r--r--src/install/lockfile.zig15
-rw-r--r--test/cli/install/bun-update.test.ts259
5 files changed, 389 insertions, 89 deletions
diff --git a/src/cli.zig b/src/cli.zig
index ca0993dbb..759c45fde 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -1092,11 +1092,11 @@ pub const Command = struct {
for (args_iter.buf) |arg| {
const span = std.mem.span(arg);
if (span.len > 0 and (strings.eqlComptime(span, "-g") or strings.eqlComptime(span, "--global"))) {
- break :brk Command.Tag.AddCommand;
+ break :brk .AddCommand;
}
}
- break :brk Command.Tag.InstallCommand;
+ break :brk .InstallCommand;
},
RootCommandMatcher.case("c"), RootCommandMatcher.case("create") => .CreateCommand,
@@ -1104,7 +1104,8 @@ pub const Command = struct {
RootCommandMatcher.case("pm") => .PackageManagerCommand,
- RootCommandMatcher.case("add"), RootCommandMatcher.case("update"), RootCommandMatcher.case("a") => .AddCommand,
+ RootCommandMatcher.case("add"), RootCommandMatcher.case("a") => .AddCommand,
+ RootCommandMatcher.case("update") => .UpdateCommand,
RootCommandMatcher.case("r"), RootCommandMatcher.case("remove"), RootCommandMatcher.case("rm"), RootCommandMatcher.case("uninstall") => .RemoveCommand,
RootCommandMatcher.case("run") => .RunCommand,
@@ -1154,6 +1155,7 @@ pub const Command = struct {
const RemoveCommand = @import("./cli/remove_command.zig").RemoveCommand;
const RunCommand = @import("./cli/run_command.zig").RunCommand;
const ShellCompletions = @import("./cli/shell_completions.zig");
+ const UpdateCommand = @import("./cli/update_command.zig").UpdateCommand;
const UpgradeCommand = @import("./cli/upgrade_command.zig").UpgradeCommand;
const BunxCommand = @import("./cli/bunx_command.zig").BunxCommand;
@@ -1173,6 +1175,7 @@ pub const Command = struct {
// _ = RunCommand;
// _ = ShellCompletions;
// _ = TestCommand;
+ // _ = UpdateCommand;
// _ = UpgradeCommand;
// _ = BunxCommand;
}
@@ -1243,6 +1246,13 @@ pub const Command = struct {
try AddCommand.exec(ctx);
return;
},
+ .UpdateCommand => {
+ if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .UpdateCommand) unreachable;
+ const ctx = try Command.Context.create(allocator, log, .UpdateCommand);
+
+ try UpdateCommand.exec(ctx);
+ return;
+ },
.BunxCommand => {
if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunxCommand) unreachable;
const ctx = try Command.Context.create(allocator, log, .BunxCommand);
@@ -1624,6 +1634,7 @@ pub const Command = struct {
RunCommand,
TestCommand,
UnlinkCommand,
+ UpdateCommand,
UpgradeCommand,
pub fn params(comptime cmd: Tag) []const Arguments.ParamType {
@@ -1637,14 +1648,14 @@ pub const Command = struct {
pub fn readGlobalConfig(this: Tag) bool {
return switch (this) {
- .BunxCommand, .PackageManagerCommand, .InstallCommand, .AddCommand, .RemoveCommand => true,
+ .BunxCommand, .PackageManagerCommand, .InstallCommand, .AddCommand, .RemoveCommand, .UpdateCommand => true,
else => false,
};
}
pub fn isNPMRelated(this: Tag) bool {
return switch (this) {
- .BunxCommand, .LinkCommand, .UnlinkCommand, .PackageManagerCommand, .InstallCommand, .AddCommand, .RemoveCommand => true,
+ .BunxCommand, .LinkCommand, .UnlinkCommand, .PackageManagerCommand, .InstallCommand, .AddCommand, .RemoveCommand, .UpdateCommand => true,
else => false,
};
}
@@ -1656,6 +1667,7 @@ pub const Command = struct {
.InstallCommand = true,
.AddCommand = true,
.RemoveCommand = true,
+ .UpdateCommand = true,
.PackageManagerCommand = true,
.BunxCommand = true,
.AutoCommand = true,
@@ -1669,6 +1681,7 @@ pub const Command = struct {
.InstallCommand = true,
.AddCommand = true,
.RemoveCommand = true,
+ .UpdateCommand = true,
.PackageManagerCommand = true,
.BunxCommand = true,
});
@@ -1678,6 +1691,7 @@ pub const Command = struct {
.InstallCommand = false,
.AddCommand = false,
.RemoveCommand = false,
+ .UpdateCommand = false,
.PackageManagerCommand = false,
.LinkCommand = false,
.UnlinkCommand = false,
diff --git a/src/cli/update_command.zig b/src/cli/update_command.zig
new file mode 100644
index 000000000..bc2bfa76c
--- /dev/null
+++ b/src/cli/update_command.zig
@@ -0,0 +1,8 @@
+const Command = @import("../cli.zig").Command;
+const PackageManager = @import("../install/install.zig").PackageManager;
+
+pub const UpdateCommand = struct {
+ pub fn exec(ctx: Command.Context) !void {
+ try PackageManager.update(ctx);
+ }
+};
diff --git a/src/install/install.zig b/src/install/install.zig
index 5e1b54aa6..38f2ae11f 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -1641,6 +1641,7 @@ pub const PackageManager = struct {
root_download_node: std.Progress.Node = undefined,
to_remove: []const UpdateRequest = &[_]UpdateRequest{},
+ to_update: bool = false,
root_package_json_file: std.fs.File,
root_dependency_list: Lockfile.DependencySlice = .{},
@@ -3821,9 +3822,9 @@ pub const PackageManager = struct {
},
}
}
- for (manager.package_json_updates) |*update| {
- if (strings.eql(update.name, name.slice())) {
- update.failed = true;
+ for (manager.package_json_updates) |*request| {
+ if (strings.eql(request.name, name.slice())) {
+ request.failed = true;
manager.options.do.save_lockfile = false;
manager.options.do.save_yarn_lock = false;
manager.options.do.install_packages = false;
@@ -4880,21 +4881,21 @@ pub const PackageManager = struct {
// 3. There is a "dependencies" (or equivalent list), and the package name exists in multiple lists
ast_modifier: {
// Try to use the existing spot in the dependencies list if possible
- for (updates) |*update| {
+ for (updates) |*request| {
inline for ([_]string{ "dependencies", "devDependencies", "optionalDependencies" }) |list| {
if (current_package_json.asProperty(list)) |query| {
if (query.expr.data == .e_object) {
if (query.expr.asProperty(
- if (update.is_aliased)
- update.name
+ if (request.is_aliased)
+ request.name
else
- update.version.literal.slice(update.version_buf),
+ request.version.literal.slice(request.version_buf),
)) |value| {
if (value.expr.data == .e_string) {
- if (!update.resolved_name.isEmpty() and strings.eql(list, dependency_list)) {
+ if (!request.resolved_name.isEmpty() and strings.eql(list, dependency_list)) {
replacing += 1;
} else {
- update.e_string = value.expr.data.e_string;
+ request.e_string = value.expr.data.e_string;
remaining -= 1;
}
}
@@ -4919,16 +4920,16 @@ pub const PackageManager = struct {
bun.copy(G.Property, new_dependencies, dependencies);
@memset(new_dependencies[dependencies.len..], G.Property{});
- outer: for (updates) |*update| {
- if (update.e_string != null) continue;
- defer if (comptime Environment.allow_assert) std.debug.assert(update.e_string != null);
+ outer: for (updates) |*request| {
+ if (request.e_string != null) continue;
+ defer if (comptime Environment.allow_assert) std.debug.assert(request.e_string != null);
var k: usize = 0;
while (k < new_dependencies.len) : (k += 1) {
if (new_dependencies[k].key) |key| {
- if (!update.is_aliased and !update.resolved_name.isEmpty() and key.data.e_string.eql(
+ if (!request.is_aliased and !request.resolved_name.isEmpty() and key.data.e_string.eql(
string,
- update.resolved_name.slice(update.version_buf),
+ request.resolved_name.slice(request.version_buf),
)) {
// This actually is a duplicate which we did not
// pick up before dependency resolution.
@@ -4943,12 +4944,12 @@ pub const PackageManager = struct {
}
if (key.data.e_string.eql(
string,
- if (update.is_aliased)
- update.name
+ if (request.is_aliased)
+ request.name
else
- update.version.literal.slice(update.version_buf),
+ request.version.literal.slice(request.version_buf),
)) {
- if (update.resolved_name.isEmpty()) {
+ if (request.resolved_name.isEmpty()) {
// This actually is a duplicate like "react"
// appearing in both "dependencies" and "optionalDependencies".
// For this case, we'll just swap remove it
@@ -4968,12 +4969,12 @@ pub const PackageManager = struct {
new_dependencies[k].key = try JSAst.Expr.init(
JSAst.E.String,
JSAst.E.String{
- .data = try allocator.dupe(u8, if (update.is_aliased)
- update.name
- else if (update.resolved_name.isEmpty())
- update.version.literal.slice(update.version_buf)
+ .data = try allocator.dupe(u8, if (request.is_aliased)
+ request.name
+ else if (request.resolved_name.isEmpty())
+ request.version.literal.slice(request.version_buf)
else
- update.resolved_name.slice(update.version_buf)),
+ request.resolved_name.slice(request.version_buf)),
},
logger.Loc.Empty,
).clone(allocator);
@@ -4986,8 +4987,8 @@ pub const PackageManager = struct {
},
logger.Loc.Empty,
).clone(allocator);
- update.e_string = new_dependencies[k].value.?.data.e_string;
- if (update.is_aliased) continue :outer;
+ request.e_string = new_dependencies[k].value.?.data.e_string;
+ if (request.is_aliased) continue :outer;
}
}
}
@@ -5051,26 +5052,26 @@ pub const PackageManager = struct {
}
}
- for (updates) |*update| {
- if (update.e_string) |e_string| {
- e_string.data = switch (update.resolution.tag) {
- .npm => if (update.version.tag == .dist_tag and update.version.literal.isEmpty())
+ for (updates) |*request| {
+ if (request.e_string) |e_string| {
+ e_string.data = switch (request.resolution.tag) {
+ .npm => if (request.version.tag == .dist_tag and request.version.literal.isEmpty())
switch (exact_versions) {
false => std.fmt.allocPrint(allocator, "^{}", .{
- update.resolution.value.npm.version.fmt(update.version_buf),
+ request.resolution.value.npm.version.fmt(request.version_buf),
}) catch unreachable,
true => std.fmt.allocPrint(allocator, "{}", .{
- update.resolution.value.npm.version.fmt(update.version_buf),
+ request.resolution.value.npm.version.fmt(request.version_buf),
}) catch unreachable,
}
else
null,
- .uninitialized => switch (update.version.tag) {
+ .uninitialized => switch (request.version.tag) {
.uninitialized => try allocator.dupe(u8, latest),
else => null,
},
else => null,
- } orelse try allocator.dupe(u8, update.version.literal.slice(update.version_buf));
+ } orelse try allocator.dupe(u8, request.version.literal.slice(request.version_buf));
}
}
}
@@ -5079,6 +5080,7 @@ pub const PackageManager = struct {
// Corresponds to possible commands from the CLI.
pub const Subcommand = enum {
install,
+ update,
pm,
add,
remove,
@@ -5086,17 +5088,7 @@ pub const PackageManager = struct {
unlink,
};
- pub fn init(
- ctx: Command.Context,
- comptime subcommand: Subcommand,
- ) !*PackageManager {
- return initMaybeInstall(ctx, subcommand);
- }
-
- fn initMaybeInstall(
- ctx: Command.Context,
- comptime subcommand: Subcommand,
- ) !*PackageManager {
+ pub fn init(ctx: Command.Context, comptime subcommand: Subcommand) !*PackageManager {
const cli = try CommandLineArguments.parse(ctx.allocator, subcommand);
if (comptime subcommand == .install) {
@@ -5443,21 +5435,19 @@ pub const PackageManager = struct {
try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
}
- pub inline fn add(
- ctx: Command.Context,
- ) !void {
+ pub inline fn update(ctx: Command.Context) !void {
+ try updatePackageJSONAndInstall(ctx, .update, .update);
+ }
+
+ pub inline fn add(ctx: Command.Context) !void {
try updatePackageJSONAndInstall(ctx, .add, .add);
}
- pub inline fn remove(
- ctx: Command.Context,
- ) !void {
+ pub inline fn remove(ctx: Command.Context) !void {
try updatePackageJSONAndInstall(ctx, .remove, .remove);
}
- pub inline fn link(
- ctx: Command.Context,
- ) !void {
+ pub inline fn link(ctx: Command.Context) !void {
var manager = PackageManager.init(ctx, .link) catch |err| brk: {
if (err == error.MissingPackageJSON) {
try attemptToCreatePackageJSON();
@@ -5602,14 +5592,12 @@ pub const PackageManager = struct {
} else {
// bun link lodash
switch (manager.options.log_level) {
- inline else => |log_level| try updatePackageJSONAndInstallWithManager(ctx, manager, .link, log_level),
+ inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, .link, log_level),
}
}
}
- pub inline fn unlink(
- ctx: Command.Context,
- ) !void {
+ pub inline fn unlink(ctx: Command.Context) !void {
var manager = PackageManager.init(ctx, .unlink) catch |err| brk: {
if (err == error.MissingPackageJSON) {
try attemptToCreatePackageJSON();
@@ -5770,6 +5758,10 @@ pub const PackageManager = struct {
clap.parseParam("<POS> ... ") catch unreachable,
};
+ const update_params = install_params_ ++ [_]ParamType{
+ clap.parseParam("<POS> ... \"name\" of packages to update") catch unreachable,
+ };
+
const pm_params = install_params_ ++ [_]ParamType{
clap.parseParam("<POS> ... ") catch unreachable,
};
@@ -5848,6 +5840,7 @@ pub const PackageManager = struct {
pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !CommandLineArguments {
comptime var params: []const ParamType = &switch (subcommand) {
.install => install_params,
+ .update => update_params,
.pm => pm_params,
.add => add_params,
.remove => remove_params,
@@ -6087,15 +6080,22 @@ pub const PackageManager = struct {
comptime op: Lockfile.Package.Diff.Op,
comptime subcommand: Subcommand,
) !void {
- var manager = PackageManager.init(ctx, subcommand) catch |err| brk: {
+ var manager = init(ctx, subcommand) catch |err| brk: {
if (err == error.MissingPackageJSON) {
- if (op == .remove) {
- Output.prettyErrorln("<r>No package.json, so nothing to remove\n", .{});
- Global.crash();
+ switch (op) {
+ .update => {
+ Output.prettyErrorln("<r>No package.json, so nothing to update\n", .{});
+ Global.crash();
+ },
+ .remove => {
+ Output.prettyErrorln("<r>No package.json, so nothing to remove\n", .{});
+ Global.crash();
+ },
+ else => {
+ try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, subcommand);
+ },
}
-
- try attemptToCreatePackageJSON();
- break :brk try PackageManager.init(ctx, subcommand);
}
return err;
@@ -6107,13 +6107,13 @@ pub const PackageManager = struct {
}
switch (manager.options.log_level) {
- inline else => |log_level| try updatePackageJSONAndInstallWithManager(ctx, manager, op, log_level),
+ inline else => |log_level| try manager.updatePackageJSONAndInstallWithManager(ctx, op, log_level),
}
}
fn updatePackageJSONAndInstallWithManager(
- ctx: Command.Context,
manager: *PackageManager,
+ ctx: Command.Context,
comptime op: Lockfile.Package.Diff.Op,
comptime log_level: Options.LogLevel,
) !void {
@@ -6125,7 +6125,7 @@ pub const PackageManager = struct {
const off = @as(u64, @intCast(std.time.milliTimestamp()));
switch (op) {
- .update, .add => {
+ .add => {
const filler = @import("../cli.zig").HelpCommand.packages_to_add_filler;
examples_to_print[0] = filler[@as(usize, @intCast((off) % filler.len))];
@@ -6210,9 +6210,8 @@ pub const PackageManager = struct {
}
var updates = UpdateRequest.parse(ctx.allocator, ctx.log, manager.options.positionals[1..], &update_requests, op);
- try updatePackageJSONAndInstallWithManagerWithUpdates(
+ try manager.updatePackageJSONAndInstallWithManagerWithUpdates(
ctx,
- manager,
updates,
false,
op,
@@ -6221,8 +6220,8 @@ pub const PackageManager = struct {
}
fn updatePackageJSONAndInstallWithManagerWithUpdates(
- ctx: Command.Context,
manager: *PackageManager,
+ ctx: Command.Context,
updates: []UpdateRequest,
auto_free: bool,
comptime op: Lockfile.Package.Diff.Op,
@@ -6304,7 +6303,7 @@ pub const PackageManager = struct {
.remove => {
// if we're removing, they don't have to specify where it is installed in the dependencies list
// they can even put it multiple times and we will just remove all of them
- for (updates) |update| {
+ for (updates) |request| {
inline for ([_]string{ "dependencies", "devDependencies", "optionalDependencies", "peerDependencies" }) |list| {
if (current_package_json.asProperty(list)) |query| {
if (query.expr.data == .e_object) {
@@ -6313,7 +6312,7 @@ pub const PackageManager = struct {
var new_len = dependencies.len;
while (i < dependencies.len) : (i += 1) {
if (dependencies[i].key.?.data == .e_string) {
- if (dependencies[i].key.?.data.e_string.eql(string, update.name)) {
+ if (dependencies[i].key.?.data.e_string.eql(string, request.name)) {
if (new_len > 1) {
dependencies[i] = dependencies[new_len - 1];
new_len -= 1;
@@ -6354,7 +6353,7 @@ pub const PackageManager = struct {
}
manager.to_remove = updates;
},
- .link, .add, .update => {
+ .link, .add => {
try PackageJSONEditor.edit(
ctx.allocator,
updates,
@@ -6364,6 +6363,10 @@ pub const PackageManager = struct {
);
manager.package_json_updates = updates;
},
+ .update => {
+ manager.package_json_updates = updates;
+ manager.to_update = true;
+ },
else => {},
}
@@ -6397,8 +6400,8 @@ pub const PackageManager = struct {
try manager.installWithManager(ctx, new_package_json_source, log_level);
if (op == .update or op == .add or op == .link) {
- for (manager.package_json_updates) |update| {
- if (update.failed) {
+ for (manager.package_json_updates) |request| {
+ if (request.failed) {
Global.exit(1);
return;
}
@@ -6454,14 +6457,14 @@ pub const PackageManager = struct {
bun.copy(u8, &node_modules_buf, "node_modules" ++ std.fs.path.sep_str);
var offset_buf = node_modules_buf["node_modules/".len..];
const name_hashes = manager.lockfile.packages.items(.name_hash);
- for (updates) |update| {
+ for (updates) |request| {
// If the package no longer exists in the updated lockfile, delete the directory
// This is not thorough.
// It does not handle nested dependencies
// This is a quick & dirty cleanup intended for when deleting top-level dependencies
- if (std.mem.indexOfScalar(PackageNameHash, name_hashes, String.Builder.stringHash(update.name)) == null) {
- bun.copy(u8, offset_buf, update.name);
- cwd.deleteTree(node_modules_buf[0 .. "node_modules/".len + update.name.len]) catch {};
+ if (std.mem.indexOfScalar(PackageNameHash, name_hashes, String.Builder.stringHash(request.name)) == null) {
+ bun.copy(u8, offset_buf, request.name);
+ cwd.deleteTree(node_modules_buf[0 .. "node_modules/".len + request.name.len]) catch {};
}
}
@@ -6500,7 +6503,7 @@ pub const PackageManager = struct {
var package_json_cwd: string = "";
pub inline fn install(ctx: Command.Context) !void {
- var manager = initMaybeInstall(ctx, .install) catch |err| {
+ var manager = init(ctx, .install) catch |err| {
if (err == error.SwitchToBunAdd) {
return add(ctx);
}
@@ -7493,6 +7496,7 @@ pub const PackageManager = struct {
&lockfile,
&root,
&maybe_root,
+ if (manager.to_update) manager.package_json_updates else null,
mapping,
);
@@ -7666,9 +7670,9 @@ pub const PackageManager = struct {
}
if (manager.lockfile.packages.len > 0) {
- for (manager.package_json_updates) |update| {
+ for (manager.package_json_updates) |request| {
// prevent redundant errors
- if (update.failed) {
+ if (request.failed) {
return error.InstallFailed;
}
}
@@ -7845,8 +7849,8 @@ pub const PackageManager = struct {
}
} else if (manager.summary.remove > 0) {
if (manager.to_remove.len > 0) {
- for (manager.to_remove) |update| {
- Output.prettyln(" <r><red>-<r> {s}", .{update.name});
+ for (manager.to_remove) |request| {
+ Output.prettyln(" <r><red>-<r> {s}", .{request.name});
}
}
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index 869d46f4d..e293b2213 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -2393,6 +2393,7 @@ pub const Package = extern struct {
to_lockfile: *Lockfile,
from: *Lockfile.Package,
to: *Lockfile.Package,
+ update_requests: ?[]PackageManager.UpdateRequest,
id_mapping: ?[]PackageID,
) !Summary {
var summary = Summary{};
@@ -2427,6 +2428,19 @@ pub const Package = extern struct {
defer to_i += 1;
if (to_deps[to_i].eql(from_dep, to_lockfile.buffers.string_bytes.items, from_lockfile.buffers.string_bytes.items)) {
+ if (update_requests) |updates| {
+ if (updates.len == 0 or brk: {
+ for (updates) |request| {
+ if (from_dep.name_hash == request.name_hash) break :brk true;
+ }
+ break :brk false;
+ }) {
+ // Listed as to be updated
+ summary.update += 1;
+ continue;
+ }
+ }
+
if (id_mapping) |mapping| {
const version = to_deps[to_i].version;
if (switch (version.tag) {
@@ -2452,6 +2466,7 @@ pub const Package = extern struct {
to_lockfile,
&from_pkg,
&workspace,
+ update_requests,
null,
);
diff --git a/test/cli/install/bun-update.test.ts b/test/cli/install/bun-update.test.ts
new file mode 100644
index 000000000..2a6ee4eaf
--- /dev/null
+++ b/test/cli/install/bun-update.test.ts
@@ -0,0 +1,259 @@
+import { file, listen, Socket, spawn } from "bun";
+import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
+import { bunExe, bunEnv as env } from "harness";
+import { access, mkdir, readlink, realpath, rm, writeFile } from "fs/promises";
+import { join } from "path";
+import {
+ dummyAfterAll,
+ dummyAfterEach,
+ dummyBeforeAll,
+ dummyBeforeEach,
+ dummyRegistry,
+ package_dir,
+ readdirSorted,
+ requested,
+ root_url,
+ setHandler,
+} from "./dummy.registry.js";
+
+beforeAll(dummyBeforeAll);
+afterAll(dummyAfterAll);
+beforeEach(dummyBeforeEach);
+afterEach(dummyAfterEach);
+
+it("should update to latest version of dependency", async () => {
+ const urls: string[] = [];
+ const registry = {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ "0.0.5": {
+ bin: {
+ "baz-exec": "index.js",
+ },
+ },
+ latest: "0.0.3",
+ };
+ setHandler(dummyRegistry(urls, registry));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ dependencies: {
+ baz: "~0.0.2",
+ },
+ }),
+ );
+ 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).not.toContain("error:");
+ 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([
+ " + baz@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+ // Perform `bun update` with updated registry & lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ urls.length = 0;
+ registry.latest = "0.0.5";
+ setHandler(dummyRegistry(urls, registry));
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "update", "baz"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("error:");
+ expect(err2).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([
+ "",
+ " installed baz@0.0.5 with binaries:",
+ " - baz-exec",
+ "",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.5.tgz`]);
+ expect(requested).toBe(4);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-exec"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-exec"))).toBe(join("..", "baz", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.5",
+ bin: {
+ "baz-exec": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should update to latest versions of dependencies", async () => {
+ const urls: string[] = [];
+ const registry = {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ "0.0.5": {
+ bin: {
+ "baz-exec": "index.js",
+ },
+ },
+ "0.1.0": {},
+ latest: "0.0.3",
+ };
+ setHandler(dummyRegistry(urls, registry));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ dependencies: {
+ "@barn/moo": "^0.1.0",
+ baz: "~0.0.2",
+ },
+ }),
+ );
+ 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).not.toContain("error:");
+ 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([
+ " + @barn/moo@0.1.0",
+ " + baz@0.0.3",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(urls.sort()).toEqual([
+ `${root_url}/@barn/moo`,
+ `${root_url}/@barn/moo-0.1.0.tgz`,
+ `${root_url}/baz`,
+ `${root_url}/baz-0.0.3.tgz`,
+ ]);
+ expect(requested).toBe(4);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "@barn", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn"))).toEqual(["moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn", "moo"))).toEqual(["package.json"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+ // Perform `bun update` with updated registry & lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ urls.length = 0;
+ registry.latest = "0.0.5";
+ setHandler(dummyRegistry(urls, registry));
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "update"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("error:");
+ expect(err2).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([
+ " + @barn/moo@0.1.0",
+ " + baz@0.0.5",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(urls.sort()).toEqual([
+ `${root_url}/@barn/moo`,
+ `${root_url}/@barn/moo-0.1.0.tgz`,
+ `${root_url}/baz`,
+ `${root_url}/baz-0.0.5.tgz`,
+ ]);
+ expect(requested).toBe(8);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "@barn", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-exec"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-exec"))).toBe(join("..", "baz", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn"))).toEqual(["moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn", "moo"))).toEqual(["package.json"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.5",
+ bin: {
+ "baz-exec": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});