diff --git a/.changeset/fast-dots-shave.md b/.changeset/fast-dots-shave.md new file mode 100644 index 0000000000..4e33b77470 --- /dev/null +++ b/.changeset/fast-dots-shave.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-config": patch +"pnpm": patch +--- + +Fixed scoped registry keys (e.g., `@scope:registry`) being parsed as property paths in `pnpm config get` when `--location=project` is used [#9362](https://github.com/pnpm/pnpm/issues/9362). diff --git a/config/plugin-commands-config/src/configGet.ts b/config/plugin-commands-config/src/configGet.ts index 4f75ac32e8..907bd6040b 100644 --- a/config/plugin-commands-config/src/configGet.ts +++ b/config/plugin-commands-config/src/configGet.ts @@ -9,13 +9,25 @@ import { parseConfigPropertyPath } from './parseConfigPropertyPath.js' import { settingShouldFallBackToNpm } from './settingShouldFallBackToNpm.js' export function configGet (opts: ConfigCommandOptions, key: string): { output: string, exitCode: number } { - if (opts.global && settingShouldFallBackToNpm(key)) { + const isScopedKey = key.startsWith('@') + // Exclude scoped keys from npm fallback because they are pnpm-native config + // that can be read directly from rawConfig (e.g., '@scope:registry') + if (opts.global && settingShouldFallBackToNpm(key) && !isScopedKey) { const { status: exitCode } = runNpm(opts.npmPath, ['config', 'get', key]) return { output: '', exitCode: exitCode ?? 0 } } - const config = isStrictlyKebabCase(key) - ? opts.rawConfig[kebabCase(key)] // we don't parse kebab-case keys as property paths because it's not a valid JS syntax - : getConfigByPropertyPath(opts.rawConfig, key) + + let config: unknown + if (isStrictlyKebabCase(key)) { + // we don't parse kebab-case keys as property paths because it's not a valid JS syntax + config = opts.rawConfig[kebabCase(key)] + } else if (isScopedKey) { + // scoped registry keys like '@scope:registry' are used as-is + config = opts.rawConfig[key] + } else { + config = getConfigByPropertyPath(opts.rawConfig, key) + } + const output = displayConfig(config, opts) return { output, exitCode: 0 } } diff --git a/config/plugin-commands-config/test/configGet.test.ts b/config/plugin-commands-config/test/configGet.test.ts index 38bc121a6b..37ea7251d2 100644 --- a/config/plugin-commands-config/test/configGet.test.ts +++ b/config/plugin-commands-config/test/configGet.test.ts @@ -179,3 +179,43 @@ describe('config get with a property path', () => { }) }) }) + +test('config get with scoped registry key (global: false)', async () => { + const getResult = await config.handler({ + dir: process.cwd(), + cliOptions: {}, + configDir: process.cwd(), + global: false, + rawConfig: { + '@scope:registry': 'https://custom-registry.example.com/', + }, + }, ['get', '@scope:registry']) + + expect(getOutputString(getResult)).toBe('https://custom-registry.example.com/') +}) + +test('config get with scoped registry key (global: true)', async () => { + const getResult = await config.handler({ + dir: process.cwd(), + cliOptions: {}, + configDir: process.cwd(), + global: true, + rawConfig: { + '@scope:registry': 'https://custom-registry.example.com/', + }, + }, ['get', '@scope:registry']) + + expect(getOutputString(getResult)).toBe('https://custom-registry.example.com/') +}) + +test('config get with scoped registry key that does not exist', async () => { + const getResult = await config.handler({ + dir: process.cwd(), + cliOptions: {}, + configDir: process.cwd(), + global: false, + rawConfig: {}, + }, ['get', '@scope:registry']) + + expect(getOutputString(getResult)).toBe('undefined') +})