From d8817c2d32a237440a7677622ba351aa95f47c22 Mon Sep 17 00:00:00 2001 From: Tiago Teixeira Date: Mon, 26 Jun 2023 01:43:39 +0200 Subject: Add support for install with --frozen-lockfile (#3365) * Add support for install with --frozen-lockfile * Add test * Add test for frozenLockfile in config file --- src/api/schema.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src/api/schema.js') diff --git a/src/api/schema.js b/src/api/schema.js index c4f2400ed..270eb9a62 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -3044,6 +3044,10 @@ function decodeBunInstall(bb) { result["global_bin_dir"] = bb.readString(); break; + case 19: + result["frozen_lockfile"] = !!bb.readByte(); + break; + default: throw new Error("Attempted to parse invalid message"); } @@ -3164,6 +3168,12 @@ function encodeBunInstall(message, bb) { bb.writeByte(18); bb.writeString(value); } + + var value = message["frozen_lockfile"]; + if (value != null) { + bb.writeByte(19); + bb.writeByte(value); + } bb.writeByte(0); } -- cgit v1.2.3 From 28f27f733b3db072944156694301651b09b7696b Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 26 Jun 2023 15:51:57 -0700 Subject: [bun install] Implement `--exact` flag (#3409) * [bun install] Implement `--exact` flag * Rename to --save-exact * Rename --exact to --save-exact * Update bun-add.test.ts * We're going with --exact as the flag name --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- src/api/schema.d.ts | 1 + src/api/schema.js | 10 ++++++++ src/api/schema.peechy | 1 + src/api/schema.zig | 10 ++++++++ src/bunfig.zig | 8 ++++++ src/install/install.zig | 53 +++++++++++++++++++++++++++++++++----- src/install/lockfile.zig | 32 +++++++++++++++++------ test/cli/install/bun-add.test.ts | 55 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 155 insertions(+), 15 deletions(-) (limited to 'src/api/schema.js') diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index ac6183878..2a86340ad 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -710,6 +710,7 @@ export interface BunInstall { global_dir?: string; global_bin_dir?: string; frozen_lockfile?: boolean; + exact?: boolean; } export interface ClientServerModule { diff --git a/src/api/schema.js b/src/api/schema.js index 270eb9a62..f1e68031e 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -3048,6 +3048,10 @@ function decodeBunInstall(bb) { result["frozen_lockfile"] = !!bb.readByte(); break; + case 20: + result["exact"] = !!bb.readByte(); + break; + default: throw new Error("Attempted to parse invalid message"); } @@ -3174,6 +3178,12 @@ function encodeBunInstall(message, bb) { bb.writeByte(19); bb.writeByte(value); } + + var value = message["exact"]; + if (value != null) { + bb.writeByte(20); + bb.writeByte(value); + } bb.writeByte(0); } diff --git a/src/api/schema.peechy b/src/api/schema.peechy index 6d28381c4..a172606f7 100644 --- a/src/api/schema.peechy +++ b/src/api/schema.peechy @@ -591,6 +591,7 @@ message BunInstall { string global_dir = 17; string global_bin_dir = 18; bool frozen_lockfile = 19; + bool exact = 20; } struct ClientServerModule { diff --git a/src/api/schema.zig b/src/api/schema.zig index 2de80d42c..ec8efa9f6 100644 --- a/src/api/schema.zig +++ b/src/api/schema.zig @@ -2904,6 +2904,9 @@ pub const Api = struct { /// frozen_lockfile frozen_lockfile: ?bool = null, + /// exact + exact: ?bool = null, + pub fn decode(reader: anytype) anyerror!BunInstall { var this = std.mem.zeroes(BunInstall); @@ -2970,6 +2973,9 @@ pub const Api = struct { 19 => { this.frozen_lockfile = try reader.readValue(bool); }, + 20 => { + this.exact = try reader.readValue(bool); + }, else => { return error.InvalidMessage; }, @@ -3055,6 +3061,10 @@ pub const Api = struct { try writer.writeFieldID(19); try writer.writeInt(@as(u8, @intFromBool(frozen_lockfile))); } + if (this.exact) |exact| { + try writer.writeFieldID(20); + try writer.writeInt(@as(u8, @intFromBool(exact))); + } try writer.endMessage(); } }; diff --git a/src/bunfig.zig b/src/bunfig.zig index 597fb0985..1244f52b8 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -259,6 +259,14 @@ pub const Bunfig = struct { } } + if (json.get("exact")) |exact_install_expr| { + try this.expect(exact_install_expr, .e_boolean); + + if (exact_install_expr.asBool().?) { + install.exact = true; + } + } + if (json.get("prefer")) |prefer_expr| { try this.expect(prefer_expr, .e_string); diff --git a/src/install/install.zig b/src/install/install.zig index 22068bbf3..9465c4897 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -4461,6 +4461,10 @@ pub const PackageManager = struct { this.remote_package_features.peer_dependencies = save; } + if (bun_install.exact) |exact| { + this.enable.exact_versions = exact; + } + if (bun_install.production) |production| { if (production) { this.local_package_features.dev_dependencies = false; @@ -4569,6 +4573,10 @@ pub const PackageManager = struct { this.scope.url = URL.parse(cli.registry); } + if (cli.exact) { + this.enable.exact_versions = true; + } + if (cli.token.len > 0) { this.scope.token = cli.token; } @@ -4755,6 +4763,8 @@ pub const PackageManager = struct { force_save_lockfile: bool = false, force_install: bool = false, + + exact_versions: bool = false, }; }; @@ -4802,6 +4812,7 @@ pub const PackageManager = struct { updates: []UpdateRequest, current_package_json: *JSAst.Expr, dependency_list: string, + exact_versions: bool, ) !void { const G = JSAst.G; @@ -4989,9 +5000,14 @@ pub const PackageManager = struct { 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()) - std.fmt.allocPrint(allocator, "^{}", .{ - update.resolution.value.npm.version.fmt(update.version_buf), - }) catch unreachable + switch (exact_versions) { + false => std.fmt.allocPrint(allocator, "^{}", .{ + update.resolution.value.npm.version.fmt(update.version_buf), + }) catch unreachable, + true => std.fmt.allocPrint(allocator, "{}", .{ + update.resolution.value.npm.version.fmt(update.version_buf), + }) catch unreachable, + } else null, .uninitialized => switch (update.version.tag) { @@ -5709,6 +5725,7 @@ pub const PackageManager = struct { 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("--exact Add the exact version instead of the ^range") catch unreachable, clap.parseParam(" ... \"name\" or \"name@version\" of packages to install") catch unreachable, }; @@ -5759,6 +5776,8 @@ pub const PackageManager = struct { no_optional: bool = false, omit: Omit = Omit{}, + exact: bool = false, + const Omit = struct { dev: bool = false, optional: bool = true, @@ -5837,6 +5856,7 @@ pub const PackageManager = struct { if (comptime subcommand == .add) { cli.development = args.flag("--development"); cli.optional = args.flag("--optional"); + cli.exact = args.flag("--exact"); } // for (args.options("--omit")) |omit| { @@ -6293,7 +6313,13 @@ pub const PackageManager = struct { manager.to_remove = updates; }, .link, .add, .update => { - try PackageJSONEditor.edit(ctx.allocator, updates, ¤t_package_json, dependency_list); + try PackageJSONEditor.edit( + ctx.allocator, + updates, + ¤t_package_json, + dependency_list, + manager.options.enable.exact_versions, + ); manager.package_json_updates = updates; }, else => {}, @@ -6346,7 +6372,13 @@ pub const PackageManager = struct { return; }; - try PackageJSONEditor.edit(ctx.allocator, updates, ¤t_package_json, dependency_list); + try PackageJSONEditor.edit( + ctx.allocator, + updates, + ¤t_package_json, + dependency_list, + manager.options.enable.exact_versions, + ); var buffer_writer_two = try JSPrinter.BufferWriter.init(ctx.allocator); try buffer_writer_two.buffer.list.ensureTotalCapacity(ctx.allocator, new_package_json_source.len + 1); buffer_writer_two.append_newline = @@ -7031,7 +7063,10 @@ pub const PackageManager = struct { ) !PackageInstall.Summary { var lockfile = lockfile_; if (!this.options.local_package_features.dev_dependencies) { - lockfile = try lockfile.maybeCloneFilteringRootPackages(this.options.local_package_features); + lockfile = try lockfile.maybeCloneFilteringRootPackages( + this.options.local_package_features, + this.options.enable.exact_versions, + ); } var root_node: *Progress.Node = undefined; @@ -7582,7 +7617,11 @@ pub const PackageManager = struct { const needs_clean_lockfile = had_any_diffs or needs_new_lockfile or manager.package_json_updates.len > 0; var did_meta_hash_change = needs_clean_lockfile; if (needs_clean_lockfile) { - manager.lockfile = try manager.lockfile.cleanWithLogger(manager.package_json_updates, manager.log); + manager.lockfile = try manager.lockfile.cleanWithLogger( + manager.package_json_updates, + manager.log, + manager.options.enable.exact_versions, + ); } if (manager.lockfile.packages.len > 0) { diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index a51d2b2ee..7d21860ef 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -538,6 +538,7 @@ pub const Tree = struct { pub fn maybeCloneFilteringRootPackages( old: *Lockfile, features: Features, + exact_versions: bool, ) !*Lockfile { const old_root_dependenices_list = old.packages.items(.dependencies)[0]; var old_root_resolutions = old.packages.items(.resolutions)[0]; @@ -555,10 +556,10 @@ pub fn maybeCloneFilteringRootPackages( if (!any_changes) return old; - return try old.clean(&.{}); + return try old.clean(&.{}, exact_versions); } -fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequest) !void { +fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequest, exact_versions: bool) !void { const root_deps_list: Lockfile.DependencySlice = old.packages.items(.dependencies)[0]; if (@as(usize, root_deps_list.off) < old.buffers.dependencies.items.len) { var string_builder = old.stringBuilder(); @@ -575,7 +576,10 @@ fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequ if (dep.name_hash == String.Builder.stringHash(update.name)) { if (old_resolution > old.packages.len) continue; const res = resolutions_of_yore[old_resolution]; - const len = std.fmt.count("^{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}); + const len = switch (exact_versions) { + false => std.fmt.count("^{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}), + true => std.fmt.count("{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}), + }; if (len >= String.max_inline_len) { string_builder.cap += len; } @@ -603,7 +607,10 @@ fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequ if (dep.name_hash == String.Builder.stringHash(update.name)) { if (old_resolution > old.packages.len) continue; const res = resolutions_of_yore[old_resolution]; - var buf = std.fmt.bufPrint(&temp_buf, "^{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}) catch break; + var buf = switch (exact_versions) { + false => std.fmt.bufPrint(&temp_buf, "^{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}) catch break, + true => std.fmt.bufPrint(&temp_buf, "{}", .{res.value.npm.fmt(old.buffers.string_bytes.items)}) catch break, + }; const external_version = string_builder.append(ExternalString, buf); const sliced = external_version.value.sliced(old.buffers.string_bytes.items); dep.version = Dependency.parse( @@ -622,7 +629,11 @@ fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequ } } } -pub fn clean(old: *Lockfile, updates: []PackageManager.UpdateRequest) !*Lockfile { +pub fn clean( + old: *Lockfile, + updates: []PackageManager.UpdateRequest, + exact_versions: bool, +) !*Lockfile { // This is wasteful, but we rarely log anything so it's fine. var log = logger.Log.init(bun.default_allocator); defer { @@ -632,17 +643,22 @@ pub fn clean(old: *Lockfile, updates: []PackageManager.UpdateRequest) !*Lockfile log.deinit(); } - return old.cleanWithLogger(updates, &log); + return old.cleanWithLogger(updates, &log, exact_versions); } -pub fn cleanWithLogger(old: *Lockfile, updates: []PackageManager.UpdateRequest, log: *logger.Log) !*Lockfile { +pub fn cleanWithLogger( + old: *Lockfile, + updates: []PackageManager.UpdateRequest, + log: *logger.Log, + exact_versions: bool, +) !*Lockfile { const old_trusted_dependencies = old.trusted_dependencies; const old_scripts = old.scripts; // We will only shrink the number of packages here. // never grow if (updates.len > 0) { - try old.preprocessUpdateRequests(updates); + try old.preprocessUpdateRequests(updates, exact_versions); } // Deduplication works like this diff --git a/test/cli/install/bun-add.test.ts b/test/cli/install/bun-add.test.ts index 79804f0e0..9dd38c8cd 100644 --- a/test/cli/install/bun-add.test.ts +++ b/test/cli/install/bun-add.test.ts @@ -303,6 +303,61 @@ it("should add dependency with capital letters", async () => { await access(join(package_dir, "bun.lockb")); }); +it("should add exact version", async () => { + const urls: string[] = []; + setHandler(dummyRegistry(urls)); + await writeFile( + join(package_dir, "package.json"), + JSON.stringify({ + name: "foo", + version: "0.0.1", + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "add", "--exact", "BaR"], + cwd: package_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, + }); + expect(stderr).toBeDefined(); + const err = await new Response(stderr).text(); + expect(err).toContain("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 BaR@0.0.2", + "", + "", + " 1 packages installed", + ]); + expect(await exited).toBe(0); + expect(urls.sort()).toEqual([`${root_url}/BaR`, `${root_url}/BaR-0.0.2.tgz`]); + expect(requested).toBe(2); + expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "BaR"]); + expect(await readdirSorted(join(package_dir, "node_modules", "BaR"))).toEqual(["package.json"]); + expect(await file(join(package_dir, "node_modules", "BaR", "package.json")).json()).toEqual({ + name: "bar", + version: "0.0.2", + }); + expect(await file(join(package_dir, "package.json")).text()).toEqual( + JSON.stringify( + { + name: "foo", + version: "0.0.1", + dependencies: { + BaR: "0.0.2", + }, + }, + null, + 2, + ), + ); + await access(join(package_dir, "bun.lockb")); +}); + it("should add dependency with specified semver", async () => { const urls: string[] = []; setHandler( -- cgit v1.2.3