Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
e9ef78276d [autofix.ci] apply automated fixes 2026-01-15 01:10:11 +00:00
Claude Bot
bb8300bef1 fix(create): respect custom registry configuration
`bun create` now respects custom registry configuration from:
- bunfig.toml `[install].registry`
- `BUN_CONFIG_REGISTRY` environment variable
- `NPM_CONFIG_REGISTRY` environment variable
- `npm_config_registry` environment variable

Previously, `bun create` hardcoded `https://registry.npmjs.org` and
ignored all registry configuration.

Fixes #617

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 01:08:19 +00:00
3 changed files with 167 additions and 3 deletions

View File

@@ -1348,6 +1348,7 @@ pub const Command = struct {
pub fn readGlobalConfig(this: Tag) bool {
return switch (this) {
.BunxCommand,
.CreateCommand,
.PackageManagerCommand,
.InstallCommand,
.AddCommand,
@@ -1366,6 +1367,7 @@ pub const Command = struct {
pub fn isNPMRelated(this: Tag) bool {
return switch (this) {
.BunxCommand,
.CreateCommand,
.LinkCommand,
.UnlinkCommand,
.PackageManagerCommand,
@@ -1401,6 +1403,7 @@ pub const Command = struct {
.UpdateInteractiveCommand = true,
.PublishCommand = true,
.AuditCommand = true,
.CreateCommand = true,
});
pub const always_loads_config: std.EnumArray(Tag, bool) = std.EnumArray(Tag, bool).initDefault(false, .{
@@ -1418,6 +1421,7 @@ pub const Command = struct {
.UpdateInteractiveCommand = true,
.PublishCommand = true,
.AuditCommand = true,
.CreateCommand = true,
});
pub const uses_global_options: std.EnumArray(Tag, bool) = std.EnumArray(Tag, bool).initDefault(true, .{
@@ -1570,6 +1574,9 @@ pub const Command = struct {
// Create command wraps bunx
const ctx = try Command.init(allocator, log, .CreateCommand);
// Load bunfig configuration to respect custom registry settings
try Arguments.loadConfig(allocator, null, ctx, .CreateCommand);
var args = try std.process.argsAlloc(allocator);
if (args.len <= 2) {

View File

@@ -1839,9 +1839,42 @@ pub const Example = struct {
}
};
const examples_url: string = "https://registry.npmjs.org/bun-examples-all/latest";
var url: URL = undefined;
/// Gets the registry URL from configuration, environment variables, or falls back to default.
/// Priority: bunfig.toml > BUN_CONFIG_REGISTRY > NPM_CONFIG_REGISTRY > npm_config_registry > default
fn getRegistryUrl(ctx: Command.Context, env_loader: *DotEnv.Loader) string {
// First check bunfig configuration
if (ctx.install) |install| {
if (install.default_registry) |registry| {
if (registry.url.len > 0) {
return registry.url;
}
}
}
// Then check environment variables
const registry_keys = [_]string{
"BUN_CONFIG_REGISTRY",
"NPM_CONFIG_REGISTRY",
"npm_config_registry",
};
inline for (registry_keys) |key| {
if (env_loader.map.get(key)) |registry_url| {
if (registry_url.len > 0 and
(strings.startsWith(registry_url, "https://") or
strings.startsWith(registry_url, "http://")))
{
return registry_url;
}
}
}
// Fall back to default npm registry
return Npm.Registry.default_url;
}
var app_name_buf: [512]u8 = undefined;
pub fn print(examples: []const Example, default_app_name: ?string) void {
for (examples) |example| {
@@ -2069,7 +2102,9 @@ pub const Example = struct {
var mutable = try ctx.allocator.create(MutableString);
mutable.* = try MutableString.init(ctx.allocator, 2048);
url = URL.parse(try std.fmt.bufPrint(&url_buf, "https://registry.npmjs.org/@bun-examples/{s}/latest", .{name}));
const registry_url = getRegistryUrl(ctx, env_loader);
const registry_without_trailing_slash = strings.withoutTrailingSlash(registry_url);
url = URL.parse(try std.fmt.bufPrint(&url_buf, "{s}/@bun-examples/{s}/latest", .{ registry_without_trailing_slash, name }));
var http_proxy: ?URL = env_loader.getHttpProxyFor(url);
@@ -2190,7 +2225,10 @@ pub const Example = struct {
}
pub fn fetchAll(ctx: Command.Context, env_loader: *DotEnv.Loader, progress_node: ?*Progress.Node) ![]Example {
url = URL.parse(examples_url);
var url_buf: [1024]u8 = undefined;
const registry_url = getRegistryUrl(ctx, env_loader);
const registry_without_trailing_slash = strings.withoutTrailingSlash(registry_url);
url = URL.parse(std.fmt.bufPrint(&url_buf, "{s}/bun-examples-all/latest", .{registry_without_trailing_slash}) catch unreachable);
const http_proxy: ?URL = env_loader.getHttpProxyFor(url);
@@ -2455,6 +2493,7 @@ const js_ast = bun.ast;
const logger = bun.logger;
const strings = bun.strings;
const Archiver = bun.libarchive.Archiver;
const Npm = bun.install.Npm;
const HTTP = bun.http;
const Headers = bun.http.Headers;

View File

@@ -0,0 +1,118 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// Test that `bun create` respects custom registry configuration
// Issue: https://github.com/oven-sh/bun/issues/617
test(
"bun create respects BUN_CONFIG_REGISTRY environment variable",
async () => {
// Use localhost on a port that doesn't exist to verify bun tries to connect there
// instead of the default registry.npmjs.org
const customRegistry = "http://127.0.0.1:12345";
using dir = tempDir("bun-create-test", {
// Empty directory for creating the app
});
await using proc = Bun.spawn({
cmd: [bunExe(), "create", "elysia", "my-app"],
cwd: String(dir),
env: {
...bunEnv,
BUN_CONFIG_REGISTRY: customRegistry,
},
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The command should fail because no server is running on 127.0.0.1:12345
const output = stdout + stderr;
// Verify we got a connection error (not a 404 from npmjs.org)
// ConnectionRefused means it tried to connect to our custom registry
expect(output.toLowerCase()).toContain("error");
expect(exitCode).not.toBe(0);
},
{ timeout: 30000 },
);
test(
"bun create respects NPM_CONFIG_REGISTRY environment variable",
async () => {
const customRegistry = "http://127.0.0.1:12346";
using dir = tempDir("bun-create-npm-test", {});
await using proc = Bun.spawn({
cmd: [bunExe(), "create", "elysia", "my-app"],
cwd: String(dir),
env: {
...bunEnv,
NPM_CONFIG_REGISTRY: customRegistry,
},
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The command should fail because no server is running on our custom port
const output = stdout + stderr;
expect(output.toLowerCase()).toContain("error");
expect(exitCode).not.toBe(0);
},
{ timeout: 30000 },
);
test(
"bun create respects bunfig.toml registry configuration",
async () => {
const customRegistry = "http://127.0.0.1:12347/";
using dir = tempDir("bun-create-bunfig-test", {
"bunfig.toml": `[install]
registry = "${customRegistry}"
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "create", "elysia", "my-app"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The command should fail because no server is running on our custom port
const output = stdout + stderr;
expect(output.toLowerCase()).toContain("error");
expect(exitCode).not.toBe(0);
},
{ timeout: 30000 },
);
test(
"bun create works with default registry when no custom registry is set",
async () => {
using dir = tempDir("bun-create-default-test", {});
await using proc = Bun.spawn({
cmd: [bunExe(), "create", "elysia", "my-app"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The command should succeed with the default registry
expect(exitCode).toBe(0);
},
{ timeout: 60000 },
);