aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cli/package_manager_command.zig6
-rw-r--r--src/install/install.zig121
-rw-r--r--test/cli/install/bun-add.test.ts45
3 files changed, 116 insertions, 56 deletions
diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig
index b7e547c7a..83d3dd023 100644
--- a/src/cli/package_manager_command.zig
+++ b/src/cli/package_manager_command.zig
@@ -51,7 +51,7 @@ pub const PackageManagerCommand = struct {
@memcpy(&lockfile_buffer, lockfile_.ptr, lockfile_.len);
lockfile_buffer[lockfile_.len] = 0;
var lockfile = lockfile_buffer[0..lockfile_.len :0];
- var pm = try PackageManager.init(ctx, null, &PackageManager.install_params);
+ var pm = try PackageManager.init(ctx, null, PackageManager.Subcommand.pm);
const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, lockfile);
handleLoadLockfileErrors(load_lockfile, pm);
@@ -87,11 +87,11 @@ pub const PackageManagerCommand = struct {
var args = try std.process.argsAlloc(ctx.allocator);
args = args[1..];
- var pm = PackageManager.init(ctx, null, &PackageManager.install_params) catch |err| {
+ var pm = PackageManager.init(ctx, null, PackageManager.Subcommand.pm) catch |err| {
// TODO: error messages here
// if (err == error.MissingPackageJSON) {
// // TODO: error messages
- // // var cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, &PackageManager.install_params, &_ctx);
+ // // var cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, PackageManager.Subcommand.pm, &_ctx);
// }
return err;
diff --git a/src/install/install.zig b/src/install/install.zig
index 37aafcc85..cfbb0cb7f 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -4968,37 +4968,46 @@ pub const PackageManager = struct {
}
};
+ // Corresponds to possible commands from the CLI.
+ pub const Subcommand = enum {
+ install,
+ pm,
+ add,
+ remove,
+ link,
+ unlink,
+ };
+
pub fn init(
ctx: Command.Context,
package_json_file_: ?std.fs.File,
- comptime params: []const ParamType,
+ comptime subcommand: Subcommand,
) !*PackageManager {
- return initMaybeInstall(ctx, package_json_file_, params, false);
+ return initMaybeInstall(ctx, package_json_file_, subcommand);
}
fn initMaybeInstall(
ctx: Command.Context,
package_json_file_: ?std.fs.File,
- comptime params: []const ParamType,
- comptime is_install: bool,
+ comptime subcommand: Subcommand,
) !*PackageManager {
- const cli = try CommandLineArguments.parse(ctx.allocator, params);
+ const cli = try CommandLineArguments.parse(ctx.allocator, subcommand);
- if (comptime is_install) {
+ if (comptime subcommand == .install) {
if (cli.positionals.len > 1) {
return error.SwitchToBunAdd;
}
}
var _ctx = ctx;
- return initWithCLI(&_ctx, package_json_file_, cli, is_install);
+ return initWithCLI(&_ctx, package_json_file_, cli, subcommand);
}
fn initWithCLI(
ctx: *Command.Context,
package_json_file_: ?std.fs.File,
cli: CommandLineArguments,
- comptime is_install: bool,
+ comptime subcommand: Subcommand,
) !*PackageManager {
// assume that spawning a thread will take a lil so we do that asap
try HTTP.HTTPThread.init();
@@ -5086,7 +5095,7 @@ pub const PackageManager = struct {
for (workspace_names.keys()) |path| {
if (strings.eql(child_cwd, path)) {
fs.top_level_dir = parent;
- if (comptime is_install) {
+ if (comptime subcommand == .install) {
found = true;
child_json.close();
break :brk json_file;
@@ -5323,31 +5332,35 @@ pub const PackageManager = struct {
return manager;
}
+ fn attemptToCreatePackageJSON() !std.fs.File {
+ var package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err| {
+ Output.prettyErrorln("<r><red>error:<r> {s} create package.json", .{@errorName(err)});
+ Global.crash();
+ };
+ try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
+ return package_json_file;
+ }
+
pub inline fn add(
ctx: Command.Context,
) !void {
- try updatePackageJSONAndInstall(ctx, .add, &add_params);
+ try updatePackageJSONAndInstall(ctx, .add, .add);
}
pub inline fn remove(
ctx: Command.Context,
) !void {
- try updatePackageJSONAndInstall(ctx, .remove, &remove_params);
+ try updatePackageJSONAndInstall(ctx, .remove, .remove);
}
pub inline fn link(
ctx: Command.Context,
) !void {
- var manager = PackageManager.init(ctx, null, &link_params) catch |err| brk: {
+ var manager = PackageManager.init(ctx, null, .link) catch |err| brk: {
switch (err) {
error.MissingPackageJSON => {
- var package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err2| {
- Output.prettyErrorln("<r><red>error:<r> {s} create package.json", .{@errorName(err2)});
- Global.crash();
- };
- try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
-
- break :brk try PackageManager.init(ctx, package_json_file, &link_params);
+ var package_json_file = try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, package_json_file, .link);
},
else => return err,
}
@@ -5502,16 +5515,11 @@ pub const PackageManager = struct {
pub inline fn unlink(
ctx: Command.Context,
) !void {
- var manager = PackageManager.init(ctx, null, &unlink_params) catch |err| brk: {
+ var manager = PackageManager.init(ctx, null, .unlink) catch |err| brk: {
switch (err) {
error.MissingPackageJSON => {
- var package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err2| {
- Output.prettyErrorln("<r><red>error:<r> {s} create package.json", .{@errorName(err2)});
- Global.crash();
- };
- try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
-
- break :brk try PackageManager.init(ctx, package_json_file, &unlink_params);
+ var package_json_file = try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, package_json_file, .unlink);
},
else => return err,
}
@@ -5633,11 +5641,12 @@ pub const PackageManager = struct {
else
"Possible values: \"hardlink\" (default), \"symlink\", \"copyfile\"";
- pub const install_params_ = [_]ParamType{
+ const install_params_ = [_]ParamType{
clap.parseParam("-c, --config <STR>? Load config (bunfig.toml)") catch unreachable,
clap.parseParam("-y, --yarn Write a yarn.lock file (yarn v1)") catch unreachable,
clap.parseParam("-p, --production Don't install devDependencies") catch unreachable,
clap.parseParam("--no-save Don't save a lockfile") catch unreachable,
+ clap.parseParam("--save Save to package.json") catch unreachable,
clap.parseParam("--dry-run Don't install anything") catch unreachable,
clap.parseParam("--lockfile <PATH> Store & load a lockfile at a specific filepath") catch unreachable,
clap.parseParam("-f, --force Always request the latest versions from the registry & reinstall all dependencies") catch unreachable,
@@ -5660,27 +5669,29 @@ pub const PackageManager = struct {
clap.parseParam("--help Print this help menu") catch unreachable,
};
- pub const install_params = install_params_ ++ [_]ParamType{
+ const install_params = install_params_ ++ [_]ParamType{
clap.parseParam("<POS> ... ") catch unreachable,
};
- pub const add_params = install_params_ ++ [_]ParamType{
+ const pm_params = install_params_ ++ [_]ParamType{
+ clap.parseParam("<POS> ... ") catch unreachable,
+ };
+
+ const add_params = install_params_ ++ [_]ParamType{
clap.parseParam("-d, --development Add dependency to \"devDependencies\"") catch unreachable,
clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("<POS> ... \"name\" or \"name@version\" of packages to install") catch unreachable,
};
- pub const remove_params = install_params_ ++ [_]ParamType{
+ const remove_params = install_params_ ++ [_]ParamType{
clap.parseParam("<POS> ... \"name\" of packages to remove from package.json") catch unreachable,
};
- pub const link_params = install_params_ ++ [_]ParamType{
- clap.parseParam("--save Save to package.json") catch unreachable,
+ const link_params = install_params_ ++ [_]ParamType{
clap.parseParam("<POS> ... \"name\" install package as a link") catch unreachable,
};
- pub const unlink_params = install_params_ ++ [_]ParamType{
- clap.parseParam("--save Save to package.json") catch unreachable,
+ const unlink_params = install_params_ ++ [_]ParamType{
clap.parseParam("<POS> ... \"name\" uninstall package as a link") catch unreachable,
};
@@ -5732,7 +5743,16 @@ pub const PackageManager = struct {
}
};
- pub fn parse(allocator: std.mem.Allocator, comptime params: []const ParamType) !CommandLineArguments {
+ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !CommandLineArguments {
+ comptime var params: []const ParamType = &switch (subcommand) {
+ .install => install_params,
+ .pm => pm_params,
+ .add => add_params,
+ .remove => remove_params,
+ .link => link_params,
+ .unlink => unlink_params,
+ };
+
var diag = clap.Diagnostic{};
var args = clap.parse(clap.Help, params, .{
@@ -5757,7 +5777,6 @@ pub const PackageManager = struct {
var cli = CommandLineArguments{};
cli.yarn = args.flag("--yarn");
cli.production = args.flag("--production");
- cli.no_save = args.flag("--no-save");
cli.no_progress = args.flag("--no-progress");
cli.dry_run = args.flag("--dry-run");
cli.global = args.flag("--global");
@@ -5769,12 +5788,13 @@ pub const PackageManager = struct {
cli.verbose = args.flag("--verbose");
cli.ignore_scripts = args.flag("--ignore-scripts");
cli.no_summary = args.flag("--no-summary");
- if (comptime @TypeOf(args).hasFlag("--save")) {
- cli.no_save = true;
- if (args.flag("--save")) {
- cli.no_save = false;
- }
+ // link and unlink default to not saving, all others default to
+ // saving.
+ if (comptime subcommand == .link or subcommand == .unlink) {
+ cli.no_save = !args.flag("--save");
+ } else {
+ cli.no_save = args.flag("--no-save");
}
if (args.option("--config")) |opt| {
@@ -5783,7 +5803,7 @@ pub const PackageManager = struct {
cli.link_native_bins = args.options("--link-native-bins");
- if (comptime params.len == add_params.len) {
+ if (comptime subcommand == .add) {
cli.development = args.flag("--development");
cli.optional = args.flag("--optional");
}
@@ -5961,19 +5981,14 @@ pub const PackageManager = struct {
fn updatePackageJSONAndInstall(
ctx: Command.Context,
comptime op: Lockfile.Package.Diff.Op,
- comptime params: []const ParamType,
+ comptime subcommand: Subcommand,
) !void {
- var manager = PackageManager.init(ctx, null, params) catch |err| brk: {
+ var manager = PackageManager.init(ctx, null, subcommand) catch |err| brk: {
switch (err) {
error.MissingPackageJSON => {
if (op == .add or op == .update) {
- var package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err2| {
- Output.prettyErrorln("<r><red>error:<r> {s} create package.json", .{@errorName(err2)});
- Global.crash();
- };
- try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
-
- break :brk try PackageManager.init(ctx, package_json_file, params);
+ var package_json_file = try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, package_json_file, subcommand);
}
Output.prettyErrorln("<r>No package.json, so nothing to remove\n", .{});
@@ -6385,7 +6400,7 @@ pub const PackageManager = struct {
var package_json_cwd: string = "";
pub inline fn install(ctx: Command.Context) !void {
- var manager = initMaybeInstall(ctx, null, &install_params, true) catch |err| {
+ var manager = initMaybeInstall(ctx, null, .install) catch |err| {
if (err == error.SwitchToBunAdd) {
return add(ctx);
}
diff --git a/test/cli/install/bun-add.test.ts b/test/cli/install/bun-add.test.ts
index 66ad6afa2..79804f0e0 100644
--- a/test/cli/install/bun-add.test.ts
+++ b/test/cli/install/bun-add.test.ts
@@ -1416,3 +1416,48 @@ it("should add dependencies to workspaces directly", async () => {
expect(await readdirSorted(join(package_dir, "node_modules", "foo"))).toEqual(["package.json"]);
expect(await file(join(package_dir, "node_modules", "foo", "package.json")).text()).toEqual(foo_package);
});
+
+it("should redirect 'install --save X' to 'add'", async () => {
+ await installRedirectsToAdd(true);
+});
+
+it("should redirect 'install X --save' to 'add'", async () => {
+ await installRedirectsToAdd(false);
+});
+
+async function installRedirectsToAdd(saveFlagFirst) {
+ await writeFile(
+ join(add_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ }),
+ );
+ const add_path = relative(package_dir, add_dir);
+
+ const args = [`file:${add_path}`, "--save"];
+ if (saveFlagFirst) args.reverse();
+
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", ...args],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun add", " Saved lockfile", ""]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ "",
+ ` installed foo@${add_path}`,
+ "",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect((await file(join(package_dir, "package.json")).text()).includes("bun-add.test"));
+}