aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-17 17:51:05 -0700
committerGravatar Dylan Conway <dylan.conway567@gmail.com> 2023-10-17 17:51:05 -0700
commit9584e60002c3fa23ec537cf0c682714add7bd796 (patch)
tree64f0200e0a40d51c479f16983a83351ae6b32f64
parent1373a2351dbbc17e6baf381a95bd373fd14eb57f (diff)
parente731eff3824d9649522c87941c84a83c09c49ca3 (diff)
downloadbun-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.cpp2
-rw-r--r--src/cli/create_command.zig8
-rw-r--r--src/install/dependency.zig62
-rw-r--r--src/install/install.zig57
-rw-r--r--src/install/lockfile.zig5
-rw-r--r--src/install/migration.zig1
-rw-r--r--src/resolver/package_json.zig14
-rw-r--r--src/resolver/resolver.zig1
-rw-r--r--test/cli/install/bun-install.test.ts160
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.