diff options
author | 2023-10-17 17:51:05 -0700 | |
---|---|---|
committer | 2023-10-17 17:51:05 -0700 | |
commit | 9584e60002c3fa23ec537cf0c682714add7bd796 (patch) | |
tree | 64f0200e0a40d51c479f16983a83351ae6b32f64 | |
parent | 1373a2351dbbc17e6baf381a95bd373fd14eb57f (diff) | |
parent | e731eff3824d9649522c87941c84a83c09c49ca3 (diff) | |
download | bun-postinstall_3.tar.gz bun-postinstall_3.tar.zst bun-postinstall_3.zip |
Merge branch 'main' into postinstall_3postinstall_3
-rw-r--r-- | src/bun.js/bindings/ZigGeneratedClasses.cpp | 2 | ||||
-rw-r--r-- | src/cli/create_command.zig | 8 | ||||
-rw-r--r-- | src/install/dependency.zig | 62 | ||||
-rw-r--r-- | src/install/install.zig | 57 | ||||
-rw-r--r-- | src/install/lockfile.zig | 5 | ||||
-rw-r--r-- | src/install/migration.zig | 1 | ||||
-rw-r--r-- | src/resolver/package_json.zig | 14 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 1 | ||||
-rw-r--r-- | test/cli/install/bun-install.test.ts | 160 |
9 files changed, 275 insertions, 35 deletions
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index 8d8c98154..f709d572a 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -25241,10 +25241,8 @@ bool JSTCPSocket::hasPendingActivity(void* ctx) JSTCPSocket::~JSTCPSocket() { - printf("~JSTCPSocket\n"); if (m_ctx) { TCPSocketClass__finalize(m_ctx); - m_ctx = nullptr; } } void JSTCPSocket::destroy(JSCell* cell) diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index b112d5899..b11a85b1e 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -1843,17 +1843,17 @@ pub const Example = struct { if (env_loader.map.get("GITHUB_ACCESS_TOKEN")) |access_token| { if (access_token.len > 0) { - headers_buf = try std.fmt.allocPrint(ctx.allocator, "Access-TokenBearer {s}", .{access_token}); + headers_buf = try std.fmt.allocPrint(ctx.allocator, "AuthorizationBearer {s}", .{access_token}); try header_entries.append( ctx.allocator, Headers.Kv{ .name = Api.StringPointer{ .offset = 0, - .length = @as(u32, @intCast("Access-Token".len)), + .length = @as(u32, @intCast("Authorization".len)), }, .value = Api.StringPointer{ - .offset = @as(u32, @intCast("Access-Token".len)), - .length = @as(u32, @intCast(headers_buf.len - "Access-Token".len)), + .offset = @as(u32, @intCast("Authorization".len)), + .length = @as(u32, @intCast(headers_buf.len - "Authorization".len)), }, }, ); diff --git a/src/install/dependency.zig b/src/install/dependency.zig index ca0d702aa..813ec9ac5 100644 --- a/src/install/dependency.zig +++ b/src/install/dependency.zig @@ -2,6 +2,7 @@ const bun = @import("root").bun; const logger = bun.logger; const Environment = @import("../env.zig"); const Install = @import("./install.zig"); +const PackageManager = Install.PackageManager; const ExternalStringList = Install.ExternalStringList; const Features = Install.Features; const PackageNameHash = Install.PackageNameHash; @@ -92,6 +93,7 @@ pub fn cloneWithDifferentBuffers(this: *const Dependency, name_buf: []const u8, .version = Dependency.parseWithTag( builder.lockfile.allocator, new_name, + String.Builder.stringHash(new_name.slice(out_slice)), new_literal.slice(out_slice), this.version.tag, &sliced, @@ -144,11 +146,12 @@ pub fn toDependency( const name = String{ .bytes = this[0..8].*, }; + const name_hash: u64 = @bitCast(this[8..16].*); return Dependency{ .name = name, - .name_hash = @as(u64, @bitCast(this[8..16].*)), + .name_hash = name_hash, .behavior = @bitCast(this[16]), - .version = Dependency.Version.toVersion(name, this[17..this.len].*, ctx), + .version = Dependency.Version.toVersion(name, name_hash, this[17..this.len].*, ctx), }; } @@ -297,6 +300,7 @@ pub const Version = struct { pub fn toVersion( alias: String, + alias_hash: PackageNameHash, bytes: Version.External, ctx: Dependency.Context, ) Dependency.Version { @@ -306,6 +310,7 @@ pub const Version = struct { return Dependency.parseWithTag( ctx.allocator, alias, + alias_hash, sliced.slice, tag, sliced, @@ -617,6 +622,7 @@ pub const Version = struct { pub const NpmInfo = struct { name: String, version: Semver.Query.Group, + is_alias: bool = false, fn eql(this: NpmInfo, that: NpmInfo, this_buf: []const u8, that_buf: []const u8) bool { return this.name.eql(that.name, this_buf, that_buf) and this.version.eql(that.version); @@ -670,17 +676,19 @@ pub fn eql( pub inline fn parse( allocator: std.mem.Allocator, alias: String, + alias_hash: ?PackageNameHash, dependency: string, sliced: *const SlicedString, log: ?*logger.Log, ) ?Version { const dep = std.mem.trimLeft(u8, dependency, " \t\n\r"); - return parseWithTag(allocator, alias, dep, Version.Tag.infer(dep), sliced, log); + return parseWithTag(allocator, alias, alias_hash, dep, Version.Tag.infer(dep), sliced, log); } pub fn parseWithOptionalTag( allocator: std.mem.Allocator, alias: String, + alias_hash: ?PackageNameHash, dependency: string, tag: ?Dependency.Version.Tag, sliced: *const SlicedString, @@ -690,6 +698,7 @@ pub fn parseWithOptionalTag( return parseWithTag( allocator, alias, + alias_hash, dep, tag orelse Version.Tag.infer(dep), sliced, @@ -700,6 +709,7 @@ pub fn parseWithOptionalTag( pub fn parseWithTag( allocator: std.mem.Allocator, alias: String, + alias_hash: ?PackageNameHash, dependency: string, tag: Dependency.Version.Tag, sliced: *const SlicedString, @@ -710,19 +720,30 @@ pub fn parseWithTag( switch (tag) { .npm => { var input = dependency; - const name = if (strings.hasPrefixComptime(input, "npm:")) sliced.sub(brk: { - var str = input["npm:".len..]; - var i: usize = @intFromBool(str.len > 0 and str[0] == '@'); - - while (i < str.len) : (i += 1) { - if (str[i] == '@') { - input = str[i + 1 ..]; - break :brk str[0..i]; + + var is_alias = false; + const name = brk: { + if (strings.hasPrefixComptime(input, "npm:")) { + is_alias = true; + var str = input["npm:".len..]; + var i: usize = @intFromBool(str.len > 0 and str[0] == '@'); + + while (i < str.len) : (i += 1) { + if (str[i] == '@') { + input = str[i + 1 ..]; + break :brk sliced.sub(str[0..i]).value(); + } } + + input = str[i..]; + + break :brk sliced.sub(str[0..i]).value(); } - input = str[i..]; - break :brk str[0..i]; - }).value() else alias; + + break :brk alias; + }; + + is_alias = is_alias and alias_hash != null; // Strip single leading v // v1.0.0 -> 1.0.0 @@ -740,16 +761,27 @@ pub fn parseWithTag( return null; }; - return .{ + const result = Version{ .literal = sliced.value(), .value = .{ .npm = .{ + .is_alias = is_alias, .name = name, .version = version, }, }, .tag = .npm, }; + + if (is_alias) { + PackageManager.instance.known_npm_aliases.put( + allocator, + alias_hash.?, + result, + ) catch unreachable; + } + + return result; }, .dist_tag => { var tag_to_use = sliced.value(); diff --git a/src/install/install.zig b/src/install/install.zig index 52025aa04..06eb7296e 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -1628,6 +1628,7 @@ const NetworkChannel = sync.Channel(*NetworkTask, .{ .Static = 8192 }); const ThreadPool = bun.ThreadPool; const PackageManifestMap = std.HashMapUnmanaged(PackageNameHash, Npm.PackageManifest, IdentityContext(PackageNameHash), 80); const RepositoryMap = std.HashMapUnmanaged(u64, bun.FileDescriptor, IdentityContext(u64), 80); +const NpmAliasMap = std.HashMapUnmanaged(PackageNameHash, Dependency.Version, IdentityContext(u64), 80); pub const CacheLevel = struct { use_cache_control_headers: bool, @@ -1754,6 +1755,9 @@ pub const PackageManager = struct { uws_event_loop: *uws.Loop, file_poll_store: JSC.FilePoll.Store, + // name hash from alias package name -> aliased package dependency version info + known_npm_aliases: NpmAliasMap = .{}, + const PreallocatedNetworkTasks = std.BoundedArray(NetworkTask, 1024); const NetworkTaskQueue = std.HashMapUnmanaged(u64, void, IdentityContext(u64), 80); pub var verbose_install = false; @@ -3063,20 +3067,47 @@ pub const PackageManager = struct { .dist_tag, .git, .github, .npm, .tarball, .workspace => String.Builder.stringHash(this.lockfile.str(&name)), else => dependency.name_hash, }; + const version = version: { - if (this.lockfile.overrides.get(name_hash)) |new| { - debug("override: {s} -> {s}", .{ this.lockfile.str(&dependency.version.literal), this.lockfile.str(&new.literal) }); - name = switch (new.tag) { - .dist_tag => new.value.dist_tag.name, - .git => new.value.git.package_name, - .github => new.value.github.package_name, - .npm => new.value.npm.name, - .tarball => new.value.tarball.package_name, - else => name, - }; - name_hash = String.Builder.stringHash(this.lockfile.str(&name)); - break :version new; + if (dependency.version.tag == .npm) { + if (this.known_npm_aliases.get(name_hash)) |aliased| { + const group = dependency.version.value.npm.version; + var curr_list: ?*const Semver.Query.List = &aliased.value.npm.version.head; + while (curr_list) |queries| { + var curr: ?*const Semver.Query = &queries.head; + while (curr) |query| { + if (group.satisfies(query.range.left.version) or group.satisfies(query.range.right.version)) { + name = aliased.value.npm.name; + name_hash = String.Builder.stringHash(this.lockfile.str(&name)); + break :version aliased; + } + curr = query.next; + } + curr_list = queries.next; + } + + // fallthrough. a package that matches the name of an alias but does not match + // the version should be enqueued as a normal npm dependency, overrides allowed + } } + + // allow overriding all dependencies unless the dependency is coming directly from an alias, "npm:<this dep>" + if (dependency.version.tag != .npm or !dependency.version.value.npm.is_alias) { + if (this.lockfile.overrides.get(name_hash)) |new| { + debug("override: {s} -> {s}", .{ this.lockfile.str(&dependency.version.literal), this.lockfile.str(&new.literal) }); + name = switch (new.tag) { + .dist_tag => new.value.dist_tag.name, + .git => new.value.git.package_name, + .github => new.value.github.package_name, + .npm => new.value.npm.name, + .tarball => new.value.tarball.package_name, + else => name, + }; + name_hash = String.Builder.stringHash(this.lockfile.str(&name)); + break :version new; + } + } + break :version dependency.version; }; var loaded_manifest: ?Npm.PackageManifest = null; @@ -6386,6 +6417,7 @@ pub const PackageManager = struct { var version = Dependency.parseWithOptionalTag( allocator, if (alias) |name| String.init(input, name) else placeholder, + if (alias) |name| String.Builder.stringHash(name) else null, value, null, &SlicedString.init(input, value), @@ -6400,6 +6432,7 @@ pub const PackageManager = struct { if (Dependency.parseWithOptionalTag( allocator, placeholder, + null, input, null, &SlicedString.init(input, input), diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 5acf326c6..ddbfaba50 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -666,6 +666,7 @@ fn preprocessUpdateRequests(old: *Lockfile, updates: []PackageManager.UpdateRequ dep.version = Dependency.parse( old.allocator, dep.name, + dep.name_hash, sliced.slice, &sliced, null, @@ -2203,6 +2204,7 @@ pub const OverrideMap = struct { .version = Dependency.parse( lockfile.allocator, name, + name_hash, literalSliced.slice, &literalSliced, log, @@ -2780,6 +2782,7 @@ pub const Package = extern struct { .version = Dependency.parse( allocator, name.value, + name.hash, sliced.slice, &sliced, log, @@ -3067,6 +3070,7 @@ pub const Package = extern struct { var dependency_version = Dependency.parseWithOptionalTag( allocator, external_alias.value, + external_alias.hash, sliced.slice, tag, &sliced, @@ -3136,6 +3140,7 @@ pub const Package = extern struct { if (Dependency.parseWithTag( allocator, external_alias.value, + external_alias.hash, path.slice, .workspace, &path, diff --git a/src/install/migration.zig b/src/install/migration.zig index d74be7265..6e32c0e5e 100644 --- a/src/install/migration.zig +++ b/src/install/migration.zig @@ -636,6 +636,7 @@ pub fn migrateNPMLockfile(this: *Lockfile, allocator: Allocator, log: *logger.Lo const version = Dependency.parse( this.allocator, dep_name, + name_hash, sliced.slice, &sliced, log, diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig index 165da7b53..7b1442898 100644 --- a/src/resolver/package_json.zig +++ b/src/resolver/package_json.zig @@ -833,7 +833,15 @@ pub const PackageJSON = struct { if (tag == .npm) { const sliced = Semver.SlicedString.init(package_json.version, package_json.version); - if (Dependency.parseWithTag(allocator, String.init(package_json.name, package_json.name), package_json.version, .npm, &sliced, r.log)) |dependency_version| { + if (Dependency.parseWithTag( + allocator, + String.init(package_json.name, package_json.name), + String.Builder.stringHash(package_json.name), + package_json.version, + .npm, + &sliced, + r.log, + )) |dependency_version| { if (dependency_version.value.npm.version.isExact()) { if (pm.lockfile.resolve(package_json.name, dependency_version)) |resolved| { package_json.package_manager_package_id = resolved; @@ -945,6 +953,7 @@ pub const PackageJSON = struct { for (group_obj.properties.slice()) |*prop| { const name_prop = prop.key orelse continue; const name_str = name_prop.asString(allocator) orelse continue; + const name_hash = String.Builder.stringHash(name_str); const name = String.init(name_str, name_str); const version_value = prop.value orelse continue; const version_str = version_value.asString(allocator) orelse continue; @@ -953,6 +962,7 @@ pub const PackageJSON = struct { if (Dependency.parse( allocator, name, + name_hash, version_str, &sliced_str, r.log, @@ -960,7 +970,7 @@ pub const PackageJSON = struct { const dependency = Dependency{ .name = name, .version = dependency_version, - .name_hash = String.Builder.stringHash(name_str), + .name_hash = name_hash, .behavior = group.behavior, }; package_json.dependencies.map.putAssumeCapacityContext( diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 4aeda410e..7c8f37aac 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -1751,6 +1751,7 @@ pub const Resolver = struct { dependency_version = Dependency.parse( r.allocator, Semver.String.init(esm.name, esm.name), + null, esm.version, &sliced_string, r.log, diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts index d79155819..7def7d75f 100644 --- a/test/cli/install/bun-install.test.ts +++ b/test/cli/install/bun-install.test.ts @@ -2578,6 +2578,121 @@ it("should not hoist if name collides with alias", async () => { await access(join(package_dir, "bun.lockb")); }); +it("should get npm alias with matching version", async () => { + const urls: string[] = []; + setHandler( + dummyRegistry(urls, { + "0.0.3": { as: "0.0.3" }, + "0.0.5": { as: "0.0.5" }, + }), + ); + await writeFile( + join(package_dir, "package.json"), + JSON.stringify({ + name: "foo", + version: "0.0.1", + workspaces: ["moo"], + dependencies: { + "boba": "npm:baz@0.0.5", + }, + }), + ); + await mkdir(join(package_dir, "moo")); + await writeFile( + join(package_dir, "moo", "package.json"), + JSON.stringify({ + name: "moo", + version: "0.0.2", + dependencies: { + boba: ">=0.0.3", + }, + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + 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([ + " + moo@workspace:moo", + " + boba@0.0.5", + "", + " 2 packages installed", + ]); + expect(await exited).toBe(0); + expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.5.tgz`]); + expect(requested).toBe(2); + expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "boba", "moo"]); + expect(await file(join(package_dir, "node_modules", "boba", "package.json")).json()).toEqual({ + name: "baz", + version: "0.0.5", + bin: { + "baz-exec": "index.js", + }, + }); + await access(join(package_dir, "bun.lockb")); +}); + +it("should not apply overrides to package name of aliased package", async () => { + const urls: string[] = []; + setHandler( + dummyRegistry(urls, { + "0.0.3": { as: "0.0.3" }, + }), + ); + await writeFile( + join(package_dir, "package.json"), + JSON.stringify({ + name: "foo", + version: "0.2.0", + dependencies: { + bar: "npm:baz@0.0.3", + }, + overrides: { + "baz": "0.0.5", + }, + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + 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([ + " + bar@0.0.3", + "", + " 1 package installed", + ]); + expect(await exited).toBe(0); + expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); + expect(requested).toBe(2); + expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ + name: "baz", + version: "0.0.3", + bin: { + "baz-run": "index.js", + }, + }); + await access(join(package_dir, "bun.lockb")); +}); + it("should handle unscoped alias on scoped dependency", async () => { const urls: string[] = []; setHandler(dummyRegistry(urls, { "0.1.0": {} })); @@ -7434,6 +7549,51 @@ it("should install peer dependencies from root package", async () => { await access(join(package_dir, "bun.lockb")); }); +it("should install correct version of peer dependency from root package", async () => { + const urls: string[] = []; + setHandler( + dummyRegistry(urls, { + "0.0.3": { as: "0.0.3" }, + "0.0.5": { as: "0.0.5" }, + }), + ); + await writeFile( + join(package_dir, "package.json"), + JSON.stringify({ + name: "foo", + dependencies: { + baz: "0.0.3", + }, + peerDependencies: { + baz: "0.0.5", + }, + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install"], + cwd: package_dir, + env, + stdout: null, + stdin: "pipe", + stderr: "pipe", + }); + 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([ + " + baz@0.0.3", + "", + " 1 package installed", + ]); + expect(await exited).toBe(0); + expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); + expect(requested).toBe(2); + + await access(join(package_dir, "bun.lockb")); +}); + describe("Registry URLs", () => { // Some of the non failing URLs are invalid, but bun's URL parser ignores // the validation error and returns a valid serialized URL anyway. |