mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-13 02:55:56 -04:00
fix(config): align scoped registry resolution between config get and publish (#11494)
Fixes [#11492](https://github.com/pnpm/pnpm/issues/11492). In pnpm v11 a scoped registry resolved to different URLs depending on which command read it: - `pnpm config get @<scope>:registry` returned the value from `.npmrc` - `pnpm publish` used the value from `pnpm-workspace.yaml`'s `registries` block When the two sources disagreed, `pnpm publish` silently targeted the URL from `pnpm-workspace.yaml`, even though `pnpm config get` reported a different (and seemingly authoritative) URL — so users could publish to the wrong registry without any indication. This PR makes `pnpm config get @<scope>:registry` read from the merged `Config.registries` map (the same map `publish` and the resolvers use) before falling back to `authConfig`. Both commands now report and use the same URL.
This commit is contained in:
6
.changeset/scoped-registry-config-get-publish.md
Normal file
6
.changeset/scoped-registry-config-get-publish.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/config.commands": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
`pnpm config get @<scope>:registry` now reports the same URL that `pnpm publish` and the resolvers actually use. Previously, `config get` only consulted `.npmrc`, while `publish`/install used the merged map that includes `pnpm-workspace.yaml`'s `registries` block — so the two could diverge silently and a publish could go to the wrong registry [#11492](https://github.com/pnpm/pnpm/issues/11492).
|
||||
@@ -21,6 +21,18 @@ interface Found<Value> {
|
||||
|
||||
function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boolean): Found<unknown> | undefined {
|
||||
if (isScopedKey) {
|
||||
// Scoped registry keys (e.g. `@scope:registry`) can be set in two places:
|
||||
// `.npmrc` (which lands in authConfig) or pnpm-workspace.yaml's
|
||||
// `registries` block (which lands in the merged Config.registries map).
|
||||
// Prefer the merged map so this command reports the same value that
|
||||
// `pnpm publish` and the resolvers actually use.
|
||||
if (key.endsWith(':registry')) {
|
||||
const scope = key.slice(0, key.length - ':registry'.length)
|
||||
const merged = opts._config.registries?.[scope]
|
||||
if (merged !== undefined) {
|
||||
return { value: merged }
|
||||
}
|
||||
}
|
||||
return { value: opts.authConfig[key] }
|
||||
}
|
||||
const kebabKey = isCamelCase(key) ? kebabCase(key) : key
|
||||
|
||||
@@ -234,6 +234,27 @@ test('config get with scoped registry key (global: true)', async () => {
|
||||
expect(getOutputString(getResult)).toBe('https://custom-registry.example.com/')
|
||||
})
|
||||
|
||||
test('config get with scoped registry returns the merged value from pnpm-workspace.yaml (#11492)', async () => {
|
||||
// .npmrc set the scope to one URL, but pnpm-workspace.yaml's `registries`
|
||||
// block overrides it. `pnpm config get` must report the URL that
|
||||
// resolvers/publish actually use, not the raw .npmrc value.
|
||||
const getResult = await config.handler(createConfigCommandOpts({
|
||||
dir: process.cwd(),
|
||||
cliOptions: {},
|
||||
configDir: process.cwd(),
|
||||
global: false,
|
||||
authConfig: {
|
||||
'@scope:registry': 'https://from-npmrc.example.com/',
|
||||
},
|
||||
registries: {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
'@scope': 'https://from-workspace-yaml.example.com/',
|
||||
},
|
||||
}), ['get', '@scope:registry'])
|
||||
|
||||
expect(getOutputString(getResult)).toBe('https://from-workspace-yaml.example.com/')
|
||||
})
|
||||
|
||||
test('config get with scoped registry key that does not exist', async () => {
|
||||
const getResult = await config.handler(createConfigCommandOpts({
|
||||
dir: process.cwd(),
|
||||
|
||||
@@ -561,6 +561,25 @@ test('registries in current directory\'s .npmrc have bigger priority then global
|
||||
})
|
||||
})
|
||||
|
||||
test('pnpm-workspace.yaml registries override the same scope from .npmrc (#11492)', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
fs.writeFileSync('.npmrc', '@my-org:registry=https://from-npmrc.example.com/', 'utf8')
|
||||
writeYamlFileSync('pnpm-workspace.yaml', {
|
||||
registries: {
|
||||
'@my-org': 'https://from-workspace-yaml.example.com/',
|
||||
},
|
||||
})
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: { name: 'pnpm', version: '1.0.0' },
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(config.registries['@my-org']).toBe('https://from-workspace-yaml.example.com/')
|
||||
})
|
||||
|
||||
test('auth tokens from pnpm auth file override ~/.npmrc', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
|
||||
@@ -27,14 +27,16 @@ test('pnpm config get reads npm options but ignores other settings from .npmrc',
|
||||
'packages[]=qux',
|
||||
].join('\n'))
|
||||
|
||||
// `config get @<scope>:registry` reports the merged (normalized) URL —
|
||||
// the same one `pnpm publish` and the resolvers use — see #11492.
|
||||
{
|
||||
const { stdout } = execPnpmSync(['config', 'get', '@my-org:registry'], { expectSuccess: true })
|
||||
expect(stdout.toString().trim()).toBe('https://my-org.registry.example.com')
|
||||
expect(stdout.toString().trim()).toBe('https://my-org.registry.example.com/')
|
||||
}
|
||||
|
||||
{
|
||||
const { stdout } = execPnpmSync(['config', 'get', '@jsr:registry'], { expectSuccess: true })
|
||||
expect(stdout.toString().trim()).toBe('https://not-actually-jsr.example.com')
|
||||
expect(stdout.toString().trim()).toBe('https://not-actually-jsr.example.com/')
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user