diff --git a/.changeset/pretty-readers-know.md b/.changeset/pretty-readers-know.md new file mode 100644 index 0000000000..80cce970f1 --- /dev/null +++ b/.changeset/pretty-readers-know.md @@ -0,0 +1,7 @@ +--- +"@pnpm/run-npm": minor +"@pnpm/plugin-commands-config": patch +"pnpm": patch +--- + +Explicitly tell `npm` the path to the global `rc` config file. diff --git a/config/plugin-commands-config/src/configGet.ts b/config/plugin-commands-config/src/configGet.ts index 0877d72fdc..00e2723287 100644 --- a/config/plugin-commands-config/src/configGet.ts +++ b/config/plugin-commands-config/src/configGet.ts @@ -1,3 +1,4 @@ +import path from 'path' import kebabCase from 'lodash.kebabcase' import { types } from '@pnpm/config' import { isCamelCase, isStrictlyKebabCase } from '@pnpm/naming-cases' @@ -13,7 +14,10 @@ export function configGet (opts: ConfigCommandOptions, key: string): { output: s // 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]) + const { status: exitCode } = runNpm(opts.npmPath, ['config', 'get', key], { + location: 'user', + userConfigPath: path.join(opts.configDir, 'rc'), + }) return { output: '', exitCode: exitCode ?? 0 } } const configResult = getRcConfig(opts.rawConfig, key, isScopedKey) ?? getConfigByPropertyPath(opts.rawConfig, key) diff --git a/config/plugin-commands-config/src/configSet.ts b/config/plugin-commands-config/src/configSet.ts index da0b830efb..cbb2179b47 100644 --- a/config/plugin-commands-config/src/configSet.ts +++ b/config/plugin-commands-config/src/configSet.ts @@ -4,7 +4,7 @@ import { types } from '@pnpm/config' import { PnpmError } from '@pnpm/error' import { isCamelCase, isStrictlyKebabCase } from '@pnpm/naming-cases' import { parsePropertyPath } from '@pnpm/object.property-path' -import { runNpm } from '@pnpm/run-npm' +import { type RunNPMOptions, runNpm } from '@pnpm/run-npm' import { updateWorkspaceManifest } from '@pnpm/workspace.manifest-writer' import camelCase from 'camelcase' import kebabCase from 'lodash.kebabcase' @@ -27,13 +27,18 @@ export async function configSet (opts: ConfigCommandOptions, key: string, valueP if (shouldFallbackToNpm) { if (opts.global) { + const configPath = path.join(opts.configDir, 'rc') + const runNpmOpts: RunNPMOptions = { + location: 'user', + userConfigPath: configPath, + } const _runNpm = runNpm.bind(null, opts.npmPath) if (value == null) { - _runNpm(['config', 'delete', key]) + _runNpm(['config', 'delete', key], runNpmOpts) return } if (typeof value === 'string') { - _runNpm(['config', 'set', `${key}=${value}`]) + _runNpm(['config', 'set', `${key}=${value}`], runNpmOpts) return } throw new PnpmError('CONFIG_SET_AUTH_NON_STRING', `Cannot set ${key} to a non-string value (${JSON.stringify(value)})`) diff --git a/config/plugin-commands-config/test/managingAuthSettings.test.ts b/config/plugin-commands-config/test/managingAuthSettings.test.ts index 72b1d42832..22032f2422 100644 --- a/config/plugin-commands-config/test/managingAuthSettings.test.ts +++ b/config/plugin-commands-config/test/managingAuthSettings.test.ts @@ -27,11 +27,17 @@ describe.each( } it(`should set ${key}`, async () => { await config.handler(configOpts, ['set', `${key}=123`]) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', `${key}=123`]) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', `${key}=123`], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) it(`should delete ${key}`, async () => { await config.handler(configOpts, ['delete', key]) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', key]) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', key], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) }) @@ -45,11 +51,17 @@ describe.each( } it(`should set ${key}`, async () => { await config.handler(configOpts, ['set', key, '"123"']) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', `${key}=123`]) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', `${key}=123`], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) it(`should delete ${key}`, async () => { await config.handler(configOpts, ['delete', key]) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', key]) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', key], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) }) }) @@ -93,10 +105,16 @@ describe.each( } it('should set _auth', async () => { await config.handler(configOpts, ['set', propertyPath, '123']) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', '_auth=123']) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'set', '_auth=123'], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) it('should delete _auth', async () => { await config.handler(configOpts, ['delete', propertyPath]) - expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', '_auth']) + expect(runNpm).toHaveBeenCalledWith(undefined, ['config', 'delete', '_auth'], expect.objectContaining({ + location: 'user', + userConfigPath: expect.any(String), + })) }) }) diff --git a/exec/run-npm/src/index.ts b/exec/run-npm/src/index.ts index 1655c5af5b..bf283f4d3b 100644 --- a/exec/run-npm/src/index.ts +++ b/exec/run-npm/src/index.ts @@ -3,9 +3,13 @@ import path from 'path' import spawn from 'cross-spawn' import PATH from 'path-name' +export type NPMLocation = 'global' | 'user' | 'project' + export interface RunNPMOptions { cwd?: string env?: Record + location?: NPMLocation + userConfigPath?: string } export function runNpm (npmPath: string | undefined, args: string[], options?: RunNPMOptions): childProcess.SpawnSyncReturns { @@ -15,6 +19,8 @@ export function runNpm (npmPath: string | undefined, args: string[], options?: R stdio: 'inherit', userAgent: undefined, env: { ...options?.env, COREPACK_ENABLE_STRICT: '0' }, + location: options?.location, + userConfigPath: options?.userConfigPath, }) } @@ -23,8 +29,10 @@ export function runScriptSync ( args: string[], opts: { cwd: string + location?: NPMLocation stdio: childProcess.StdioOptions userAgent?: string + userConfigPath?: string env: Record } ): childProcess.SpawnSyncReturns { @@ -43,7 +51,9 @@ export function runScriptSync ( function createEnv ( opts: { cwd: string + location?: NPMLocation userAgent?: string + userConfigPath?: string } ): NodeJS.ProcessEnv { const env = { ...process.env } @@ -58,5 +68,13 @@ function createEnv ( env.npm_config_user_agent = opts.userAgent } + if (opts.location) { + env.npm_config_location = opts.location + } + + if (opts.userConfigPath) { + env.npm_config_userconfig = opts.userConfigPath + } + return env } diff --git a/pnpm/src/runNpm.ts b/pnpm/src/runNpm.ts index 882b0a4c36..c254e58ddd 100644 --- a/pnpm/src/runNpm.ts +++ b/pnpm/src/runNpm.ts @@ -1,3 +1,4 @@ +import path from 'path' import { type SpawnSyncReturns } from 'child_process' import { packageManager } from '@pnpm/cli-meta' import { getConfig, types as allTypes } from '@pnpm/config' @@ -14,5 +15,8 @@ export async function runNpm (args: string[]): Promise> ], allTypes), }, }) - return _runNpm(config.npmPath, args) + return _runNpm(config.npmPath, args, { + // This code is only used in `passThruToNpm`, so it is safe to specify `userConfigPath` here. + userConfigPath: path.join(config.configDir, 'rc'), + }) }