refactor(config): rename rawConfig to authConfig, add nodeDownloadMirrors, simplify config reader (#11194)

Major cleanup of the config system after migrating settings from `.npmrc` to `pnpm-workspace.yaml`.

### Config reader simplification
- Remove `checkUnknownSetting` (dead code, always `false`)
- Trim `npmConfigTypes` from ~127 to ~67 keys (remove unused npm config keys)
- Replace `rcOptions` iteration over all type keys with direct construction from defaults + auth overlay
- Remove `rcOptionsTypes` parameter from `getConfig()` and its assembly chain

### Rename `rawConfig` to `authConfig`
- `rawConfig` was a confusing mix of auth data and general settings
- Non-auth settings are already on the typed `Config` object — stop duplicating them in `rawConfig`
- Rename `rawConfig` → `authConfig` across the codebase to clarify it only contains auth/registry data from `.npmrc`

### Remove `rawConfig` from non-auth consumers
- **Lifecycle hooks**: replace `rawConfig: object` with `userAgent?: string` — only user-agent was read
- **Fetchers**: remove unused `rawConfig` from git fetcher, binary fetcher, tarball fetcher, prepare-package
- **Update command**: use `opts.production/dev/optional` instead of `rawConfig.*`
- **`pnpm init`**: accept typed init properties instead of parsing `rawConfig`

### Add `nodeDownloadMirrors` setting
- New `nodeDownloadMirrors?: Record<string, string>` on `PnpmSettings` and `Config`
- Replaces the `node-mirror:<channel>` pattern that was stored in `rawConfig`
- Configured in `pnpm-workspace.yaml`:
  ```yaml
  nodeDownloadMirrors:
    release: https://my-mirror.example.com/download/release/
  ```
- Remove unused `rawConfig` from deno-resolver and bun-resolver

### Refactor `pnpm config get/list`
- New `configToRecord()` builds display data from typed Config properties on the fly
- Excludes sensitive internals (`authInfos`, `sslConfigs`, etc.)
- Non-types keys (e.g., `package-extensions`) resolve through `configToRecord` instead of direct property access
- Delete `processConfig.ts` (replaced by `configToRecord.ts`)

### Pre-push hook improvement
- Add `compile-only` (`tsgo --build`) to pre-push hook to catch type errors before push
This commit is contained in:
Zoltan Kochan
2026-04-04 20:33:43 +02:00
committed by GitHub
parent c7203b99ad
commit 96704a1c58
123 changed files with 595 additions and 811 deletions

View File

@@ -0,0 +1,29 @@
---
"@pnpm/config.reader": major
"@pnpm/exec.lifecycle": minor
"@pnpm/exec.prepare-package": minor
"@pnpm/fetching.git-fetcher": minor
"@pnpm/fetching.tarball-fetcher": minor
"@pnpm/fetching.binary-fetcher": minor
"@pnpm/installing.client": major
"@pnpm/config.commands": minor
"@pnpm/workspace.commands": major
"@pnpm/engine.runtime.node-resolver": minor
"@pnpm/resolving.default-resolver": minor
"@pnpm/store.connection-manager": minor
"pnpm": minor
---
Renamed `rawConfig` to `authConfig` on the `Config` interface. This field now only contains auth/registry data from `.npmrc` files. Non-auth settings are no longer written to it.
Added `nodeDownloadMirrors` setting to configure custom Node.js download mirrors in `pnpm-workspace.yaml`:
```yaml
nodeDownloadMirrors:
release: https://my-mirror.example.com/download/release/
nightly: https://my-mirror.example.com/download/nightly/
```
Replaced `rawConfig: object` with `userAgent?: string` in lifecycle hook options. Removed unused `rawConfig` from fetcher and prepare-package options.
Removed support for the npm `init-module` setting. Custom init scripts via `.pnpm-init.js` are no longer executed by `pnpm init`.

View File

@@ -1 +1 @@
pnpm run lint --quiet
pnpm run compile-only && pnpm run lint --quiet

View File

@@ -63,7 +63,7 @@ export type LoginCommandOptions = Pick<Config,
| 'fetchRetryMaxtimeout'
| 'fetchRetryMintimeout'
| 'fetchTimeout'
| 'rawConfig'
| 'authConfig'
> & {
registry?: string
}

View File

@@ -90,7 +90,7 @@ describe('login', () => {
stdin: { isTTY: false },
},
})
const opts = { configDir: '/mock/config', dir: '/mock', rawConfig: {} }
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {} }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_NON_INTERACTIVE')
await expect(promise).rejects.toHaveProperty(['message'], 'The login command requires an interactive terminal')
@@ -130,7 +130,7 @@ describe('login', () => {
throw new Error(`Unexpected call to fetch: ${url}`)
},
})
const opts = { configDir: '/custom/config', dir: '/mock', rawConfig: {}, registry: 'https://example.com/npm/' }
const opts = { configDir: '/custom/config', dir: '/mock', authConfig: {}, registry: 'https://example.com/npm/' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.com/npm/')
expect(fetchedUrls[0]).toBe('https://example.com/npm/-/v1/login')
@@ -183,7 +183,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/other/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/other/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.org/')
expect(fetchedUrls[0]).toBe('https://example.org/-/v1/login')
@@ -240,7 +240,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/otp/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/otp/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.org/')
expect(putCallCount).toBe(2)
@@ -302,7 +302,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/otp/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/otp/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.org/')
expect(putCallCount).toBe(2)
@@ -338,7 +338,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/otp/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/otp/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_FAILED')
await expect(promise).rejects.toHaveProperty(['message'], 'Login failed (HTTP 403): Forbidden')
@@ -367,7 +367,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/mock/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_MISSING_CREDENTIALS')
await expect(promise).rejects.toHaveProperty(['message'], 'Username, password, and email are all required')
@@ -403,7 +403,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/mock/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_NO_TOKEN')
await expect(promise).rejects.toHaveProperty(['message'], 'The registry did not return an authentication token')
@@ -424,7 +424,7 @@ describe('login', () => {
throw new Error(`Unexpected call to fetch: ${url}`)
},
})
const opts = { configDir: '/mock/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_INVALID_RESPONSE')
await expect(promise).rejects.toHaveProperty(['message'], 'The registry returned an invalid response for web-based login')
@@ -465,7 +465,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/mock/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.org/')
expect(savedSettings).toMatchObject({
@@ -503,7 +503,7 @@ describe('login', () => {
},
},
})
const opts = { configDir: '/otp/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/otp/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'ERR_PNPM_LOGIN_FAILED')
await expect(promise).rejects.toHaveProperty(['message'], 'Login failed (HTTP 401): Unauthorized')
@@ -538,7 +538,7 @@ describe('login', () => {
})
},
})
const opts = { configDir: '/nonexistent/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/nonexistent/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const result = await login({ context, opts })
expect(result).toBe('Logged in on https://example.org/')
expect(savedSettings).toMatchObject({
@@ -573,7 +573,7 @@ describe('login', () => {
})
},
})
const opts = { configDir: '/broken/config', dir: '/mock', rawConfig: {}, registry: 'https://example.org' }
const opts = { configDir: '/broken/config', dir: '/mock', authConfig: {}, registry: 'https://example.org' }
const promise = login({ context, opts })
await expect(promise).rejects.toHaveProperty(['code'], 'EACCES')
await expect(promise).rejects.toHaveProperty(['message'], 'EACCES: permission denied')

View File

@@ -35,7 +35,7 @@ export type StrictBuildOptions = {
production: boolean
development: boolean
optional: boolean
rawConfig: object
authConfig: object
userConfig: Record<string, string>
userAgent: string
packageManager: {
@@ -73,7 +73,7 @@ const defaults = async (opts: BuildOptions): Promise<StrictBuildOptions> => {
packageManager,
pending: false,
production: true,
rawConfig: {},
authConfig: {},
registries: DEFAULT_REGISTRIES,
scriptsPrependNodePath: false,
shamefullyHoist: false,

View File

@@ -198,12 +198,12 @@ export async function buildProjects (
extraNodePaths: ctx.extraNodePaths,
extraEnv: opts.extraEnv,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,
shellEmulator: opts.shellEmulator,
storeController: store.ctrl,
unsafePerm: opts.unsafePerm || false,
userAgent: opts.userAgent,
}
await runLifecycleHooksConcurrently(
['preinstall', 'install', 'postinstall', 'prepublish', 'prepare'],
@@ -386,11 +386,11 @@ async function _rebuild (
extraEnv: opts.extraEnv,
optional: pkgSnapshot.optional === true,
pkgRoot,
rawConfig: opts.rawConfig,
rootModulesDir: ctx.rootModulesDir,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
shellEmulator: opts.shellEmulator,
unsafePerm: opts.unsafePerm || false,
userAgent: opts.userAgent,
})
if (hasSideEffects && (opts.sideEffectsCacheWrite ?? true) && resolution.integrity) {
builtDepPaths.add(depPath)

View File

@@ -137,10 +137,6 @@ export async function recursiveRebuild (
...localConfig,
dir: rootDir,
pending: opts.pending === true,
rawConfig: {
...rebuildOpts.rawConfig,
...localConfig,
},
}
)
result[rootDir].status = 'passed'

View File

@@ -35,7 +35,7 @@ export const DEFAULT_OPTS = {
pnpmfile: ['./.pnpmfile.cjs'],
pnpmHomeDir: '',
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
registry: REGISTRY,

View File

@@ -43,7 +43,6 @@ export async function buildModules<T extends string> (
lockfileDir: string
optional: boolean
preferSymlinkedExecutables?: boolean
rawConfig: object
unsafePerm: boolean
userAgent: string
scriptsPrependNodePath?: boolean | 'warn-only'
@@ -141,7 +140,6 @@ async function buildDependency<T extends string> (
lockfileDir: string
optional: boolean
preferSymlinkedExecutables?: boolean
rawConfig: object
rootModulesDir: string
scriptsPrependNodePath?: boolean | 'warn-only'
scriptShell?: string
@@ -149,6 +147,7 @@ async function buildDependency<T extends string> (
sideEffectsCacheWrite: boolean
storeController: StoreController
unsafePerm: boolean
userAgent?: string
hoistedLocations?: Record<string, string[]>
builtHoistedDeps?: Record<string, DeferredPromise<void>>
enableGlobalVirtualStore?: boolean
@@ -184,12 +183,12 @@ async function buildDependency<T extends string> (
initCwd: opts.lockfileDir,
optional: depNode.optional,
pkgRoot: depNode.dir,
rawConfig: opts.rawConfig,
rootModulesDir: opts.rootModulesDir,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,
shellEmulator: opts.shellEmulator,
unsafePerm: opts.unsafePerm || false,
userAgent: opts.userAgent,
})
// Remove the .pnpm-needs-build marker before uploading side effects,
// so it doesn't get cached as part of the package's side effects diff.

View File

@@ -420,7 +420,7 @@ function reportAuthError (
config?: Config
): ErrorInfo {
const foundSettings = [] as string[]
for (const [key, value] of Object.entries(config?.rawConfig ?? {})) {
for (const [key, value] of Object.entries(config?.authConfig ?? {})) {
if (key[0] === '@') {
foundSettings.push(`${key}=${String(value)}`)
continue

View File

@@ -412,7 +412,7 @@ some hint`)
})
test('prints authorization error with auth settings', async () => {
const rawConfig = {
const authConfig = {
'//foo.bar:_auth': '9876543219',
'//foo.bar:_authToken': '9876543219',
'//foo.bar:_password': '9876543219',
@@ -424,7 +424,7 @@ test('prints authorization error with auth settings', async () => {
username: 'nagy.gabor',
}
const output$ = toOutput$({
context: { argv: ['install'], config: { rawConfig } as any }, // eslint-disable-line
context: { argv: ['install'], config: { authConfig } as any }, // eslint-disable-line
streamParser: createStreamParser(),
})
@@ -452,7 +452,7 @@ ${ERROR_PAD}username=nagy.gabor`)
test('prints authorization error without auth settings, where there are none', async () => {
const output$ = toOutput$({
context: { argv: ['install'], config: { rawConfig: {} } as any }, // eslint-disable-line
context: { argv: ['install'], config: { authConfig: {} } as any }, // eslint-disable-line
streamParser: createStreamParser(),
})

View File

@@ -5,9 +5,12 @@ export type ConfigCommandOptions = Pick<Config,
| 'cliOptions'
| 'dir'
| 'global'
| 'rawConfig'
| 'authConfig'
| 'workspaceDir'
> & {
json?: boolean
location?: 'global' | 'project'
// The config commands receive the full Config object at runtime
// and read arbitrary typed properties for display.
[key: string]: unknown
}

View File

@@ -1,15 +1,16 @@
import { isIniConfigKey, types } from '@pnpm/config.reader'
import { type Config, isIniConfigKey, types } from '@pnpm/config.reader'
import { getObjectValueByPropertyPath } from '@pnpm/object.property-path'
import { isCamelCase, isStrictlyKebabCase } from '@pnpm/text.naming-cases'
import { isCamelCase } from '@pnpm/text.naming-cases'
import camelcase from 'camelcase'
import kebabCase from 'lodash.kebabcase'
import type { ConfigCommandOptions } from './ConfigCommandOptions.js'
import { configToRecord } from './configToRecord.js'
import { parseConfigPropertyPath } from './parseConfigPropertyPath.js'
import { processConfig } from './processConfig.js'
export function configGet (opts: ConfigCommandOptions, key: string): { output: string, exitCode: number } {
const isScopedKey = key.startsWith('@')
const configResult = getRcConfig(opts.rawConfig, key, isScopedKey) ?? getConfigByPropertyPath(opts.rawConfig, key)
const configResult = lookupConfig(opts, key, isScopedKey) ?? (isPropertyPath(key) ? lookupByPropertyPath(opts, key) : { value: undefined })
const output = displayConfig(configResult?.value, opts)
return { output, exitCode: 0 }
}
@@ -18,39 +19,54 @@ interface Found<Value> {
value: Value
}
function getRcConfig (rawConfig: Record<string, unknown>, key: string, isScopedKey: boolean): Found<unknown> | undefined {
function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boolean): Found<unknown> | undefined {
if (isScopedKey) {
const value = rawConfig[key]
return { value }
return { value: opts.authConfig[key] }
}
const rcKey = isCamelCase(key) ? kebabCase(key) : key
if (Object.hasOwn(types, rcKey)) {
const value = rawConfig[rcKey]
return { value }
}
if (isStrictlyKebabCase(key)) {
const value = rawConfig[key]
return { value }
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)
if (Object.hasOwn(types, kebabKey)) {
const camelKey = camelcase(kebabKey, { locale: 'en-US' })
const explicit = (opts as unknown as Config).explicitlySetKeys
if (!explicit || explicit.has(camelKey)) {
return { value: (opts as unknown as Record<string, unknown>)[camelKey] }
}
// Fall back to authConfig for INI keys (registry, ca, etc.)
if (kebabKey in opts.authConfig) {
return { value: opts.authConfig[kebabKey] }
}
return { value: undefined }
}
// Auth-specific INI keys (//host:_authToken, _auth, etc.) from authConfig
if (isIniConfigKey(key)) {
const value = rawConfig[key]
return { value }
return { value: opts.authConfig[key] }
}
// For keys not in types (e.g., package-extensions), look up via configToRecord
// which excludes internal/sensitive fields.
const camelKey = camelcase(key, { locale: 'en-US' })
const record = configToRecord(opts as unknown as Config)
if (Object.hasOwn(record, camelKey)) {
return { value: record[camelKey] }
}
return undefined
}
function getConfigByPropertyPath (rawConfig: Record<string, unknown>, propertyPath: string): Found<unknown> {
function lookupByPropertyPath (opts: ConfigCommandOptions, propertyPath: string): Found<unknown> {
const parsedPropertyPath = Array.from(parseConfigPropertyPath(propertyPath))
if (parsedPropertyPath.length === 0) {
return {
value: processConfig(rawConfig),
}
return { value: configToRecord(opts as unknown as Config) }
}
const record = configToRecord(opts as unknown as Config)
return {
value: getObjectValueByPropertyPath(rawConfig, parsedPropertyPath),
value: getObjectValueByPropertyPath(record, parsedPropertyPath),
}
}
function isPropertyPath (key: string): boolean {
return key === '' || key.includes('.') || key.includes('[')
}
type DisplayConfigOptions = Pick<ConfigCommandOptions, 'json'>
function displayConfig (config: unknown, opts: DisplayConfigOptions): string {

View File

@@ -1,9 +1,8 @@
import type { Config } from '@pnpm/config.reader'
import type { ConfigCommandOptions } from './ConfigCommandOptions.js'
import { processConfig } from './processConfig.js'
import { configToRecord } from './configToRecord.js'
export type ConfigListOptions = Pick<ConfigCommandOptions, 'rawConfig'>
export async function configList (opts: ConfigListOptions): Promise<string> {
const processedConfig = processConfig(opts.rawConfig)
return JSON.stringify(processedConfig, undefined, 2)
export async function configList (opts: ConfigCommandOptions): Promise<string> {
return JSON.stringify(configToRecord(opts as unknown as Config), undefined, 2)
}

View File

@@ -0,0 +1,50 @@
import { type Config, types } from '@pnpm/config.reader'
import { sortDirectKeys } from '@pnpm/object.key-sorting'
import camelcase from 'camelcase'
import { censorProtectedSettings } from './protectedSettings.js'
const INTERNAL_CONFIG_KEYS = new Set([
'authConfig', 'authInfos', 'rawLocalConfig', 'cliOptions',
'explicitlySetKeys',
'hooks', 'finders', 'allProjects', 'selectedProjectsGraph',
'packageManager', 'wantedPackageManager', 'rootProjectManifest',
'storeController', 'rootProjectManifestDir', 'sslConfigs',
])
/**
* Convert a Config object to a camelCase record for display.
* Only includes explicitly set values (from CLI, env vars, or workspace yaml),
* not default values. Auth/registry keys from authConfig are always included.
*/
export function configToRecord (config: Config): Record<string, unknown> {
const result: Record<string, unknown> = {}
const explicit = config.explicitlySetKeys
// Add typed settings (only explicitly set ones if tracking is available)
for (const kebabKey of Object.keys(types)) {
const camelKey = camelcase(kebabKey, { locale: 'en-US' })
if (explicit && !explicit.has(camelKey)) continue
const value = (config as unknown as Record<string, unknown>)[camelKey]
if (value !== undefined) {
result[camelKey] = value
}
}
// Add non-types config properties (e.g., packageExtensions, overrides)
for (const [key, value] of Object.entries(config)) {
if (value === undefined || INTERNAL_CONFIG_KEYS.has(key)) continue
if (!(key in result) && (!explicit || explicit.has(key))) {
result[key] = value
}
}
// Add auth/registry keys (scoped keys, auth tokens) — keep original casing
for (const [key, value] of Object.entries(config.authConfig)) {
if (!(key in result)) {
result[key] = value
}
}
// Always include user-agent for debugging connectivity issues
if (config.userAgent) {
result.userAgent = config.userAgent
}
return censorProtectedSettings(sortDirectKeys(result))
}

View File

@@ -1,30 +1,16 @@
import { types } from '@pnpm/config.reader'
import { parsePropertyPath } from '@pnpm/object.property-path'
import kebabCase from 'lodash.kebabcase'
import camelcase from 'camelcase'
/**
* Just like {@link parsePropertyPath} but the first element may be converted into kebab-case
* if it's part of {@link types}.
* Just like {@link parsePropertyPath} but the first element is converted to camelCase
* to match the camelCase keys produced by {@link configToRecord}.
*/
export function * parseConfigPropertyPath (propertyPath: string): Generator<string | number, void, void> {
const iter = parsePropertyPath(propertyPath)
const first = iter.next()
if (first.done) return
yield normalizeTopLevelConfigName(first.value)
yield typeof first.value === 'number' ? first.value : camelcase(first.value, { locale: 'en-US' })
yield * iter
}
/**
* Turn a top-level config name into kebab-case if it's part of {@link types}.
* Otherwise, return the string as-is.
*/
function normalizeTopLevelConfigName (configName: string | number): string {
if (typeof configName === 'number') return configName.toString()
const kebabKey = kebabCase(configName)
if (Object.hasOwn(types, kebabKey)) return kebabKey
return configName
}

View File

@@ -1,23 +0,0 @@
import { sortDirectKeys } from '@pnpm/object.key-sorting'
import camelcase from 'camelcase'
import { censorProtectedSettings } from './protectedSettings.js'
const shouldChangeCase = (key: string): boolean => key[0] !== '@' && !key.startsWith('//')
function camelCaseConfig (rawConfig: Record<string, unknown>): Record<string, unknown> {
const result: Record<string, unknown> = {}
for (const key in rawConfig) {
const targetKey = shouldChangeCase(key) ? camelcase(key) : key
result[targetKey] = rawConfig[key]
}
return result
}
export interface ProcessConfigOptions {
json?: boolean
}
export function processConfig (rawConfig: Record<string, unknown>): Record<string, unknown> {
return camelCaseConfig(censorProtectedSettings(sortDirectKeys(rawConfig)))
}

View File

@@ -18,7 +18,7 @@ test('config delete on registry key not set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'registry'])
expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({
@@ -37,7 +37,7 @@ test('config delete on registry key set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'registry'])
expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({})
@@ -54,7 +54,7 @@ test('config delete on npm-compatible key not set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'cafile'])
expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({
@@ -73,7 +73,7 @@ test('config delete on npm-compatible key set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'cafile'])
// NOTE: pnpm currently does not delete empty rc files.
@@ -94,7 +94,7 @@ test('config delete on pnpm-specific key not set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'store-dir'])
expect(readYamlFileSync(path.join(configDir, 'config.yaml'))).toStrictEqual({
@@ -115,7 +115,7 @@ test('config delete on pnpm-specific key set', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', 'cache-dir'])
expect(fs.readdirSync(configDir)).not.toContain('config.yaml')

View File

@@ -8,9 +8,8 @@ test('config get', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
'store-dir': '~/store',
},
authConfig: {},
storeDir: '~/store',
}, ['get', 'store-dir'])
expect(getOutputString(getResult)).toBe('~/store')
@@ -22,9 +21,8 @@ test('config get works with camelCase', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
'store-dir': '~/store',
},
authConfig: {},
storeDir: '~/store',
}, ['get', 'storeDir'])
expect(getOutputString(getResult)).toBe('~/store')
@@ -36,9 +34,8 @@ test('config get a boolean should return string format', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
'update-notifier': true,
},
authConfig: {},
updateNotifier: true,
}, ['get', 'update-notifier'])
expect(getOutputString(getResult)).toBe('true')
@@ -50,12 +47,11 @@ test('config get on array should return a comma-separated list', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
'public-hoist-pattern': [
'*eslint*',
'*prettier*',
],
},
authConfig: {},
publicHoistPattern: [
'*eslint*',
'*prettier*',
],
}, ['get', 'public-hoist-pattern'])
expect(JSON.parse(getOutputString(getResult))).toStrictEqual([
@@ -70,10 +66,9 @@ test('config get on object should return a JSON string', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
catalog: {
react: '^19.0.0',
},
authConfig: {},
catalog: {
react: '^19.0.0',
},
}, ['get', 'catalog'])
@@ -81,7 +76,7 @@ test('config get on object should return a JSON string', async () => {
})
test('config get without key show list all settings', async () => {
const rawConfig = {
const authConfig = {
'store-dir': '~/store',
'fetch-retries': '2',
}
@@ -90,78 +85,74 @@ test('config get without key show list all settings', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig,
authConfig,
}, ['get'])
const listOutput = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
rawConfig,
authConfig,
}, ['list'])
expect(getOutput).toStrictEqual(listOutput)
})
describe('config get with a property path', () => {
// TODO: change `rawConfig` into camelCase (to emulate pnpm-workspace.yaml)
const rawConfig = {
'dlx-cache-max-age': '1234',
'trust-policy-exclude': ['foo', 'bar'],
packageExtensions: {
'@babel/parser': {
peerDependencies: {
'@babel/types': '*',
},
},
'jest-circus': {
dependencies: {
slash: '3',
},
const packageExtensions = {
'@babel/parser': {
peerDependencies: {
'@babel/types': '*',
},
},
'jest-circus': {
dependencies: {
slash: '3',
},
},
}
const configData = {
dlxCacheMaxAge: '1234',
trustPolicyExclude: ['foo', 'bar'],
packageExtensions,
}
const baseOpts = {
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
authConfig: {},
...configData,
}
describe('anything with --json', () => {
test('«»', async () => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
...baseOpts,
json: true,
rawConfig,
}, ['get', ''])
expect(JSON.parse(getOutputString(getResult))).toStrictEqual({
dlxCacheMaxAge: rawConfig['dlx-cache-max-age'],
trustPolicyExclude: rawConfig['trust-policy-exclude'],
packageExtensions: rawConfig.packageExtensions,
})
expect(JSON.parse(getOutputString(getResult))).toMatchObject(configData)
})
test.each([
['dlx-cache-max-age', rawConfig['dlx-cache-max-age']],
['dlxCacheMaxAge', rawConfig['dlx-cache-max-age']],
['trust-policy-exclude', rawConfig['trust-policy-exclude']],
['trustPolicyExclude', rawConfig['trust-policy-exclude']],
['trustPolicyExclude[0]', rawConfig['trust-policy-exclude'][0]],
['trustPolicyExclude[1]', rawConfig['trust-policy-exclude'][1]],
['packageExtensions', rawConfig.packageExtensions],
['packageExtensions["@babel/parser"]', rawConfig.packageExtensions['@babel/parser']],
['packageExtensions["@babel/parser"].peerDependencies', rawConfig.packageExtensions['@babel/parser'].peerDependencies],
['packageExtensions["@babel/parser"].peerDependencies["@babel/types"]', rawConfig.packageExtensions['@babel/parser'].peerDependencies['@babel/types']],
['packageExtensions["jest-circus"]', rawConfig.packageExtensions['jest-circus']],
['packageExtensions["jest-circus"].dependencies', rawConfig.packageExtensions['jest-circus'].dependencies],
['packageExtensions["jest-circus"].dependencies.slash', rawConfig.packageExtensions['jest-circus'].dependencies.slash],
['dlx-cache-max-age', configData.dlxCacheMaxAge],
['dlxCacheMaxAge', configData.dlxCacheMaxAge],
['trust-policy-exclude', configData.trustPolicyExclude],
['trustPolicyExclude', configData.trustPolicyExclude],
['trustPolicyExclude[0]', configData.trustPolicyExclude[0]],
['trustPolicyExclude[1]', configData.trustPolicyExclude[1]],
['packageExtensions', configData.packageExtensions],
['packageExtensions["@babel/parser"]', configData.packageExtensions['@babel/parser']],
['packageExtensions["@babel/parser"].peerDependencies', configData.packageExtensions['@babel/parser'].peerDependencies],
['packageExtensions["@babel/parser"].peerDependencies["@babel/types"]', configData.packageExtensions['@babel/parser'].peerDependencies['@babel/types']],
['packageExtensions["jest-circus"]', configData.packageExtensions['jest-circus']],
['packageExtensions["jest-circus"].dependencies', configData.packageExtensions['jest-circus'].dependencies],
['packageExtensions["jest-circus"].dependencies.slash', configData.packageExtensions['jest-circus'].dependencies.slash],
] as Array<[string, unknown]>)('«%s»', async (propertyPath, expected) => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
...baseOpts,
json: true,
rawConfig,
}, ['get', propertyPath])
expect(JSON.parse(getOutputString(getResult))).toStrictEqual(expected)
@@ -169,27 +160,21 @@ describe('config get with a property path', () => {
})
describe('object without --json', () => {
test.each([
// TODO: change `rawConfig` into camelCase and replace this object with just `rawConfig`.
['', {
dlxCacheMaxAge: rawConfig['dlx-cache-max-age'],
trustPolicyExclude: rawConfig['trust-policy-exclude'],
packageExtensions: rawConfig.packageExtensions,
}],
// Note: empty path returns all config including dir/global/configDir,
// so we use toMatchObject for the empty-path case.
test('«»', async () => {
const getResult = await config.handler(baseOpts, ['get', ''])
expect(JSON.parse(getOutputString(getResult))).toMatchObject(configData)
})
['packageExtensions', rawConfig.packageExtensions],
['packageExtensions["@babel/parser"]', rawConfig.packageExtensions['@babel/parser']],
['packageExtensions["@babel/parser"].peerDependencies', rawConfig.packageExtensions['@babel/parser'].peerDependencies],
['packageExtensions["jest-circus"]', rawConfig.packageExtensions['jest-circus']],
['packageExtensions["jest-circus"].dependencies', rawConfig.packageExtensions['jest-circus'].dependencies],
test.each([
['packageExtensions', configData.packageExtensions],
['packageExtensions["@babel/parser"]', configData.packageExtensions['@babel/parser']],
['packageExtensions["@babel/parser"].peerDependencies', configData.packageExtensions['@babel/parser'].peerDependencies],
['packageExtensions["jest-circus"]', configData.packageExtensions['jest-circus']],
['packageExtensions["jest-circus"].dependencies', configData.packageExtensions['jest-circus'].dependencies],
] as Array<[string, unknown]>)('«%s»', async (propertyPath, expected) => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig,
}, ['get', propertyPath])
const getResult = await config.handler(baseOpts, ['get', propertyPath])
expect(JSON.parse(getOutputString(getResult))).toStrictEqual(expected)
})
@@ -197,35 +182,28 @@ describe('config get with a property path', () => {
describe('string without --json', () => {
test.each([
['dlx-cache-max-age', rawConfig['dlx-cache-max-age']],
['dlxCacheMaxAge', rawConfig['dlx-cache-max-age']],
['trustPolicyExclude[0]', rawConfig['trust-policy-exclude'][0]],
['trustPolicyExclude[1]', rawConfig['trust-policy-exclude'][1]],
['package-extensions', 'undefined'], // it cannot be defined by rc, it can't be kebab-case
['packageExtensions["@babel/parser"].peerDependencies["@babel/types"]', rawConfig.packageExtensions['@babel/parser'].peerDependencies['@babel/types']],
['packageExtensions["jest-circus"].dependencies.slash', rawConfig.packageExtensions['jest-circus'].dependencies.slash],
['dlx-cache-max-age', configData.dlxCacheMaxAge],
['dlxCacheMaxAge', configData.dlxCacheMaxAge],
['trustPolicyExclude[0]', configData.trustPolicyExclude[0]],
['trustPolicyExclude[1]', configData.trustPolicyExclude[1]],
['packageExtensions["@babel/parser"].peerDependencies["@babel/types"]', configData.packageExtensions['@babel/parser'].peerDependencies['@babel/types']],
['packageExtensions["jest-circus"].dependencies.slash', configData.packageExtensions['jest-circus'].dependencies.slash],
] as Array<[string, string]>)('«%s»', async (propertyPath, expected) => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig,
}, ['get', propertyPath])
const getResult = await config.handler(baseOpts, ['get', propertyPath])
expect(getOutputString(getResult)).toStrictEqual(expected)
})
})
describe('non-rc kebab-case keys', () => {
test('«package-extensions»', async () => {
const getResult = await config.handler({
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig,
}, ['get', 'package-extensions'])
test('«package-extensions» resolves to packageExtensions on Config', async () => {
const getResult = await config.handler(baseOpts, ['get', 'package-extensions'])
expect(JSON.parse(getOutputString(getResult))).toStrictEqual(configData.packageExtensions)
})
test('unknown kebab-case key returns undefined', async () => {
const getResult = await config.handler(baseOpts, ['get', 'no-such-setting'])
expect(getOutputString(getResult)).toBe('undefined')
})
@@ -238,7 +216,7 @@ test('config get with scoped registry key (global: false)', async () => {
cliOptions: {},
configDir: process.cwd(),
global: false,
rawConfig: {
authConfig: {
'@scope:registry': 'https://custom-registry.example.com/',
},
}, ['get', '@scope:registry'])
@@ -252,7 +230,7 @@ test('config get with scoped registry key (global: true)', async () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {
authConfig: {
'@scope:registry': 'https://custom-registry.example.com/',
},
}, ['get', '@scope:registry'])
@@ -266,7 +244,7 @@ test('config get with scoped registry key that does not exist', async () => {
cliOptions: {},
configDir: process.cwd(),
global: false,
rawConfig: {},
authConfig: {},
}, ['get', '@scope:registry'])
expect(getOutputString(getResult)).toBe('undefined')
@@ -288,7 +266,7 @@ describe('does not traverse the prototype chain (#10296)', () => {
cliOptions: {},
configDir: process.cwd(),
global: true,
rawConfig: {},
authConfig: {},
}, ['get', key])
expect(getOutputString(getResult)).toBe('undefined')

View File

@@ -7,13 +7,12 @@ test('config list', async () => {
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
rawConfig: {
'store-dir': '~/store',
'fetch-retries': '2',
},
authConfig: {},
storeDir: '~/store',
fetchRetries: '2',
}, ['list'])
expect(JSON.parse(getOutputString(output))).toStrictEqual({
expect(JSON.parse(getOutputString(output))).toMatchObject({
fetchRetries: '2',
storeDir: '~/store',
})
@@ -25,22 +24,20 @@ test('config list --json', async () => {
cliOptions: {},
configDir: process.cwd(),
json: true,
rawConfig: {
'store-dir': '~/store',
'fetch-retries': '2',
},
authConfig: {},
storeDir: '~/store',
fetchRetries: '2',
}, ['list'])
expect(output).toEqual(JSON.stringify({
const parsed = JSON.parse(output as string)
expect(parsed).toMatchObject({
fetchRetries: '2',
storeDir: '~/store',
}, null, 2))
})
})
test('config list censors protected settings', async () => {
const rawConfig = {
'store-dir': '~/store',
'fetch-retries': '2',
const authConfig = {
username: 'general-username',
'@my-org:registry': 'https://my-org.example.com/registry',
'//my-org.example.com:username': 'my-username-in-my-org',
@@ -50,10 +47,12 @@ test('config list censors protected settings', async () => {
dir: process.cwd(),
cliOptions: {},
configDir: process.cwd(),
rawConfig,
storeDir: '~/store',
fetchRetries: '2',
authConfig,
}, ['list'])
expect(JSON.parse(getOutputString(output))).toStrictEqual({
expect(JSON.parse(getOutputString(output))).toMatchObject({
storeDir: '~/store',
fetchRetries: '2',
'@my-org:registry': 'https://my-org.example.com/registry',
@@ -63,9 +62,7 @@ test('config list censors protected settings', async () => {
})
test('config list --json censors protected settings', async () => {
const rawConfig = {
'store-dir': '~/store',
'fetch-retries': '2',
const authConfig = {
username: 'general-username',
'@my-org:registry': 'https://my-org.example.com/registry',
'//my-org.example.com:username': 'my-username-in-my-org',
@@ -76,14 +73,16 @@ test('config list --json censors protected settings', async () => {
json: true,
cliOptions: {},
configDir: process.cwd(),
rawConfig,
storeDir: '~/store',
fetchRetries: '2',
authConfig,
}, ['list'])
expect(JSON.parse(getOutputString(output))).toStrictEqual({
storeDir: rawConfig['store-dir'],
fetchRetries: rawConfig['fetch-retries'],
expect(JSON.parse(getOutputString(output))).toMatchObject({
storeDir: '~/store',
fetchRetries: '2',
username: '(protected)',
'@my-org:registry': rawConfig['@my-org:registry'],
'@my-org:registry': 'https://my-org.example.com/registry',
'//my-org.example.com:username': '(protected)',
})
})

View File

@@ -29,7 +29,7 @@ test('config set registry setting using the global option', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', 'registry', 'https://npm-registry.example.com/'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -61,7 +61,7 @@ test('config set npm-compatible setting using the global option', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', 'cafile', 'some-cafile'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -93,7 +93,7 @@ test('config set pnpm-specific key using the global option', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-retries', '1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -125,7 +125,7 @@ test('config set using the location=global option', async () => {
cliOptions: {},
configDir,
location: 'global',
rawConfig: {},
authConfig: {},
}, ['set', 'fetchRetries', '1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -157,7 +157,7 @@ test('config set pnpm-specific setting using the location=project option', async
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'virtual-store-dir', '.pnpm'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -179,7 +179,7 @@ test('config delete with location=project, when delete the last setting from pnp
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'virtual-store-dir', '.pnpm'])
expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({
@@ -191,7 +191,7 @@ test('config delete with location=project, when delete the last setting from pnp
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['delete', 'virtual-store-dir'])
expect(fs.existsSync(path.join(tmp, 'pnpm-workspace.yaml'))).toBeFalsy()
@@ -218,7 +218,7 @@ test('config set registry setting using the location=project option', async () =
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'registry', 'https://npm-registry.example.com/'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -251,7 +251,7 @@ test('config set npm-compatible setting using the location=project option', asyn
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'cafile', 'some-cafile'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -273,7 +273,7 @@ test('config set saves the setting in the right format to pnpm-workspace.yaml',
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-timeout', '1000'])
expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({
@@ -306,7 +306,7 @@ test('config set registry setting in project .npmrc file', async () => {
configDir,
global: false,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'registry', 'https://npm-registry.example.com/'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -343,7 +343,7 @@ test('config set npm-compatible setting in project .npmrc file', async () => {
configDir,
global: false,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'cafile', 'some-cafile'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -380,7 +380,7 @@ test('config set pnpm-specific setting in project pnpm-workspace.yaml file', asy
configDir,
global: false,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-retries', '1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -416,7 +416,7 @@ test('config set key=value', async () => {
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-retries=1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -452,7 +452,7 @@ test('config set key=value, when value contains a "="', async () => {
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'lockfile-dir=foo=bar'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -475,7 +475,7 @@ test('config set or delete throws missing params error', async () => {
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config set` requires the config key'))
await expect(config.handler({
@@ -483,7 +483,7 @@ test('config set or delete throws missing params error', async () => {
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['delete'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config delete` requires the config key'))
})
@@ -505,7 +505,7 @@ test('config set with dot leading key', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', '.fetchRetries', '1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -535,7 +535,7 @@ test('config set with subscripted key', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', '["fetch-retries"]', '1'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -558,7 +558,7 @@ test('config set rejects complex property path', async () => {
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', '.catalog.react', '19'])).rejects.toMatchObject({
code: 'ERR_PNPM_CONFIG_SET_DEEP_KEY',
})
@@ -575,7 +575,7 @@ test('config set with location=project and json=true', async () => {
configDir,
location: 'project',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'catalog', '{ "react": "19" }'])
expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toStrictEqual({
@@ -590,7 +590,7 @@ test('config set with location=project and json=true', async () => {
configDir,
location: 'project',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'packageExtensions', JSON.stringify({
'@babel/parser': {
peerDependencies: {
@@ -642,7 +642,7 @@ test('config set refuses writing workspace-specific settings to the global confi
configDir,
location: 'global',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'catalog', '{ "react": "19" }'])).rejects.toMatchObject({
code: 'ERR_PNPM_CONFIG_SET_UNSUPPORTED_YAML_CONFIG_KEY',
key: 'catalog',
@@ -654,7 +654,7 @@ test('config set refuses writing workspace-specific settings to the global confi
configDir,
location: 'global',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'packageExtensions', JSON.stringify({
'@babel/parser': {
peerDependencies: {
@@ -677,7 +677,7 @@ test('config set refuses writing workspace-specific settings to the global confi
configDir,
location: 'global',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'package-extensions', JSON.stringify({
'@babel/parser': {
peerDependencies: {
@@ -715,7 +715,7 @@ test('config set writes workspace-specific settings to pnpm-workspace.yaml', asy
configDir,
location: 'project',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'catalog', JSON.stringify(catalog)])
expect(readConfigFiles(configDir, tmp)).toEqual({
...initConfig,
@@ -743,7 +743,7 @@ test('config set writes workspace-specific settings to pnpm-workspace.yaml', asy
configDir,
location: 'project',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'packageExtensions', JSON.stringify(packageExtensions)])
expect(readConfigFiles(configDir, tmp)).toEqual({
...initConfig,
@@ -766,7 +766,7 @@ test('config set refuses kebab-case workspace-specific settings', async () => {
configDir,
location: 'project',
json: true,
rawConfig: {},
authConfig: {},
}, ['set', 'package-extensions', JSON.stringify({
'@babel/parser': {
peerDependencies: {
@@ -794,7 +794,7 @@ test('config set registry-specific setting with --location=project should create
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', '//registry.example.com/:_auth', 'test-auth-value'])
expect(readIniFileSync(path.join(tmp, '.npmrc'))).toEqual({
@@ -813,7 +813,7 @@ test('config set scoped registry with --location=project should create .npmrc',
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', '@myorg:registry', 'https://test-registry.example.com/'])
expect(readIniFileSync(path.join(tmp, '.npmrc'))).toEqual({
@@ -836,7 +836,7 @@ test('config set when both pnpm-workspace.yaml and .npmrc exist, pnpm-workspace.
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-timeout', '2000'])
expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({
@@ -861,7 +861,7 @@ test('config set when only pnpm-workspace.yaml exists, writes to it', async () =
cliOptions: {},
configDir,
location: 'project',
rawConfig: {},
authConfig: {},
}, ['set', 'fetch-timeout', '3000'])
expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({

View File

@@ -32,7 +32,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', `${key}=123`])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -56,7 +56,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', key])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -84,7 +84,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', key, '"123"'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -109,7 +109,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', key])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -141,7 +141,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', `${key}=https://registry.example.com/`])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -165,7 +165,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', key])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -195,7 +195,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', key, '{}'])).rejects.toMatchObject({
code: 'ERR_PNPM_CONFIG_SET_AUTH_NON_STRING',
})
@@ -224,7 +224,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['set', propertyPath, '123'])
expect(readConfigFiles(configDir, tmp)).toEqual({
@@ -248,7 +248,7 @@ describe.each(
cliOptions: {},
configDir,
global: true,
rawConfig: {},
authConfig: {},
}, ['delete', propertyPath])
expect(readConfigFiles(configDir, tmp)).toEqual({

View File

@@ -14,7 +14,7 @@ import type {
import type { OptionsFromRootManifest } from './getOptionsFromRootManifest.js'
import type { AuthInfo } from './parseAuthInfo.js'
export type UniversalOptions = Pick<Config, 'color' | 'dir' | 'rawConfig' | 'rawLocalConfig'>
export type UniversalOptions = Pick<Config, 'color' | 'dir' | 'authConfig' | 'rawLocalConfig'>
export type VerifyDepsBeforeRun = 'install' | 'warn' | 'error' | 'prompt' | false
@@ -38,7 +38,9 @@ export interface Config extends AuthInfo, OptionsFromRootManifest {
filter: string[]
filterProd: string[]
rawLocalConfig: Record<string, any>, // eslint-disable-line
rawConfig: Record<string, any>, // eslint-disable-line
authConfig: Record<string, any>, // eslint-disable-line
/** Keys explicitly set from workspace yaml, CLI, or env vars (not defaults). */
explicitlySetKeys: Set<string>
dryRun?: boolean // This option might be not supported ever
global?: boolean
dir: string
@@ -75,6 +77,7 @@ export interface Config extends AuthInfo, OptionsFromRootManifest {
depth?: number
engineStrict?: boolean
nodeVersion?: string
nodeDownloadMirrors?: Record<string, string>
offline?: boolean
registry?: string
optional?: boolean

View File

@@ -6,7 +6,7 @@ test('inheritAuthConfig copies only auth keys from source to target', () => {
bin: 'foo',
cacheDir: '/path/to/cache/dir',
registry: 'https://npmjs.com/registry/',
rawConfig: {
authConfig: {
'cache-dir': '/path/to/cache/dir',
registry: 'https://npmjs.com/registry/',
},
@@ -21,7 +21,7 @@ test('inheritAuthConfig copies only auth keys from source to target', () => {
cacheDir: '/path/to/another/cache/dir',
storeDir: '/path/to/custom/store/dir',
registry: 'https://example.com/local-registry/',
rawConfig: {
authConfig: {
registry: 'https://example.com/global-registry/',
'//example.com/global-registry/:_auth': 'MY_SECRET_GLOBAL_AUTH',
},
@@ -38,7 +38,7 @@ test('inheritAuthConfig copies only auth keys from source to target', () => {
bin: 'foo',
cacheDir: '/path/to/cache/dir',
registry: 'https://example.com/local-registry/',
rawConfig: {
authConfig: {
'cache-dir': '/path/to/cache/dir',
registry: 'https://example.com/global-registry/',
'//example.com/global-registry/:_auth': 'MY_SECRET_GLOBAL_AUTH',

View File

@@ -22,7 +22,7 @@ import { omit } from 'ramda'
import { realpathMissing } from 'realpath-missing'
import semver from 'semver'
import { inheritAuthConfig, isIniConfigKey, pickIniConfig } from './auth.js'
import { inheritAuthConfig, pickIniConfig } from './auth.js'
import { checkGlobalBinDir } from './checkGlobalBinDir.js'
import { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency.js'
import type {
@@ -85,9 +85,7 @@ export async function getConfig (opts: {
name: string
version: string
}
rcOptionsTypes?: Record<string, unknown>
workspaceDir?: string | undefined
checkUnknownSetting?: boolean
env?: Record<string, string | undefined>
ignoreNonAuthSettingsFromLocal?: boolean
ignoreLocalSettings?: boolean
@@ -124,7 +122,6 @@ export async function getConfig (opts: {
if (cliOptions.dir) {
cliOptions.dir = await realpathMissing(cliOptions.dir)
}
const rcOptionsTypes = { ...types, ...opts.rcOptionsTypes }
const defaultOptions: Partial<KebabCaseConfig> = {
'auto-install-peers': true,
bail: true,
@@ -235,25 +232,29 @@ export async function getConfig (opts: {
})
const warnings = npmrcResult.warnings
const rcOptions = Object.keys(rcOptionsTypes)
const configFromCliOpts = Object.fromEntries(Object.entries(cliOptions)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([name, value]) => [camelcase(name, { locale: 'en-US' }), value])
)
// Build initial config from defaults, then overlay auth/registry values from .npmrc
const pnpmConfig = Object.fromEntries(
rcOptions
.map((configKey) => [
camelcase(configKey, { locale: 'en-US' }),
isIniConfigKey(configKey)
? (npmrcResult.mergedConfig[configKey] ?? (defaultOptions as Record<string, unknown>)[configKey])
: (defaultOptions as Record<string, unknown>)[configKey],
])
Object.entries(defaultOptions)
.map(([key, value]) => [camelcase(key, { locale: 'en-US' }), value])
) as unknown as ConfigWithDeprecatedSettings
for (const [key, value] of Object.entries(npmrcResult.mergedConfig)) {
if (Object.hasOwn(types, key)) {
;(pnpmConfig as unknown as Record<string, unknown>)[camelcase(key, { locale: 'en-US' })] = value
}
}
const globalDepsBuildConfig = extractAndRemoveDependencyBuildOptions(pnpmConfig)
// Track which keys are explicitly set (not defaults)
const explicitlySetKeys = new Set<string>(Object.keys(configFromCliOpts))
pnpmConfig.explicitlySetKeys = explicitlySetKeys
Object.assign(pnpmConfig, configFromCliOpts)
// Resolving the current working directory to its actual location is crucial.
// This prevents potential inconsistencies in the future, especially when processing or mapping subdirectories.
@@ -280,14 +281,9 @@ export async function getConfig (opts: {
npmrcResult.workspaceNpmrc,
cliOptions
)
pnpmConfig.userAgent = pnpmConfig.rawLocalConfig['user-agent']
? pnpmConfig.rawLocalConfig['user-agent']
: `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`
pnpmConfig.rawConfig = Object.assign(
{},
pickIniConfig(npmrcResult.rawConfig),
{ 'user-agent': pnpmConfig.userAgent }
)
pnpmConfig.userAgent = (cliOptions['user-agent'] as string | undefined)
?? `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`
pnpmConfig.authConfig = pickIniConfig(npmrcResult.rawConfig)
// Reuse the global config.yaml already read for npmrcAuthFile
const globalYamlConfig = globalYamlConfigForNpmrcAuthFile
@@ -304,15 +300,15 @@ export async function getConfig (opts: {
workspaceManifest: globalYamlConfig,
})
}
const networkConfigs = getNetworkConfigs(pnpmConfig.rawConfig)
const networkConfigs = getNetworkConfigs(pnpmConfig.authConfig)
const registriesFromNpmrc = {
default: normalizeRegistryUrl(pnpmConfig.rawConfig.registry),
default: normalizeRegistryUrl(pnpmConfig.authConfig.registry),
...networkConfigs.registries,
}
pnpmConfig.registries = { ...registriesFromNpmrc }
pnpmConfig.authInfos = networkConfigs.authInfos ?? {} // TODO: remove `?? {}` (when possible)
pnpmConfig.sslConfigs = networkConfigs.sslConfigs
Object.assign(pnpmConfig, getDefaultAuthInfo(pnpmConfig.rawConfig))
Object.assign(pnpmConfig, getDefaultAuthInfo(pnpmConfig.authConfig))
pnpmConfig.pnpmHomeDir = getDataDir({ env, platform: process.platform })
let globalDirRoot
if (pnpmConfig.globalDir) {
@@ -446,7 +442,6 @@ export async function getConfig (opts: {
'init-version', // the type is a private function named 'semver'
'node-version', // the type is a private function named 'semver'
'umask', // the type is a private function named 'Umask'
'logstream', // the custom parser doesn't have logic to handle 'Stream' yet
], types)
for (const { key, value } of parseEnvVars(key => envPnpmTypes[key as keyof typeof envPnpmTypes], env)) {
@@ -458,6 +453,7 @@ export async function getConfig (opts: {
// @ts-expect-error
pnpmConfig[key] = value
explicitlySetKeys.add(key)
if (key === 'registry') {
if (typeof value !== 'string') {
@@ -577,21 +573,6 @@ export async function getConfig (opts: {
pnpmConfig.sideEffectsCacheRead = pnpmConfig.sideEffectsCache ?? pnpmConfig.sideEffectsCacheReadonly
pnpmConfig.sideEffectsCacheWrite = pnpmConfig.sideEffectsCache
// TODO: consider removing checkUnknownSetting entirely
if (opts.checkUnknownSetting) {
const settingKeys = Object.keys(npmrcResult.workspaceNpmrc)
.filter(key => key.trim() !== '')
const unknownKeys = []
for (const key of settingKeys) {
if (!rcOptions.includes(key) && !key.startsWith('//') && !(key[0] === '@' && key.endsWith(':registry'))) {
unknownKeys.push(key)
}
}
if (unknownKeys.length > 0) {
warnings.push(`Your .npmrc file contains unknown setting: ${unknownKeys.join(', ')}`)
}
}
if (pnpmConfig.sharedWorkspaceLockfile && !pnpmConfig.lockfileDir && pnpmConfig.workspaceDir) {
pnpmConfig.lockfileDir = pnpmConfig.workspaceDir
}
@@ -755,13 +736,7 @@ function addSettingsFromWorkspaceManifestToConfig (pnpmConfig: Config, {
// @ts-expect-error
pnpmConfig[key] = value
const kebabKey = kebabCase(key)
// Q: Why `types` instead of `rcOptionTypes`?
// A: `rcOptionTypes` includes options that would matter to the `npm` cli which wouldn't care about `pnpm-workspace.yaml`.
const isRc = kebabKey in types
const targetKey = isRc ? kebabKey : key
pnpmConfig.rawConfig[targetKey] = value
pnpmConfig.explicitlySetKeys.add(key)
}
// All the pnpm_config_ env variables should override the settings from pnpm-workspace.yaml,
// as it happens with .npmrc.
@@ -770,7 +745,7 @@ function addSettingsFromWorkspaceManifestToConfig (pnpmConfig: Config, {
// Related issue: https://github.com/pnpm/pnpm/issues/10060
if (process.env.pnpm_config_verify_deps_before_run != null) {
pnpmConfig.verifyDepsBeforeRun = process.env.pnpm_config_verify_deps_before_run as VerifyDepsBeforeRun
pnpmConfig.rawConfig['verify-deps-before-run'] = pnpmConfig.verifyDepsBeforeRun
}
pnpmConfig.catalogs = getCatalogsFromWorkspaceManifest(workspaceManifest)
}

View File

@@ -1,6 +1,6 @@
import type { Config } from './Config.js'
export type InheritableConfig = Partial<Config> & Pick<Config, 'rawConfig' | 'rawLocalConfig'>
export type InheritableConfig = Partial<Config> & Pick<Config, 'authConfig' | 'rawLocalConfig'>
export type PickConfig = (cfg: Partial<Config>) => Partial<Config>
export type PickRawConfig = (cfg: Record<string, unknown>) => Record<string, unknown>
@@ -12,6 +12,6 @@ export function inheritPickedConfig (
pickRawLocalConfig: PickRawConfig = pickRawConfig
): void {
Object.assign(targetCfg, pickConfig(srcCfg))
Object.assign(targetCfg.rawConfig, pickRawConfig(srcCfg.rawConfig))
Object.assign(targetCfg.authConfig, pickRawConfig(srcCfg.authConfig))
Object.assign(targetCfg.rawLocalConfig, pickRawLocalConfig(srcCfg.rawLocalConfig))
}

View File

@@ -13,9 +13,9 @@ export interface NpmrcConfigResult {
* Priority (lowest to highest): builtin < defaults < user < auth.ini < workspace < CLI
*/
mergedConfig: Record<string, unknown>
/** Raw config suitable for pnpmConfig.rawConfig (filtered through pickIniConfig by consumer) */
/** Raw config suitable for pnpmConfig.authConfig (filtered through pickIniConfig by consumer) */
rawConfig: Record<string, unknown>
/** Workspace .npmrc data (for rawLocalConfig and checkUnknownSetting) */
/** Workspace .npmrc data (for rawLocalConfig) */
workspaceNpmrc: Record<string, unknown>
/** User ~/.npmrc data (for token helpers) */
userConfig: Record<string, unknown>

View File

@@ -1,55 +1,32 @@
import path from 'node:path'
import { Stream } from 'node:stream'
import url from 'node:url'
// Inlined from @pnpm/npm-conf/lib/types.js
// These are the npm config type definitions used for config key validation.
// Subset of npm config type definitions that pnpm actually uses.
// Originally inlined from @pnpm/npm-conf/lib/types.js, trimmed to only
// keys referenced by pnpm commands or passed through to npm-compatible tooling.
export const npmConfigTypes = {
access: [null, 'restricted', 'public'],
'allow-same-version': Boolean,
'always-auth': Boolean,
also: [null, 'dev', 'development'],
audit: Boolean,
'auth-type': ['legacy', 'sso', 'saml', 'oauth'],
'bin-links': Boolean,
browser: [null, String],
ca: [null, String, Array],
cafile: path,
cache: path,
'cache-lock-stale': Number,
'cache-lock-retries': Number,
'cache-lock-wait': Number,
'cache-max': Number,
'cache-min': Number,
cert: [null, String],
cidr: [null, String, Array],
color: ['always', Boolean],
'commit-hooks': Boolean,
depth: Number,
description: Boolean,
dev: Boolean,
'dry-run': Boolean,
editor: String,
'engine-strict': Boolean,
force: Boolean,
'fetch-retries': Number,
'fetch-retry-factor': Number,
'fetch-retry-mintimeout': Number,
'fetch-retry-maxtimeout': Number,
force: Boolean,
git: String,
'git-tag-version': Boolean,
'commit-hooks': Boolean,
global: Boolean,
globalconfig: path,
'global-style': Boolean,
group: [Number, String],
'https-proxy': [null, url],
'user-agent': String,
'ham-it-up': Boolean,
heading: String,
'if-present': Boolean,
'ignore-prepublish': Boolean,
'ignore-scripts': Boolean,
'init-module': path,
'init-author-name': String,
'init-author-email': String,
'init-author-url': ['', url],
@@ -57,40 +34,27 @@ export const npmConfigTypes = {
'init-version': String,
json: Boolean,
key: [null, String],
'legacy-bundling': Boolean,
link: Boolean,
'local-address': String,
loglevel: ['silent', 'error', 'warn', 'notice', 'http', 'timing', 'info', 'verbose', 'silly'],
logstream: Stream,
'logs-max': Number,
long: Boolean,
maxsockets: Number,
message: String,
'metrics-registry': [null, String],
'node-options': [null, String],
'node-version': [null, String],
'no-proxy': [null, String, Array],
offline: Boolean,
'onload-script': [null, String],
only: [null, 'dev', 'development', 'prod', 'production'],
optional: Boolean,
'package-lock': Boolean,
otp: [null, String],
'package-lock-only': Boolean,
'package-lock': Boolean,
parseable: Boolean,
'prefer-offline': Boolean,
'prefer-online': Boolean,
prefix: path,
production: Boolean,
progress: Boolean,
proxy: [null, false, url],
provenance: Boolean,
'read-only': Boolean,
'rebuild-bundle': Boolean,
proxy: [null, false, url],
registry: [null, url],
rollback: Boolean,
save: Boolean,
'save-bundle': Boolean,
'save-dev': Boolean,
'save-exact': Boolean,
'save-optional': Boolean,
@@ -99,29 +63,13 @@ export const npmConfigTypes = {
scope: String,
'script-shell': [null, String],
'scripts-prepend-node-path': [false, true, 'auto', 'warn-only'],
searchopts: String,
searchexclude: [null, String],
searchlimit: Number,
searchstaleness: Number,
'send-metrics': Boolean,
shell: String,
shrinkwrap: Boolean,
'sign-git-tag': Boolean,
'sso-poll-frequency': Number,
'sso-type': [null, 'oauth', 'saml'],
'strict-ssl': Boolean,
tag: String,
timing: Boolean,
tmp: path,
unicode: Boolean,
'tag-version-prefix': String,
'unsafe-perm': Boolean,
usage: Boolean,
user: [Number, String],
'user-agent': String,
userconfig: path,
umask: Number,
version: Boolean,
'tag-version-prefix': String,
versions: Boolean,
viewer: String,
_exit: Boolean,
}

View File

@@ -238,7 +238,7 @@ test('.npmrc does not load pnpm settings', async () => {
})
// rc options appear as usual
expect(config.rawConfig).toMatchObject({
expect(config.authConfig).toMatchObject({
'//my-org.registry.example.com:username': 'some-employee',
'//my-org.registry.example.com:_authToken': 'some-employee-token',
'@my-org:registry': 'https://my-org.registry.example.com',
@@ -248,16 +248,16 @@ test('.npmrc does not load pnpm settings', async () => {
})
// workspace-specific settings are omitted
expect(config.rawConfig['dlx-cache-max-age']).toBeUndefined()
expect(config.rawConfig['dlxCacheMaxAge']).toBeUndefined()
expect(config.authConfig['dlx-cache-max-age']).toBeUndefined()
expect(config.authConfig['dlxCacheMaxAge']).toBeUndefined()
expect(config.dlxCacheMaxAge).toBe(24 * 60) // TODO: refactor to make defaultOptions importable
expect(config.rawConfig['trust-policy-exclude']).toBeUndefined()
expect(config.rawConfig['trustPolicyExclude']).toBeUndefined()
expect(config.authConfig['trust-policy-exclude']).toBeUndefined()
expect(config.authConfig['trustPolicyExclude']).toBeUndefined()
expect(config.trustPolicyExclude).toBeUndefined()
expect(config.rawConfig.packages).toBeUndefined()
expect(config.authConfig.packages).toBeUndefined()
})
test('rc options appear as kebab-case in rawConfig even if it was defined as camelCase by pnpm-workspace.yaml', async () => {
test('camelCase settings from pnpm-workspace.yaml are read into typed Config properties', async () => {
prepareEmpty()
writeYamlFileSync('pnpm-workspace.yaml', {
@@ -283,21 +283,10 @@ test('rc options appear as kebab-case in rawConfig even if it was defined as cam
linkWorkspacePackages: true,
nodeLinker: 'hoisted',
sharedWorkspaceLockfile: true,
rawConfig: {
'ignore-scripts': true,
'link-workspace-packages': true,
'node-linker': 'hoisted',
'shared-workspace-lockfile': true,
},
})
expect(config.rawConfig.ignoreScripts).toBeUndefined()
expect(config.rawConfig.linkWorkspacePackages).toBeUndefined()
expect(config.rawConfig.nodeLinker).toBeUndefined()
expect(config.rawConfig.sharedWorkspaceLockfile).toBeUndefined()
})
test('workspace-specific settings preserve case in rawConfig', async () => {
test('workspace-specific settings are read into typed Config properties', async () => {
prepareEmpty()
writeYamlFileSync('pnpm-workspace.yaml', {
@@ -327,20 +316,7 @@ test('workspace-specific settings preserve case in rawConfig', async () => {
workspaceDir: process.cwd(),
})
expect(config.rawConfig.packages).toStrictEqual(['foo', 'bar'])
expect(config.rawConfig.packageExtensions).toStrictEqual({
'@babel/parser': {
peerDependencies: {
'@babel/types': '*',
},
},
'jest-circus': {
dependencies: {
slash: '3',
},
},
})
expect(config.rawConfig['package-extensions']).toBeUndefined()
expect(config.workspacePackagePatterns).toStrictEqual(['foo', 'bar'])
expect(config.packageExtensions).toStrictEqual({
'@babel/parser': {
peerDependencies: {
@@ -475,7 +451,7 @@ test('auth tokens from pnpm auth file override ~/.npmrc', async () => {
},
})
expect(config.rawConfig['//registry.npmjs.org/:_authToken']).toBe('fresh-token')
expect(config.authConfig['//registry.npmjs.org/:_authToken']).toBe('fresh-token')
} finally {
if (originalXdg != null) {
process.env.XDG_CONFIG_HOME = originalXdg
@@ -514,7 +490,7 @@ test('workspace .npmrc overrides pnpm auth file', async () => {
},
})
expect(config.rawConfig['//registry.npmjs.org/:_authToken']).toBe('workspace-token')
expect(config.authConfig['//registry.npmjs.org/:_authToken']).toBe('workspace-token')
} finally {
if (originalXdg != null) {
process.env.XDG_CONFIG_HOME = originalXdg
@@ -812,7 +788,7 @@ test.skip('read only supported settings from config', async () => {
expect(config.storeDir).toBe('__store__')
// @ts-expect-error
expect(config['foo']).toBeUndefined() // NOTE: This line current fails as there are yet a way to verify fields in pnpm-workspace.yaml
expect(config.rawConfig['foo']).toBe('bar')
expect(config.authConfig['foo']).toBe('bar')
})
test('all CLI options are added to the config', async () => {
@@ -984,7 +960,7 @@ test('dir is resolved to real path', async () => {
expect(config.dir).toBe(realDir)
})
test('warn user unknown settings in npmrc', async () => {
test('non-auth settings in npmrc do not produce warnings', async () => {
prepare()
const npmrc = [
@@ -1004,20 +980,9 @@ test('warn user unknown settings in npmrc', async () => {
name: 'pnpm',
version: '1.0.0',
},
checkUnknownSetting: true,
})
expect(warnings).toStrictEqual([])
const { warnings: noWarnings } = await getConfig({
cliOptions: {},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(noWarnings).toStrictEqual([])
})
test('getConfig() converts noproxy to noProxy', async () => {
@@ -1332,7 +1297,6 @@ test('settings from pnpm-workspace.yaml are read', async () => {
})
expect(config.trustPolicyExclude).toStrictEqual(['foo', 'bar'])
expect(config.rawConfig['trust-policy-exclude']).toStrictEqual(['foo', 'bar'])
})
test('settings sharedWorkspaceLockfile in pnpm-workspace.yaml should take effect', async () => {
@@ -1365,7 +1329,6 @@ test('settings shamefullyHoist in pnpm-workspace.yaml should take effect', async
})
expect(config.shamefullyHoist).toBe(true)
expect(config.rawConfig['shamefully-hoist']).toBe(true)
})
test('settings gitBranchLockfile in pnpm-workspace.yaml should take effect', async () => {
@@ -1382,7 +1345,6 @@ test('settings gitBranchLockfile in pnpm-workspace.yaml should take effect', asy
expect(config.gitBranchLockfile).toBe(true)
expect(config.useGitBranchLockfile).toBe(true)
expect(config.rawConfig['git-branch-lockfile']).toBe(true)
})
test('loads setting from environment variable pnpm_config_*', async () => {
@@ -1557,9 +1519,7 @@ describe('global config.yaml', () => {
expect(config.dangerouslyAllowAllBuilds).toBe(true)
// NOTE: the field may appear kebab-case here, but only internally,
// `pnpm config list` would convert them to camelCase.
// TODO: switch to camelCase entirely later.
expect(config.rawConfig).toHaveProperty(['dangerously-allow-all-builds'])
expect(config.dangerouslyAllowAllBuilds).toBeDefined()
})
})

View File

@@ -187,6 +187,7 @@ export interface PnpmSettings {
auditConfig?: AuditConfig
requiredScripts?: string[]
supportedArchitectures?: SupportedArchitectures
nodeDownloadMirrors?: Record<string, string>
}
export interface ProjectManifest extends BaseManifest {

View File

@@ -165,7 +165,7 @@ export type AuditOptions = Pick<UniversalOptions, 'dir'> & {
| 'overrides'
| 'optional'
| 'userConfig'
| 'rawConfig'
| 'authConfig'
| 'rootProjectManifest'
| 'rootProjectManifestDir'
| 'virtualStoreDirMaxLength'
@@ -187,7 +187,7 @@ export async function handler (opts: AuditOptions): Promise<{ exitCode: number,
optionalDependencies: opts.optional !== false,
}
let auditReport!: AuditReport
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.rawConfig, userSettings: opts.userConfig })
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig, userSettings: opts.userConfig })
try {
auditReport = await audit(lockfile, getAuthHeader, {
dispatcherOptions: {

View File

@@ -201,7 +201,7 @@ describe('plugin-commands-audit', () => {
...AUDIT_REGISTRY_OPTS,
dir: hasVulnerabilitiesDir,
rootProjectManifestDir: hasVulnerabilitiesDir,
rawConfig: {
authConfig: {
registry: AUDIT_REGISTRY,
[`${AUDIT_REGISTRY.replace(/^https?:/, '')}:_authToken`]: '123',
},

View File

@@ -3,7 +3,7 @@ import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
const registries = {
default: 'https://registry.npmjs.org/',
}
const rawConfig = {
const authConfig = {
registry: registries.default,
}
export const DEFAULT_OPTS = {
@@ -42,7 +42,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig,
authConfig,
rawLocalConfig: {},
registries,
rootProjectManifestDir: '',
@@ -66,7 +66,7 @@ export const AUDIT_REGISTRY_OPTS = {
registries: {
default: AUDIT_REGISTRY,
},
rawConfig: {
authConfig: {
registry: AUDIT_REGISTRY,
},
}
@@ -78,7 +78,7 @@ export const MOCK_REGISTRY_OPTS = {
registries: {
default: MOCK_REGISTRY,
},
rawConfig: {
authConfig: {
registry: MOCK_REGISTRY,
},
}

View File

@@ -36,7 +36,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
rootProjectManifestDir: '',

View File

@@ -36,7 +36,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
rootProjectManifestDir: '',

View File

@@ -165,7 +165,7 @@ export type OutdatedCommandOptions = {
| 'offline'
| 'optional'
| 'production'
| 'rawConfig'
| 'authConfig'
| 'registries'
| 'selectedProjectsGraph'
| 'strictSsl'

View File

@@ -87,7 +87,7 @@ export async function handler (
}
const registry = pickRegistryForPackage(opts.registries, packageName)
const fetchFromRegistry = createFetchFromRegistry(opts)
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.rawConfig ?? {}, userSettings: opts.userConfig ?? {} })
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig ?? {}, userSettings: opts.userConfig ?? {} })
const fetchResult = await fetchMetadataFromFromRegistry(
{
fetch: fetchFromRegistry,

View File

@@ -38,7 +38,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
proxy: undefined,
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
registry: REGISTRY,

View File

@@ -34,7 +34,7 @@ const OUTDATED_OPTIONS = {
global: false,
networkConcurrency: 16,
offline: false,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
registries: { default: REGISTRY_URL },
strictSsl: false,
tag: 'latest',
@@ -83,7 +83,7 @@ test('pnpm outdated: show details (using the public registry to verify that full
...OUTDATED_OPTIONS,
dir: process.cwd(),
long: true,
rawConfig: { registry: 'https://registry.npmjs.org/' },
authConfig: { registry: 'https://registry.npmjs.org/' },
registries: { default: 'https://registry.npmjs.org/' },
})

View File

@@ -41,7 +41,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
registry: REGISTRY,

View File

@@ -9,14 +9,14 @@ import type { DependencyManifest, PackageVersionPolicy } from '@pnpm/types'
interface GetManifestOpts {
dir: string
lockfileDir: string
rawConfig: object
authConfig: object
minimumReleaseAge?: number
minimumReleaseAgeExclude?: string[]
}
export type ManifestGetterOptions = Omit<ClientOptions, 'authConfig' | 'minimumReleaseAgeExclude' | 'storeIndex'>
& GetManifestOpts
& { fullMetadata: boolean, rawConfig: Record<string, string> }
& { fullMetadata: boolean, authConfig: Record<string, string> }
export function createManifestGetter (
opts: ManifestGetterOptions
@@ -27,7 +27,7 @@ export function createManifestGetter (
const { resolve } = createResolver({
...opts,
authConfig: opts.rawConfig,
authConfig: opts.authConfig,
filterMetadata: false, // We need all the data from metadata for "outdated --long" to work.
strictPublishedByCheck: Boolean(opts.minimumReleaseAge),
})

View File

@@ -8,7 +8,7 @@ test('getManifest()', async () => {
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
}
const resolve: ResolveFunction = async function (_wantedPackage, _opts) {
@@ -52,7 +52,7 @@ test('getManifest() with minimumReleaseAge filters latest when too new', async (
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
minimumReleaseAge: 10080,
}
@@ -78,7 +78,7 @@ test('getManifest() does not convert non-latest specifiers', async () => {
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
}
const resolve = jest.fn<ResolveFunction>(async (wantedPackage) => {
@@ -105,7 +105,7 @@ test('getManifest() returns null for NO_MATCHING_VERSION when publishedBy is set
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
}
const publishedBy = new Date(Date.now() - 10080 * 60 * 1000)
@@ -129,7 +129,7 @@ test('getManifest() throws NO_MATCHING_VERSION when publishedBy is not set', asy
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
}
const resolve: ResolveFunction = jest.fn(async function () {
@@ -145,7 +145,7 @@ test('getManifest() with minimumReleaseAgeExclude', async () => {
const opts = {
dir: '',
lockfileDir: '',
rawConfig: {},
authConfig: {},
}
const publishedBy = new Date(Date.now() - 10080 * 60 * 1000)

View File

@@ -272,7 +272,7 @@ async function installFromLockfile (
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
sideEffectsCacheRead: false,
sideEffectsCacheWrite: false,
rawConfig: {},
authConfig: {},
unsafePerm: false,
userAgent: '',
packageManager: opts.packageManager ?? { name: 'pnpm', version: '' },

View File

@@ -63,7 +63,7 @@ export async function handler (
if (isExecutedByCorepack()) {
throw new PnpmError('CANT_SELF_UPDATE_IN_COREPACK', 'You should update pnpm with corepack')
}
const { resolve } = createResolver({ ...opts, authConfig: opts.rawConfig })
const { resolve } = createResolver({ ...opts, authConfig: opts.authConfig })
const pkgName = 'pnpm'
const bareSpecifier = params[0] ?? 'latest'
const resolution = await resolve({ alias: pkgName, bareSpecifier }, {

View File

@@ -60,7 +60,7 @@ function prepareOptions (dir: string) {
workspaceConcurrency: 1,
extraEnv: {},
pnpmfile: '',
rawConfig: {},
authConfig: {},
cacheDir: path.join(dir, '.cache'),
virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120,
dir,

View File

@@ -22,7 +22,6 @@ export interface BunRuntimeResolveResult extends ResolveResult {
export async function resolveBunRuntime (
ctx: {
fetchFromRegistry: FetchFromRegistry
rawConfig: Record<string, string>
offline?: boolean
resolveFromNpm: NpmResolver
},

View File

@@ -12,6 +12,6 @@ export async function envList (opts: NvmNodeCommandOptions, params: string[]): P
async function listRemoteVersions (opts: NvmNodeCommandOptions, versionSpec?: string): Promise<string[]> {
const fetch = createFetchFromRegistry(opts)
const { releaseChannel, versionSpecifier } = versionSpec ? parseNodeSpecifier(versionSpec) : { releaseChannel: 'release', versionSpecifier: '' }
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseChannel)
const nodeMirrorBaseUrl = getNodeMirror(opts.nodeDownloadMirrors, releaseChannel)
return resolveNodeVersions(fetch, versionSpecifier, nodeMirrorBaseUrl)
}

View File

@@ -16,7 +16,8 @@ export type NvmNodeCommandOptions = Pick<Config,
| 'key'
| 'localAddress'
| 'noProxy'
| 'rawConfig'
| 'nodeDownloadMirrors'
| 'authConfig'
| 'strictSsl'
| 'storeDir'
| 'pnpmHomeDir'

View File

@@ -18,7 +18,7 @@ test('env use calls pnpm add with the correct arguments', async () => {
cacheDir: '/tmp/cache',
global: true,
pnpmHomeDir: '/tmp/pnpm-home',
rawConfig: {},
authConfig: {},
storeDir: '/tmp/store',
}, ['use', '18'])
@@ -33,7 +33,7 @@ test('env use passes lts specifier through unchanged', async () => {
bin: '/usr/local/bin',
global: true,
pnpmHomeDir: '/tmp/pnpm-home',
rawConfig: {},
authConfig: {},
storeDir: '/tmp/store',
}, ['use', 'lts'])
@@ -48,7 +48,7 @@ test('env use passes codename specifier through unchanged', async () => {
bin: '/usr/local/bin',
global: true,
pnpmHomeDir: '/tmp/pnpm-home',
rawConfig: {},
authConfig: {},
storeDir: '/tmp/store',
}, ['use', 'argon'])
@@ -64,7 +64,7 @@ test('fail if not run with --global', async () => {
bin: '/usr/local/bin',
global: false,
pnpmHomeDir: '/tmp/pnpm-home',
rawConfig: {},
authConfig: {},
}, ['use', '18'])
).rejects.toEqual(new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use <version>" can only be used with the "--global" option currently'))
@@ -78,7 +78,7 @@ test('fail if there is no global bin directory', async () => {
bin: undefined,
global: true,
pnpmHomeDir: '/tmp/pnpm-home',
rawConfig: {},
authConfig: {},
}, ['use', 'lts'])
).rejects.toEqual(new PnpmError('CANNOT_MANAGE_NODE', 'Unable to manage Node.js because pnpm was not installed using the standalone installation script'))

View File

@@ -32,7 +32,6 @@ export interface DenoRuntimeResolveResult extends ResolveResult {
export async function resolveDenoRuntime (
ctx: {
fetchFromRegistry: FetchFromRegistry
rawConfig: Record<string, string>
offline?: boolean
resolveFromNpm: NpmResolver
},

View File

@@ -1,9 +1,5 @@
import type { Config } from '@pnpm/config.reader'
export function getNodeMirror (rawConfig: Config['rawConfig'], releaseChannel: string): string {
// This is a dynamic lookup since the 'use-node-version' option is allowed to be '<releaseChannel>/<version>'
const configKey = `node-mirror:${releaseChannel}`
const nodeMirror = rawConfig[configKey] ?? `https://nodejs.org/download/${releaseChannel}/`
export function getNodeMirror (nodeDownloadMirrors: Record<string, string> | undefined, releaseChannel: string): string {
const nodeMirror = nodeDownloadMirrors?.[releaseChannel] ?? `https://nodejs.org/download/${releaseChannel}/`
return normalizeNodeMirror(nodeMirror)
}

View File

@@ -31,7 +31,7 @@ export interface NodeRuntimeResolveResult extends ResolveResult {
export async function resolveNodeRuntime (
ctx: {
fetchFromRegistry: FetchFromRegistry
rawConfig: Record<string, string>
nodeDownloadMirrors?: Record<string, string>
offline?: boolean
},
wantedDependency: WantedDependency,
@@ -50,7 +50,7 @@ export async function resolveNodeRuntime (
if (ctx.offline) throw new PnpmError('NO_OFFLINE_NODEJS_RESOLUTION', 'Offline Node.js resolution is not supported')
const versionSpec = wantedDependency.bareSpecifier.substring('runtime:'.length)
const { releaseChannel, versionSpecifier } = parseNodeSpecifier(versionSpec)
const nodeMirrorBaseUrl = getNodeMirror(ctx.rawConfig, releaseChannel)
const nodeMirrorBaseUrl = getNodeMirror(ctx.nodeDownloadMirrors, releaseChannel)
const version = await resolveNodeVersion(ctx.fetchFromRegistry, versionSpecifier, nodeMirrorBaseUrl)
if (!version) {
throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`)

View File

@@ -1,23 +1,23 @@
import { getNodeMirror } from '../lib/getNodeMirror.js'
test.each([
['release', { 'node-mirror:release': 'http://test.mirror.localhost/release' }, 'http://test.mirror.localhost/release/'],
['nightly', { 'node-mirror:nightly': 'http://test.mirror.localhost/nightly' }, 'http://test.mirror.localhost/nightly/'],
['rc', { 'node-mirror:rc': 'http://test.mirror.localhost/rc' }, 'http://test.mirror.localhost/rc/'],
['test', { 'node-mirror:test': 'http://test.mirror.localhost/test' }, 'http://test.mirror.localhost/test/'],
['v8-canary', { 'node-mirror:v8-canary': 'http://test.mirror.localhost/v8-canary' }, 'http://test.mirror.localhost/v8-canary/'],
])('getNodeMirror(%s, %s)', (releaseDir, rawConfig, expected) => {
expect(getNodeMirror(rawConfig, releaseDir)).toBe(expected)
['release', { release: 'http://test.mirror.localhost/release' }, 'http://test.mirror.localhost/release/'],
['nightly', { nightly: 'http://test.mirror.localhost/nightly' }, 'http://test.mirror.localhost/nightly/'],
['rc', { rc: 'http://test.mirror.localhost/rc' }, 'http://test.mirror.localhost/rc/'],
['test', { test: 'http://test.mirror.localhost/test' }, 'http://test.mirror.localhost/test/'],
['v8-canary', { 'v8-canary': 'http://test.mirror.localhost/v8-canary' }, 'http://test.mirror.localhost/v8-canary/'],
])('getNodeMirror(%s, %s)', (releaseDir, mirrors, expected) => {
expect(getNodeMirror(mirrors, releaseDir)).toBe(expected)
})
test('getNodeMirror uses defaults', () => {
const rawConfig = {}
expect(getNodeMirror(rawConfig, 'release')).toBe('https://nodejs.org/download/release/')
expect(getNodeMirror({}, 'release')).toBe('https://nodejs.org/download/release/')
})
test('getNodeMirror with undefined mirrors uses defaults', () => {
expect(getNodeMirror(undefined, 'release')).toBe('https://nodejs.org/download/release/')
})
test('getNodeMirror returns base url with trailing /', () => {
const rawConfig = {
'node-mirror:release': 'http://test.mirror.localhost',
}
expect(getNodeMirror(rawConfig, 'release')).toBe('http://test.mirror.localhost/')
expect(getNodeMirror({ release: 'http://test.mirror.localhost' }, 'release')).toBe('http://test.mirror.localhost/')
})

View File

@@ -103,7 +103,7 @@ export async function handler (
const catalogResolver = resolveFromCatalog.bind(null, opts.catalogs ?? {})
const { resolve } = createResolver({
...opts,
authConfig: opts.rawConfig,
authConfig: opts.authConfig,
fullMetadata,
filterMetadata: fullMetadata,
retry: {

View File

@@ -156,7 +156,6 @@ export type ExecOpts = Required<Pick<Config, 'selectedProjectsGraph'>> & {
| 'modulesDir'
| 'nodeOptions'
| 'pnpmHomeDir'
| 'rawConfig'
| 'recursive'
| 'reporterHidePrefix'
| 'userAgent'

View File

@@ -275,7 +275,6 @@ so you may run "pnpm -w run ${scriptName}"`,
extraBinPaths: opts.extraBinPaths,
extraEnv: opts.extraEnv,
pkgRoot: dir,
rawConfig: opts.rawConfig,
rootModulesDir: await realpathMissing(path.join(dir, 'node_modules')),
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,
@@ -283,6 +282,7 @@ so you may run "pnpm -w run ${scriptName}"`,
shellEmulator: opts.shellEmulator,
stdio: (specifiedScripts.length > 1 && concurrency > 1) ? 'pipe' : 'inherit',
unsafePerm: true, // when running scripts explicitly, assume that they're trusted.
userAgent: opts.userAgent,
}
const existsPnp = existsInDir.bind(null, '.pnp.cjs')
const pnpPath = (opts.workspaceDir && existsPnp(opts.workspaceDir)) ?? existsPnp(dir)

View File

@@ -26,8 +26,8 @@ export type RecursiveRunOpts = Pick<Config,
| 'enablePrePostScripts'
| 'unsafePerm'
| 'pnpmHomeDir'
| 'rawConfig'
| 'requiredScripts'
| 'userAgent'
| 'rootProjectManifest'
| 'scriptsPrependNodePath'
| 'scriptShell'
@@ -123,7 +123,7 @@ export async function runRecursive (
extraBinPaths: opts.extraBinPaths,
extraEnv: opts.extraEnv,
pkgRoot: prefix,
rawConfig: opts.rawConfig,
userAgent: opts.userAgent,
rootModulesDir: await realpathMissing(path.join(prefix, 'node_modules')),
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,

View File

@@ -36,7 +36,6 @@ test('pnpm run: returns correct exit code', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['exit0'])
let err!: Error & { errno: number }
@@ -49,7 +48,6 @@ test('pnpm run: returns correct exit code', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['exit1'])
} catch (_err: any) { // eslint-disable-line
err = _err
@@ -74,7 +72,6 @@ test('pnpm run --no-bail never fails', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['exit1'])
const { default: args } = await import(path.resolve('args.json'))
@@ -101,7 +98,6 @@ test('run: pass the args to the command that is specified in the build script',
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['foo', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
@@ -126,7 +122,6 @@ test('run: pass the args to the command that is specified in the build script of
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['foo', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
@@ -151,7 +146,6 @@ test('test: pass the args to the command that is specified in the build script o
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['test', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
@@ -176,7 +170,6 @@ test('run start: pass the args to the command that is specified in the build scr
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['start', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
@@ -201,7 +194,6 @@ test('run stop: pass the args to the command that is specified in the build scri
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['stop', 'arg', '--flag=true', '--help', '-h'])
const { default: args } = await import(path.resolve('args.json'))
@@ -234,7 +226,6 @@ test('restart: run stop, restart and start', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, [])
expect(server.getLines()).toStrictEqual([
@@ -271,7 +262,6 @@ test('restart: run stop, restart and start and all the pre/post scripts', async
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, [])
expect(server.getLines()).toStrictEqual([
@@ -302,7 +292,6 @@ test('"pnpm run" prints the list of available commands', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, [])
expect(output).toBe(`\
@@ -351,7 +340,6 @@ test('"pnpm run" prints the list of available commands, including commands of th
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
selectedProjectsGraph,
workspaceDir,
}, [])
@@ -381,7 +369,6 @@ Commands of the root workspace project (to run them, use "pnpm -w run"):
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
selectedProjectsGraph,
workspaceDir,
}, [])
@@ -408,7 +395,6 @@ test('pnpm run does not fail with --if-present even if the wanted script is not
extraEnv: {},
ifPresent: true,
pnpmHomeDir: '',
rawConfig: {},
}, ['build'])
})
@@ -477,7 +463,6 @@ test('scripts work with PnP', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['foo'])
// https://github.com/pnpm/registry-mock/blob/ac2e129eb262009d2e7cd43ed869c31097793073/packages/hello-world-js-bin%401.0.0/index.js#L2
@@ -514,7 +499,6 @@ skipOnWindows('pnpm run with custom shell', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
scriptShell: path.resolve('node_modules/.bin/shell-mock'),
}, ['build'])
@@ -547,7 +531,6 @@ onlyOnWindows('pnpm shows error if script-shell is .cmd', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
scriptShell: path.resolve('node_modules/.bin/shell-mock.cmd'),
}, ['build'])
}
@@ -578,7 +561,6 @@ test('pnpm run with RegExp script selector should work', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['/^(lint|build):.*/'])
expect(fs.readFileSync('output-build-a.txt', { encoding: 'utf-8' })).toBe('a')
@@ -605,7 +587,6 @@ test('pnpm run with RegExp script selector should work also for pre/post script'
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
enablePrePostScripts: true,
}, ['/build:.*/'])
@@ -631,7 +612,6 @@ test('pnpm run with RegExp script selector should work parallel as a default beh
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
}, ['/build:.*/'])
const outputsA = serverA.getLines().map(x => Number.parseInt(x))
@@ -658,7 +638,6 @@ test('pnpm run with RegExp script selector should work sequentially with --works
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
workspaceConcurrency: 1,
}, ['/build:.*/'])
@@ -688,7 +667,6 @@ test.each(['d', 'g', 'i', 'm', 'u', 'v', 'y', 's'])('pnpm run with RegExp script
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
workspaceConcurrency: 1,
}, [`/build:.*/${flag}`])
} catch (_err: any) { // eslint-disable-line
@@ -712,7 +690,6 @@ test('pnpm run with slightly incorrect command suggests correct one', async () =
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
workspaceConcurrency: 1,
}, ['buil'])).rejects.toMatchObject({
code: 'ERR_PNPM_NO_SCRIPT',
@@ -734,7 +711,6 @@ test('pnpm run with custom node-options', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
nodeOptions: '--max-old-space-size=1200',
workspaceConcurrency: 1,
}, ['build'])
@@ -754,7 +730,6 @@ test('pnpm run without node version', async () => {
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: process.cwd(),
rawConfig: {},
workspaceConcurrency: 1,
}, ['assert-node-version'])
})

View File

@@ -43,7 +43,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: {},
rootProjectManifestDir: '',
registries: { default: REGISTRY_URL },
@@ -85,7 +85,7 @@ export const DLX_DEFAULT_OPTS = {
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -44,7 +44,6 @@ async function runTest (verifyDepsBeforeRun: VerifyDepsBeforeRun): Promise<void>
extraBinPaths: [],
extraEnv: {},
pnpmHomeDir: '',
rawConfig: {},
verifyDepsBeforeRun,
rootProjectManifest,
rootProjectManifestDir: process.cwd(),

View File

@@ -20,7 +20,6 @@ export interface RunLifecycleHookOptions {
initCwd?: string
optional?: boolean
pkgRoot: string
rawConfig: object
rootModulesDir: string
scriptShell?: string
silent?: boolean
@@ -28,6 +27,7 @@ export interface RunLifecycleHookOptions {
shellEmulator?: boolean
stdio?: string
unsafePerm: boolean
userAgent?: string
}
export async function runLifecycleHook (
@@ -120,7 +120,7 @@ Please unset the scriptShell option, or configure it to a .exe instead.
...opts.extraEnv,
INIT_CWD: opts.initCwd ?? process.cwd(),
PNPM_SCRIPT_SRC_DIR: opts.pkgRoot,
...('user-agent' in opts.rawConfig ? { npm_config_user_agent: (opts.rawConfig as Record<string, string>)['user-agent'] } : {}),
...(opts.userAgent ? { npm_config_user_agent: opts.userAgent } : {}),
},
log: {
clearProgress: noop,

View File

@@ -24,7 +24,6 @@ test('runLifecycleHook()', async () => {
depPath: '/simple/1.0.0',
optional: false,
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
})
@@ -38,7 +37,6 @@ test('runLifecycleHook() escapes the args passed to the script', async () => {
await runLifecycleHook('echo', pkg, {
depPath: '/escape-args/1.0.0',
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
args: ['Revert "feature (#1)"'],
@@ -53,7 +51,6 @@ test('runLifecycleHook() passes newline correctly', async () => {
await runLifecycleHook('echo', pkg, {
depPath: 'escape-newline@1.0.0',
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
args: ['a\nb != \'A\\nB\''],
@@ -71,9 +68,6 @@ test('runLifecycleHook() does not set npm_config env vars', async () => {
await runLifecycleHook('postinstall', pkg, {
depPath: '/inspect-frozen-lockfile/1.0.0',
pkgRoot,
rawConfig: {
'frozen-lockfile': true,
},
rootModulesDir,
unsafePerm: true,
})
@@ -88,7 +82,6 @@ test('runPostinstallHooks()', async () => {
depPath: '/with-many-scripts/1.0.0',
optional: false,
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
})
@@ -104,7 +97,6 @@ test('runLifecycleHook() should throw an error while missing script start or fil
depPath: '/without-script-start-serverjs/1.0.0',
optional: false,
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
})
@@ -118,7 +110,6 @@ test('preinstall script does not trigger node-gyp rebuild', async () => {
depPath: '/gyp-with-preinstall/1.0.0',
optional: false,
pkgRoot,
rawConfig: {},
rootModulesDir,
unsafePerm: true,
})
@@ -148,7 +139,6 @@ skipOnWindows('runLifecycleHooksConcurrently() should check binding.gyp', async
await runLifecycleHooksConcurrently(['install'], [{ buildIndex: 0, rootDir: projectDir as ProjectRootDir, modulesDir: '', manifest: {} }], 5, {
storeController: {} as StoreController,
optional: false,
rawConfig: {},
unsafePerm: true,
})

View File

@@ -9,7 +9,6 @@ import { safeReadPackageJsonFromDir } from '@pnpm/pkg-manifest.reader'
import type { AllowBuild, PackageManifest } from '@pnpm/types'
import { rimraf } from '@zkochan/rimraf'
import { preferredPM } from 'preferred-pm'
import { omit } from 'ramda'
// We don't run prepublishOnly to prepare the dependency.
// This might be counterintuitive as prepublishOnly is where a lot of packages put their build scripts.
@@ -23,8 +22,8 @@ const PREPUBLISH_SCRIPTS = [
export interface PreparePackageOptions {
allowBuild?: AllowBuild
ignoreScripts?: boolean
rawConfig: Record<string, unknown>
unsafePerm?: boolean
userAgent?: string
}
export async function preparePackage (opts: PreparePackageOptions, gitRootDir: string, subDir: string): Promise<{ shouldBeBuilt: boolean, pkgDir: string }> {
@@ -49,11 +48,9 @@ allowBuilds:
const execOpts: RunLifecycleHookOptions = {
depPath: `${manifest.name}@${manifest.version}`,
pkgRoot: pkgDir,
// We can't prepare a package without running its lifecycle scripts.
// An alternative solution could be to throw an exception.
rawConfig: omit(['ignore-scripts'], opts.rawConfig),
rootModulesDir: pkgDir, // We don't need this property but there is currently no way to not set it.
unsafePerm: Boolean(opts.unsafePerm),
userAgent: opts.userAgent,
}
try {
const installScriptName = `${pm}-install`

View File

@@ -12,7 +12,7 @@ test('prepare package runs the prepublish script', async () => {
const tmp = tempDir()
await using server = await createTestIpcServer(path.join(tmp, 'test.sock'))
f.copy('has-prepublish-script', tmp)
await preparePackage({ allowBuild, rawConfig: {} }, tmp, '')
await preparePackage({ allowBuild }, tmp, '')
expect(server.getLines()).toStrictEqual([
'prepublish',
])
@@ -22,7 +22,7 @@ test('prepare package does not run the prepublish script if the main file is pre
const tmp = tempDir()
await using server = await createTestIpcServer(path.join(tmp, 'test.sock'))
f.copy('has-prepublish-script-and-main-file', tmp)
await preparePackage({ allowBuild, rawConfig: {} }, tmp, '')
await preparePackage({ allowBuild }, tmp, '')
expect(server.getLines()).toStrictEqual([
'prepublish',
])
@@ -32,7 +32,7 @@ test('prepare package runs the prepublish script in the sub folder if pkgDir is
const tmp = tempDir()
await using server = await createTestIpcServer(path.join(tmp, 'test.sock'))
f.copy('has-prepublish-script-in-workspace', tmp)
await preparePackage({ allowBuild, rawConfig: {} }, tmp, 'packages/foo')
await preparePackage({ allowBuild }, tmp, 'packages/foo')
expect(server.getLines()).toStrictEqual([
'prepublish',
])

View File

@@ -15,7 +15,6 @@ import { temporaryDirectory } from 'tempy'
export function createBinaryFetcher (ctx: {
fetch: FetchFromRegistry
fetchFromRemoteTarball: FetchFunction
rawConfig: Record<string, string>
storeIndex: StoreIndex
offline?: boolean
}): { binary: BinaryFetcher } {

View File

@@ -15,9 +15,9 @@ import { safeExeca as execa } from 'execa'
export interface CreateGitFetcherOptions {
gitShallowHosts?: string[]
rawConfig: Record<string, unknown>
storeIndex: StoreIndex
unsafePerm?: boolean
userAgent?: string
ignoreScripts?: boolean
}
@@ -44,8 +44,8 @@ export function createGitFetcher (createOpts: CreateGitFetcherOptions): { git: G
const prepareResult = await preparePackage({
allowBuild: opts.allowBuild,
ignoreScripts: createOpts.ignoreScripts,
rawConfig: createOpts.rawConfig,
unsafePerm: createOpts.unsafePerm,
userAgent: createOpts.userAgent,
}, tempLocation, resolution.path ?? '')
pkgDir = prepareResult.pkgDir
if (ignoreScripts && prepareResult.shouldBeBuilt) {

View File

@@ -49,7 +49,7 @@ beforeEach(() => {
test('fetch', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const { filesMap, manifest } = await fetch(
createCafsStore(storeDir),
{
@@ -68,7 +68,7 @@ test('fetch', async () => {
test('fetch a package from Git sub folder', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const { filesMap } = await fetch(
createCafsStore(storeDir),
{
@@ -86,7 +86,7 @@ test('fetch a package from Git sub folder', async () => {
test('prevent directory traversal attack when using Git sub folder', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const repo = 'https://github.com/RexSkz/test-git-subfolder-fetch.git'
const pkgDir = '../../etc'
await expect(
@@ -107,7 +107,7 @@ test('prevent directory traversal attack when using Git sub folder', async () =>
test('prevent directory traversal attack when using Git sub folder #2', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const repo = 'https://github.com/RexSkz/test-git-subfolder-fetch.git'
const pkgDir = 'not/exists'
await expect(
@@ -129,7 +129,6 @@ test('prevent directory traversal attack when using Git sub folder #2', async ()
test('fetch a package from Git that has a prepare script', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({
rawConfig: {},
storeIndex: createStoreIndex(storeDir),
}).git
const { filesMap } = await fetch(
@@ -150,7 +149,7 @@ test('fetch a package from Git that has a prepare script', async () => {
// Test case for https://github.com/pnpm/pnpm/issues/1866
test('fetch a package without a package.json', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const { filesMap } = await fetch(
createCafsStore(storeDir),
{
@@ -169,7 +168,7 @@ test('fetch a package without a package.json', async () => {
// Covers the regression reported in https://github.com/pnpm/pnpm/issues/4064
test('fetch a big repository', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const { filesMap } = await fetch(createCafsStore(storeDir),
{
commit: 'a65fbf5a90f53c9d72fed4daaca59da50f074355',
@@ -183,7 +182,7 @@ test('fetch a big repository', async () => {
test('still able to shallow fetch for allowed hosts', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ gitShallowHosts: ['github.com'], rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ gitShallowHosts: ['github.com'], storeIndex: createStoreIndex(storeDir) }).git
const resolution = {
commit: 'c9b30e71d704cd30fa71f2edd1ecc7dcc4985493',
repo: 'https://github.com/kevva/is-positive.git',
@@ -213,7 +212,6 @@ test('still able to shallow fetch for allowed hosts', async () => {
test('fail when preparing a git-hosted package', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({
rawConfig: {},
storeIndex: createStoreIndex(storeDir),
}).git
await expect(
@@ -232,7 +230,6 @@ test('fail when preparing a git-hosted package', async () => {
test('fail when preparing a git-hosted package with a partial commit', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({
rawConfig: {},
storeIndex: createStoreIndex(storeDir),
}).git
await expect(
@@ -249,7 +246,7 @@ test('fail when preparing a git-hosted package with a partial commit', async ()
test('do not build the package when scripts are ignored', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ ignoreScripts: true, rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ ignoreScripts: true, storeIndex: createStoreIndex(storeDir) }).git
const { filesMap } = await fetch(createCafsStore(storeDir),
{
commit: '55416a9c468806a935636c0ad0371a14a64df8c9',
@@ -265,7 +262,7 @@ test('do not build the package when scripts are ignored', async () => {
test('block git package with prepare script', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const repo = 'https://github.com/pnpm-e2e/prepare-script-works.git'
await expect(
fetch(createCafsStore(storeDir),
@@ -283,7 +280,6 @@ test('block git package with prepare script', async () => {
test('allow git package with prepare script', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({
rawConfig: {},
storeIndex: createStoreIndex(storeDir),
}).git
// This should succeed without throwing because the package is in the allowlist
@@ -307,7 +303,7 @@ function prefixGitArgs (): string[] {
test('fetch only the included files', async () => {
const storeDir = temporaryDirectory()
const fetch = createGitFetcher({ rawConfig: {}, storeIndex: createStoreIndex(storeDir) }).git
const fetch = createGitFetcher({ storeIndex: createStoreIndex(storeDir) }).git
const { filesMap } = await fetch(
createCafsStore(storeDir),
{

View File

@@ -302,7 +302,7 @@ describe('custom fetcher implementation examples', () => {
const tarballFetchers = createTarballFetcher(
fetchFromRegistry,
() => undefined,
{ rawConfig: {}, storeIndex }
{ storeIndex }
)
// Custom fetcher that maps custom URLs to tarballs
@@ -353,7 +353,7 @@ describe('custom fetcher implementation examples', () => {
const tarballFetchers = createTarballFetcher(
fetchFromRegistry,
() => undefined,
{ rawConfig: {}, storeIndex }
{ storeIndex }
)
// Custom fetcher that maps custom local paths to tarballs
@@ -410,7 +410,7 @@ describe('custom fetcher implementation examples', () => {
const tarballFetchers = createTarballFetcher(
fetchFromRegistry,
() => undefined,
{ rawConfig: {}, storeIndex }
{ storeIndex }
)
// Custom fetcher that transforms custom resolution to tarball URL
@@ -462,7 +462,7 @@ describe('custom fetcher implementation examples', () => {
const tarballFetchers = createTarballFetcher(
fetchFromRegistry,
() => undefined,
{ rawConfig: {}, storeIndex, ignoreScripts: true }
{ storeIndex, ignoreScripts: true }
)
// Custom fetcher that maps custom git resolution to git-hosted tarball

View File

@@ -19,7 +19,6 @@ interface Resolution {
export interface CreateGitHostedTarballFetcher {
ignoreScripts?: boolean
rawConfig: Record<string, unknown>
storeIndex: StoreIndex
unsafePerm?: boolean
}

View File

@@ -40,7 +40,6 @@ export function createTarballFetcher (
fetchFromRegistry: FetchFromRegistry,
getAuthHeader: GetAuthHeader,
opts: {
rawConfig: Record<string, unknown>
unsafePerm?: boolean
ignoreScripts?: boolean
storeIndex: StoreIndex

View File

@@ -69,7 +69,6 @@ const fetchFromRegistry = createFetchFromRegistry({})
const getAuthHeader = () => undefined
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -274,7 +273,6 @@ test("don't fail when fetching a local tarball in offline mode", async () => {
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
offline: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -302,7 +300,6 @@ test('fail when trying to fetch a non-local tarball in offline mode', async () =
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
offline: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -428,7 +425,6 @@ test('accessing private packages', async () => {
const getAuthHeader = () => 'Bearer ofjergrg349gj3f2'
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -547,7 +543,6 @@ test('do not build the package when scripts are ignored', async () => {
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
ignoreScripts: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -596,7 +591,6 @@ test('use the subfolder when path is present', async () => {
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
ignoreScripts: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -626,7 +620,6 @@ test('prevent directory traversal attack when path is present', async () => {
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
ignoreScripts: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,
@@ -654,7 +647,6 @@ test('fail when path is not exists', async () => {
const fetch = createTarballFetcher(fetchFromRegistry, getAuthHeader, {
storeIndex,
ignoreScripts: true,
rawConfig: {},
retry: {
maxTimeout: 100,
minTimeout: 0,

View File

@@ -22,11 +22,11 @@ export type ClientOptions = {
customResolvers?: CustomResolver[]
customFetchers?: CustomFetcher[]
ignoreScripts?: boolean
rawConfig: Record<string, string>
sslConfigs?: Record<string, SslConfig>
retry?: RetryTimeoutOptions
storeIndex: StoreIndex
timeout?: number
nodeDownloadMirrors?: Record<string, string>
unsafePerm?: boolean
userAgent?: string
userConfig?: Record<string, string>
@@ -71,7 +71,7 @@ type Fetchers = {
function createFetchers (
fetchFromRegistry: FetchFromRegistry,
getAuthHeader: GetAuthHeader,
opts: Pick<ClientOptions, 'rawConfig' | 'retry' | 'gitShallowHosts' | 'resolveSymlinksInInjectedDirs' | 'unsafePerm' | 'includeOnlyPackageFiles' | 'offline' | 'fetchMinSpeedKiBps' | 'storeIndex'>
opts: Pick<ClientOptions, 'retry' | 'gitShallowHosts' | 'resolveSymlinksInInjectedDirs' | 'unsafePerm' | 'userAgent' | 'includeOnlyPackageFiles' | 'offline' | 'fetchMinSpeedKiBps' | 'storeIndex'>
): Fetchers {
const tarballFetchers = createTarballFetcher(fetchFromRegistry, getAuthHeader, opts)
return {
@@ -82,7 +82,6 @@ function createFetchers (
fetch: fetchFromRegistry,
fetchFromRemoteTarball: tarballFetchers.remoteTarball,
offline: opts.offline,
rawConfig: opts.rawConfig,
storeIndex: opts.storeIndex,
}),
}

View File

@@ -11,9 +11,8 @@ test('createClient()', () => {
const storeIndex = new StoreIndex('.store')
storeIndexes.push(storeIndex)
const client = createClient({
authConfig: { registry: 'https://registry.npmjs.org/' },
authConfig: {},
cacheDir: '',
rawConfig: {},
registries: {
default: 'https://reigstry.npmjs.org/',
},
@@ -25,9 +24,8 @@ test('createClient()', () => {
test('createResolver()', () => {
const { resolve } = createResolver({
authConfig: { registry: 'https://registry.npmjs.org/' },
authConfig: {},
cacheDir: '',
rawConfig: {},
registries: {
default: 'https://reigstry.npmjs.org/',
},

View File

@@ -413,8 +413,8 @@ export async function recursive (
saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact,
savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix,
}),
rawConfig: {
...installOpts.rawConfig,
authConfig: {
...installOpts.authConfig,
...localConfig,
},
storeController: store.ctrl,

View File

@@ -161,6 +161,7 @@ dependencies is not found inside the workspace',
}
export type UpdateCommandOptions = InstallCommandOptions & {
include?: IncludedDependencies
interactive?: boolean
latest?: boolean
packageVulnerabilityAudit?: PackageVulnerabilityAudit
@@ -294,10 +295,15 @@ async function update (
}
}
const includeDirect = makeIncludeDependenciesFromCLI(opts.cliOptions)
// include is always all-true for updates: updates should not change which
// dep types the modules directory supports. The filtering of which deps to
// actually resolve/update is handled by includeDirect (from CLI flags).
// This matches the original behavior where rawConfig didn't have derived
// values like dev=false from --prod, so include defaulted to all-true.
const include = {
dependencies: opts.rawConfig.production !== false,
devDependencies: opts.rawConfig.dev !== false,
optionalDependencies: opts.rawConfig.optional !== false,
dependencies: true,
devDependencies: true,
optionalDependencies: true,
}
const depth = opts.depth ?? Infinity
let updateMatching: UpdateMatchingFunction | undefined
@@ -315,8 +321,8 @@ async function update (
allowNew: false,
depth,
ignoreCurrentSpecifiers: false,
includeDirect,
include,
includeDirect,
update: true,
updateToLatest: opts.latest,
updateMatching,

View File

@@ -32,7 +32,7 @@ const DEFAULT_OPTIONS = {
preferWorkspacePackages: true,
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -11,8 +11,8 @@ import { DEFAULT_OPTS } from './utils/index.js'
// This must be a function because some of its values depend on CWD
const createOptions = (jsr: string = 'https://npm.jsr.io/') => ({
...DEFAULT_OPTS,
rawConfig: {
...DEFAULT_OPTS.rawConfig,
authConfig: {
...DEFAULT_OPTS.authConfig,
'@jsr:registry': jsr,
},
registries: {

View File

@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
preferWorkspacePackages: true,
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -33,7 +33,7 @@ const DEFAULT_OPTS = {
preferWorkspacePackages: true,
proxy: undefined,
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
registries: { default: REGISTRY },
registry: REGISTRY,
rootProjectManifestDir: '',

View File

@@ -31,7 +31,7 @@ const DEFAULT_OPTS = {
preferWorkspacePackages: true,
proxy: undefined,
pnpmHomeDir: '',
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
registries: { default: REGISTRY },
registry: REGISTRY,
rootProjectManifestDir: '',

View File

@@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = {
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -85,7 +85,7 @@ test('saveCatalogName works with different protocols', async () => {
.reply(200)
const options = createOptions()
options.registries['@jsr'] = options.rawConfig['@jsr:registry'] = 'https://npm.jsr.io/'
options.registries['@jsr'] = options.authConfig['@jsr:registry'] = 'https://npm.jsr.io/'
await add.handler(options, [
'@pnpm.e2e/foo@100.1.0',
'jsr:@rus/greet@0.0.3',

View File

@@ -35,7 +35,7 @@ const DEFAULT_OPTIONS = {
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -33,7 +33,7 @@ const DEFAULT_OPTIONS = {
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
rawConfig: { registry: REGISTRY_URL },
authConfig: { registry: REGISTRY_URL },
rawLocalConfig: { registry: REGISTRY_URL },
registries: {
default: REGISTRY_URL,

View File

@@ -12,8 +12,8 @@ import { DEFAULT_OPTS } from '../utils/index.js'
// This must be a function because some of its values depend on CWD
const createOptions = (jsr: string = DEFAULT_OPTS.registry) => ({
...DEFAULT_OPTS,
rawConfig: {
...DEFAULT_OPTS.rawConfig,
authConfig: {
...DEFAULT_OPTS.authConfig,
'@jsr:registry': jsr,
},
registries: {

View File

@@ -82,7 +82,6 @@ test('recursive update prod dependencies only', async () => {
allProjects,
dir: process.cwd(),
lockfileDir: process.cwd(),
optional: false,
recursive: true,
selectedProjectsGraph,
workspaceDir: process.cwd(),
@@ -95,16 +94,11 @@ test('recursive update prod dependencies only', async () => {
...DEFAULT_OPTS,
allProjects,
cliOptions: {
dev: false,
optional: false,
production: true,
},
dir: process.cwd(),
lockfileDir: process.cwd(),
rawConfig: {
...DEFAULT_OPTS.rawConfig,
optional: false,
},
recursive: true,
selectedProjectsGraph,
workspaceDir: process.cwd(),
@@ -120,7 +114,7 @@ test('recursive update prod dependencies only', async () => {
expect(modules?.included).toStrictEqual({
dependencies: true,
devDependencies: true,
optionalDependencies: false,
optionalDependencies: true,
})
})

View File

@@ -40,7 +40,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
registry: REGISTRY,

View File

@@ -78,7 +78,7 @@ export interface StrictInstallOptions {
depth: number
lockfileDir: string
modulesDir: string
rawConfig: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
authConfig: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
verifyStoreIntegrity: boolean
engineStrict: boolean
allowBuilds?: Record<string, boolean | string>
@@ -242,7 +242,7 @@ const defaults = (opts: InstallOptions): StrictInstallOptions => {
preserveWorkspaceProtocol: true,
pruneLockfileImporters: false,
pruneStore: false,
rawConfig: {},
authConfig: {},
registries: DEFAULT_REGISTRIES,
resolutionMode: 'highest',
saveWorkspaceProtocol: 'rolling',
@@ -338,7 +338,7 @@ export function extendOptions (
extendedOpts.userAgent = `${extendedOpts.packageManager.name}/${extendedOpts.packageManager.version} ${extendedOpts.userAgent}`
}
extendedOpts.registries = normalizeRegistries(extendedOpts.registries)
extendedOpts.rawConfig['registry'] = extendedOpts.registries.default
extendedOpts.authConfig['registry'] = extendedOpts.registries.default
if (extendedOpts.enableGlobalVirtualStore) {
if (extendedOpts.virtualStoreDir == null) {
extendedOpts.virtualStoreDir = path.join(extendedOpts.storeDir, 'links')

View File

@@ -424,7 +424,7 @@ export async function mutateModules (
extraNodePaths: ctx.extraNodePaths,
extraEnv: opts.extraEnv,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
userAgent: opts.userAgent,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,
@@ -1461,7 +1461,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
lockfileDir: ctx.lockfileDir,
optional: opts.include.optionalDependencies,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
rootModulesDir: ctx.virtualStoreDir,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,

View File

@@ -172,7 +172,7 @@ test('scoped package with custom registry', async () => {
await addDependenciesToPackage({}, ['@scoped/peer'], testDefaults({
// setting an incorrect default registry URL
rawConfig: {
authConfig: {
'@scoped:registry': `http://localhost:${REGISTRY_MOCK_PORT}/`,
},
registry: 'http://localhost:9999/',

View File

@@ -349,7 +349,7 @@ test(`respects ${WANTED_LOCKFILE} for top dependencies`, async () => {
// shouldn't care about what the registry in npmrc is
// the one in lockfile should be used
await install(manifest, testDefaults({
rawConfig: {
authConfig: {
registry: 'https://registry.npmjs.org',
},
registry: 'https://registry.npmjs.org',

View File

@@ -160,7 +160,7 @@ export interface HeadlessOptions {
disableRelinkLocalDirDeps?: boolean
force: boolean
storeDir: string
rawConfig: object
authConfig: object
unsafePerm: boolean
userAgent: string
registries: Registries
@@ -234,7 +234,7 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
extraNodePaths: opts.extraNodePaths,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
extraEnv: opts.extraEnv,
rawConfig: opts.rawConfig,
authConfig: opts.authConfig,
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,
@@ -575,7 +575,6 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
lockfileDir,
optional: opts.include.optionalDependencies,
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
rawConfig: opts.rawConfig,
rootModulesDir: virtualStoreDir,
scriptsPrependNodePath: opts.scriptsPrependNodePath,
scriptShell: opts.scriptShell,

View File

@@ -64,7 +64,7 @@ export async function testDefaults (
allProjects: Object.fromEntries(
await Promise.all(projects.map(async (project) => [project.rootDir, { ...project, manifest: await safeReadPackageJsonFromDir(project.rootDir) }]))
),
rawConfig: {},
authConfig: {},
registries: {
default: registry,
},

View File

@@ -25,8 +25,6 @@ const IS_POSITIVE_TARBALL = f.find('is-positive-1.0.0.tgz')
const registries = { default: registry }
const authConfig = { registry }
const storeIndexes: StoreIndex[] = []
afterAll(() => {
for (const si of storeIndexes) si.close()
@@ -36,10 +34,9 @@ const topStoreIndex = new StoreIndex('.store')
storeIndexes.push(topStoreIndex)
const { resolve, fetchers } = createClient({
authConfig,
authConfig: {},
cacheDir: '.store',
storeDir: '.store',
rawConfig: {},
registries,
storeIndex: topStoreIndex,
})
@@ -48,8 +45,7 @@ function createFetchersForStore (storeDir: string) {
const si = new StoreIndex(storeDir)
storeIndexes.push(si)
return createClient({
authConfig,
rawConfig: {},
authConfig: {},
cacheDir: storeDir,
storeDir,
registries,
@@ -595,8 +591,7 @@ test('fetchPackageToStore() does not cache errors', async () => {
const noRetryStoreIndex = new StoreIndex('.store')
storeIndexes.push(noRetryStoreIndex)
const noRetry = createClient({
authConfig,
rawConfig: {},
authConfig: {},
retry: { retries: 0 },
cacheDir: '.pnpm',
storeDir: '.store',

View File

@@ -25,7 +25,7 @@ const f = fixtures(import.meta.dirname)
const basePatchOption = {
pnpmHomeDir: '',
rawConfig: {
authConfig: {
registry: `http://localhost:${REGISTRY_MOCK_PORT}/`,
},
registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` },

View File

@@ -38,7 +38,7 @@ export const DEFAULT_OPTS = {
pnpmHomeDir: '',
preferWorkspacePackages: true,
proxy: undefined,
rawConfig: { registry: REGISTRY },
authConfig: { registry: REGISTRY },
rawLocalConfig: {},
registries: { default: REGISTRY },
registry: REGISTRY,

View File

@@ -27,7 +27,7 @@ export async function checkForUpdates (config: Config): Promise<void> {
const { resolve } = createResolver({
...config,
authConfig: config.rawConfig,
authConfig: config.authConfig,
retry: {
retries: 0,
},

View File

@@ -183,7 +183,6 @@ const cliOptionsTypesByCommandName: Record<string, () => Record<string, unknown>
const aliasToFullName = new Map<string, string>()
const completionByCommandName: Record<string, CompletionFunc> = {}
const shorthandsByCommandName: Record<string, Record<string, string | string[]>> = {}
const rcOptionsTypes: Record<string, unknown> = {}
const skipPackageManagerCheckForCommandArray = ['completion-server']
const recursiveByDefaultCommandArray: string[] = []
const overridableByScriptCommandArray: string[] = []
@@ -195,7 +194,6 @@ for (let i = 0; i < commands.length; i++) {
completion,
handler,
help,
rcOptionsTypes,
shorthands,
skipPackageManagerCheck,
recursiveByDefault,
@@ -212,7 +210,6 @@ for (let i = 0; i < commands.length; i++) {
if (completion != null) {
completionByCommandName[commandName] = completion
}
Object.assign(rcOptionsTypes, rcOptionsTypes())
}
if (skipPackageManagerCheck) {
skipPackageManagerCheckForCommandArray.push(...commandNames)
@@ -262,4 +259,4 @@ export const recursiveByDefaultCommands = new Set(recursiveByDefaultCommandArray
export const overridableByScriptCommands = new Set(overridableByScriptCommandArray)
export { NOT_IMPLEMENTED_COMMAND_SET, rcOptionsTypes, shorthandsByCommandName }
export { NOT_IMPLEMENTED_COMMAND_SET, shorthandsByCommandName }

View File

@@ -15,9 +15,7 @@ export async function getConfig (
opts: {
excludeReporter: boolean
globalDirShouldAllowWrite?: boolean
rcOptionsTypes: Record<string, unknown>
workspaceDir: string | undefined
checkUnknownSetting?: boolean
ignoreNonAuthSettingsFromLocal?: boolean
}
): Promise<Config> {
@@ -25,9 +23,7 @@ export async function getConfig (
cliOptions,
globalDirShouldAllowWrite: opts.globalDirShouldAllowWrite,
packageManager,
rcOptionsTypes: opts.rcOptionsTypes,
workspaceDir: opts.workspaceDir,
checkUnknownSetting: opts.checkUnknownSetting,
ignoreNonAuthSettingsFromLocal: opts.ignoreNonAuthSettingsFromLocal,
})
config.cliOptions = cliOptions

Some files were not shown because too many files have changed in this diff Show More