Compare commits

...

5 Commits

Author SHA1 Message Date
Jarred Sumner
9cd0c299a3 Use https://registry.npmmirror.com/-/binary/bun for bun upgrade in China 2024-07-29 23:02:08 -07:00
Jarred Sumner
de0e7b342a Use mirror source 2024-07-29 22:26:38 -07:00
Jarred Sumner
aed7f383f3 tweak the output 2024-07-29 21:53:16 -07:00
Jarred Sumner
f51b86a749 Tweak output 2024-07-29 21:46:34 -07:00
Jarred Sumner
65ae7f18e9 Make bun install faster in China 2024-07-29 21:20:10 -07:00
5 changed files with 284 additions and 9 deletions

View File

@@ -169,6 +169,196 @@ pub const UpgradeCommand = struct {
var unzip_path_buf: bun.PathBuffer = undefined;
var tmpdir_path_buf: bun.PathBuffer = undefined;
const npmmirror_api_url_base = "https://registry.npmmirror.com/-/binary/bun";
pub fn getLatestVersionFromMainlandChinaMirror(
allocator: std.mem.Allocator,
env_loader: *DotEnv.Loader,
refresher: ?*Progress,
progress: ?*Progress.Node,
use_profile: bool,
comptime silent: bool,
) !?Version {
const npmmirror_api_url = URL.parse(npmmirror_api_url_base);
const http_proxy: ?URL = env_loader.getHttpProxy(npmmirror_api_url);
var metadata_body = try MutableString.init(allocator, 2048);
// ensure very stable memory address
var async_http: *HTTP.AsyncHTTP = try allocator.create(HTTP.AsyncHTTP);
async_http.* = HTTP.AsyncHTTP.initSync(
allocator,
.GET,
npmmirror_api_url,
.{},
"",
&metadata_body,
"",
http_proxy,
null,
HTTP.FetchRedirect.follow,
);
async_http.client.reject_unauthorized = env_loader.getTLSRejectUnauthorized();
if (!silent) async_http.client.progress_node = progress.?;
const response = try async_http.sendSync(true);
switch (response.status_code) {
404 => return error.HTTP404,
403 => return error.HTTPForbidden,
429 => return error.HTTPTooManyRequests,
499...599 => return error.GitHubIsDown,
200 => {},
else => return error.HTTPError,
}
var log = logger.Log.init(allocator);
defer if (comptime silent) log.deinit();
var source = logger.Source.initPathString("releases.json", metadata_body.list.items);
initializeStore();
var expr = ParseJSON(&source, &log, allocator) catch |err| {
if (!silent) {
progress.?.end();
refresher.?.refresh();
if (log.errors > 0) {
if (Output.enable_ansi_colors) {
try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true);
} else {
try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false);
}
Global.exit(1);
} else {
Output.prettyErrorln("Error parsing releases from GitHub: <r><red>{s}<r>", .{@errorName(err)});
Global.exit(1);
}
}
return null;
};
if (log.errors > 0) {
if (!silent) {
progress.?.end();
refresher.?.refresh();
if (Output.enable_ansi_colors) {
try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true);
} else {
try log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false);
}
Global.exit(1);
}
return null;
}
var version = Version{ .zip_url = "", .tag = "", .buf = metadata_body, .size = 0 };
if (expr.data != .e_array) {
if (!silent) {
progress.?.end();
refresher.?.refresh();
const json_type: js_ast.Expr.Tag = @as(js_ast.Expr.Tag, expr.data);
Output.prettyErrorln("JSON error - expected an array but received {s}", .{@tagName(json_type)});
Global.exit(1);
}
return null;
}
var highest_version_object: ?*js_ast.E.Object = null;
var highest_semver_version: ?bun.Semver.Version = .{};
for (expr.data.e_array.slice()) |item| {
if (item.data == .e_object) {
if (item.get("name")) |name_str| {
if (name_str.data == .e_string and !name_str.data.e_string.eqlComptime("canary")) {
// name: "bun-v1.1.7/",
var name = name_str.data.e_string.slice(bun.default_allocator);
name = strings.withoutTrailingSlash(name);
const name_for_tag = name;
if (strings.hasPrefixComptime(name, "bun-v")) {
name = name["bun-v".len..];
} else if (strings.hasPrefixComptime(name, "bun-")) {
// just being cautious.
name = name["bun-".len..];
}
const semver_version = bun.Semver.Version.parse(bun.Semver.SlicedString.init(name, name));
if (semver_version.valid) {
if (highest_semver_version == null or highest_semver_version.?.orderWithoutBuild(semver_version.version.min(), name, name).compare(.lt)) {
highest_semver_version = semver_version.version.min();
highest_version_object = item.data.e_object;
version.tag = name_for_tag;
}
}
}
}
}
}
if (highest_version_object == null) {
if (!silent) {
progress.?.end();
refresher.?.refresh();
Output.prettyErrorln("JSON Error parsing releases from https://registry.npmmirror.com/-/binary/bun: {s}\n", .{metadata_body.list.items});
Global.exit(1);
}
return null;
}
const base_url_expr: js_ast.Expr = highest_version_object.?.get("url") orelse {
if (!silent) {
progress.?.end();
refresher.?.refresh();
Output.prettyErrorln("JSON Error parsing releases from https://registry.npmmirror.com/-/binary/bun: {s}\n", .{metadata_body.list.items});
Global.exit(1);
}
return null;
};
const base_url = base_url_expr.asString(allocator) orelse {
if (!silent) {
progress.?.end();
refresher.?.refresh();
Output.prettyErrorln("JSON Error parsing releases from https://registry.npmmirror.com/-/binary/bun: {s}\n", .{metadata_body.list.items});
Global.exit(1);
}
return null;
};
if (use_profile) {
version.zip_url = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ strings.withoutTrailingSlash(base_url), Version.profile_zip_filename });
} else {
version.zip_url = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ strings.withoutTrailingSlash(base_url), Version.zip_filename });
}
version.buf = metadata_body;
return version;
}
pub fn getLatestVersionMaybeMirror(
allocator: std.mem.Allocator,
env_loader: *DotEnv.Loader,
refresher: ?*Progress,
progress: ?*Progress.Node,
use_profile: bool,
comptime silent: bool,
) !?Version {
if (env_loader.isUserProbablyInMainlandChina()) {
return getLatestVersionFromMainlandChinaMirror(allocator, env_loader, refresher, progress, use_profile, silent);
}
return getLatestVersion(allocator, env_loader, refresher, progress, use_profile, silent);
}
pub fn getLatestVersion(
allocator: std.mem.Allocator,
env_loader: *DotEnv.Loader,
@@ -465,9 +655,10 @@ pub const UpgradeCommand = struct {
const version: Version = if (!use_canary) v: {
var refresher = Progress{};
var progress = refresher.start("Fetching version tags", 0);
const version = (try getLatestVersion(ctx.allocator, &env_loader, &refresher, progress, use_profile, false)) orelse return;
var progress = if (!env_loader.isUserProbablyInMainlandChina()) refresher.start("Fetching version tags", 0) else refresher.start("Fetching via registry.npmmirror.com", 0);
const version = (try getLatestVersionMaybeMirror(ctx.allocator, &env_loader, &refresher, progress, use_profile, false)) orelse return;
progress.end();
refresher.refresh();

View File

@@ -45,10 +45,41 @@ pub const Loader = struct {
did_load_process: bool = false,
reject_unauthorized: ?bool = null,
is_user_probably_in_mainland_china: ?bool = null,
pub fn iterator(this: *const Loader) Map.HashTable.Iterator {
return this.map.iterator();
}
fn isUserProbablyInMainlandChinaImpl(this: *Loader) bool {
if (this.get("LANG") orelse this.get("LC_ALL") orelse this.get("LC_CTYPE")) |lang| {
// LANG=zh_CN
// LANG=zh-CN
if (strings.indexOfChar(lang, '_') orelse strings.indexOf(lang, "-")) |i| {
var after = lang[@min(i + 1, lang.len)..];
if (after.len > 0) {
// LANG=zh_CN.UTF-8
// LANG=zh-CN.UTF-8
if (strings.indexOfChar(after, '.')) |separator| {
after = after[0..separator];
}
// We specifically want mainland china.
return strings.eqlComptime(after, "CN") or strings.eqlComptime(after, "cn");
}
}
}
return false;
}
pub fn isUserProbablyInMainlandChina(this: *Loader) bool {
return this.is_user_probably_in_mainland_china orelse {
this.is_user_probably_in_mainland_china = this.isUserProbablyInMainlandChinaImpl();
return this.is_user_probably_in_mainland_china.?;
};
}
pub fn has(this: *const Loader, input: []const u8) bool {
const value = this.get(input) orelse return false;
if (value.len == 0) return false;

View File

@@ -1049,6 +1049,10 @@ pub fn loadNpmrc(
if (install.default_registry) |dr|
break :brk bun.URL.parse(dr.url);
if (env.isUserProbablyInMainlandChina()) {
break :brk bun.URL.parse(Registry.default_china_url);
}
break :brk bun.URL.parse(Registry.default_url);
};
@@ -1128,7 +1132,7 @@ pub fn loadNpmrc(
.password = "",
.token = "",
.username = "",
.url = Registry.default_url,
.url = default_registry_url.href,
};
break :brk &install.default_registry.?;
};

View File

@@ -2976,6 +2976,29 @@ pub const PackageManager = struct {
this.env.loadCCachePath(this_bundler.fs);
if (this.env.isUserProbablyInMainlandChina()) {
// When the user is in china, use npmmirror.com as the default registry and provide mirrors for common binary downloads.
// https://gist.github.com/blackcater/e3ba5ec0f5059f498b76e3157e786442
// Keep in mind:
// - Windows limits environment variable length to 32,767 characters.
try this.env.map.putDefault("npm_config_disturl", "https://npmmirror.com/dist");
try this.env.map.putDefault("npm_config_sharp_binary_host", "https://npmmirror.com/mirrors/sharp/");
try this.env.map.putDefault("npm_config_sharp_libvips_binary_host", "https://npmmirror.com/mirrors/sharp-libvips");
try this.env.map.putDefault("npm_config_profiler_binary_host_mirror", "https://npmmirror.com/mirrors/node-inspector/");
try this.env.map.putDefault("npm_config_fse_binary_host_mirror", "https://npmmirror.com/mirrors/fsevents");
try this.env.map.putDefault("npm_config_node_sqlite3_binary_host_mirror", "https://npmmirror.com/mirrors");
try this.env.map.putDefault("npm_config_sqlite3_binary_host_mirror", "https://npmmirror.com/mirrors");
try this.env.map.putDefault("npm_config_sqlite3_binary_site", "https://npmmirror.com/mirrors/sqlite3");
// https://github.com/electron-userland/electron-builder/issues/6445#issuecomment-1113991036
try this.env.map.putDefault("npm_config_electron_mirror", "https://npmmirror.com/mirrors/electron/v");
try this.env.map.putDefault("PUPPETEER_DOWNLOAD_BASE_URL", "https://npmmirror.com/mirrors");
try this.env.map.putDefault("npm_config_chromedriver_cdnurl", "https://npmmirror.com/mirrors/chromedriver");
try this.env.map.putDefault("npm_config_operadriver_cdnurl", "https://npmmirror.com/mirrors/operadriver");
try this.env.map.putDefault("npm_config_phantomjs_cdnurl", "https://npmmirror.com/mirrors/phantomjs");
try this.env.map.putDefault("npm_config_python_mirror", "https://npmmirror.com/mirrors/python");
try this.env.map.putDefault("npm_config_sass_binary_site", "https://npmmirror.com/mirrors/node-sass");
}
{
var node_path: bun.PathBuffer = undefined;
if (this.env.getNodePath(this_bundler.fs, &node_path)) |node_pathZ| {
@@ -6986,6 +7009,7 @@ pub const PackageManager = struct {
return error.@"Missing global bin directory: try setting $BUN_INSTALL";
}
pub var is_using_mainland_china_mirror_source = false;
pub fn load(
this: *Options,
@@ -7007,7 +7031,14 @@ pub const PackageManager = struct {
base = registry;
}
}
if (base.url.len == 0) base.url = Npm.Registry.default_url;
if (base.url.len == 0) {
if (env.isUserProbablyInMainlandChina()) {
base.url = Npm.Registry.default_china_url;
is_using_mainland_china_mirror_source = true;
} else {
base.url = Npm.Registry.default_url;
}
}
this.scope = try Npm.Registry.Scope.fromAPI("", base, allocator, env);
defer {
this.did_override_default_scope = this.scope.url_hash != Npm.Registry.default_url_hash;
@@ -8740,7 +8771,7 @@ pub const PackageManager = struct {
};
if (manager.options.shouldPrintCommandName()) {
Output.prettyErrorln("<r><b>bun link <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{});
printCommandName("link");
Output.flush();
}
@@ -8922,7 +8953,7 @@ pub const PackageManager = struct {
};
if (manager.options.shouldPrintCommandName()) {
Output.prettyErrorln("<r><b>bun unlink <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{});
printCommandName("unlink");
Output.flush();
}
@@ -9723,7 +9754,7 @@ pub const PackageManager = struct {
};
if (manager.options.shouldPrintCommandName()) {
Output.prettyErrorln("<r><b>bun {s} <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{@tagName(subcommand)});
printCommandName(@tagName(subcommand));
Output.flush();
}
@@ -11455,6 +11486,14 @@ pub const PackageManager = struct {
var package_json_cwd_buf: bun.PathBuffer = undefined;
pub var package_json_cwd: string = "";
fn printCommandName(name: []const u8) void {
if (Options.is_using_mainland_china_mirror_source) {
Output.prettyErrorln("<r><b>bun {s} <r><d>v" ++ Global.package_json_version_with_sha ++ " (using registry.npmmirror.com)<r>\n", .{name});
} else {
Output.prettyErrorln("<r><b>bun {s} <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{name});
}
}
pub fn install(ctx: Command.Context) !void {
const cli = try CommandLineArguments.parse(ctx.allocator, .install);
var manager = try init(ctx, cli, .install);
@@ -11462,9 +11501,11 @@ pub const PackageManager = struct {
// switch to `bun add <package>`
if (manager.options.positionals.len > 1) {
if (manager.options.shouldPrintCommandName()) {
Output.prettyErrorln("<r><b>bun add <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{});
printCommandName("add");
Output.flush();
}
manager.subcommand = .add;
return try switch (manager.options.log_level) {
inline else => |log_level| manager.updatePackageJSONAndInstallWithManager(ctx, log_level),
@@ -11472,7 +11513,7 @@ pub const PackageManager = struct {
}
if (manager.options.shouldPrintCommandName()) {
Output.prettyErrorln("<r><b>bun install <r><d>v" ++ Global.package_json_version_with_sha ++ "<r>\n", .{});
printCommandName("install");
Output.flush();
}

View File

@@ -37,6 +37,13 @@ const Npm = @This();
pub const Registry = struct {
pub const default_url = "https://registry.npmjs.org/";
pub const default_url_hash = bun.Wyhash11.hash(0, strings.withoutTrailingSlash(default_url));
// Users have reported bun install being slow in China.
// https://npmmirror.com/
// https://cnpmjs.org
pub const default_china_url = "https://registry.npmmirror.com/";
pub const default_china_url_hash = bun.Wyhash11.hash(0, strings.withoutTrailingSlash(default_china_url));
pub const BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8);
pub const Scope = struct {
@@ -879,6 +886,7 @@ pub const PackageManifest = struct {
fn manifestFileName(buf: []u8, file_id: u64, scope: *const Registry.Scope) ![:0]const u8 {
const file_id_hex_fmt = bun.fmt.hexIntLower(file_id);
// For compatibility & complexity reasons, we prefix even if the default registry is the china mirror.
return if (scope.url_hash == Registry.default_url_hash)
try std.fmt.bufPrintZ(buf, "{any}.npm", .{file_id_hex_fmt})
else