fix: explicitly tell npm the config file path (#10154)

* fix: explicitly tell npm the config file path

* fix: `managingAuthSettings`

* feat: other `npm` call-sites

* docs: changeset

* fix: make optional again

* feat: remove the change from `publish`

* fix: eslint

* refactor: just one is sufficient
This commit is contained in:
Khải
2025-11-06 05:46:20 +07:00
committed by GitHub
parent 5e65855aa8
commit d392c3d059
6 changed files with 67 additions and 11 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)})`)

View File

@@ -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),
}))
})
})

View File

@@ -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<string, string>
location?: NPMLocation
userConfigPath?: string
}
export function runNpm (npmPath: string | undefined, args: string[], options?: RunNPMOptions): childProcess.SpawnSyncReturns<Buffer> {
@@ -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<string, string>
}
): childProcess.SpawnSyncReturns<Buffer> {
@@ -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
}

View File

@@ -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<SpawnSyncReturns<Buffer>>
], 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'),
})
}