fix(config): warn on ignored settings in global config.yaml (#11470)

- pnpm v11 silently drops local-only settings (e.g. `nodeLinker`, `hoistPattern`, `linkWorkspacePackages`) when they appear in the global `config.yaml`. Users had no way to tell their global configuration was being ignored.
- The reader now emits a warning that names the ignored keys and the path of the global config file, and directs users to either move the settings to a project-level `pnpm-workspace.yaml` or share them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies).
- Allowed keys (e.g. `dangerouslyAllowAllBuilds`, proxy settings) continue to be accepted with no warning.

close #11429
This commit is contained in:
Zoltan Kochan
2026-05-05 18:58:01 +02:00
parent 0d791f3107
commit 2de318b2e9
3 changed files with 50 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/config.reader": patch
"pnpm": patch
---
Print a warning when settings that are not allowed in the global config file (e.g. `nodeLinker`, `hoistPattern`) are present in `config.yaml` and silently ignored. Previously these settings were dropped without any feedback, leaving users unsure why their global configuration had no effect. The warning suggests moving those settings to a project-level `pnpm-workspace.yaml`, or sharing them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies).

View File

@@ -290,11 +290,17 @@ export async function getConfig (opts: {
// Reuse the global config.yaml already read for npmrcAuthFile
const globalYamlConfig = globalYamlConfigForNpmrcAuthFile
if (globalYamlConfig) {
const ignoredKeys: string[] = []
for (const key in globalYamlConfig) {
if (!isConfigFileKey(kebabCase(key))) {
ignoredKeys.push(key)
delete globalYamlConfig[key as keyof typeof globalYamlConfig]
}
}
if (ignoredKeys.length > 0) {
const globalYamlConfigPath = path.join(configDir, GLOBAL_CONFIG_YAML_FILENAME)
warnings.push(`The following settings cannot be set in the global config file ("${globalYamlConfigPath}") and were ignored: ${ignoredKeys.map(k => `"${k}"`).join(', ')}. Move them to a project-level pnpm-workspace.yaml. To share these settings across projects, use config dependencies: https://pnpm.io/11.x/config-dependencies`)
}
addSettingsFromWorkspaceManifestToConfig(pnpmConfig, {
configFromCliOpts,
projectManifest: undefined,

View File

@@ -1687,6 +1687,44 @@ describe('global config.yaml', () => {
expect(config.dangerouslyAllowAllBuilds).toBeDefined()
})
test('warns when global config.yaml contains settings that are not allowed in the global config', async () => {
prepareEmpty()
fs.mkdirSync('.config/pnpm', { recursive: true })
writeYamlFileSync('.config/pnpm/config.yaml', {
dangerouslyAllowAllBuilds: true,
nodeLinker: 'hoisted',
hoistPattern: ['*eslint*'],
})
process.env.XDG_CONFIG_HOME = path.resolve('.config')
const { config, warnings } = await getConfig({
cliOptions: {},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
workspaceDir: process.cwd(),
})
// Allowed setting is still applied.
expect(config.dangerouslyAllowAllBuilds).toBe(true)
// Ignored settings do not leak into the config.
expect(config.nodeLinker).not.toBe('hoisted')
expect(config.hoistPattern).toEqual(['*'])
const warning = warnings.find((w) => w.includes('global config file'))
expect(warning).toBeDefined()
expect(warning).toContain('"nodeLinker"')
expect(warning).toContain('"hoistPattern"')
expect(warning).not.toContain('"dangerouslyAllowAllBuilds"')
expect(warning).toContain(path.join(process.env.XDG_CONFIG_HOME!, 'pnpm', 'config.yaml'))
expect(warning).toContain('pnpm-workspace.yaml')
expect(warning).toContain('https://pnpm.io/11.x/config-dependencies')
expect(warning).not.toContain('.npmrc')
})
test('reads proxy settings from global config.yaml', async () => {
prepareEmpty()