refactor(config): move network settings from .npmrc to YAML config (#11209)
Proxy settings (httpProxy, httpsProxy, noProxy), local-address, strict-ssl, and git-shallow-hosts are now written to config.yaml (global) or pnpm-workspace.yaml (local) instead of auth.ini/.npmrc. They are still readable from .npmrc for easier migration from npm CLI. The canonical YAML key names (httpProxy, httpsProxy, noProxy) match Yarn Berry's naming convention. - Add httpProxy, httpsProxy, noProxy to PnpmSettings type - Add http-proxy to pnpmTypes and pnpmConfigFileKeys - Separate network keys from auth keys in config routing - Add isNpmrcReadableKey for backward-compatible .npmrc reading
This commit is contained in:
1 parent
705e3fe3cc
commit
b65204762a
8 files changed
+203
-14
No files matched your search
@@ -870,3 +870,82 @@ test('config set when only pnpm-workspace.yaml exists, writes to it', async () =
|
||||
})
|
||||
expect(fs.existsSync(path.join(tmp, '.npmrc'))).toBeFalsy()
|
||||
})
|
||||
|
||||
test('config set --global https-proxy writes to config.yaml, not auth.ini', async () => {
|
||||
const tmp = tempDir()
|
||||
const configDir = path.join(tmp, 'global-config')
|
||||
const initConfig = {
|
||||
globalRc: undefined,
|
||||
globalYaml: undefined,
|
||||
localRc: undefined,
|
||||
localYaml: undefined,
|
||||
} satisfies ConfigFilesData
|
||||
writeConfigFiles(configDir, tmp, initConfig)
|
||||
|
||||
await config.handler(createConfigCommandOpts({
|
||||
dir: process.cwd(),
|
||||
cliOptions: {},
|
||||
configDir,
|
||||
global: true,
|
||||
authConfig: {},
|
||||
}), ['set', 'https-proxy', 'http://proxy.example.com:8443'])
|
||||
|
||||
const result = readConfigFiles(configDir, tmp)
|
||||
expect(result.globalYaml).toEqual({
|
||||
httpsProxy: 'http://proxy.example.com:8443',
|
||||
})
|
||||
// Must NOT write to auth.ini
|
||||
expect(result.globalRc).toBeUndefined()
|
||||
})
|
||||
|
||||
test('config set --global httpProxy writes to config.yaml', async () => {
|
||||
const tmp = tempDir()
|
||||
const configDir = path.join(tmp, 'global-config')
|
||||
const initConfig = {
|
||||
globalRc: undefined,
|
||||
globalYaml: undefined,
|
||||
localRc: undefined,
|
||||
localYaml: undefined,
|
||||
} satisfies ConfigFilesData
|
||||
writeConfigFiles(configDir, tmp, initConfig)
|
||||
|
||||
await config.handler(createConfigCommandOpts({
|
||||
dir: process.cwd(),
|
||||
cliOptions: {},
|
||||
configDir,
|
||||
global: true,
|
||||
authConfig: {},
|
||||
}), ['set', 'httpProxy', 'http://proxy.example.com:8080'])
|
||||
|
||||
const result = readConfigFiles(configDir, tmp)
|
||||
expect(result.globalYaml).toEqual({
|
||||
httpProxy: 'http://proxy.example.com:8080',
|
||||
})
|
||||
expect(result.globalRc).toBeUndefined()
|
||||
})
|
||||
|
||||
test('config set --global no-proxy writes to config.yaml', async () => {
|
||||
const tmp = tempDir()
|
||||
const configDir = path.join(tmp, 'global-config')
|
||||
const initConfig = {
|
||||
globalRc: undefined,
|
||||
globalYaml: undefined,
|
||||
localRc: undefined,
|
||||
localYaml: undefined,
|
||||
} satisfies ConfigFilesData
|
||||
writeConfigFiles(configDir, tmp, initConfig)
|
||||
|
||||
await config.handler(createConfigCommandOpts({
|
||||
dir: process.cwd(),
|
||||
cliOptions: {},
|
||||
configDir,
|
||||
global: true,
|
||||
authConfig: {},
|
||||
}), ['set', 'no-proxy', 'localhost,127.0.0.1'])
|
||||
|
||||
const result = readConfigFiles(configDir, tmp)
|
||||
expect(result.globalYaml).toEqual({
|
||||
noProxy: 'localhost,127.0.0.1',
|
||||
})
|
||||
expect(result.globalRc).toBeUndefined()
|
||||
})
|
||||
+19
-10
@@ -7,14 +7,21 @@ const RAW_AUTH_CFG_KEYS = [
|
||||
'cafile',
|
||||
'cert',
|
||||
'key',
|
||||
'local-address',
|
||||
'git-shallow-hosts',
|
||||
'registry',
|
||||
] satisfies Array<keyof typeof types>
|
||||
|
||||
/**
|
||||
* Network-related keys that should be readable from .npmrc (for migration from npm)
|
||||
* but written to YAML config files (config.yaml / pnpm-workspace.yaml).
|
||||
*/
|
||||
const NETWORK_INI_KEYS = [
|
||||
'https-proxy',
|
||||
'proxy',
|
||||
'no-proxy',
|
||||
'registry',
|
||||
'http-proxy',
|
||||
'local-address',
|
||||
'strict-ssl',
|
||||
] satisfies Array<keyof typeof types>
|
||||
]
|
||||
|
||||
const RAW_AUTH_CFG_KEY_SUFFIXES = [
|
||||
':ca',
|
||||
@@ -34,14 +41,8 @@ const AUTH_CFG_KEYS = [
|
||||
'cert',
|
||||
'configByUri',
|
||||
'key',
|
||||
'localAddress',
|
||||
'gitShallowHosts',
|
||||
'httpsProxy',
|
||||
'httpProxy',
|
||||
'noProxy',
|
||||
'registry',
|
||||
'registries',
|
||||
'strictSsl',
|
||||
] satisfies Array<keyof Config>
|
||||
|
||||
const NPM_AUTH_SETTINGS = [
|
||||
@@ -94,6 +95,14 @@ export function inheritAuthConfig (target: InheritableConfigPair, src: Inheritab
|
||||
export const isIniConfigKey = (key: string): boolean =>
|
||||
key.startsWith('@') || key.startsWith('//') || NPM_AUTH_SETTINGS.includes(key)
|
||||
|
||||
/**
|
||||
* Whether the config key should be read from .npmrc files.
|
||||
* This includes auth keys and proxy keys (proxy keys are readable from .npmrc
|
||||
* for easier migration from npm, but are written to YAML config files).
|
||||
*/
|
||||
export const isNpmrcReadableKey = (key: string): boolean =>
|
||||
isIniConfigKey(key) || NETWORK_INI_KEYS.includes(key)
|
||||
|
||||
/**
|
||||
* Filter keys that are allowed to be read from an INI config file.
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,7 @@ export const pnpmConfigFileKeys = [
|
||||
'global-dir',
|
||||
'global-path',
|
||||
'global-pnpmfile',
|
||||
'http-proxy',
|
||||
'optimistic-repeat-install',
|
||||
'loglevel',
|
||||
'maxsockets',
|
||||
|
||||
@@ -66,7 +66,7 @@ export {
|
||||
} from './projectConfig.js'
|
||||
export type { Config, ConfigContext, ProjectConfig, UniversalOptions, VerifyDepsBeforeRun }
|
||||
|
||||
export { isIniConfigKey } from './auth.js'
|
||||
export { isIniConfigKey, isNpmrcReadableKey } from './auth.js'
|
||||
export { type ConfigFileKey, isConfigFileKey } from './configFileKey.js'
|
||||
|
||||
type CamelToKebabCase<S extends string> = S extends `${infer T}${infer U}`
|
||||
|
||||
@@ -5,7 +5,7 @@ import path from 'node:path'
|
||||
import { envReplace } from '@pnpm/config.env-replace'
|
||||
import { readIniFileSync } from 'read-ini-file'
|
||||
|
||||
import { isIniConfigKey } from './auth.js'
|
||||
import { isNpmrcReadableKey } from './auth.js'
|
||||
|
||||
export interface NpmrcConfigResult {
|
||||
/**
|
||||
@@ -95,7 +95,7 @@ export function loadNpmrcConfig (opts: LoadNpmrcConfigOpts): NpmrcConfigResult {
|
||||
const mergedConfig: Record<string, unknown> = {}
|
||||
for (const source of [pnpmBuiltinConfig, opts.defaultOptions, userConfig, pnpmAuthConfig, workspaceNpmrc, opts.cliOptions]) {
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (isIniConfigKey(key)) {
|
||||
if (isNpmrcReadableKey(key)) {
|
||||
mergedConfig[key] = value
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ function readAndFilterNpmrc (
|
||||
: rawValue
|
||||
|
||||
// Only keep auth/registry related keys
|
||||
if (isIniConfigKey(key)) {
|
||||
if (isNpmrcReadableKey(key)) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export const pnpmTypes = {
|
||||
'global-pnpmfile': String,
|
||||
'git-branch-lockfile': Boolean,
|
||||
hoist: Boolean,
|
||||
'http-proxy': [null, String],
|
||||
'hoist-pattern': Array,
|
||||
'hoist-workspace-packages': Boolean,
|
||||
'ignore-compatibility-db': Boolean,
|
||||
|
||||
@@ -1451,6 +1451,102 @@ describe('global config.yaml', () => {
|
||||
// NOTE: the field may appear kebab-case here, but only internally,
|
||||
expect(config.dangerouslyAllowAllBuilds).toBeDefined()
|
||||
})
|
||||
|
||||
test('reads proxy settings from global config.yaml', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
fs.mkdirSync('.config/pnpm', { recursive: true })
|
||||
writeYamlFileSync('.config/pnpm/config.yaml', {
|
||||
httpProxy: 'http://proxy.example.com:8080',
|
||||
httpsProxy: 'http://proxy.example.com:8443',
|
||||
noProxy: 'localhost,127.0.0.1',
|
||||
})
|
||||
|
||||
process.env.XDG_CONFIG_HOME = path.resolve('.config')
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(config.httpProxy).toBe('http://proxy.example.com:8080')
|
||||
expect(config.httpsProxy).toBe('http://proxy.example.com:8443')
|
||||
expect(config.noProxy).toBe('localhost,127.0.0.1')
|
||||
})
|
||||
|
||||
test('proxy settings from global config.yaml override .npmrc', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
// Set proxy in .npmrc (npm-style keys)
|
||||
fs.writeFileSync('.npmrc', 'https-proxy=http://npmrc-proxy.example.com:8080', 'utf8')
|
||||
|
||||
// Set different proxy in global config.yaml
|
||||
fs.mkdirSync('.config/pnpm', { recursive: true })
|
||||
writeYamlFileSync('.config/pnpm/config.yaml', {
|
||||
httpsProxy: 'http://yaml-proxy.example.com:9090',
|
||||
})
|
||||
|
||||
process.env.XDG_CONFIG_HOME = path.resolve('.config')
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
// Global YAML should override .npmrc
|
||||
expect(config.httpsProxy).toBe('http://yaml-proxy.example.com:9090')
|
||||
})
|
||||
|
||||
test('CLI flags override proxy settings from global config.yaml', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
fs.mkdirSync('.config/pnpm', { recursive: true })
|
||||
writeYamlFileSync('.config/pnpm/config.yaml', {
|
||||
httpsProxy: 'http://yaml-proxy.example.com:9090',
|
||||
})
|
||||
|
||||
process.env.XDG_CONFIG_HOME = path.resolve('.config')
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
'https-proxy': 'http://cli-proxy.example.com:7070',
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(config.httpsProxy).toBe('http://cli-proxy.example.com:7070')
|
||||
})
|
||||
})
|
||||
|
||||
test('proxy settings are still read from .npmrc', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
fs.writeFileSync('.npmrc', 'https-proxy=http://npmrc-proxy.example.com:8080\nproxy=http://npmrc-http-proxy.example.com:3128\nno-proxy=internal.example.com', 'utf8')
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(config.httpsProxy).toBe('http://npmrc-proxy.example.com:8080')
|
||||
expect(config.httpProxy).toBe('http://npmrc-proxy.example.com:8080')
|
||||
expect(config.noProxy).toBe('internal.example.com')
|
||||
})
|
||||
|
||||
test('lockfile: false in pnpm-workspace.yaml sets useLockfile to false', async () => {
|
||||
|
||||
@@ -188,6 +188,9 @@ export interface PnpmSettings {
|
||||
requiredScripts?: string[]
|
||||
supportedArchitectures?: SupportedArchitectures
|
||||
nodeDownloadMirrors?: Record<string, string>
|
||||
httpProxy?: string
|
||||
httpsProxy?: string
|
||||
noProxy?: string | boolean
|
||||
}
|
||||
|
||||
export interface ProjectManifest extends BaseManifest {
|
||||
|
||||
Reference in new issue
Block a user