From 6b5d91a4cce3ee22448d10e44b6441f0d7648f5d Mon Sep 17 00:00:00 2001 From: marko1olo <96879250+marko1olo@users.noreply.github.com> Date: Sun, 7 Jun 2026 15:25:52 +0400 Subject: [PATCH] fix(config): restore globalconfig lookup (#12252) * fix(config): restore globalconfig lookup * refactor(config): derive globalconfig path from a single shared helper Extract getGlobalConfigPath into @pnpm/config.reader and use it for both the reader's warning message and 'config get globalconfig' so the reported path cannot drift from the file pnpm actually reads. Add an e2e test that writes a setting to the reported path and reads it back. --------- Co-authored-by: Zoltan Kochan --- .changeset/fix-config-globalconfig.md | 7 +++++++ config/commands/src/configGet.ts | 5 ++++- config/commands/test/configGet.test.ts | 15 ++++++++++++++- config/reader/src/dirs.ts | 6 ++++++ config/reader/src/index.ts | 5 +++-- pnpm/test/config/get.ts | 16 ++++++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-config-globalconfig.md diff --git a/.changeset/fix-config-globalconfig.md b/.changeset/fix-config-globalconfig.md new file mode 100644 index 0000000000..79582d2769 --- /dev/null +++ b/.changeset/fix-config-globalconfig.md @@ -0,0 +1,7 @@ +--- +"@pnpm/config.commands": patch +"@pnpm/config.reader": patch +"pnpm": patch +--- + +Fixed `pnpm config get globalconfig` to return the global `config.yaml` path again [pnpm/pnpm#11962](https://github.com/pnpm/pnpm/issues/11962). diff --git a/config/commands/src/configGet.ts b/config/commands/src/configGet.ts index a0e927b303..4522324518 100644 --- a/config/commands/src/configGet.ts +++ b/config/commands/src/configGet.ts @@ -1,4 +1,4 @@ -import { isIniConfigKey, types } from '@pnpm/config.reader' +import { getGlobalConfigPath, isIniConfigKey, types } from '@pnpm/config.reader' import { getObjectValueByPropertyPath } from '@pnpm/object.property-path' import { isCamelCase } from '@pnpm/text.naming-cases' import camelcase from 'camelcase' @@ -35,6 +35,9 @@ function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boo } return { value: opts.authConfig[key] } } + if (key === 'globalconfig') { + return { value: getGlobalConfigPath(opts.configDir) } + } const kebabKey = isCamelCase(key) ? kebabCase(key) : key // Resolve typed keys from Config — check explicitly set values first, // then fall back to authConfig (for keys like registry set in .npmrc) diff --git a/config/commands/test/configGet.test.ts b/config/commands/test/configGet.test.ts index edebf26c37..89bc535105 100644 --- a/config/commands/test/configGet.test.ts +++ b/config/commands/test/configGet.test.ts @@ -1,3 +1,5 @@ +import path from 'node:path' + import { describe, expect, test } from '@jest/globals' import { config } from '@pnpm/config.commands' @@ -267,7 +269,18 @@ test('config get with scoped registry key that does not exist', async () => { expect(getOutputString(getResult)).toBe('undefined') }) -// globalconfig and npm-globalconfig tests removed — pnpm no longer exposes these npm-compat properties +test('config get globalconfig returns the global config.yaml path', async () => { + const configDir = path.join(process.cwd(), 'global-config') + const getResult = await config.handler(createConfigCommandOpts({ + dir: process.cwd(), + cliOptions: {}, + configDir, + global: true, + authConfig: {}, + }), ['get', 'globalconfig']) + + expect(getOutputString(getResult)).toBe(path.join(configDir, 'config.yaml')) +}) describe('does not traverse the prototype chain (#10296)', () => { test.each([ diff --git a/config/reader/src/dirs.ts b/config/reader/src/dirs.ts index a1fdcc24a7..5a8cd78ee9 100644 --- a/config/reader/src/dirs.ts +++ b/config/reader/src/dirs.ts @@ -1,6 +1,12 @@ import os from 'node:os' import path from 'node:path' +import { GLOBAL_CONFIG_YAML_FILENAME } from '@pnpm/constants' + +export function getGlobalConfigPath (configDir: string): string { + return path.join(configDir, GLOBAL_CONFIG_YAML_FILENAME) +} + export function getCacheDir ( opts: { env: NodeJS.ProcessEnv diff --git a/config/reader/src/index.ts b/config/reader/src/index.ts index dc3372b6cb..baade0fc61 100644 --- a/config/reader/src/index.ts +++ b/config/reader/src/index.ts @@ -36,7 +36,7 @@ import type { } from './Config.js' import { isConfigFileKey } from './configFileKey.js' import { extractAndRemoveDependencyBuildOptions, hasDependencyBuildOptions } from './dependencyBuildOptions.js' -import { getCacheDir, getConfigDir, getDataDir, getStateDir } from './dirs.js' +import { getCacheDir, getConfigDir, getDataDir, getGlobalConfigPath, getStateDir } from './dirs.js' import { parseEnvVars } from './env.js' import { getNetworkConfigs } from './getNetworkConfigs.js' import { getOptionsFromPnpmSettings } from './getOptionsFromRootManifest.js' @@ -52,6 +52,7 @@ import { types } from './types.js' export { types } export { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency.js' +export { getGlobalConfigPath } from './dirs.js' export { getDefaultCreds, getNetworkConfigs, type NetworkConfigs } from './getNetworkConfigs.js' export { getOptionsFromPnpmSettings, type OptionsFromRootManifest } from './getOptionsFromRootManifest.js' export type { Creds } from './parseCreds.js' @@ -305,7 +306,7 @@ export async function getConfig (opts: { } } if (ignoredKeys.length > 0) { - const globalYamlConfigPath = path.join(configDir, GLOBAL_CONFIG_YAML_FILENAME) + const globalYamlConfigPath = getGlobalConfigPath(configDir) 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, { diff --git a/pnpm/test/config/get.ts b/pnpm/test/config/get.ts index 56a47f5fd6..cc47564e6c 100644 --- a/pnpm/test/config/get.ts +++ b/pnpm/test/config/get.ts @@ -276,6 +276,7 @@ test('pnpm config get shows settings from global config.yaml', () => { expect(configGet('dangerously-allow-all-builds')).toBe('true') expect(configGet('dlxCacheMaxAge')).toBe('1234') expect(configGet('dlx-cache-max-age')).toBe('1234') + expect(configGet('globalconfig')).toBe(path.join(configDir, 'config.yaml')) // doesn't list CLI options expect(configGet('dev')).toBe('undefined') @@ -289,3 +290,18 @@ test('pnpm config get shows settings from global config.yaml', () => { expect(configGet('packageExtensions')).toBe('undefined') expect(configGet('package-extensions')).toBe('undefined') }) + +test('the path from "config get globalconfig" is the file that pnpm actually reads global settings from', () => { + prepare() + + const XDG_CONFIG_HOME = path.resolve('.config') + const env = { XDG_CONFIG_HOME } + const configGet = (key: string) => + execPnpmSync(['config', 'get', key], { expectSuccess: true, env }).stdout.toString().trim() + + const globalConfigPath = configGet('globalconfig') + fs.mkdirSync(path.dirname(globalConfigPath), { recursive: true }) + fs.writeFileSync(globalConfigPath, 'dlxCacheMaxAge: 4321\n') + + expect(configGet('dlx-cache-max-age')).toBe('4321') +})