fix(cli/config): phantom keys (#10323)

* fix(cli/config): phantom keys

Fixes https://github.com/pnpm/pnpm/issues/10296

This patch also include other refactors.

* test: does not traverse the prototype chain

* test: more properties

* test: fix other tests

* feat: revert unrelated changes
This commit is contained in:
Khải
2025-12-22 18:26:14 +07:00
committed by GitHub
parent 226e22392b
commit 97cf97609e
6 changed files with 31 additions and 5 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-config": patch
---
Fix phantom keys in `pnpm config get <key>` [#10296](https://github.com/pnpm/pnpm/issues/10296).

View File

@@ -444,7 +444,7 @@ export async function getConfig (opts: {
// TODO: should we throw some error or print some warning here?
if (value === undefined) continue
if (key in cliOptions || kebabCase(key) in cliOptions) continue
if (Object.hasOwn(cliOptions, key) || Object.hasOwn(cliOptions, kebabCase(key))) continue
// @ts-expect-error
pnpmConfig[key] = value

View File

@@ -35,7 +35,7 @@ function getRcConfig (rawConfig: Record<string, unknown>, key: string, isScopedK
return { value }
}
const rcKey = isCamelCase(key) ? kebabCase(key) : key
if (rcKey in types) {
if (Object.hasOwn(types, rcKey)) {
const value = rawConfig[rcKey]
return { value }
}

View File

@@ -185,7 +185,7 @@ export class ConfigSetUnsupportedIniConfigKeyError extends PnpmError {
*/
function validateIniConfigKey (key: string): string {
const kebabKey = kebabCase(key)
if (kebabKey in types) {
if (Object.hasOwn(types, kebabKey)) {
return kebabKey
}
throw new ConfigSetUnsupportedIniConfigKeyError(key)
@@ -207,7 +207,7 @@ export class ConfigSetUnsupportedWorkspaceKeyError extends PnpmError {
* Return the camelCase of {@link key} if it's valid.
*/
function validateWorkspaceKey (key: string): string {
if (key in types) return camelCase(key)
if (Object.hasOwn(types, key)) return camelCase(key)
if (!isCamelCase(key)) throw new ConfigSetUnsupportedWorkspaceKeyError(key)
return key
}

View File

@@ -24,7 +24,7 @@ function normalizeTopLevelConfigName (configName: string | number): string {
if (typeof configName === 'number') return configName.toString()
const kebabKey = kebabCase(configName)
if (kebabKey in types) return kebabKey
if (Object.hasOwn(types, kebabKey)) return kebabKey
return configName
}

View File

@@ -302,3 +302,24 @@ test('config get npm-globalconfig', async () => {
expect(getOutputString(getResult)).toBe(npmGlobalconfigPath)
})
describe('does not traverse the prototype chain (#10296)', () => {
test.each([
'constructor',
'hasOwnProperty',
'isPrototypeOf',
'toString',
'valueOf',
'__proto__',
])('%s', async key => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {},
}, ['get', key])
expect(getOutputString(getResult)).toBe('undefined')
})
})