Compare commits

...

39 Commits

Author SHA1 Message Date
RiskyMH
dd676098f2 not used anymore 2025-08-02 18:06:50 +10:00
Michael H
414f08aa8d Merge branch 'main' into riskymh/install-libc 2025-08-02 18:04:57 +10:00
RiskyMH
86ca965c7f Update yarn.zig 2025-07-30 16:28:40 +10:00
RiskyMH
0110d3316f Update yarn.zig 2025-07-30 16:27:47 +10:00
RiskyMH
9d9ccbcba4 Update yarn.zig 2025-07-30 16:27:25 +10:00
RiskyMH
afbeae15eb Merge branch 'main' into riskymh/install-libc 2025-07-30 16:26:47 +10:00
RiskyMH
ec2f2aaf3f an actual solution? 2025-07-29 12:58:46 +10:00
RiskyMH
0037bbe961 Merge branch 'main' into riskymh/install-libc 2025-07-29 12:56:49 +10:00
RiskyMH
006b2215f6 Update npm.zig 2025-07-29 12:30:28 +10:00
RiskyMH
7b78979cc0 nvm 2025-07-29 12:21:58 +10:00
RiskyMH
ad5f00eea3 feedback 2025-07-29 11:44:34 +10:00
autofix-ci[bot]
56c766772b [autofix.ci] apply automated fixes 2025-07-24 20:07:38 +00:00
RiskyMH
b1914a6235 . 2025-07-25 06:05:05 +10:00
RiskyMH
1445e86e12 . 2025-07-25 06:03:17 +10:00
RiskyMH
2db492ea5a oops 2025-07-25 06:01:09 +10:00
RiskyMH
a8a722547d simplify 2025-07-25 05:56:20 +10:00
RiskyMH
bfcc678601 . 2025-07-24 21:32:07 +10:00
autofix-ci[bot]
a39255d275 [autofix.ci] apply automated fixes 2025-07-24 10:27:12 +00:00
RiskyMH
4be5a57c84 . 2025-07-24 20:24:34 +10:00
RiskyMH
c5eb9c1113 Update package_json.zig 2025-07-24 19:48:46 +10:00
RiskyMH
8e6d4e4372 Update package_json.zig 2025-07-24 19:47:15 +10:00
RiskyMH
0a2aea07a8 Update bun.lock.zig 2025-07-24 19:44:03 +10:00
RiskyMH
5f95d87a21 Update Tree.zig 2025-07-24 19:42:24 +10:00
RiskyMH
8a78796923 more feedback fixes 2025-07-24 19:40:39 +10:00
autofix-ci[bot]
9db6309723 [autofix.ci] apply automated fixes 2025-07-24 08:55:52 +00:00
RiskyMH
6703de0028 more tests 2025-07-24 18:53:10 +10:00
RiskyMH
f3879a5104 test more 2025-07-24 18:20:44 +10:00
RiskyMH
07f1beb8a0 Merge branch 'main' into riskymh/install-libc 2025-07-24 18:06:03 +10:00
autofix-ci[bot]
b73c90692f [autofix.ci] apply automated fixes 2025-07-24 07:35:17 +00:00
RiskyMH
5b56b6e7ea better tests 2025-07-24 17:32:10 +10:00
autofix-ci[bot]
9d6b3d0945 [autofix.ci] apply automated fixes 2025-07-22 16:47:58 +00:00
RiskyMH
48f0a7f149 . 2025-07-23 02:45:29 +10:00
RiskyMH
e76f90f3c9 Merge branch 'main' into riskymh/install-libc 2025-07-23 02:44:36 +10:00
RiskyMH
5cacd0d466 Update npm.zig 2025-07-23 02:34:09 +10:00
RiskyMH
c31bd9505a feedback 2025-07-23 02:08:33 +10:00
RiskyMH
1df8ad0199 . 2025-07-19 03:11:39 +10:00
RiskyMH
22f707baa1 fix tests 2025-07-19 01:37:15 +10:00
autofix-ci[bot]
c5d2928156 [autofix.ci] apply automated fixes 2025-07-18 13:27:53 +00:00
RiskyMH
10e8075872 manual install flags + basic level of libc filtering 2025-07-18 23:17:24 +10:00
45 changed files with 1111 additions and 104 deletions

View File

@@ -158,6 +158,18 @@ $ bun install --omit dev
$ bun install --omit=dev --omit=peer --omit=optional
```
## Platform-specific installation
To add packages for a specific platform and omit downloading the rest:
```bash
$ bun install --os=win32 # i.e @oven/bun-windows-x64
$ bun install --os=darwin --cpu=arm64 # i.e @oven/bun-darwin-aarch64
$ bun install --os=linux --cpu=x64 --libc=glibc # i.e @oven/bun-linux-x64
$ bun install --os=linux --cpu=arm64 --libc=musl # i.e @oven/bun-linux-aarch64-musl
$ bun install --os=* --cpu=* --libc=* # <everything>
```
## Dry run
To perform a dry run (i.e. don't actually install anything):

View File

@@ -7989,7 +7989,7 @@ declare module "bun" {
/**
* ```
* INFO = { prod/dev/optional/peer dependencies, os, cpu, libc (TODO), bin, binDir }
* INFO = { prod/dev/optional/peer dependencies, os, cpu, libc, bin, binDir }
*
* // first index is resolution for each type of package
* npm -> [ "name@version", registry (TODO: remove if default), INFO, integrity]
@@ -8025,6 +8025,7 @@ declare module "bun" {
type BunLockFilePackageInfo = BunLockFileBasePackageInfo & {
os?: string | string[];
cpu?: string | string[];
libc?: string | string[];
bundled?: true;
};

View File

@@ -43,8 +43,10 @@ pub const Authorization = enum {
// https://github.com/oven-sh/bun/issues/341
// https://www.jfrog.com/jira/browse/RTFACT-18398
const accept_header_value = "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*";
const accept_header_value_unoptimised = "application/json; q=1.0, */*";
const default_headers_buf: string = "Accept" ++ accept_header_value;
const default_headers_buf_unoptimised: string = "Accept" ++ accept_header_value_unoptimised;
fn appendAuth(header_builder: *HeaderBuilder, scope: *const Npm.Registry.Scope) void {
if (scope.token.len > 0) {
@@ -70,6 +72,35 @@ fn countAuth(header_builder: *HeaderBuilder, scope: *const Npm.Registry.Scope) v
header_builder.count("npm-auth-type", "legacy");
}
// if this package is likely to have a "libc" field in its package.json.
// npm's optimised response doesn't include it, so we need to use the unoptimised one
fn isPlatformSpecificPackage(name: string) bool {
if (name.len < 5) return false;
const platform_keywords = [_][]const u8{
"musl", "linux", "x64", "aarch64", "glibc", "arm64",
};
if (name[0] == '@') {
if (strings.indexOfChar(name, '/')) |i| {
const scope_and_pkg = name[i + 1 ..];
inline for (platform_keywords) |keyword| {
if (strings.containsComptime(scope_and_pkg, keyword)) {
return true;
}
}
}
} else if (strings.indexOfChar(name, '-')) |i| {
const pkg_name = name[i + 1 ..];
inline for (platform_keywords) |keyword| {
if (strings.containsComptime(pkg_name, keyword)) {
return true;
}
}
}
return false;
}
pub fn forManifest(
this: *NetworkTask,
name: string,
@@ -163,8 +194,12 @@ pub fn forManifest(
header_builder.count("If-Modified-Since", last_modified);
}
const use_unoptimized = is_optional and isPlatformSpecificPackage(name);
const accept_value = if (use_unoptimized) accept_header_value_unoptimised else accept_header_value;
const accept_buf = if (use_unoptimized) default_headers_buf_unoptimised else default_headers_buf;
if (header_builder.header_count > 0) {
header_builder.count("Accept", accept_header_value);
header_builder.count("Accept", accept_value);
if (last_modified.len > 0 and etag.len > 0) {
header_builder.content.count(last_modified);
}
@@ -178,7 +213,7 @@ pub fn forManifest(
header_builder.append("If-Modified-Since", last_modified);
}
header_builder.append("Accept", accept_header_value);
header_builder.append("Accept", accept_value);
if (last_modified.len > 0 and etag.len > 0) {
last_modified = header_builder.content.append(last_modified);
@@ -188,11 +223,11 @@ pub fn forManifest(
allocator,
.{
.name = .{ .offset = 0, .length = @as(u32, @truncate("Accept".len)) },
.value = .{ .offset = "Accept".len, .length = @as(u32, @truncate(default_headers_buf.len - "Accept".len)) },
.value = .{ .offset = "Accept".len, .length = @as(u32, @truncate(accept_buf.len - "Accept".len)) },
},
);
header_builder.header_count = 1;
header_builder.content = GlobalStringBuilder{ .ptr = @as([*]u8, @ptrFromInt(@intFromPtr(bun.span(default_headers_buf).ptr))), .len = default_headers_buf.len, .cap = default_headers_buf.len };
header_builder.content = GlobalStringBuilder{ .ptr = @as([*]u8, @ptrFromInt(@intFromPtr(bun.span(accept_buf).ptr))), .len = accept_buf.len, .cap = accept_buf.len };
}
this.response_buffer = try MutableString.init(allocator, 0);

View File

@@ -49,19 +49,25 @@ const shared_params = [_]ParamType{
clap.parseParam("--omit <dev|optional|peer>... Exclude 'dev', 'optional', or 'peer' dependencies from install") catch unreachable,
clap.parseParam("--lockfile-only Generate a lockfile without installing dependencies") catch unreachable,
clap.parseParam("--linker <STR> Linker strategy (one of \"isolated\" or \"hoisted\")") catch unreachable,
clap.parseParam("--os <STR> Override OS of native modules to install (e.g., linux, darwin, win32, *)") catch unreachable,
clap.parseParam("--cpu <STR> Override CPU architecture of native modules to install (e.g., x64, arm64, *)") catch unreachable,
clap.parseParam("--libc <STR> Override libc of native modules to install (e.g., glibc, musl, *)") catch unreachable,
clap.parseParam("-h, --help Print this help menu") catch unreachable,
};
pub const install_params: []const ParamType = &(shared_params ++ [_]ParamType{
clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable,
clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable,
clap.parseParam("-D, --development") catch unreachable,
clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("--peer Add dependency to \"peerDependencies\"") catch unreachable,
clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable,
clap.parseParam("--filter <STR>... Install packages for the matching workspaces") catch unreachable,
clap.parseParam("-a, --analyze Analyze & install all dependencies of files passed as arguments recursively (using Bun's bundler)") catch unreachable,
clap.parseParam("--only-missing Only add dependencies to package.json if they are not already present") catch unreachable,
clap.parseParam("<POS> ... ") catch unreachable,
clap.parseParam("-O, --optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("--peer Add dependency to \"peerDependencies\"") catch unreachable,
clap.parseParam("--save-dev") catch unreachable,
clap.parseParam("--save-optional") catch unreachable,
clap.parseParam("--save-peer") catch unreachable,
clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable,
clap.parseParam("--filter <STR>... Install packages for the matching workspaces") catch unreachable,
clap.parseParam("-a, --analyze Analyze & install all dependencies of files passed as arguments recursively (using Bun's bundler)") catch unreachable,
clap.parseParam("--only-missing Only add dependencies to package.json if they are not already present") catch unreachable,
clap.parseParam("<POS> ... ") catch unreachable,
});
pub const update_params: []const ParamType = &(shared_params ++ [_]ParamType{
@@ -88,11 +94,14 @@ pub const pm_params: []const ParamType = &(shared_params ++ [_]ParamType{
});
pub const add_params: []const ParamType = &(shared_params ++ [_]ParamType{
clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable,
clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable,
clap.parseParam("-D, --development") catch unreachable,
clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("--peer Add dependency to \"peerDependencies\"") catch unreachable,
clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable,
clap.parseParam("-O, --optional Add dependency to \"optionalDependencies\"") catch unreachable,
clap.parseParam("--peer Add dependency to \"peerDependencies\"") catch unreachable,
clap.parseParam("--save-dev") catch unreachable,
clap.parseParam("--save-optional") catch unreachable,
clap.parseParam("--save-peer") catch unreachable,
clap.parseParam("-E, --exact Add the exact version instead of the ^range") catch unreachable,
clap.parseParam("-a, --analyze Recursively analyze & install dependencies of files passed as arguments (using Bun's bundler)") catch unreachable,
clap.parseParam("--only-missing Only add dependencies to package.json if they are not already present") catch unreachable,
clap.parseParam("<POS> ... \"name\" or \"name@version\" of package(s) to install") catch unreachable,
@@ -220,6 +229,10 @@ lockfile_only: bool = false,
node_linker: ?Options.NodeLinker = null,
target_os: ?string = null,
target_cpu: ?string = null,
target_libc: ?string = null,
// `bun pm version` options
git_tag_version: bool = true,
allow_same_version: bool = false,
@@ -735,6 +748,18 @@ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !Com
cli.node_linker = .fromStr(linker);
}
if (args.option("--os")) |os| {
cli.target_os = os;
}
if (args.option("--cpu")) |cpu| {
cli.target_cpu = cpu;
}
if (args.option("--libc")) |libc| {
cli.target_libc = libc;
}
if (args.option("--cache-dir")) |cache_dir| {
cli.cache_dir = cache_dir;
}
@@ -862,9 +887,9 @@ pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !Com
}
if (comptime subcommand == .add or subcommand == .install) {
cli.development = args.flag("--development") or args.flag("--dev");
cli.optional = args.flag("--optional");
cli.peer = args.flag("--peer");
cli.development = args.flag("--development") or args.flag("--dev") or args.flag("--save-dev");
cli.optional = args.flag("--optional") or args.flag("--save-optional");
cli.peer = args.flag("--peer") or args.flag("--save-peer");
cli.exact = args.flag("--exact");
cli.analyze = args.flag("--analyze");
cli.only_missing = args.flag("--only-missing");

View File

@@ -83,7 +83,7 @@ pub fn determinePreinstallState(
// Do not automatically start downloading packages which are disabled
// i.e. don't download all of esbuild's versions or SWCs
if (pkg.isDisabled()) {
if (pkg.isDisabled(manager)) {
manager.setPreinstallState(pkg.meta.id, lockfile, .done);
return .done;
}

View File

@@ -71,6 +71,10 @@ depth: ?usize = null,
/// isolated installs (pnpm-like) or hoisted installs (yarn-like, original)
node_linker: NodeLinker = .auto,
target_os: ?Npm.OperatingSystem = null,
target_cpu: ?Npm.Architecture = null,
target_libc: ?Npm.Libc = null,
pub const PublishConfig = struct {
access: ?Access = null,
tag: string = "",
@@ -539,6 +543,39 @@ pub fn load(
this.node_linker = node_linker;
}
if (cli.target_os) |os_str| {
if (strings.eqlComptime(os_str, "*")) {
this.target_os = Npm.OperatingSystem.all;
} else if (Npm.OperatingSystem.NameMap.get(os_str)) |os_value| {
this.target_os = @enumFromInt(os_value);
} else {
Output.errGeneric("invalid --os value: '{s}'. Valid values are: " ++ Npm.OperatingSystem.valid_values_string ++ ", *", .{os_str});
bun.Global.exit(1);
}
}
if (cli.target_cpu) |cpu_str| {
if (strings.eqlComptime(cpu_str, "*")) {
this.target_cpu = Npm.Architecture.all;
} else if (Npm.Architecture.NameMap.get(cpu_str)) |cpu_value| {
this.target_cpu = @enumFromInt(cpu_value);
} else {
Output.errGeneric("invalid --cpu value: '{s}'. Valid values are: " ++ Npm.Architecture.valid_values_string ++ ", *", .{cpu_str});
bun.Global.exit(1);
}
}
if (cli.target_libc) |libc_str| {
if (strings.eqlComptime(libc_str, "*")) {
this.target_libc = Npm.Libc.all;
} else if (Npm.Libc.NameMap.get(libc_str)) |libc_value| {
this.target_libc = @enumFromInt(libc_value);
} else {
Output.errGeneric("invalid --libc value: '{s}'. Valid values are: " ++ Npm.Libc.valid_values_string ++ ", *", .{libc_str});
bun.Global.exit(1);
}
}
const disable_progress_bar = default_disable_progress_bar or cli.no_progress;
if (cli.verbose) {

View File

@@ -382,8 +382,9 @@ pub fn isResolvedDependencyDisabled(
dep_id: DependencyID,
features: Features,
meta: *const Package.Meta,
manager: *PackageManager,
) bool {
if (meta.isDisabled()) return true;
if (meta.isDisabled(manager)) return true;
const dep = lockfile.buffers.dependencies.items[dep_id];

View File

@@ -52,8 +52,8 @@ pub const Package = extern struct {
pub const workspaces = DependencyGroup{ .prop = "workspaces", .field = "workspaces", .behavior = .{ .workspace = true } };
};
pub inline fn isDisabled(this: *const Package) bool {
return this.meta.isDisabled();
pub inline fn isDisabled(this: *const Package, manager: *const PackageManager) bool {
return this.meta.isDisabled(manager);
}
pub const Alphabetizer = struct {
@@ -277,6 +277,7 @@ pub const Package = extern struct {
package.meta.arch = package_json.arch;
package.meta.os = package_json.os;
package.meta.libc = package_json.libc;
package.dependencies.off = @as(u32, @truncate(dependencies_list.items.len));
package.dependencies.len = total_dependencies_count - @as(u32, @truncate(dependencies.len));
@@ -487,6 +488,7 @@ pub const Package = extern struct {
package.meta.arch = package_version.cpu;
package.meta.os = package_version.os;
package.meta.libc = package_version.libc;
package.meta.integrity = package_version.integrity;
package.meta.setHasInstallScript(package_version.has_install_script);

View File

@@ -9,7 +9,8 @@ pub const Meta = extern struct {
arch: Npm.Architecture = .all,
os: Npm.OperatingSystem = .all,
_padding_os: u16 = 0,
libc: Npm.Libc = .none,
_padding_after_platform: u8 = 0,
id: PackageID = invalid_package_id,
@@ -28,12 +29,16 @@ pub const Meta = extern struct {
true,
} = .false,
_padding_integrity: [2]u8 = .{0} ** 2,
_padding_integrity: u8 = 0,
_padding_end: u8 = 0,
/// Does the `cpu` arch and `os` match the requirements listed in the package?
/// This is completely unrelated to "devDependencies", "peerDependencies", "optionalDependencies" etc
pub fn isDisabled(this: *const Meta) bool {
return !this.arch.isMatch() or !this.os.isMatch();
pub fn isDisabled(this: *const Meta, manager: *const PackageManager) bool {
const os_match = this.os.isMatch(manager.options.target_os);
const cpu_match = this.arch.isMatch(manager.options.target_cpu);
const libc_match = this.libc.isMatch(manager.options.target_libc);
return !os_match or !cpu_match or !libc_match;
}
pub fn hasInstallScript(this: *const Meta) bool {
@@ -63,6 +68,7 @@ pub const Meta = extern struct {
.integrity = this.integrity,
.arch = this.arch,
.os = this.os,
.libc = this.libc,
.origin = this.origin,
.has_install_script = this.has_install_script,
};
@@ -78,4 +84,5 @@ const install = bun.install;
const Npm = install.Npm;
const Origin = install.Origin;
const PackageID = install.PackageID;
const PackageManager = install.PackageManager;
const invalid_package_id = install.invalid_package_id;

View File

@@ -339,17 +339,27 @@ pub fn isFilteredDependencyOrWorkspace(
const res = &pkg_resolutions[pkg_id];
const parent_res = &pkg_resolutions[parent_pkg_id];
if (pkg_metas[pkg_id].isDisabled()) {
if (pkg_metas[pkg_id].isDisabled(manager)) {
if (manager.options.log_level.isVerbose()) {
const meta = &pkg_metas[pkg_id];
const name = lockfile.str(&pkg_names[pkg_id]);
if (!meta.os.isMatch() and !meta.arch.isMatch()) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu & os mismatch<r>", .{name});
} else if (!meta.os.isMatch()) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- os mismatch<r>", .{name});
} else if (!meta.arch.isMatch()) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu mismatch<r>", .{name});
const os_match = meta.os.isMatch(manager.options.target_os);
const arch_match = meta.arch.isMatch(manager.options.target_cpu);
const libc_match = meta.libc.isMatch(manager.options.target_libc);
Output.prettyError("<d>Skip installing<r> <b>{s}<r> <d>- ", .{name});
if (!os_match) {
Output.prettyError("os", .{});
}
if (!arch_match) {
if (!os_match) Output.prettyError(" & ", .{});
Output.prettyError("cpu", .{});
}
if (!libc_match) {
if (!os_match or !arch_match) Output.prettyError(" & ", .{});
Output.prettyError("libc", .{});
}
Output.prettyErrorln(" mismatch<r>", .{});
}
return true;
}

View File

@@ -453,7 +453,7 @@ pub const Stringifier = struct {
TreeDepsSortCtx.isLessThan,
);
// INFO = { prod/dev/optional/peer dependencies, os, cpu, libc (TODO), bin, binDir }
// INFO = { prod/dev/optional/peer dependencies, os, cpu, libc, bin, binDir }
// first index is resolution for each type of package
// npm -> [ "name@version", registry (TODO: remove if default), INFO, integrity]
@@ -656,7 +656,7 @@ pub const Stringifier = struct {
try writer.writeAll("}\n");
}
/// Writes a single line object. Contains dependencies, os, cpu, libc (soon), and bin
/// Writes a single line object. Contains dependencies, os, cpu, libc, and bin
/// { "devDependencies": { "one": "1.1.1", "two": "2.2.2" }, "os": "none" }
fn writePackageInfoObject(
writer: anytype,
@@ -751,14 +751,15 @@ pub const Stringifier = struct {
);
}
// TODO(dylan-conway)
// if (meta.libc != .all) {
// try writer.writeAll(
// \\"libc": [
// );
// try Negatable(Npm.Libc).toJson(meta.libc, writer);
// try writer.writeAll("], ");
// }
if (meta.libc != .all and meta.libc != .none) {
if (any) {
try writer.writeByte(',');
} else {
any = true;
}
try writer.writeAll(" \"libc\": ");
try Negatable(Npm.Libc).toJson(meta.libc, writer);
}
if (meta.os != .all) {
if (any) {
@@ -1798,10 +1799,9 @@ pub fn parseIntoBinaryLockfile(
if (deps_os_cpu_libc_bin_bundle_obj.get("cpu")) |arch| {
pkg.meta.arch = try Negatable(Npm.Architecture).fromJson(allocator, arch);
}
// TODO(dylan-conway)
// if (os_cpu_libc_obj.get("libc")) |libc| {
// pkg.meta.libc = Negatable(Npm.Libc).fromJson(allocator, libc);
// }
if (deps_os_cpu_libc_bin_bundle_obj.get("libc")) |libc| {
pkg.meta.libc = try Negatable(Npm.Libc).fromJson(allocator, libc);
}
}
},
.root => {

View File

@@ -289,7 +289,7 @@ pub fn jsonStringify(this: *const Lockfile, w: anytype) !void {
}
}
if (@as(u16, @intFromEnum(pkg.meta.arch)) != Npm.Architecture.all_value) {
if (pkg.meta.arch != .all) {
try w.objectField("arch");
try w.beginArray();
defer w.endArray() catch {};
@@ -301,7 +301,7 @@ pub fn jsonStringify(this: *const Lockfile, w: anytype) !void {
}
}
if (@as(u16, @intFromEnum(pkg.meta.os)) != Npm.OperatingSystem.all_value) {
if (pkg.meta.os != .all) {
try w.objectField("os");
try w.beginArray();
defer w.endArray() catch {};
@@ -313,6 +313,18 @@ pub fn jsonStringify(this: *const Lockfile, w: anytype) !void {
}
}
if (pkg.meta.libc != .none) {
try w.objectField("libc");
try w.beginArray();
defer w.endArray() catch {};
for (Npm.Libc.NameMap.kvs) |kv| {
if (pkg.meta.libc.has(kv.value)) {
try w.write(kv.key);
}
}
}
try w.objectField("integrity");
if (pkg.meta.integrity.tag != .unknown) {
try w.print("\"{}\"", .{pkg.meta.integrity});

View File

@@ -136,11 +136,7 @@ fn shouldPrintPackageInstall(
// It's possible this package was installed but the dependency is disabled.
// Have "zod@1.0.0" in dependencies and `zod2@npm:zod@1.0.0` in devDependencies
// and install with --omit=dev.
if (this.lockfile.isResolvedDependencyDisabled(
dep_id,
this.options.local_package_features,
&pkg_metas[package_id],
)) {
if (this.lockfile.isResolvedDependencyDisabled(dep_id, this.options.local_package_features, &pkg_metas[package_id], manager)) {
return .no;
}

View File

@@ -449,30 +449,57 @@ pub fn migrateNPMLockfile(
.arch = if (pkg.get("cpu")) |cpu_array| arch: {
var arch = Npm.Architecture.none.negatable();
if (cpu_array.data != .e_array) return error.InvalidNPMLockfile;
if (cpu_array.data.e_array.items.len == 0) {
break :arch arch.combine();
}
for (cpu_array.data.e_array.items.slice()) |item| {
if (item.data != .e_string) return error.InvalidNPMLockfile;
arch.apply(item.data.e_string.data);
if (cpu_array.data == .e_array) {
if (cpu_array.data.e_array.items.len == 0) {
break :arch arch.combine();
}
for (cpu_array.data.e_array.items.slice()) |item| {
if (item.data != .e_string) return error.InvalidNPMLockfile;
arch.apply(item.data.e_string.data);
}
} else if (cpu_array.data == .e_string) {
arch.apply(cpu_array.data.e_string.data);
} else {
return error.InvalidNPMLockfile;
}
break :arch arch.combine();
} else .all,
.os = if (pkg.get("os")) |cpu_array| arch: {
.os = if (pkg.get("os")) |os_array| os: {
var os = Npm.OperatingSystem.none.negatable();
if (cpu_array.data != .e_array) return error.InvalidNPMLockfile;
if (cpu_array.data.e_array.items.len == 0) {
break :arch .all;
if (os_array.data == .e_array) {
if (os_array.data.e_array.items.len == 0) {
break :os .all;
}
for (os_array.data.e_array.items.slice()) |item| {
if (item.data != .e_string) return error.InvalidNPMLockfile;
os.apply(item.data.e_string.data);
}
} else if (os_array.data == .e_string) {
os.apply(os_array.data.e_string.data);
} else {
return error.InvalidNPMLockfile;
}
for (cpu_array.data.e_array.items.slice()) |item| {
if (item.data != .e_string) return error.InvalidNPMLockfile;
os.apply(item.data.e_string.data);
break :os os.combine();
} else .all,
.libc = if (pkg.get("libc")) |libc_array| libc: {
var libc = Npm.Libc.none.negatable();
if (libc_array.data == .e_array) {
if (libc_array.data.e_array.items.len == 0) {
break :libc .all;
}
for (libc_array.data.e_array.items.slice()) |item| {
if (item.data != .e_string) return error.InvalidNPMLockfile;
libc.apply(item.data.e_string.data);
}
} else if (libc_array.data == .e_string) {
libc.apply(libc_array.data.e_string.data);
} else {
return error.InvalidNPMLockfile;
}
break :arch os.combine();
break :libc libc.combine();
} else .all,
.man_dir = String{},

View File

@@ -599,7 +599,7 @@ pub fn Negatable(comptime T: type) type {
}
}
const included = kvs.len - removed;
const print_included = removed > kvs.len - removed;
const print_included = removed >= kvs.len - removed;
const one = (print_included and included == 1) or (!print_included and removed == 1);
@@ -644,6 +644,8 @@ pub const OperatingSystem = enum(u16) {
pub const sunos: u16 = 1 << 6;
pub const win32: u16 = 1 << 7;
pub const android: u16 = 1 << 8;
// some more in use on npm:
// netbsd, haiku, cygwin
pub const all_value: u16 = aix | darwin | freebsd | linux | openbsd | sunos | win32 | android;
@@ -654,8 +656,8 @@ pub const OperatingSystem = enum(u16) {
else => @compileError("Unsupported operating system: " ++ @tagName(Environment.os)),
};
pub fn isMatch(this: OperatingSystem) bool {
return (@intFromEnum(this) & @intFromEnum(current)) != 0;
pub fn isMatch(this: OperatingSystem, target: ?OperatingSystem) bool {
return (@intFromEnum(this) & @intFromEnum(target orelse current)) != 0;
}
pub inline fn has(this: OperatingSystem, other: u16) bool {
@@ -680,6 +682,15 @@ pub const OperatingSystem = enum(u16) {
else => @compileError("Unsupported operating system: " ++ @tagName(current)),
};
pub const valid_values_string = blk: {
var result: []const u8 = "";
for (NameMap.kvs, 0..) |kv, i| {
if (i > 0) result = result ++ ", ";
result = result ++ kv.key;
}
break :blk result;
};
pub fn negatable(this: OperatingSystem) Negatable(OperatingSystem) {
return .{ .added = this, .removed = .none };
}
@@ -696,7 +707,7 @@ pub const OperatingSystem = enum(u16) {
if (globalObject.hasException()) return .zero;
}
if (globalObject.hasException()) return .zero;
return jsc.JSValue.jsBoolean(operating_system.combine().isMatch());
return jsc.JSValue.jsBoolean(operating_system.combine().isMatch(current));
}
};
@@ -715,16 +726,31 @@ pub const Libc = enum(u8) {
.{ "musl", musl },
});
pub const valid_values_string = blk: {
var result: []const u8 = "";
for (NameMap.kvs, 0..) |kv, i| {
if (i > 0) result = result ++ ", ";
result = result ++ kv.key;
}
break :blk result;
};
pub inline fn has(this: Libc, other: u8) bool {
return (@intFromEnum(this) & other) != 0;
return (@intFromEnum(this) & other) != 0 or this == .none;
}
pub fn isMatch(this: Libc, target: ?Libc) bool {
return (@intFromEnum(this) & @intFromEnum(target orelse current)) != 0 or this == .none;
}
pub fn negatable(this: Libc) Negatable(Libc) {
return .{ .added = this, .removed = .none };
}
// TODO:
pub const current: Libc = @intFromEnum(glibc);
pub const current: Libc = switch (Environment.os) {
.linux => if (Environment.isMusl) @enumFromInt(musl) else @enumFromInt(glibc),
else => .all,
};
const jsc = bun.jsc;
pub fn jsFunctionLibcIsMatch(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
@@ -738,7 +764,7 @@ pub const Libc = enum(u8) {
if (globalObject.hasException()) return .zero;
}
if (globalObject.hasException()) return .zero;
return jsc.JSValue.jsBoolean(libc.combine().isMatch());
return jsc.JSValue.jsBoolean(libc.combine().isMatch(current));
}
};
@@ -760,6 +786,8 @@ pub const Architecture = enum(u16) {
pub const s390x: u16 = 1 << 9;
pub const x32: u16 = 1 << 10;
pub const x64: u16 = 1 << 11;
// some more in use on npm:
// wasm32, riscv64, loong64, mips64el, sparc
pub const all_value: u16 = arm | arm64 | ia32 | mips | mipsel | ppc | ppc64 | s390 | s390x | x32 | x64;
@@ -789,12 +817,21 @@ pub const Architecture = enum(u16) {
.{ "x64", x64 },
});
pub const valid_values_string = blk: {
var result: []const u8 = "";
for (NameMap.kvs, 0..) |kv, i| {
if (i > 0) result = result ++ ", ";
result = result ++ kv.key;
}
break :blk result;
};
pub inline fn has(this: Architecture, other: u16) bool {
return (@intFromEnum(this) & other) != 0;
}
pub fn isMatch(this: Architecture) bool {
return @intFromEnum(this) & @intFromEnum(current) != 0;
pub fn isMatch(this: Architecture, target: ?Architecture) bool {
return @intFromEnum(this) & @intFromEnum(target orelse current) != 0;
}
pub fn negatable(this: Architecture) Negatable(Architecture) {
@@ -813,7 +850,7 @@ pub const Architecture = enum(u16) {
if (globalObject.hasException()) return .zero;
}
if (globalObject.hasException()) return .zero;
return jsc.JSValue.jsBoolean(architecture.combine().isMatch());
return jsc.JSValue.jsBoolean(architecture.combine().isMatch(current));
}
};
@@ -863,9 +900,8 @@ pub const PackageVersion = extern struct {
os: OperatingSystem = OperatingSystem.all,
/// `"cpu"` field in package.json
cpu: Architecture = Architecture.all,
/// `"libc"` field in package.json, not exposed in npm registry api yet.
libc: Libc = Libc.none,
/// `"libc"` field in package.json
libc: Libc = Libc.all,
/// `hasInstallScript` field in registry API.
has_install_script: bool = false,

View File

@@ -13,6 +13,7 @@ pub const YarnLock = struct {
file: ?string = null,
os: ?[]const []const u8 = null,
cpu: ?[]const []const u8 = null,
libc: ?[]const []const u8 = null,
git_repo_name: ?string = null,
pub fn deinit(self: *Entry, allocator: Allocator) void {
@@ -32,6 +33,9 @@ pub const YarnLock = struct {
if (self.os) |os_list| {
allocator.free(os_list);
}
if (self.libc) |libc_list| {
allocator.free(libc_list);
}
if (self.cpu) |cpu_list| {
allocator.free(cpu_list);
}
@@ -416,6 +420,14 @@ pub const YarnLock = struct {
try cpu_list.append(trimmed_cpu);
}
current_entry.?.cpu = try cpu_list.toOwnedSlice();
} else if (strings.eqlComptime(key, "libc")) {
var libc_list = std.ArrayList([]const u8).init(self.allocator);
var libc_it = strings.split(value[1 .. value.len - 1], ",");
while (libc_it.next()) |libc| {
const trimmed_libc = strings.trim(libc, " \"");
try libc_list.append(trimmed_libc);
}
current_entry.?.libc = try libc_list.toOwnedSlice();
}
}
}
@@ -697,6 +709,7 @@ pub fn migrateYarnLockfile(
.origin = .local,
.arch = .all,
.os = .all,
.libc = .none,
.man_dir = String{},
.has_install_script = .false,
.integrity = Integrity{},
@@ -997,6 +1010,13 @@ pub fn migrateYarnLockfile(
}
break :os os.combine();
} else .all,
.libc = if (entry.libc) |libc_list| libc: {
var libc = Npm.Libc.none.negatable();
for (libc_list) |libc_str| {
libc.apply(libc_str);
}
break :libc libc.combine();
} else .none,
.man_dir = String{},
.has_install_script = .false,
.integrity = if (entry.integrity) |integrity|

View File

@@ -63,6 +63,7 @@ pub const PackageJSON = struct {
arch: Architecture = Architecture.all,
os: OperatingSystem = OperatingSystem.all,
libc: Libc = Libc.all,
package_manager_package_id: Install.PackageID = Install.invalid_package_id,
dependencies: DependencyMap = .{},
@@ -822,31 +823,54 @@ pub const PackageJSON = struct {
}
}
}
if (json.get("cpu")) |os_field| {
if (os_field.asArray()) |array_const| {
var array = array_const;
if (json.get("cpu")) |cpu_field| {
var cpu_array = cpu_field.asArray();
if (cpu_array) |*array| {
var arch = Architecture.none.negatable();
while (array.next()) |item| {
if (item.asString(bun.default_allocator)) |str| {
arch.apply(str);
}
}
package_json.arch = arch.combine();
} else if (cpu_field.asString(bun.default_allocator)) |str| {
var arch = Architecture.none.negatable();
arch.apply(str);
package_json.arch = arch.combine();
}
}
if (json.get("os")) |os_field| {
var tmp = os_field.asArray();
if (tmp) |*array| {
var os_array = os_field.asArray();
if (os_array) |*array| {
var os = OperatingSystem.none.negatable();
while (array.next()) |item| {
if (item.asString(bun.default_allocator)) |str| {
os.apply(str);
}
}
package_json.os = os.combine();
} else if (os_field.asString(bun.default_allocator)) |str| {
var os = OperatingSystem.none.negatable();
os.apply(str);
package_json.os = os.combine();
}
}
if (json.get("libc")) |libc_field| {
var libc_array = libc_field.asArray();
if (libc_array) |*array| {
var libc = Libc.none.negatable();
while (array.next()) |item| {
if (item.asString(bun.default_allocator)) |str| {
libc.apply(str);
}
}
package_json.libc = libc.combine();
} else if (libc_field.asString(bun.default_allocator)) |str| {
var libc = Libc.none.negatable();
libc.apply(str);
package_json.libc = libc.combine();
}
}
@@ -2036,6 +2060,7 @@ const resolver = @import("./resolver.zig");
const std = @import("std");
const Architecture = @import("../install/npm.zig").Architecture;
const Libc = @import("../install/npm.zig").Libc;
const OperatingSystem = @import("../install/npm.zig").OperatingSystem;
const bun = @import("bun");

View File

@@ -366,9 +366,9 @@ exports[`should not change formatting unexpectedly 2`] = `
"native-foo-x86": ["native-foo-x86@1.0.0", "http://localhost:1234/native-foo-x86/-/native-foo-x86-1.0.0.tgz", { "os": "none", "cpu": "none" }, "sha512-pUktFGar8JctgQh4Ow5Y9bMp3PB5bHBgbC6M3igED5q99z51WErG2GO3LnPG651SyHtRf+zdeMdhGFWzP54apQ=="],
"native-libc-glibc": ["native-libc-glibc@1.0.0", "http://localhost:1234/native-libc-glibc/-/native-libc-glibc-1.0.0.tgz", {}, "sha512-D7ivPUqV+bs4jZCFt/fm0BRchhE1kO3XMKZ7/Tt3cF2gfJcewMy/zuId79iaVn9aztJYkOk1GWFpMPXmX5rJHA=="],
"native-libc-glibc": ["native-libc-glibc@1.0.0", "http://localhost:1234/native-libc-glibc/-/native-libc-glibc-1.0.0.tgz", { "libc": "glibc" }, "sha512-D7ivPUqV+bs4jZCFt/fm0BRchhE1kO3XMKZ7/Tt3cF2gfJcewMy/zuId79iaVn9aztJYkOk1GWFpMPXmX5rJHA=="],
"native-libc-musl": ["native-libc-musl@1.0.0", "http://localhost:1234/native-libc-musl/-/native-libc-musl-1.0.0.tgz", {}, "sha512-1uffg8IA4EJ4VUnuZU4zyRO9EyduuNfbqg2MMVCWSMAsQkfzZnNR0hqtL0GW/EuhE8FWU/FE//Srf1px1pnN2Q=="],
"native-libc-musl": ["native-libc-musl@1.0.0", "http://localhost:1234/native-libc-musl/-/native-libc-musl-1.0.0.tgz", { "libc": "musl" }, "sha512-1uffg8IA4EJ4VUnuZU4zyRO9EyduuNfbqg2MMVCWSMAsQkfzZnNR0hqtL0GW/EuhE8FWU/FE//Srf1px1pnN2Q=="],
"optional-native": ["optional-native@1.0.0", "http://localhost:1234/optional-native/-/optional-native-1.0.0.tgz", { "optionalDependencies": { "native-bar-x64": "1.0.0", "native-foo-x64": "1.0.0", "native-foo-x86": "1.0.0", "native-libc-glibc": "1.0.0", "native-libc-musl": "1.0.0" } }, "sha512-E+XTkTpxRqU09BnKGkOkS9vk0sPDhPtArBw6FfL5ciYkb7k6EljnqXEQ1b9l0S1YCVZxZkOZIJCYZfCwj7AgSw=="],

View File

@@ -8,6 +8,7 @@ import {
bunExe,
bunEnv as env,
isFlaky,
isLinux,
isMacOS,
isWindows,
mergeWindowEnvs,
@@ -3237,7 +3238,7 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
expect.stringContaining("+ uses-what-bin@1.5.0"),
expect.stringContaining("+ what-bin@1.0.0"),
"",
"19 packages installed",
isLinux ? "18 packages installed" : "19 packages installed",
"",
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
"",
@@ -3281,7 +3282,7 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
expect.stringContaining("+ uses-what-bin@1.5.0"),
expect.stringContaining("+ what-bin@1.0.0"),
"",
"19 packages installed",
isLinux ? "18 packages installed" : "19 packages installed",
"",
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
"",
@@ -3325,7 +3326,9 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
isLinux
? expect.stringContaining("Checked 18 installs across 23 packages (no changes)")
: expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
]);
expect(await exited).toBe(0);
@@ -3352,7 +3355,9 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
isLinux
? expect.stringContaining("Checked 18 installs across 23 packages (no changes)")
: expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
]);
expect(await exited).toBe(0);
assertManifestsPopulated(join(packageDir, ".bun-cache"), registryUrl());
@@ -3381,7 +3386,9 @@ test("it should install with missing bun.lockb, node_modules, and/or cache", asy
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
isLinux
? expect.stringContaining("Checked 18 installs across 23 packages (no changes)")
: expect.stringContaining("Checked 19 installs across 23 packages (no changes)"),
]);
});
@@ -6098,7 +6105,7 @@ test("missing package on reinstall, some with binaries", async () => {
expect.stringContaining("+ uses-what-bin@1.5.0"),
expect.stringContaining("+ what-bin@1.0.0"),
"",
"19 packages installed",
isLinux ? "18 packages installed" : "19 packages installed",
"",
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
"",

View File

@@ -11,12 +11,15 @@ import {
setDefaultTimeout,
test,
} from "bun:test";
import { existsSync, readdirSync } from "fs";
import { access, cp, exists, mkdir, readlink, rm, stat, writeFile } from "fs/promises";
import {
bunEnv,
bunExe,
bunEnv as env,
isLinux,
isWindows,
libcFamily,
readdirSorted,
runBunInstall,
tempDirWithFiles,
@@ -25,7 +28,7 @@ import {
toBeWorkspaceLink,
toHaveBins,
} from "harness";
import { join, resolve, sep } from "path";
import { basename, join, resolve, sep } from "path";
import {
dummyAfterAll,
dummyAfterEach,
@@ -8595,3 +8598,284 @@ test("non-optional dependencies need to be resolvable in text lockfile", async (
expect(await exited).toBe(1);
});
describe("platform-specific dependencies", () => {
const registryDir = join(__dirname, "registry", "packages");
beforeEach(async () => {
await rm(join(package_dir, "node_modules"), { recursive: true, force: true }).catch(() => {});
await rm(join(package_dir, "bun.lockb"), { force: true }).catch(() => {});
await rm(join(package_dir, "bun.lock"), { force: true }).catch(() => {});
});
test("handles missing platform packages gracefully", async () => {
await write(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-missing-platform",
dependencies: {
"platform-test": `file:${join(registryDir, "platform-test", "platform-test-1.0.0.tgz")}`,
},
}),
);
const { stderr, exited } = spawn({
cmd: [bunExe(), "install", "--os=freebsd", "--cpu=x64"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [err, exitCode] = await Promise.all([new Response(stderr).text(), exited]);
expect(exitCode).toBe(0);
expect(err).toContain("Saved lockfile");
expect(await exists(join(package_dir, "node_modules", "platform-test", "package.json"))).toBe(true);
});
const platformTestHandler = (req: Request, urls: string[]) => {
const url = new URL(req.url);
const pathname = url.pathname.replaceAll("%2f", "/");
urls.push(req.url);
if (pathname === "/platform-test") {
return Response.json({
name: "platform-test",
versions: {
"1.0.0": {
name: "platform-test",
version: "1.0.0",
main: "index.js",
optionalDependencies: {
"@platform-test/windows-x64": "1.0.0",
"@platform-test/windows-ia32": "1.0.0",
"@platform-test/linux-x64": "1.0.0",
"@platform-test/linux-x64-glibc": "1.0.0",
"@platform-test/linux-arm64": "1.0.0",
"@platform-test/linux-arm64-glibc": "1.0.0",
"@platform-test/linux-arm": "1.0.0",
"@platform-test/linux-x64-musl": "1.0.0",
"@platform-test/linux-arm64-musl": "1.0.0",
"@platform-test/darwin-x64": "1.0.0",
"@platform-test/darwin-arm64": "1.0.0",
},
dist: {
tarball: `${root_url}/platform-test-1.0.0.tgz`,
},
},
},
"dist-tags": {
latest: "1.0.0",
},
});
}
if (pathname.startsWith("/@platform-test/")) {
const pkgName = pathname.slice(1);
const platform = pkgName.split("/")[1];
const constraints: any = {};
if (platform.includes("darwin")) {
constraints.os = ["darwin"];
} else if (platform.includes("linux")) {
constraints.os = ["linux"];
} else if (platform.includes("windows")) {
constraints.os = ["win32"];
}
if (platform.includes("x64")) {
constraints.cpu = ["x64"];
} else if (platform.includes("arm64")) {
constraints.cpu = ["arm64"];
} else if (platform.includes("arm") && !platform.includes("arm64")) {
constraints.cpu = ["arm"];
} else if (platform.includes("ia32")) {
constraints.cpu = ["ia32"];
}
if (!req.headers.get("accept")?.includes("application/vnd.bun.install-lockfile+json")) {
if (platform.includes("musl")) {
constraints.libc = ["musl"];
} else if (platform.includes("glibc")) {
constraints.libc = ["glibc"];
}
}
return Response.json({
name: pkgName,
versions: {
"1.0.0": {
name: pkgName,
version: "1.0.0",
main: "index.js",
...constraints,
dist: {
tarball: `${root_url}/${pkgName.replace("/", "-")}-1.0.0.tgz`,
},
},
},
"dist-tags": {
latest: "1.0.0",
},
});
}
if (pathname.endsWith(".tgz")) {
const filename = basename(pathname);
let tgzPath: string;
if (filename.startsWith("platform-test-")) {
// Main package: platform-test-1.0.0.tgz
tgzPath = join(registryDir, "platform-test", filename);
} else if (filename.includes("@platform-test-")) {
// Platform packages: @platform-test-linux-x64-1.0.0.tgz -> @platform-test/linux-x64/platform-test-linux-x64-1.0.0.tgz
const match = filename.match(/@platform-test-([^-]+(?:-[^-]+)*)-\d+\.\d+\.\d+\.tgz/);
if (match) {
const platform = match[1];
tgzPath = join(registryDir, "@platform-test", platform, filename.replace("@", ""));
} else {
return new Response("Invalid platform package filename", { status: 400 });
}
} else {
return new Response("Unknown package", { status: 404 });
}
try {
return new Response(file(tgzPath));
} catch (error) {
console.log("Failed to find tarball at:", tgzPath);
return new Response("Tarball not found", { status: 404 });
}
}
return new Response("Not found", { status: 404 });
};
const platforms = [
{ os: "darwin", cpu: "x64", libc: "*", packages: ["darwin-x64"] },
{ os: "darwin", cpu: "arm64", libc: "*", packages: ["darwin-arm64"] },
{ os: "linux", cpu: "x64", libc: "*", packages: ["linux-x64", "linux-x64-glibc", "linux-x64-musl"] },
{ os: "linux", cpu: "arm64", libc: "*", packages: ["linux-arm64", "linux-arm64-glibc", "linux-arm64-musl"] },
{ os: "linux", cpu: "arm", libc: "*", packages: ["linux-arm"] },
{ os: "linux", cpu: "x64", libc: "glibc", packages: ["linux-x64-glibc", "linux-x64"] },
{ os: "linux", cpu: "arm64", libc: "glibc", packages: ["linux-arm64-glibc", "linux-arm64"] },
{ os: "linux", cpu: "x64", libc: "musl", packages: ["linux-x64-musl", "linux-x64"] },
{ os: "linux", cpu: "arm64", libc: "musl", packages: ["linux-arm64-musl", "linux-arm64"] },
{ os: "win32", cpu: "x64", libc: "*", packages: ["windows-x64"] },
{ os: "win32", cpu: "ia32", libc: "*", packages: ["windows-ia32"] },
];
test.each(platforms)("filters (os: $os, cpu: $cpu, libc: $libc)", async ({ os, cpu, libc, packages }) => {
const urls: string[] = [];
setHandler(e => platformTestHandler(e, urls));
await write(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-registry-platform-filtering",
dependencies: {
"platform-test": "1.0.0",
},
}),
);
const { stderr, exited } = spawn({
cmd: [bunExe(), "install", `--os=${os}`, `--cpu=${cpu}`, `--libc=${libc}`],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [err, exitCode] = await Promise.all([new Response(stderr).text(), exited]);
if (exitCode !== 0) {
console.log(err);
}
expect(exitCode).toBe(0);
const platformDir = join(package_dir, "node_modules", "@platform-test");
const installedPackages = existsSync(platformDir) ? readdirSync(platformDir) : [];
expect(installedPackages).toContainValues(packages);
expect(installedPackages).toHaveLength(packages.length);
});
test(`installs current platform of (os: ${process.platform}, cpu: ${process.arch}, libc: ${isLinux ? libcFamily : "n/a"})`, async () => {
const urls: string[] = [];
setHandler(e => platformTestHandler(e, urls));
await write(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-registry-platform-filtering",
dependencies: {
"platform-test": "1.0.0",
},
}),
);
const { stderr, exited } = spawn({
cmd: [bunExe(), "install"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [err, exitCode] = await Promise.all([new Response(stderr).text(), exited]);
if (exitCode !== 0) {
console.log(err);
}
expect(exitCode).toBe(0);
const platformDir = join(package_dir, "node_modules", "@platform-test");
const installedPackages = existsSync(platformDir) ? readdirSync(platformDir) : [];
const packages = [`${process.platform}-${process.arch}`];
if (isLinux) {
packages.push(`linux-${process.arch}-${libcFamily}`);
} else if (isWindows) {
packages[0] = packages[0].replace("win32", "windows");
}
expect(installedPackages).toContainValues(packages);
expect(installedPackages).toHaveLength(packages.length);
});
test("wildcards should show all platforms", async () => {
const urls: string[] = [];
setHandler(e => platformTestHandler(e, urls));
await write(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-registry-wildcard-filtering",
dependencies: {
"platform-test": "1.0.0",
},
}),
);
const { stderr, exited } = spawn({
cmd: [bunExe(), "install", "--os=*", "--cpu=*", "--libc=*"],
cwd: package_dir,
stdout: "pipe",
stderr: "pipe",
env,
});
const [err, exitCode] = await Promise.all([new Response(stderr).text(), exited]);
expect(exitCode).toBe(0);
const platformDir = join(package_dir, "node_modules", "@platform-test");
const installedPackages = existsSync(platformDir) ? readdirSync(platformDir) : [];
expect(installedPackages.length).toBeGreaterThan(5);
expect(installedPackages).toContain("linux-x64");
expect(installedPackages).toContain("darwin-x64");
expect(installedPackages).toContain("windows-x64");
});
});

View File

@@ -4,6 +4,7 @@ import { access, copyFile, cp, exists, open, rm, writeFile } from "fs/promises";
import {
bunExe,
bunEnv as env,
isLinux,
isWindows,
readdirSorted,
runBunInstall,
@@ -468,10 +469,13 @@ index d156130662798530e852e1afaec5b1c03d429cdc..b4ddf35975a952fdaed99f2b14236519
});
expect(await exited).toBe(0);
const out1 = (await stdout.text())
let out1 = (await stdout.text())
.replaceAll(/\s*\[[0-9\.]+m?s\]\s*$/g, "")
.split(/\r?\n/)
.slice(1);
if (isLinux) {
out1 = out1.map(line => line.replace("12 packages installed", "13 packages installed"));
}
expect(out1).toMatchInlineSnapshot(`
[
"preinstall",
@@ -500,10 +504,13 @@ index d156130662798530e852e1afaec5b1c03d429cdc..b4ddf35975a952fdaed99f2b14236519
}));
expect(await exited).toBe(0);
const out2 = (await stdout.text())
let out2 = (await stdout.text())
.replaceAll(/\s*\[[0-9\.]+m?s\]\s*$/g, "")
.split(/\r?\n/)
.slice(1);
if (isLinux) {
out2 = out2.map(line => line.replace("12 packages installed", "13 packages installed"));
}
expect(out2).toMatchInlineSnapshot(`
[
"preinstall",

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/darwin-arm64",
"versions": {
"1.0.0": {
"name": "@platform-test/darwin-arm64",
"version": "1.0.0",
"description": "Platform test package for darwin-arm64",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"_id": "@platform-test/darwin-arm64@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/darwin-arm64/-/platform-test-darwin-arm64-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.125Z",
"created": "2025-07-18T16:17:21.125Z",
"1.0.0": "2025-07-18T16:17:21.125Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/darwin-arm64"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/darwin-x64",
"versions": {
"1.0.0": {
"name": "@platform-test/darwin-x64",
"version": "1.0.0",
"description": "Platform test package for darwin-x64",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"darwin"
],
"cpu": [
"x64"
],
"_id": "@platform-test/darwin-x64@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/darwin-x64/-/platform-test-darwin-x64-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.111Z",
"created": "2025-07-18T16:17:21.111Z",
"1.0.0": "2025-07-18T16:17:21.111Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/darwin-x64"
}

View File

@@ -0,0 +1,40 @@
{
"name": "@platform-test/linux-arm",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-arm",
"version": "1.0.0",
"description": "Platform test package for linux-arm",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"_id": "@platform-test/linux-arm@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-arm/-/platform-test-linux-arm-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.064Z",
"created": "2025-07-18T16:17:21.064Z",
"1.0.0": "2025-07-18T16:17:21.064Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-arm"
}

View File

@@ -0,0 +1,40 @@
{
"name": "@platform-test/linux-arm64-glibc",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-arm64-glibc",
"version": "1.0.0",
"description": "Platform test package for linux-arm64-glibc",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"_id": "@platform-test/linux-arm64-glibc@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-arm64-glibc/-/platform-test-linux-arm64-glibc-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.043Z",
"created": "2025-07-18T16:17:21.043Z",
"1.0.0": "2025-07-18T16:17:21.043Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-arm64-glibc"
}

View File

@@ -0,0 +1,40 @@
{
"name": "@platform-test/linux-arm64-musl",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-arm64-musl",
"version": "1.0.0",
"description": "Platform test package for linux-arm64-musl",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"_id": "@platform-test/linux-arm64-musl@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-arm64-musl/-/platform-test-linux-arm64-musl-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.098Z",
"created": "2025-07-18T16:17:21.098Z",
"1.0.0": "2025-07-18T16:17:21.098Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-arm64-musl"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/linux-arm64",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-arm64",
"version": "1.0.0",
"description": "Platform test package for linux-arm64 (no libc constraint)",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"arm64"
],
"_id": "@platform-test/linux-arm64@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-arm64/-/platform-test-linux-arm64-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.043Z",
"created": "2025-07-18T16:17:21.043Z",
"1.0.0": "2025-07-18T16:17:21.043Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-arm64"
}

View File

@@ -0,0 +1,40 @@
{
"name": "@platform-test/linux-x64-glibc",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-x64-glibc",
"version": "1.0.0",
"description": "Platform test package for linux-x64-glibc",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"_id": "@platform-test/linux-x64-glibc@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-x64-glibc/-/platform-test-linux-x64-glibc-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.022Z",
"created": "2025-07-18T16:17:21.022Z",
"1.0.0": "2025-07-18T16:17:21.022Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-x64-glibc"
}

View File

@@ -0,0 +1,40 @@
{
"name": "@platform-test/linux-x64-musl",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-x64-musl",
"version": "1.0.0",
"description": "Platform test package for linux-x64-musl",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"x64"
],
"libc": [
"musl"
],
"_id": "@platform-test/linux-x64-musl@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-x64-musl/-/platform-test-linux-x64-musl-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.085Z",
"created": "2025-07-18T16:17:21.085Z",
"1.0.0": "2025-07-18T16:17:21.085Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-x64-musl"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/linux-x64",
"versions": {
"1.0.0": {
"name": "@platform-test/linux-x64",
"version": "1.0.0",
"description": "Platform test package for linux-x64 (no libc constraint)",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"linux"
],
"cpu": [
"x64"
],
"_id": "@platform-test/linux-x64@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/linux-x64/-/platform-test-linux-x64-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.022Z",
"created": "2025-07-18T16:17:21.022Z",
"1.0.0": "2025-07-18T16:17:21.022Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/linux-x64"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/windows-ia32",
"versions": {
"1.0.0": {
"name": "@platform-test/windows-ia32",
"version": "1.0.0",
"description": "Platform test package for windows-ia32",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"win32"
],
"cpu": [
"ia32"
],
"_id": "@platform-test/windows-ia32@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/windows-ia32/-/platform-test-windows-ia32-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.002Z",
"created": "2025-07-18T16:17:21.002Z",
"1.0.0": "2025-07-18T16:17:21.002Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/windows-ia32"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@platform-test/windows-x64",
"versions": {
"1.0.0": {
"name": "@platform-test/windows-x64",
"version": "1.0.0",
"description": "Platform test package for windows-x64",
"main": "index.js",
"scripts": {
"postinstall": "node postinstall.js"
},
"os": [
"win32"
],
"cpu": [
"x64"
],
"_id": "@platform-test/windows-x64@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/@platform-test/windows-x64/-/platform-test-windows-x64-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:20.981Z",
"created": "2025-07-18T16:17:20.981Z",
"1.0.0": "2025-07-18T16:17:20.981Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "@platform-test/windows-x64"
}

View File

@@ -0,0 +1,41 @@
{
"name": "platform-test",
"versions": {
"1.0.0": {
"name": "platform-test",
"version": "1.0.0",
"description": "Main platform test package with optional platform-specific dependencies",
"main": "index.js",
"optionalDependencies": {
"@platform-test/windows-x64": "1.0.0",
"@platform-test/windows-ia32": "1.0.0",
"@platform-test/linux-x64": "1.0.0",
"@platform-test/linux-x64-glibc": "1.0.0",
"@platform-test/linux-arm64": "1.0.0",
"@platform-test/linux-arm64-glibc": "1.0.0",
"@platform-test/linux-arm": "1.0.0",
"@platform-test/linux-x64-musl": "1.0.0",
"@platform-test/linux-arm64-musl": "1.0.0",
"@platform-test/darwin-x64": "1.0.0",
"@platform-test/darwin-arm64": "1.0.0"
},
"_id": "platform-test@1.0.0",
"_nodeVersion": "20.8.0",
"_npmVersion": "10.1.0",
"dist": {
"integrity": "sha512-PLACEHOLDER",
"shasum": "PLACEHOLDER",
"tarball": "http://localhost:4873/platform-test/-/platform-test-1.0.0.tgz"
}
}
},
"time": {
"modified": "2025-07-18T16:17:21.135Z",
"created": "2025-07-18T16:17:21.135Z",
"1.0.0": "2025-07-18T16:17:21.135Z"
},
"dist-tags": {
"latest": "1.0.0"
},
"_id": "platform-test"
}