diff --git a/src/install/PackageManager/PackageManagerOptions.rs b/src/install/PackageManager/PackageManagerOptions.rs index da7ddb805c55..038b88070f20 100644 --- a/src/install/PackageManager/PackageManagerOptions.rs +++ b/src/install/PackageManager/PackageManagerOptions.rs @@ -631,6 +631,30 @@ impl Options { } } + if let Some(cli) = &maybe_cli { + if !cli.registry.is_empty() { + let new_url = bun_url::URL::parse(cli.registry); + let same_origin = { + let prev_url = self.scope.url.url(); + bun_core::without_trailing_slash(new_url.host) + == bun_core::without_trailing_slash(prev_url.host) + && (new_url.is_https() || !prev_url.is_https()) + }; + if !same_origin { + self.scope.token = Box::default(); + self.scope.auth = Box::default(); + self.scope.user = Box::default(); + } + let href: Box<[u8]> = cli.registry.into(); + self.scope.url_hash = + Npm::registry::Scope::hash(bun_core::without_trailing_slash(&href)); + self.scope.url = bun_url::OwnedURL::from_href(href); + } + } + + // Unlike the credentials dropped by the registry overrides above, these + // are not tied to a host: they apply to whichever registry ended up as + // the default, so they are read only once that is settled. { const TOKEN_KEYS: [&[u8]; 3] = [ b"BUN_CONFIG_TOKEN", @@ -687,25 +711,6 @@ impl Options { self.enable .set(Enable::ONLY_MISSING, cli.only_missing || cli.analyze); - if !cli.registry.is_empty() { - let new_url = bun_url::URL::parse(cli.registry); - let same_origin = { - let prev_url = self.scope.url.url(); - bun_core::without_trailing_slash(new_url.host) - == bun_core::without_trailing_slash(prev_url.host) - && (new_url.is_https() || !prev_url.is_https()) - }; - if !same_origin { - self.scope.token = Box::default(); - self.scope.auth = Box::default(); - self.scope.user = Box::default(); - } - let href: Box<[u8]> = cli.registry.into(); - self.scope.url_hash = - Npm::registry::Scope::hash(bun_core::without_trailing_slash(&href)); - self.scope.url = bun_url::OwnedURL::from_href(href); - } - if let Some(cache_dir) = cli.cache_dir { self.cache_directory = cache_dir; } diff --git a/test/cli/install/config-precedence.test.ts b/test/cli/install/config-precedence.test.ts index c5d77e070fcf..8daaf166eb9b 100644 --- a/test/cli/install/config-precedence.test.ts +++ b/test/cli/install/config-precedence.test.ts @@ -528,6 +528,38 @@ describe.concurrent("bun install config precedence", () => { expect(exitCode).toBe(0); }); + test.each(["BUN_CONFIG_TOKEN", "NPM_CONFIG_TOKEN"])( + "%s applies to the registry passed with --registry", + async key => { + using capture = capturingRegistry(); + using dir = tempDir("config-precedence", { + "project/package.json": packageJson({ "@needs-auth/test-pkg": "1.0.0" }), + }); + const { stderr, exitCode } = await install(String(dir), ["--registry", capture.url], { [key]: authToken }); + expect(stderr).not.toContain("error:"); + expect(new Set(capture.authorizations)).toStrictEqual(new Set([`Bearer ${authToken}`])); + expect(installed(String(dir), "@needs-auth", "test-pkg")).toBe(true); + expect(exitCode).toBe(0); + }, + ); + + test("--registry drops the _authToken of the .npmrc registry but keeps BUN_CONFIG_TOKEN", async () => { + using dead = deadRegistry(); + using capture = capturingRegistry(); + using dir = tempDir("config-precedence", { + "project/.npmrc": `registry=${dead.url}\n//localhost:${dead.server.port}/:_authToken=token-for-dead\n`, + "project/package.json": packageJson({ "@needs-auth/test-pkg": "1.0.0" }), + }); + const { stderr, exitCode } = await install(String(dir), ["--registry", capture.url], { + BUN_CONFIG_TOKEN: authToken, + }); + expect(stderr).not.toContain("error:"); + expect(dead.hits).toBe(0); + expect(new Set(capture.authorizations)).toStrictEqual(new Set([`Bearer ${authToken}`])); + expect(installed(String(dir), "@needs-auth", "test-pkg")).toBe(true); + expect(exitCode).toBe(0); + }); + test("NPM_CONFIG_REGISTRY beats registry= in project .npmrc", async () => { using dead = deadRegistry(); using dir = tempDir("config-precedence", {