From 45a6cb6b2a0323f5e33cb8cec3def221c8dd06fa Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 5 Apr 2026 20:15:10 +0200 Subject: [PATCH] refactor(auth): unify auth/SSL into structured configByUri (#11201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the dual `authConfig` (raw .npmrc) + `authInfos` (parsed auth) + `sslConfigs` (parsed SSL) pattern with a single structured `configByUri: Record` field on Config. ### New types (`@pnpm/types`) - **`RegistryConfig`** — per-registry config: `{ creds?: Creds, tls?: TlsConfig }` - **`Creds`** — auth credentials: `{ authToken?, basicAuth?, tokenHelper? }` - **`TlsConfig`** — TLS config: `{ cert?, key?, ca? }` ### Key changes - Rewrite `createGetAuthHeaderByURI` to accept `Record` instead of raw .npmrc key-value pairs - Eliminate duplicate auth parsing between `getAuthHeadersFromConfig` and `getNetworkConfigs` - Remove `authConfig` from the install pipeline (`StrictInstallOptions`, `HeadlessOptions`), replaced by `configByUri` - Remove `sslConfigs` from Config — SSL fields now live in `configByUri[uri].tls` - Remove `authConfig['registry']` mutation in `extendInstallOptions` (default registry now passed directly to `createGetAuthHeaderByURI`) - `authConfig` remains on Config only for raw .npmrc access (config commands, error reporting, config inheritance) ### Security - tokenHelper in project .npmrc now throws instead of being silently stripped - tokenHelper execution uses `shell: false` to prevent shell metacharacter injection - Basic auth uses `Buffer.from().toString('base64')` instead of `btoa()` for Unicode safety - Dispatcher only creates custom agents when entries actually have TLS fields --- .../after-install/src/extendBuildOptions.ts | 8 +- building/commands/test/build/utils/index.ts | 2 +- config/commands/src/configToRecord.ts | 2 +- config/reader/src/Config.ts | 8 +- config/reader/src/auth.ts | 1 + config/reader/src/getNetworkConfigs.ts | 103 +++++------- config/reader/src/index.ts | 23 ++- .../src/{parseAuthInfo.ts => parseCreds.ts} | 48 ++---- config/reader/test/getNetworkConfigs.test.ts | 74 +++++---- config/reader/test/index.ts | 3 +- ...rseAuthInfo.test.ts => parseCreds.test.ts} | 60 +++---- core/types/src/misc.ts | 35 +++- cspell.json | 1 + deps/compliance/commands/src/audit/audit.ts | 5 +- deps/compliance/commands/test/audit/index.ts | 5 +- .../commands/test/audit/utils/options.ts | 13 +- .../commands/test/licenses/utils/index.ts | 2 +- .../commands/test/sbom/utils/index.ts | 2 +- .../commands/src/outdated/outdated.ts | 2 +- deps/inspection/commands/src/view/index.ts | 2 +- .../commands/test/listing/utils/index.ts | 2 +- .../commands/test/outdated/index.ts | 4 +- .../commands/test/outdated/utils/index.ts | 2 +- .../outdated/src/createManifestGetter.ts | 10 +- .../outdated/test/getManifest.spec.ts | 12 +- .../commands/src/self-updater/installPnpm.ts | 2 +- .../commands/src/self-updater/selfUpdate.ts | 2 +- .../test/self-updater/selfUpdate.test.ts | 2 +- engine/runtime/commands/src/env/node.ts | 3 +- engine/runtime/commands/test/env/env.test.ts | 10 +- exec/commands/src/dlx.ts | 2 +- exec/commands/test/utils/index.ts | 4 +- installing/client/src/index.ts | 10 +- installing/client/test/index.ts | 4 +- installing/commands/src/recursive.ts | 5 +- installing/commands/test/add.ts | 2 +- installing/commands/test/addJsr.ts | 5 +- installing/commands/test/fetch.ts | 2 +- installing/commands/test/import.ts | 2 +- installing/commands/test/importRecursive.ts | 2 +- installing/commands/test/peerDependencies.ts | 2 +- installing/commands/test/prune.ts | 2 +- installing/commands/test/saveCatalog.ts | 2 +- .../commands/test/update/interactive.ts | 2 +- installing/commands/test/update/issue-7415.ts | 2 +- installing/commands/test/update/jsr.ts | 5 +- installing/commands/test/utils/index.ts | 2 +- .../src/install/extendInstallOptions.ts | 6 +- .../deps-installer/test/install/auth.ts | 117 +++++++------- installing/deps-restorer/src/index.ts | 5 +- .../src/resolveAndInstallConfigDeps.ts | 7 +- .../env-installer/src/resolveConfigDeps.ts | 6 +- .../test/resolveConfigDeps.test.ts | 2 - installing/package-requester/test/index.ts | 6 +- network/auth-header/package.json | 3 +- .../src/getAuthHeadersFromConfig.ts | 88 ++++------ network/auth-header/src/index.ts | 19 +-- .../auth-header/test/getAuthHeaderByURI.ts | 55 +++---- .../test/getAuthHeadersFromConfig.test.ts | 150 +++++------------- network/auth-header/tsconfig.json | 3 + network/fetch/src/dispatcher.ts | 17 +- network/fetch/src/fetchFromRegistry.ts | 20 ++- network/fetch/test/fetchFromRegistry.test.ts | 12 +- patching/commands/test/patch.test.ts | 4 +- patching/commands/test/utils/index.ts | 2 +- pnpm-lock.yaml | 3 + pnpm/src/checkForUpdates.ts | 2 +- releasing/commands/src/publish/pack.ts | 1 - .../commands/src/publish/publishPackedPkg.ts | 113 ++++--------- .../commands/src/publish/recursivePublish.ts | 6 +- releasing/commands/test/deploy/utils/index.ts | 2 +- .../commands/test/publish/recursivePublish.ts | 5 +- .../commands/test/publish/utils/index.ts | 4 +- store/commands/src/inspecting/catIndex.ts | 5 +- store/commands/test/store/storeAdd.ts | 15 +- store/commands/test/store/storePath.ts | 10 +- store/commands/test/store/storePrune.ts | 80 ++-------- store/commands/test/store/storeStatus.ts | 15 +- .../src/createNewStoreController.ts | 8 +- store/controller/test/index.ts | 4 +- testing/temp-store/src/index.ts | 4 +- 81 files changed, 554 insertions(+), 748 deletions(-) rename config/reader/src/{parseAuthInfo.ts => parseCreds.ts} (73%) rename config/reader/test/{parseAuthInfo.test.ts => parseCreds.test.ts} (73%) diff --git a/building/after-install/src/extendBuildOptions.ts b/building/after-install/src/extendBuildOptions.ts index 7798380c9e..4408e0043e 100644 --- a/building/after-install/src/extendBuildOptions.ts +++ b/building/after-install/src/extendBuildOptions.ts @@ -4,7 +4,7 @@ import { DEFAULT_REGISTRIES, normalizeRegistries } from '@pnpm/config.normalize- import type { Config, ConfigContext } from '@pnpm/config.reader' import type { LogBase } from '@pnpm/logger' import type { StoreController } from '@pnpm/store.controller-types' -import type { Registries } from '@pnpm/types' +import type { Registries, RegistryConfig } from '@pnpm/types' import { loadJsonFile } from 'load-json-file' export type StrictBuildOptions = { @@ -35,7 +35,7 @@ export type StrictBuildOptions = { production: boolean development: boolean optional: boolean - authConfig: object + configByUri: Record userConfig: Record userAgent: string packageManager: { @@ -52,7 +52,7 @@ export type StrictBuildOptions = { peersSuffixMaxLength: number strictStorePkgContentCheck: boolean fetchFullMetadata?: boolean -} & Pick +} & Pick export type BuildOptions = Partial & Pick & Pick @@ -73,7 +73,7 @@ const defaults = async (opts: BuildOptions): Promise => { packageManager, pending: false, production: true, - authConfig: {}, + configByUri: {}, registries: DEFAULT_REGISTRIES, scriptsPrependNodePath: false, shamefullyHoist: false, diff --git a/building/commands/test/build/utils/index.ts b/building/commands/test/build/utils/index.ts index ebe0027c37..d35822281a 100644 --- a/building/commands/test/build/utils/index.ts +++ b/building/commands/test/build/utils/index.ts @@ -35,7 +35,7 @@ export const DEFAULT_OPTS = { pnpmfile: ['./.pnpmfile.cjs'], pnpmHomeDir: '', proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/config/commands/src/configToRecord.ts b/config/commands/src/configToRecord.ts index 3fdd29d064..ebbd36e4bc 100644 --- a/config/commands/src/configToRecord.ts +++ b/config/commands/src/configToRecord.ts @@ -6,7 +6,7 @@ import { censorProtectedSettings } from './protectedSettings.js' // Auth-related Config fields that are internal objects, not user settings. const NON_SETTING_CONFIG_KEYS = new Set([ - 'authConfig', 'authInfos', 'sslConfigs', + 'authConfig', 'configByUri', ]) /** diff --git a/config/reader/src/Config.ts b/config/reader/src/Config.ts index 6ba3702db0..15bdd8a06a 100644 --- a/config/reader/src/Config.ts +++ b/config/reader/src/Config.ts @@ -7,12 +7,11 @@ import type { ProjectManifest, ProjectsGraph, Registries, - SslConfig, + RegistryConfig, TrustPolicy, } from '@pnpm/types' import type { OptionsFromRootManifest } from './getOptionsFromRootManifest.js' -import type { AuthInfo } from './parseAuthInfo.js' export type UniversalOptions = Pick @@ -51,7 +50,7 @@ export interface ConfigContext { * User-facing settings + auth/network config. * Does NOT include runtime state — see {@link ConfigContext} for that. */ -export interface Config extends AuthInfo, OptionsFromRootManifest { +export interface Config extends OptionsFromRootManifest { allowNew: boolean autoConfirmAllPrompts?: boolean autoInstallPeers?: boolean @@ -213,8 +212,7 @@ export interface Config extends AuthInfo, OptionsFromRootManifest { blockExoticSubdeps?: boolean registries: Registries - authInfos: Record - sslConfigs: Record + configByUri: Record ignoreWorkspaceRootCheck: boolean workspaceRoot: boolean diff --git a/config/reader/src/auth.ts b/config/reader/src/auth.ts index b6fde03b5f..dda7be19b1 100644 --- a/config/reader/src/auth.ts +++ b/config/reader/src/auth.ts @@ -32,6 +32,7 @@ const RAW_AUTH_CFG_KEY_SUFFIXES = [ const AUTH_CFG_KEYS = [ 'ca', 'cert', + 'configByUri', 'key', 'localAddress', 'gitShallowHosts', diff --git a/config/reader/src/getNetworkConfigs.ts b/config/reader/src/getNetworkConfigs.ts index 9831bf9e90..36aeb7aaba 100644 --- a/config/reader/src/getNetworkConfigs.ts +++ b/config/reader/src/getNetworkConfigs.ts @@ -1,74 +1,59 @@ import fs from 'node:fs' -import type { SslConfig } from '@pnpm/types' +import type { Creds, RegistryConfig } from '@pnpm/types' import normalizeRegistryUrl from 'normalize-registry-url' -import { type AuthInfo, type AuthInfoInput, parseAuthInfo } from './parseAuthInfo.js' +import { parseCreds, type RawCreds } from './parseCreds.js' export interface NetworkConfigs { - authInfos?: Record // TODO: remove optional from here, this means that tests would have to be updated. - sslConfigs: Record + configByUri?: Record // TODO: remove optional from here, this means that tests would have to be updated. registries: Record } export function getNetworkConfigs (rawConfig: Record): NetworkConfigs { - const authInfoInputs: Record = {} - const sslConfigs: Record = {} + const rawCredsMap: Record = {} const registries: Record = {} + const networkConfigs: NetworkConfigs = { registries } for (const [configKey, value] of Object.entries(rawConfig)) { if (configKey[0] === '@' && configKey.endsWith(':registry')) { registries[configKey.slice(0, configKey.indexOf(':'))] = normalizeRegistryUrl(value as string) continue } - const parsed = tryParseAuthSetting(configKey) ?? tryParseSslSetting(configKey) + const parsedCreds = tryParseCredsKey(configKey) + if (parsedCreds) { + const { credsField, registry } = parsedCreds + rawCredsMap[registry] ??= {} + rawCredsMap[registry][credsField] = value as string + continue + } - switch (parsed?.target) { - case undefined: - continue - case 'auth': { - const { authInputKey, registry } = parsed - authInfoInputs[registry] ??= {} - authInfoInputs[registry][authInputKey] = value as string - continue - } - case 'ssl': { - const { registry, sslConfigKey, isFile } = parsed - sslConfigs[registry] ??= { cert: '', key: '' } - sslConfigs[registry][sslConfigKey] = isFile - ? fs.readFileSync(value as string, 'utf8') - : (value as string).replace(/\\n/g, '\n') - continue - } - default: { - const _typeGuard: never = parsed - throw new Error(`Unhandled variant: ${JSON.stringify(_typeGuard)}`) - } + const parsedSsl = tryParseSslKey(configKey) + if (parsedSsl) { + const { registry, sslField, isFile } = parsedSsl + networkConfigs.configByUri ??= {} + networkConfigs.configByUri[registry] ??= {} + networkConfigs.configByUri[registry].tls ??= {} + networkConfigs.configByUri[registry].tls[sslField] = isFile + ? fs.readFileSync(value as string, 'utf8') + : (value as string).replace(/\\n/g, '\n') } } - // Instead of directly returning the object literal at the end of the function, - // we create a temporary object of `networkConfigs` to avoid adding - // `authInfos: undefined` to the returning object to prevent the failures of - // existing tests which use `expect().to[Strict]Equal()` methods. - const networkConfigs: NetworkConfigs = { - registries, - sslConfigs, - } - - for (const key in authInfoInputs) { - const authInfo = parseAuthInfo(authInfoInputs[key]) - if (authInfo) { - networkConfigs.authInfos ??= {} - networkConfigs.authInfos[key] = authInfo + for (const uri in rawCredsMap) { + const creds = parseCreds(rawCredsMap[uri]) + if (creds) { + networkConfigs.configByUri ??= {} + networkConfigs.configByUri[uri] ??= {} + networkConfigs.configByUri[uri].creds = creds } } return networkConfigs } -export function getDefaultAuthInfo (rawConfig: Record): AuthInfo | undefined { - const input: AuthInfoInput = {} +export function getDefaultCreds (rawConfig: Record): Creds | undefined { + const input: RawCreds = {} for (const rawKey in AUTH_SUFFIX_KEY_MAP) { const key = AUTH_SUFFIX_KEY_MAP[rawKey] const value = rawConfig[rawKey] as string | undefined @@ -76,11 +61,11 @@ export function getDefaultAuthInfo (rawConfig: Record): AuthInf input[key] = value } } - return parseAuthInfo(input) + return parseCreds(input) } const AUTH_SUFFIX_RE = /:(?_auth|_authToken|_password|username|tokenHelper)$/ -const AUTH_SUFFIX_KEY_MAP: Record = { +const AUTH_SUFFIX_KEY_MAP: Record = { _auth: 'authPairBase64', _authToken: 'authToken', _password: 'authPassword', @@ -88,41 +73,41 @@ const AUTH_SUFFIX_KEY_MAP: Record = { tokenHelper: 'tokenHelper', } -interface ParsedAuthSetting { - target: 'auth' +interface ParsedCredsKey { registry: string - authInputKey: keyof AuthInfoInput + credsField: keyof RawCreds } -function tryParseAuthSetting (key: string): ParsedAuthSetting | undefined { +function tryParseCredsKey (key: string): ParsedCredsKey | undefined { const match = key.match(AUTH_SUFFIX_RE) if (!match?.groups) { return undefined } const registry = key.slice(0, match.index!) // already includes the trailing slash - const authInputKey = AUTH_SUFFIX_KEY_MAP[match.groups.key] - if (!authInputKey) { + const credsField = AUTH_SUFFIX_KEY_MAP[match.groups.key] + if (!credsField) { throw new Error(`Unexpected key: ${match.groups.key}`) } - return { target: 'auth', registry, authInputKey } + return { registry, credsField } } const SSL_SUFFIX_RE = /:(?cert|key|ca)(?file)?$/ -interface ParsedSslSetting { - target: 'ssl' +type SslField = 'cert' | 'key' | 'ca' + +interface ParsedSslKey { registry: string - sslConfigKey: keyof SslConfig + sslField: SslField isFile: boolean } -function tryParseSslSetting (key: string): ParsedSslSetting | undefined { +function tryParseSslKey (key: string): ParsedSslKey | undefined { const match = key.match(SSL_SUFFIX_RE) if (!match?.groups) { return undefined } const registry = key.slice(0, match.index!) // already includes the trailing slash - const sslConfigKey = match.groups.id as keyof SslConfig + const sslField = match.groups.id as SslField const isFile = Boolean(match.groups.kind) - return { target: 'ssl', registry, sslConfigKey, isFile } + return { registry, sslField, isFile } } diff --git a/config/reader/src/index.ts b/config/reader/src/index.ts index dfee537b64..3a7255363b 100644 --- a/config/reader/src/index.ts +++ b/config/reader/src/index.ts @@ -37,7 +37,7 @@ import { isConfigFileKey } from './configFileKey.js' import { extractAndRemoveDependencyBuildOptions, hasDependencyBuildOptions } from './dependencyBuildOptions.js' import { getCacheDir, getConfigDir, getDataDir, getStateDir } from './dirs.js' import { parseEnvVars } from './env.js' -import { getDefaultAuthInfo, getNetworkConfigs } from './getNetworkConfigs.js' +import { getDefaultCreds, getNetworkConfigs } from './getNetworkConfigs.js' import { getOptionsFromPnpmSettings } from './getOptionsFromRootManifest.js' import { loadNpmrcConfig } from './loadNpmrcFiles.js' import { npmDefaults } from './npmDefaults.js' @@ -51,6 +51,7 @@ export { types } export { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency.js' export { getOptionsFromPnpmSettings, type OptionsFromRootManifest } from './getOptionsFromRootManifest.js' +export type { Creds } from './parseCreds.js' export { createProjectConfigRecord, type CreateProjectConfigRecordOptions, @@ -63,7 +64,6 @@ export { ProjectConfigsMatchItemIsNotAStringError, ProjectConfigUnsupportedFieldError, } from './projectConfig.js' - export type { Config, ConfigContext, ProjectConfig, UniversalOptions, VerifyDepsBeforeRun } export { isIniConfigKey } from './auth.js' @@ -302,9 +302,22 @@ export async function getConfig (opts: { ...networkConfigs.registries, } pnpmConfig.registries = { ...registriesFromNpmrc } - pnpmConfig.authInfos = networkConfigs.authInfos ?? {} // TODO: remove `?? {}` (when possible) - pnpmConfig.sslConfigs = networkConfigs.sslConfigs - Object.assign(pnpmConfig, getDefaultAuthInfo(pnpmConfig.authConfig)) + const defaultCreds = getDefaultCreds(pnpmConfig.authConfig) + pnpmConfig.configByUri = { + ...networkConfigs.configByUri, + ...defaultCreds ? { '': { creds: defaultCreds } } : {}, + } + // tokenHelper must only come from user-level config (~/.npmrc or global auth.ini), + // not project-level, to prevent project .npmrc from executing arbitrary commands. + const userConfig = npmrcResult.userConfig as Record + for (const [key, value] of Object.entries(pnpmConfig.authConfig)) { + if (!key.endsWith('tokenHelper') && key !== 'tokenHelper') continue + if (!(key in userConfig) || userConfig[key] !== value) { + throw new PnpmError('TOKEN_HELPER_IN_PROJECT_CONFIG', + 'tokenHelper must not be configured in project-level .npmrc', + { hint: `The key "${key}" was found in project config. Move it to ~/.npmrc or the global pnpm auth.ini.` }) + } + } pnpmConfig.pnpmHomeDir = getDataDir({ env, platform: process.platform }) let globalDirRoot if (pnpmConfig.globalDir) { diff --git a/config/reader/src/parseAuthInfo.ts b/config/reader/src/parseCreds.ts similarity index 73% rename from config/reader/src/parseAuthInfo.ts rename to config/reader/src/parseCreds.ts index 7c46997c55..2f3d8ebe67 100644 --- a/config/reader/src/parseAuthInfo.ts +++ b/config/reader/src/parseCreds.ts @@ -1,17 +1,10 @@ import { PnpmError } from '@pnpm/error' +import type { BasicAuth, Creds, TokenHelper } from '@pnpm/types' -/** Authentication information of each registry in the rc file. */ -export interface AuthInfo { - /** Parsed value of `_auth` of each registry in the rc file. */ - authUserPass?: AuthUserPass - /** The value of `_authToken` of each registry in the rc file. */ - authToken?: string - /** Parsed value of `tokenHelper` of each registry in the rc file. */ - tokenHelper?: TokenHelper -} +export type { BasicAuth, Creds, TokenHelper } /** Unparsed authentication information of each registry in the rc file. */ -export interface AuthInfoInput { +export interface RawCreds { /** Value of `_authToken` in the rc file. */ authToken?: string /** Value of `_auth` in the rc file. */ @@ -24,39 +17,34 @@ export interface AuthInfoInput { tokenHelper?: string } -export function parseAuthInfo (input: AuthInfoInput): AuthInfo | undefined { - let authInfo: AuthInfo | undefined +export function parseCreds (input: RawCreds): Creds | undefined { + let parsedCreds: Creds | undefined if (input.tokenHelper) { - authInfo = { - ...authInfo, + parsedCreds = { + ...parsedCreds, tokenHelper: parseTokenHelper(input.tokenHelper), } } if (input.authToken) { - authInfo = { - ...authInfo, + parsedCreds = { + ...parsedCreds, authToken: input.authToken, } } - const authUserPass = getAuthUserPass(input) - if (authUserPass) { - authInfo = { - ...authInfo, - authUserPass, + const basicAuth = parseBasicAuth(input) + if (basicAuth) { + parsedCreds = { + ...parsedCreds, + basicAuth, } } - return authInfo + return parsedCreds } -/** Parsed value of `_auth` of each registry in the rc file. */ -export interface AuthUserPass { - username: string - password: string -} /** * Extract a pair of username and password from either a base64 encoded string @@ -65,11 +53,11 @@ export interface AuthUserPass { * The function input mirrors the rc file which has 3 properties to define username * and password which are: `_auth`, `username`, and `_password`. */ -function getAuthUserPass ({ +function parseBasicAuth ({ authPairBase64, authUsername, authPassword, -}: Pick): AuthUserPass | undefined { +}: Pick): BasicAuth | undefined { if (authPairBase64) { const pair = atob(authPairBase64) const colonIndex = pair.indexOf(':') @@ -96,8 +84,6 @@ export class AuthMissingSeparatorError extends PnpmError { } } -/** Parsed value of `tokenHelper` of each registry in the rc file. */ -export type TokenHelper = [string, ...string[]] /** Characters reserved for more advanced features in the future. */ const RESERVED_CHARACTERS = new Set(['$', '%', '`', '"', "'"]) diff --git a/config/reader/test/getNetworkConfigs.test.ts b/config/reader/test/getNetworkConfigs.test.ts index 99bd01a238..ca8be7a80d 100644 --- a/config/reader/test/getNetworkConfigs.test.ts +++ b/config/reader/test/getNetworkConfigs.test.ts @@ -7,7 +7,6 @@ import { getNetworkConfigs, type NetworkConfigs } from '../src/getNetworkConfigs test('without files', () => { expect(getNetworkConfigs({})).toStrictEqual({ registries: {}, - sslConfigs: {}, } as NetworkConfigs) expect(getNetworkConfigs({ @@ -16,18 +15,15 @@ test('without files', () => { registries: { '@foo': 'https://example.com/foo', }, - sslConfigs: {}, } as NetworkConfigs) expect(getNetworkConfigs({ '//example.com/foo:ca': 'some-ca', })).toStrictEqual({ registries: {}, - sslConfigs: { + configByUri: { '//example.com/foo': { - ca: 'some-ca', - cert: '', - key: '', + tls: { ca: 'some-ca' }, }, }, } as NetworkConfigs) @@ -36,10 +32,9 @@ test('without files', () => { '//example.com/foo:cert': 'some-cert', })).toStrictEqual({ registries: {}, - sslConfigs: { + configByUri: { '//example.com/foo': { - cert: 'some-cert', - key: '', + tls: { cert: 'some-cert' }, }, }, } as NetworkConfigs) @@ -52,11 +47,9 @@ test('without files', () => { registries: { '@foo': 'https://example.com/foo', }, - sslConfigs: { + configByUri: { '//example.com/foo': { - ca: 'some-ca', - cert: 'some-cert', - key: '', + tls: { ca: 'some-ca', cert: 'some-cert' }, }, }, } as NetworkConfigs) @@ -76,17 +69,15 @@ test('with files', () => { registries: { '@foo': 'https://example.com/foo', }, - sslConfigs: { + configByUri: { '//example.com/foo': { - ca: 'some-ca', - cert: 'some-cert', - key: '', + tls: { ca: 'some-ca', cert: 'some-cert' }, }, }, } as NetworkConfigs) }) -test('auth infos', () => { +test('auth and tls combined', () => { expect(getNetworkConfigs({ '@foo:registry': 'https://example.com/foo', '//example.com/foo:_authToken': 'example auth token', @@ -94,12 +85,11 @@ test('auth infos', () => { registries: { '@foo': 'https://example.com/foo', }, - authInfos: { + configByUri: { '//example.com/foo': { - authToken: 'example auth token', + creds: { authToken: 'example auth token' }, }, }, - sslConfigs: {}, } as NetworkConfigs) expect(getNetworkConfigs({ @@ -109,15 +99,16 @@ test('auth infos', () => { registries: { '@foo': 'https://example.com/foo', }, - authInfos: { + configByUri: { '//example.com/foo': { - authUserPass: { - username: 'foo', - password: 'bar', + creds: { + basicAuth: { + username: 'foo', + password: 'bar', + }, }, }, }, - sslConfigs: {}, } as NetworkConfigs) expect(getNetworkConfigs({ @@ -128,15 +119,16 @@ test('auth infos', () => { registries: { '@foo': 'https://example.com/foo', }, - authInfos: { + configByUri: { '//example.com/foo': { - authUserPass: { - username: 'foo', - password: 'bar', + creds: { + basicAuth: { + username: 'foo', + password: 'bar', + }, }, }, }, - sslConfigs: {}, } as NetworkConfigs) expect(getNetworkConfigs({ @@ -146,12 +138,25 @@ test('auth infos', () => { registries: { '@foo': 'https://example.com/foo', }, - authInfos: { + configByUri: { '//example.com/foo': { - tokenHelper: ['node', './my-token-helper.cjs'], + creds: { tokenHelper: ['node', './my-token-helper.cjs'] }, + }, + }, + } as NetworkConfigs) + + expect(getNetworkConfigs({ + '//example.com/foo:_authToken': 'token', + '//example.com/foo:cert': 'some-cert', + '//example.com/foo:key': 'some-key', + })).toStrictEqual({ + registries: {}, + configByUri: { + '//example.com/foo': { + creds: { authToken: 'token' }, + tls: { cert: 'some-cert', key: 'some-key' }, }, }, - sslConfigs: {}, } as NetworkConfigs) }) @@ -163,6 +168,5 @@ test('unsupported key', () => { registries: { '@foo': 'https://example.com/foo', }, - sslConfigs: {}, } as NetworkConfigs) }) diff --git a/config/reader/test/index.ts b/config/reader/test/index.ts index 2d7317b6e5..a8fa31ed60 100644 --- a/config/reader/test/index.ts +++ b/config/reader/test/index.ts @@ -1017,8 +1017,7 @@ test('getConfig() should read inline SSL certificates from .npmrc', async () => }) // After processing, \n should be converted to actual newlines - expect(config.sslConfigs).toBeDefined() - expect(config.sslConfigs['//registry.example.com/']).toStrictEqual({ + expect(config.configByUri['//registry.example.com/']?.tls).toMatchObject({ ca: inlineCa.replace(/\\n/g, '\n'), cert: inlineCert.replace(/\\n/g, '\n'), key: inlineKey.replace(/\\n/g, '\n'), diff --git a/config/reader/test/parseAuthInfo.test.ts b/config/reader/test/parseCreds.test.ts similarity index 73% rename from config/reader/test/parseAuthInfo.test.ts rename to config/reader/test/parseCreds.test.ts index 36251baafb..08e74bc798 100644 --- a/config/reader/test/parseAuthInfo.test.ts +++ b/config/reader/test/parseCreds.test.ts @@ -1,107 +1,107 @@ import { - type AuthInfo, AuthMissingSeparatorError, - parseAuthInfo, + type Creds, + parseCreds, TokenHelperUnsupportedCharacterError, -} from '../src/parseAuthInfo.js' +} from '../src/parseCreds.js' -describe('parseAuthInfo', () => { +describe('parseCreds', () => { test('empty object', () => { - expect(parseAuthInfo({})).toBeUndefined() + expect(parseCreds({})).toBeUndefined() }) test('authToken', () => { - expect(parseAuthInfo({ + expect(parseCreds({ authToken: 'example auth token', })).toStrictEqual({ authToken: 'example auth token', - } as AuthInfo) + } as Creds) }) test('authPairBase64', () => { - expect(parseAuthInfo({ + expect(parseCreds({ authPairBase64: btoa('foo:bar'), })).toStrictEqual({ - authUserPass: { + basicAuth: { username: 'foo', password: 'bar', }, - } as AuthInfo) + } as Creds) - expect(parseAuthInfo({ + expect(parseCreds({ authPairBase64: btoa('foo:bar:baz'), })).toStrictEqual({ - authUserPass: { + basicAuth: { username: 'foo', password: 'bar:baz', }, - } as AuthInfo) + } as Creds) }) test('authPairBase64 must have a separator', () => { - expect(() => parseAuthInfo({ + expect(() => parseCreds({ authPairBase64: btoa('foo'), })).toThrow(new AuthMissingSeparatorError()) }) test('authUsername and authPassword', () => { - expect(parseAuthInfo({ + expect(parseCreds({ authUsername: 'foo', authPassword: btoa('bar'), })).toStrictEqual({ - authUserPass: { + basicAuth: { username: 'foo', password: 'bar', }, - } as AuthInfo) + } as Creds) - expect(parseAuthInfo({ + expect(parseCreds({ authUsername: 'foo', })).toBeUndefined() - expect(parseAuthInfo({ + expect(parseCreds({ authPassword: 'bar', })).toBeUndefined() }) test('tokenHelper', () => { - expect(parseAuthInfo({ + expect(parseCreds({ tokenHelper: 'example-token-helper --foo --bar baz', })).toStrictEqual({ tokenHelper: ['example-token-helper', '--foo', '--bar', 'baz'], - } as AuthInfo) + } as Creds) - expect(parseAuthInfo({ + expect(parseCreds({ tokenHelper: './example-token-helper.sh --foo --bar baz', })).toStrictEqual({ tokenHelper: ['./example-token-helper.sh', '--foo', '--bar', 'baz'], - } as AuthInfo) + } as Creds) - expect(parseAuthInfo({ + expect(parseCreds({ tokenHelper: 'node ./example-token-helper.js --foo --bar baz', })).toStrictEqual({ tokenHelper: ['node', './example-token-helper.js', '--foo', '--bar', 'baz'], - } as AuthInfo) + } as Creds) - expect(parseAuthInfo({ + expect(parseCreds({ tokenHelper: './example-token-helper.sh', })).toStrictEqual({ tokenHelper: ['./example-token-helper.sh'], - } as AuthInfo) + } as Creds) }) test('tokenHelper does not support environment variable', () => { - expect(() => parseAuthInfo({ + expect(() => parseCreds({ tokenHelper: 'example-token-helper $MY_VAR', })).toThrow(new TokenHelperUnsupportedCharacterError('$')) }) test('tokenHelper does not support quotations', () => { - expect(() => parseAuthInfo({ + expect(() => parseCreds({ tokenHelper: 'example-token-helper "hello world"', })).toThrow(new TokenHelperUnsupportedCharacterError('"')) - expect(() => parseAuthInfo({ + expect(() => parseCreds({ tokenHelper: "example-token-helper 'hello world'", })).toThrow(new TokenHelperUnsupportedCharacterError("'")) }) diff --git a/core/types/src/misc.ts b/core/types/src/misc.ts index f9c9fc74b8..14a32971d8 100644 --- a/core/types/src/misc.ts +++ b/core/types/src/misc.ts @@ -19,12 +19,41 @@ export interface Registries { [scope: string]: string } -export interface SslConfig { - cert: string - key: string +/** Parsed value of `_auth` of each registry in the rc file. */ +export interface BasicAuth { + username: string + password: string +} + +/** Parsed value of `tokenHelper` of each registry in the rc file. */ +export type TokenHelper = [string, ...string[]] + +/** Per-registry authentication credentials. */ +export interface Creds { + /** Parsed value of `_auth` of each registry in the rc file. */ + basicAuth?: BasicAuth + /** The value of `_authToken` of each registry in the rc file. */ + authToken?: string + /** Parsed value of `tokenHelper` of each registry in the rc file. */ + tokenHelper?: TokenHelper +} + +/** Per-registry TLS configuration. */ +export interface TlsConfig { + /** Client certificate (PEM). */ + cert?: string + /** Client private key (PEM). */ + key?: string + /** Certificate authority (PEM). */ ca?: string } +/** Per-registry configuration (credentials + TLS). */ +export interface RegistryConfig { + creds?: Creds + tls?: TlsConfig +} + export type HoistedDependencies = Record> export type PkgResolutionId = string & { __brand: 'PkgResolutionId' } diff --git a/cspell.json b/cspell.json index d8edfdf864..15857392ae 100644 --- a/cspell.json +++ b/cspell.json @@ -44,6 +44,7 @@ "corepack", "corge", "cowsay", + "Creds", "cves", "cwsay", "cyclonedx", diff --git a/deps/compliance/commands/src/audit/audit.ts b/deps/compliance/commands/src/audit/audit.ts index 5f71b3777c..3d073d6525 100644 --- a/deps/compliance/commands/src/audit/audit.ts +++ b/deps/compliance/commands/src/audit/audit.ts @@ -164,8 +164,7 @@ export type AuditOptions = Pick & { | 'dev' | 'overrides' | 'optional' -| 'userConfig' -| 'authConfig' +| 'configByUri' | 'virtualStoreDirMaxLength' | 'workspaceDir' > & Pick { ...AUDIT_REGISTRY_OPTS, dir: hasVulnerabilitiesDir, rootProjectManifestDir: hasVulnerabilitiesDir, - authConfig: { - registry: AUDIT_REGISTRY, - [`${AUDIT_REGISTRY.replace(/^https?:/, '')}:_authToken`]: '123', + configByUri: { + '//audit.registry/': { creds: { authToken: '123' } }, }, }) diff --git a/deps/compliance/commands/test/audit/utils/options.ts b/deps/compliance/commands/test/audit/utils/options.ts index 1bf6d2e752..f342092440 100644 --- a/deps/compliance/commands/test/audit/utils/options.ts +++ b/deps/compliance/commands/test/audit/utils/options.ts @@ -3,9 +3,6 @@ import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' const registries = { default: 'https://registry.npmjs.org/', } -const authConfig = { - registry: registries.default, -} export const DEFAULT_OPTS = { argv: { original: [], @@ -42,7 +39,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig, + configByUri: {}, registries, rootProjectManifestDir: '', registry: registries.default, @@ -65,9 +62,7 @@ export const AUDIT_REGISTRY_OPTS = { registries: { default: AUDIT_REGISTRY, }, - authConfig: { - registry: AUDIT_REGISTRY, - }, + configByUri: {}, } export const MOCK_REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` @@ -77,7 +72,5 @@ export const MOCK_REGISTRY_OPTS = { registries: { default: MOCK_REGISTRY, }, - authConfig: { - registry: MOCK_REGISTRY, - }, + configByUri: {}, } diff --git a/deps/compliance/commands/test/licenses/utils/index.ts b/deps/compliance/commands/test/licenses/utils/index.ts index 57cad5ead0..606575d249 100644 --- a/deps/compliance/commands/test/licenses/utils/index.ts +++ b/deps/compliance/commands/test/licenses/utils/index.ts @@ -36,7 +36,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, rootProjectManifestDir: '', // registry: REGISTRY, diff --git a/deps/compliance/commands/test/sbom/utils/index.ts b/deps/compliance/commands/test/sbom/utils/index.ts index 66a33d42a9..5de7d83b1c 100644 --- a/deps/compliance/commands/test/sbom/utils/index.ts +++ b/deps/compliance/commands/test/sbom/utils/index.ts @@ -36,7 +36,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, rootProjectManifestDir: '', sort: true, diff --git a/deps/inspection/commands/src/outdated/outdated.ts b/deps/inspection/commands/src/outdated/outdated.ts index 7b60f29b8c..7f31d6a23b 100644 --- a/deps/inspection/commands/src/outdated/outdated.ts +++ b/deps/inspection/commands/src/outdated/outdated.ts @@ -164,7 +164,7 @@ export type OutdatedCommandOptions = { | 'offline' | 'optional' | 'production' -| 'authConfig' +| 'configByUri' | 'registries' | 'strictSsl' | 'tag' diff --git a/deps/inspection/commands/src/view/index.ts b/deps/inspection/commands/src/view/index.ts index 5929598463..be7d6032f2 100644 --- a/deps/inspection/commands/src/view/index.ts +++ b/deps/inspection/commands/src/view/index.ts @@ -87,7 +87,7 @@ export async function handler ( } const registry = pickRegistryForPackage(opts.registries, packageName) const fetchFromRegistry = createFetchFromRegistry(opts) - const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig ?? {}, userSettings: opts.userConfig ?? {} }) + const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {}, opts.registries?.default) const fetchResult = await fetchMetadataFromFromRegistry( { fetch: fetchFromRegistry, diff --git a/deps/inspection/commands/test/listing/utils/index.ts b/deps/inspection/commands/test/listing/utils/index.ts index c7b76ef7b3..86bb7220c0 100644 --- a/deps/inspection/commands/test/listing/utils/index.ts +++ b/deps/inspection/commands/test/listing/utils/index.ts @@ -38,7 +38,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', proxy: undefined, preferWorkspacePackages: true, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/deps/inspection/commands/test/outdated/index.ts b/deps/inspection/commands/test/outdated/index.ts index 5499cc4e52..d3c3b7cca0 100644 --- a/deps/inspection/commands/test/outdated/index.ts +++ b/deps/inspection/commands/test/outdated/index.ts @@ -34,7 +34,7 @@ const OUTDATED_OPTIONS = { global: false, networkConcurrency: 16, offline: false, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, 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, - authConfig: { registry: 'https://registry.npmjs.org/' }, + configByUri: {}, registries: { default: 'https://registry.npmjs.org/' }, }) diff --git a/deps/inspection/commands/test/outdated/utils/index.ts b/deps/inspection/commands/test/outdated/utils/index.ts index defb74694f..92637ace81 100644 --- a/deps/inspection/commands/test/outdated/utils/index.ts +++ b/deps/inspection/commands/test/outdated/utils/index.ts @@ -41,7 +41,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/deps/inspection/outdated/src/createManifestGetter.ts b/deps/inspection/outdated/src/createManifestGetter.ts index c1f7473aa4..f2fe183512 100644 --- a/deps/inspection/outdated/src/createManifestGetter.ts +++ b/deps/inspection/outdated/src/createManifestGetter.ts @@ -4,19 +4,19 @@ import { createResolver, type ResolveFunction, } from '@pnpm/installing.client' -import type { DependencyManifest, PackageVersionPolicy } from '@pnpm/types' +import type { DependencyManifest, PackageVersionPolicy, RegistryConfig } from '@pnpm/types' interface GetManifestOpts { dir: string lockfileDir: string - authConfig: object + configByUri: object minimumReleaseAge?: number minimumReleaseAgeExclude?: string[] } -export type ManifestGetterOptions = Omit +export type ManifestGetterOptions = Omit & GetManifestOpts -& { fullMetadata: boolean, authConfig: Record } +& { fullMetadata: boolean, configByUri: Record } export function createManifestGetter ( opts: ManifestGetterOptions @@ -27,7 +27,7 @@ export function createManifestGetter ( const { resolve } = createResolver({ ...opts, - authConfig: opts.authConfig, + configByUri: opts.configByUri, filterMetadata: false, // We need all the data from metadata for "outdated --long" to work. strictPublishedByCheck: Boolean(opts.minimumReleaseAge), }) diff --git a/deps/inspection/outdated/test/getManifest.spec.ts b/deps/inspection/outdated/test/getManifest.spec.ts index 770d22b027..41a16dc641 100644 --- a/deps/inspection/outdated/test/getManifest.spec.ts +++ b/deps/inspection/outdated/test/getManifest.spec.ts @@ -8,7 +8,7 @@ test('getManifest()', async () => { const opts = { dir: '', lockfileDir: '', - authConfig: {}, + configByUri: {}, } 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: '', - authConfig: {}, + configByUri: {}, minimumReleaseAge: 10080, } @@ -78,7 +78,7 @@ test('getManifest() does not convert non-latest specifiers', async () => { const opts = { dir: '', lockfileDir: '', - authConfig: {}, + configByUri: {}, } const resolve = jest.fn(async (wantedPackage) => { @@ -105,7 +105,7 @@ test('getManifest() returns null for NO_MATCHING_VERSION when publishedBy is set const opts = { dir: '', lockfileDir: '', - authConfig: {}, + configByUri: {}, } 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: '', - authConfig: {}, + configByUri: {}, } const resolve: ResolveFunction = jest.fn(async function () { @@ -145,7 +145,7 @@ test('getManifest() with minimumReleaseAgeExclude', async () => { const opts = { dir: '', lockfileDir: '', - authConfig: {}, + configByUri: {}, } const publishedBy = new Date(Date.now() - 10080 * 60 * 1000) diff --git a/engine/pm/commands/src/self-updater/installPnpm.ts b/engine/pm/commands/src/self-updater/installPnpm.ts index d2157d3113..df51594a9a 100644 --- a/engine/pm/commands/src/self-updater/installPnpm.ts +++ b/engine/pm/commands/src/self-updater/installPnpm.ts @@ -272,7 +272,7 @@ async function installFromLockfile ( virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength, sideEffectsCacheRead: false, sideEffectsCacheWrite: false, - authConfig: {}, + configByUri: {}, unsafePerm: false, userAgent: '', packageManager: opts.packageManager ?? { name: 'pnpm', version: '' }, diff --git a/engine/pm/commands/src/self-updater/selfUpdate.ts b/engine/pm/commands/src/self-updater/selfUpdate.ts index e94c1bbdf0..075985b48d 100644 --- a/engine/pm/commands/src/self-updater/selfUpdate.ts +++ b/engine/pm/commands/src/self-updater/selfUpdate.ts @@ -64,7 +64,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.authConfig }) + const { resolve } = createResolver({ ...opts, configByUri: opts.configByUri }) const pkgName = 'pnpm' const bareSpecifier = params[0] ?? 'latest' const resolution = await resolve({ alias: pkgName, bareSpecifier }, { diff --git a/engine/pm/commands/test/self-updater/selfUpdate.test.ts b/engine/pm/commands/test/self-updater/selfUpdate.test.ts index 6a73e36bf7..6e790432a7 100644 --- a/engine/pm/commands/test/self-updater/selfUpdate.test.ts +++ b/engine/pm/commands/test/self-updater/selfUpdate.test.ts @@ -59,7 +59,7 @@ function prepareOptions (dir: string) { workspaceConcurrency: 1, extraEnv: {}, pnpmfile: '', - authConfig: {}, + configByUri: {}, cacheDir: path.join(dir, '.cache'), virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, dir, diff --git a/engine/runtime/commands/src/env/node.ts b/engine/runtime/commands/src/env/node.ts index 20676fec71..980bcc26ba 100644 --- a/engine/runtime/commands/src/env/node.ts +++ b/engine/runtime/commands/src/env/node.ts @@ -17,14 +17,13 @@ export type NvmNodeCommandOptions = Pick & Partial { cacheDir: '/tmp/cache', global: true, pnpmHomeDir: '/tmp/pnpm-home', - authConfig: {}, + configByUri: {}, 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', - authConfig: {}, + configByUri: {}, 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', - authConfig: {}, + configByUri: {}, 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', - authConfig: {}, + configByUri: {}, }, ['use', '18']) ).rejects.toEqual(new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use " 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', - authConfig: {}, + configByUri: {}, }, ['use', 'lts']) ).rejects.toEqual(new PnpmError('CANNOT_MANAGE_NODE', 'Unable to manage Node.js because pnpm was not installed using the standalone installation script')) diff --git a/exec/commands/src/dlx.ts b/exec/commands/src/dlx.ts index 2688e4c035..c2b6a10932 100644 --- a/exec/commands/src/dlx.ts +++ b/exec/commands/src/dlx.ts @@ -103,7 +103,7 @@ export async function handler ( const catalogResolver = resolveFromCatalog.bind(null, opts.catalogs ?? {}) const { resolve } = createResolver({ ...opts, - authConfig: opts.authConfig, + configByUri: opts.configByUri, fullMetadata, filterMetadata: fullMetadata, retry: { diff --git a/exec/commands/test/utils/index.ts b/exec/commands/test/utils/index.ts index a3cee39736..1c8044a650 100644 --- a/exec/commands/test/utils/index.ts +++ b/exec/commands/test/utils/index.ts @@ -43,7 +43,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, rootProjectManifestDir: '', registries: { default: REGISTRY_URL }, registry: REGISTRY_URL, @@ -84,7 +84,7 @@ export const DLX_DEFAULT_OPTS = { pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/client/src/index.ts b/installing/client/src/index.ts index fe9ce21e62..8cb9276d40 100644 --- a/installing/client/src/index.ts +++ b/installing/client/src/index.ts @@ -13,23 +13,21 @@ import { type ResolverFactoryOptions, } from '@pnpm/resolving.default-resolver' import type { StoreIndex } from '@pnpm/store.index' -import type { SslConfig } from '@pnpm/types' +import type { RegistryConfig } from '@pnpm/types' export type { ResolveFunction } export type ClientOptions = { - authConfig: Record + configByUri: Record customResolvers?: CustomResolver[] customFetchers?: CustomFetcher[] ignoreScripts?: boolean - sslConfigs?: Record retry?: RetryTimeoutOptions storeIndex: StoreIndex timeout?: number nodeDownloadMirrors?: Record unsafePerm?: boolean userAgent?: string - userConfig?: Record gitShallowHosts?: string[] resolveSymlinksInInjectedDirs?: boolean includeOnlyPackageFiles?: boolean @@ -45,7 +43,7 @@ export interface Client { export function createClient (opts: ClientOptions): Client { const fetchFromRegistry = createFetchFromRegistry(opts) - const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig, userSettings: opts.userConfig }) + const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri, opts.registries?.default) const { resolve, clearCache: clearResolutionCache } = _createResolver(fetchFromRegistry, getAuthHeader, { ...opts, customResolvers: opts.customResolvers }) return { @@ -57,7 +55,7 @@ export function createClient (opts: ClientOptions): Client { export function createResolver (opts: Omit): { resolve: ResolveFunction, clearCache: () => void } { const fetchFromRegistry = createFetchFromRegistry(opts) - const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig, userSettings: opts.userConfig }) + const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri, opts.registries?.default) return _createResolver(fetchFromRegistry, getAuthHeader, { ...opts, customResolvers: opts.customResolvers }) } diff --git a/installing/client/test/index.ts b/installing/client/test/index.ts index 28392c6bbd..de9b8115a8 100644 --- a/installing/client/test/index.ts +++ b/installing/client/test/index.ts @@ -11,7 +11,7 @@ test('createClient()', () => { const storeIndex = new StoreIndex('.store') storeIndexes.push(storeIndex) const client = createClient({ - authConfig: {}, + configByUri: {}, cacheDir: '', registries: { default: 'https://reigstry.npmjs.org/', @@ -24,7 +24,7 @@ test('createClient()', () => { test('createResolver()', () => { const { resolve } = createResolver({ - authConfig: {}, + configByUri: {}, cacheDir: '', registries: { default: 'https://reigstry.npmjs.org/', diff --git a/installing/commands/src/recursive.ts b/installing/commands/src/recursive.ts index 4b903ea39a..c797971123 100755 --- a/installing/commands/src/recursive.ts +++ b/installing/commands/src/recursive.ts @@ -409,10 +409,7 @@ export async function recursive ( saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact, savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix, }), - authConfig: { - ...installOpts.authConfig, - ...localConfig, - }, + configByUri: installOpts.configByUri, storeController: store.ctrl, } ) diff --git a/installing/commands/test/add.ts b/installing/commands/test/add.ts index 84818a27c8..2200e70d16 100644 --- a/installing/commands/test/add.ts +++ b/installing/commands/test/add.ts @@ -32,7 +32,7 @@ const DEFAULT_OPTIONS = { preferWorkspacePackages: true, pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/addJsr.ts b/installing/commands/test/addJsr.ts index 0acbc2042f..044011f929 100644 --- a/installing/commands/test/addJsr.ts +++ b/installing/commands/test/addJsr.ts @@ -11,10 +11,7 @@ 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, - authConfig: { - ...DEFAULT_OPTS.authConfig, - '@jsr:registry': jsr, - }, + configByUri: {}, registries: { ...DEFAULT_OPTS.registries, '@jsr': jsr, diff --git a/installing/commands/test/fetch.ts b/installing/commands/test/fetch.ts index 1f7f6f6fba..3d712a53b9 100644 --- a/installing/commands/test/fetch.ts +++ b/installing/commands/test/fetch.ts @@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = { preferWorkspacePackages: true, pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/import.ts b/installing/commands/test/import.ts index 6cf635d2c2..dd00595177 100644 --- a/installing/commands/test/import.ts +++ b/installing/commands/test/import.ts @@ -33,7 +33,7 @@ const DEFAULT_OPTS = { preferWorkspacePackages: true, proxy: undefined, pnpmHomeDir: '', - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/installing/commands/test/importRecursive.ts b/installing/commands/test/importRecursive.ts index a8936263d2..cd6d6465d0 100644 --- a/installing/commands/test/importRecursive.ts +++ b/installing/commands/test/importRecursive.ts @@ -31,7 +31,7 @@ const DEFAULT_OPTS = { preferWorkspacePackages: true, proxy: undefined, pnpmHomeDir: '', - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/installing/commands/test/peerDependencies.ts b/installing/commands/test/peerDependencies.ts index c36ea1a7a2..23b9fcae37 100644 --- a/installing/commands/test/peerDependencies.ts +++ b/installing/commands/test/peerDependencies.ts @@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = { pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/prune.ts b/installing/commands/test/prune.ts index 89ed0a0f2e..ae3d995def 100644 --- a/installing/commands/test/prune.ts +++ b/installing/commands/test/prune.ts @@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = { pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/saveCatalog.ts b/installing/commands/test/saveCatalog.ts index 171a0aa2e6..f19ff3def9 100644 --- a/installing/commands/test/saveCatalog.ts +++ b/installing/commands/test/saveCatalog.ts @@ -85,7 +85,7 @@ test('saveCatalogName works with different protocols', async () => { .reply(200) const options = createOptions() - options.registries['@jsr'] = options.authConfig['@jsr:registry'] = 'https://npm.jsr.io/' + options.registries['@jsr'] = 'https://npm.jsr.io/' await add.handler(options, [ '@pnpm.e2e/foo@100.1.0', 'jsr:@rus/greet@0.0.3', diff --git a/installing/commands/test/update/interactive.ts b/installing/commands/test/update/interactive.ts index dc873be8ce..1bd85a874f 100644 --- a/installing/commands/test/update/interactive.ts +++ b/installing/commands/test/update/interactive.ts @@ -35,7 +35,7 @@ const DEFAULT_OPTIONS = { pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/update/issue-7415.ts b/installing/commands/test/update/issue-7415.ts index 25a090f6e7..40e1662b3a 100644 --- a/installing/commands/test/update/issue-7415.ts +++ b/installing/commands/test/update/issue-7415.ts @@ -33,7 +33,7 @@ const DEFAULT_OPTIONS = { pnpmfile: ['.pnpmfile.cjs'], pnpmHomeDir: '', preferWorkspacePackages: true, - authConfig: { registry: REGISTRY_URL }, + configByUri: {}, registries: { default: REGISTRY_URL, }, diff --git a/installing/commands/test/update/jsr.ts b/installing/commands/test/update/jsr.ts index 9ca7d87d46..8e033a9289 100644 --- a/installing/commands/test/update/jsr.ts +++ b/installing/commands/test/update/jsr.ts @@ -12,10 +12,7 @@ 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, - authConfig: { - ...DEFAULT_OPTS.authConfig, - '@jsr:registry': jsr, - }, + configByUri: {}, registries: { ...DEFAULT_OPTS.registries, '@jsr': jsr, diff --git a/installing/commands/test/utils/index.ts b/installing/commands/test/utils/index.ts index 15266d4574..6be41bf413 100644 --- a/installing/commands/test/utils/index.ts +++ b/installing/commands/test/utils/index.ts @@ -40,7 +40,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/installing/deps-installer/src/install/extendInstallOptions.ts b/installing/deps-installer/src/install/extendInstallOptions.ts index fc342fb41a..15615234e6 100644 --- a/installing/deps-installer/src/install/extendInstallOptions.ts +++ b/installing/deps-installer/src/install/extendInstallOptions.ts @@ -20,6 +20,7 @@ import type { PeerDependencyRules, ReadPackageHook, Registries, + RegistryConfig, SupportedArchitectures, TrustPolicy, } from '@pnpm/types' @@ -77,7 +78,7 @@ export interface StrictInstallOptions { depth: number lockfileDir: string modulesDir: string - authConfig: Record // eslint-disable-line @typescript-eslint/no-explicit-any + configByUri: Record verifyStoreIntegrity: boolean engineStrict: boolean allowBuilds?: Record @@ -239,7 +240,7 @@ const defaults = (opts: InstallOptions): StrictInstallOptions => { preserveWorkspaceProtocol: true, pruneLockfileImporters: false, pruneStore: false, - authConfig: {}, + configByUri: {}, registries: DEFAULT_REGISTRIES, resolutionMode: 'highest', saveWorkspaceProtocol: 'rolling', @@ -335,7 +336,6 @@ export function extendOptions ( extendedOpts.userAgent = `${extendedOpts.packageManager.name}/${extendedOpts.packageManager.version} ${extendedOpts.userAgent}` } extendedOpts.registries = normalizeRegistries(extendedOpts.registries) - extendedOpts.authConfig['registry'] = extendedOpts.registries.default if (extendedOpts.enableGlobalVirtualStore) { if (extendedOpts.virtualStoreDir == null) { extendedOpts.virtualStoreDir = path.join(extendedOpts.storeDir, 'links') diff --git a/installing/deps-installer/test/install/auth.ts b/installing/deps-installer/test/install/auth.ts index cc50eafd81..12e9fe7370 100644 --- a/installing/deps-installer/test/install/auth.ts +++ b/installing/deps-installer/test/install/auth.ts @@ -3,6 +3,7 @@ import path from 'node:path' import { addDependenciesToPackage, install } from '@pnpm/installing.deps-installer' import { prepareEmpty } from '@pnpm/prepare' import { addUser, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' +import type { RegistryConfig } from '@pnpm/types' import { rimrafSync } from '@zkochan/rimraf' import { testDefaults } from '../utils/index.js' @@ -18,14 +19,13 @@ test('a package that need authentication', async () => { username: 'foo', }) - let authConfig = { - [`//localhost:${REGISTRY_MOCK_PORT}/:_authToken`]: data.token, - registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, + let configByUri: Record = { + [`//localhost:${REGISTRY_MOCK_PORT}/`]: { creds: { authToken: data.token } }, } const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/needs-auth'], testDefaults({}, { - authConfig, + configByUri, }, { - authConfig, + configByUri, })) project.has('@pnpm.e2e/needs-auth') @@ -35,15 +35,14 @@ test('a package that need authentication', async () => { rimrafSync('node_modules') rimrafSync(path.join('..', '.store')) - authConfig = { - [`//localhost:${REGISTRY_MOCK_PORT}/:_authToken`]: data.token, - registry: 'https://registry.npmjs.org/', + configByUri = { + [`//localhost:${REGISTRY_MOCK_PORT}/`]: { creds: { authToken: data.token } }, } await addDependenciesToPackage(manifest, ['@pnpm.e2e/needs-auth'], testDefaults({}, { - authConfig, + configByUri, registry: 'https://registry.npmjs.org/', }, { - authConfig, + configByUri, })) project.has('@pnpm.e2e/needs-auth') @@ -58,16 +57,13 @@ test('installing a package that need authentication, using password', async () = username: 'foo', }) - const encodedPassword = Buffer.from('bar').toString('base64') - const authConfig = { - [`//localhost:${REGISTRY_MOCK_PORT}/:_password`]: encodedPassword, - [`//localhost:${REGISTRY_MOCK_PORT}/:username`]: 'foo', - registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, + const configByUri: Record = { + [`//localhost:${REGISTRY_MOCK_PORT}/`]: { creds: { basicAuth: { username: 'foo', password: 'bar' } } }, } await addDependenciesToPackage({}, ['@pnpm.e2e/needs-auth'], testDefaults({}, { - authConfig, + configByUri, }, { - authConfig, + configByUri, })) project.has('@pnpm.e2e/needs-auth') @@ -82,14 +78,13 @@ test('a package that need authentication, legacy way', async () => { username: 'foo', }) - const authConfig = { - _auth: 'Zm9vOmJhcg==', // base64 encoded foo:bar - registry: `http://localhost:${REGISTRY_MOCK_PORT}`, + const configByUri: Record = { + '': { creds: { basicAuth: { username: 'foo', password: 'bar' } } }, } await addDependenciesToPackage({}, ['@pnpm.e2e/needs-auth'], testDefaults({}, { - authConfig, + configByUri, }, { - authConfig, + configByUri, })) project.has('@pnpm.e2e/needs-auth') @@ -104,16 +99,19 @@ test('a scoped package that need authentication specific to scope', async () => username: 'foo', }) - const authConfig = { - [`//localhost:${REGISTRY_MOCK_PORT}/:_authToken`]: data.token, - '@private:registry': `http://localhost:${REGISTRY_MOCK_PORT}/`, - registry: 'https://registry.npmjs.org/', + const configByUri: Record = { + [`//localhost:${REGISTRY_MOCK_PORT}/`]: { creds: { authToken: data.token } }, } - let opts = testDefaults({}, { - authConfig, + let opts = testDefaults({ + registries: { + default: 'https://registry.npmjs.org/', + '@private': `http://localhost:${REGISTRY_MOCK_PORT}/`, + }, + }, { + configByUri, registry: 'https://registry.npmjs.org/', }, { - authConfig, + configByUri, }) const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@private/foo'], opts) @@ -124,11 +122,16 @@ test('a scoped package that need authentication specific to scope', async () => rimrafSync(path.join('..', '.store')) // Recreating options to have a new storeController with clean cache - opts = testDefaults({}, { - authConfig, + opts = testDefaults({ + registries: { + default: 'https://registry.npmjs.org/', + '@private': `http://localhost:${REGISTRY_MOCK_PORT}/`, + }, + }, { + configByUri, registry: 'https://registry.npmjs.org/', }, { - authConfig, + configByUri, }) await addDependenciesToPackage(manifest, ['@private/foo'], opts) @@ -144,16 +147,19 @@ test('a scoped package that need legacy authentication specific to scope', async username: 'foo', }) - const authConfig = { - [`//localhost:${REGISTRY_MOCK_PORT}/:_auth`]: 'Zm9vOmJhcg==', // base64 encoded foo:bar - '@private:registry': `http://localhost:${REGISTRY_MOCK_PORT}/`, - registry: 'https://registry.npmjs.org/', + const configByUri: Record = { + [`//localhost:${REGISTRY_MOCK_PORT}/`]: { creds: { basicAuth: { username: 'foo', password: 'bar' } } }, } - let opts = testDefaults({}, { - authConfig, + let opts = testDefaults({ + registries: { + default: 'https://registry.npmjs.org/', + '@private': `http://localhost:${REGISTRY_MOCK_PORT}/`, + }, + }, { + configByUri, registry: 'https://registry.npmjs.org/', }, { - authConfig, + configByUri, }) const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@private/foo'], opts) @@ -164,11 +170,16 @@ test('a scoped package that need legacy authentication specific to scope', async rimrafSync(path.join('..', '.store')) // Recreating options to have a new storeController with clean cache - opts = testDefaults({}, { - authConfig, + opts = testDefaults({ + registries: { + default: 'https://registry.npmjs.org/', + '@private': `http://localhost:${REGISTRY_MOCK_PORT}/`, + }, + }, { + configByUri, registry: 'https://registry.npmjs.org/', }, { - authConfig, + configByUri, }) await addDependenciesToPackage(manifest, ['@private/foo'], opts) @@ -184,19 +195,18 @@ skipOnNode17('a package that need authentication reuses authorization tokens for username: 'foo', }) - const authConfig = { - [`//127.0.0.1:${REGISTRY_MOCK_PORT}/:_authToken`]: data.token, - registry: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, + const configByUri: Record = { + [`//127.0.0.1:${REGISTRY_MOCK_PORT}/`]: { creds: { authToken: data.token } }, } await addDependenciesToPackage({}, ['@pnpm.e2e/needs-auth'], testDefaults({ registries: { default: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, }, { - authConfig, + configByUri, registry: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, { - authConfig, + configByUri, })) project.has('@pnpm.e2e/needs-auth') @@ -211,19 +221,18 @@ skipOnNode17('a package that need authentication reuses authorization tokens for username: 'foo', }) - const authConfig = { - [`//127.0.0.1:${REGISTRY_MOCK_PORT}/:_authToken`]: data.token, - registry: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, + const configByUri: Record = { + [`//127.0.0.1:${REGISTRY_MOCK_PORT}/`]: { creds: { authToken: data.token } }, } let opts = testDefaults({ registries: { default: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, }, { - authConfig, + configByUri, registry: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, { - authConfig, + configByUri, }) const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/needs-auth'], opts) @@ -238,10 +247,10 @@ skipOnNode17('a package that need authentication reuses authorization tokens for default: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, }, { - authConfig, + configByUri, registry: `http://127.0.0.1:${REGISTRY_MOCK_PORT}`, }, { - authConfig, + configByUri, }) await install(manifest, opts) diff --git a/installing/deps-restorer/src/index.ts b/installing/deps-restorer/src/index.ts index 9722798bd8..f96b405edf 100644 --- a/installing/deps-restorer/src/index.ts +++ b/installing/deps-restorer/src/index.ts @@ -77,6 +77,7 @@ import { type ProjectManifest, type ProjectRootDir, type Registries, + type RegistryConfig, type SupportedArchitectures, } from '@pnpm/types' import { symlinkAllModules } from '@pnpm/worker' @@ -160,7 +161,7 @@ export interface HeadlessOptions { disableRelinkLocalDirDeps?: boolean force: boolean storeDir: string - authConfig: object + configByUri: Record unsafePerm: boolean userAgent: string registries: Registries @@ -234,7 +235,7 @@ export async function headlessInstall (opts: HeadlessOptions): Promise + configByUri?: Record } /** @@ -98,9 +98,8 @@ export async function resolveAndInstallConfigDeps ( } // Resolve missing deps - const userConfig = opts.userConfig ?? {} const fetch = createFetchFromRegistry(opts) - const getAuthHeader = createGetAuthHeaderByURI({ allSettings: userConfig, userSettings: userConfig }) + const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {}, opts.registries?.default) const { resolveFromNpm } = createNpmResolver(fetch, getAuthHeader, opts) await Promise.all(depsToResolve.map(async ({ name, specifier }) => { diff --git a/installing/env-installer/src/resolveConfigDeps.ts b/installing/env-installer/src/resolveConfigDeps.ts index 0ab5d3736c..cb174c0ada 100644 --- a/installing/env-installer/src/resolveConfigDeps.ts +++ b/installing/env-installer/src/resolveConfigDeps.ts @@ -12,14 +12,14 @@ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header' import { createFetchFromRegistry, type CreateFetchFromRegistryOptions } from '@pnpm/network.fetch' import { createNpmResolver, type ResolverFactoryOptions } from '@pnpm/resolving.npm-resolver' import { parseWantedDependency } from '@pnpm/resolving.parse-wanted-dependency' -import type { ConfigDependencies, ConfigDependencySpecifiers } from '@pnpm/types' +import type { ConfigDependencies, ConfigDependencySpecifiers, RegistryConfig } from '@pnpm/types' import { installConfigDeps, type InstallConfigDepsOpts } from './installConfigDeps.js' export type ResolveConfigDepsOpts = CreateFetchFromRegistryOptions & ResolverFactoryOptions & InstallConfigDepsOpts & { configDependencies?: ConfigDependencies rootDir: string - userConfig?: Record + configByUri?: Record } export async function resolveConfigDeps (configDeps: string[], opts: ResolveConfigDepsOpts): Promise { @@ -28,7 +28,7 @@ export async function resolveConfigDeps (configDeps: string[], opts: ResolveConf } const fetch = createFetchFromRegistry(opts) - const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.userConfig!, userSettings: opts.userConfig }) + const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {}, opts.registries?.default) const { resolveFromNpm } = createNpmResolver(fetch, getAuthHeader, opts) // Extract existing specifiers from configDependencies (handles both old and new formats) diff --git a/installing/env-installer/test/resolveConfigDeps.test.ts b/installing/env-installer/test/resolveConfigDeps.test.ts index a9c2888f0e..6d5621b9bf 100644 --- a/installing/env-installer/test/resolveConfigDeps.test.ts +++ b/installing/env-installer/test/resolveConfigDeps.test.ts @@ -19,7 +19,6 @@ test('configuration dependency is resolved', async () => { }, rootDir: process.cwd(), cacheDir: path.resolve('cache'), - userConfig: {}, store: storeController, storeDir, }) @@ -55,7 +54,6 @@ test('fails with frozenLockfile', async () => { }, rootDir: process.cwd(), cacheDir: path.resolve('cache'), - userConfig: {}, store: storeController, storeDir, frozenLockfile: true, diff --git a/installing/package-requester/test/index.ts b/installing/package-requester/test/index.ts index e51a01d824..8d91505cf1 100644 --- a/installing/package-requester/test/index.ts +++ b/installing/package-requester/test/index.ts @@ -34,7 +34,7 @@ const topStoreIndex = new StoreIndex('.store') storeIndexes.push(topStoreIndex) const { resolve, fetchers } = createClient({ - authConfig: {}, + configByUri: {}, cacheDir: '.store', storeDir: '.store', registries, @@ -45,7 +45,7 @@ function createFetchersForStore (storeDir: string) { const si = new StoreIndex(storeDir) storeIndexes.push(si) return createClient({ - authConfig: {}, + configByUri: {}, cacheDir: storeDir, storeDir, registries, @@ -591,7 +591,7 @@ test('fetchPackageToStore() does not cache errors', async () => { const noRetryStoreIndex = new StoreIndex('.store') storeIndexes.push(noRetryStoreIndex) const noRetry = createClient({ - authConfig: {}, + configByUri: {}, retry: { retries: 0 }, cacheDir: '.pnpm', storeDir: '.store', diff --git a/network/auth-header/package.json b/network/auth-header/package.json index 0ba8a792ea..684a9dd7a9 100644 --- a/network/auth-header/package.json +++ b/network/auth-header/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "@pnpm/config.nerf-dart": "catalog:", - "@pnpm/error": "workspace:*" + "@pnpm/error": "workspace:*", + "@pnpm/types": "workspace:*" }, "devDependencies": { "@pnpm/network.auth-header": "workspace:*", diff --git a/network/auth-header/src/getAuthHeadersFromConfig.ts b/network/auth-header/src/getAuthHeadersFromConfig.ts index 0817417278..0aeaa7e58b 100644 --- a/network/auth-header/src/getAuthHeadersFromConfig.ts +++ b/network/auth-header/src/getAuthHeadersFromConfig.ts @@ -1,80 +1,54 @@ import { spawnSync } from 'node:child_process' -import fs from 'node:fs' -import path from 'node:path' -import { nerfDart } from '@pnpm/config.nerf-dart' import { PnpmError } from '@pnpm/error' +import type { Creds, RegistryConfig, TokenHelper } from '@pnpm/types' -export function getAuthHeadersFromConfig ( - { allSettings, userSettings }: { - allSettings: Record - userSettings: Record - } +export function getAuthHeadersFromCreds ( + configByUri: Record, + defaultRegistry: string ): Record { const authHeaderValueByURI: Record = {} - for (const [key, value] of Object.entries(allSettings)) { - const [uri, authType] = splitKey(key) - switch (authType) { - case '_authToken': { - authHeaderValueByURI[uri] = `Bearer ${value}` - continue - } - case '_auth': { - authHeaderValueByURI[uri] = `Basic ${value}` - continue - } - case 'username': { - if (`${uri}:_password` in allSettings) { - authHeaderValueByURI[uri] = basicAuth(value, allSettings[`${uri}:_password`]) - } - } + for (const [uri, registryConfig] of Object.entries(configByUri)) { + if (uri === '') continue // default auth handled below + const header = credsToHeader(registryConfig.creds) + if (header) { + authHeaderValueByURI[uri] = header } } - for (const [key, value] of Object.entries(userSettings)) { - const [uri, authType] = splitKey(key) - if (authType === 'tokenHelper') { - authHeaderValueByURI[uri] = loadToken(value, key) + const defaultConfig = configByUri[''] + if (defaultConfig?.creds) { + const header = credsToHeader(defaultConfig.creds) + if (header) { + authHeaderValueByURI[defaultRegistry] = header } } - const registry = allSettings['registry'] ? nerfDart(allSettings['registry']) : '//registry.npmjs.org/' - if (userSettings['tokenHelper']) { - authHeaderValueByURI[registry] = loadToken(userSettings['tokenHelper'], 'tokenHelper') - } else if (allSettings['_authToken']) { - authHeaderValueByURI[registry] = `Bearer ${allSettings['_authToken']}` - } else if (allSettings['_auth']) { - authHeaderValueByURI[registry] = `Basic ${allSettings['_auth']}` - } else if (allSettings['_password'] && allSettings['username']) { - authHeaderValueByURI[registry] = basicAuth(allSettings['username'], allSettings['_password']) - } return authHeaderValueByURI } -function basicAuth (username: string, encodedPassword: string): string { - const password = Buffer.from(encodedPassword, 'base64').toString('utf8') - return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}` +function credsToHeader (creds?: Creds): string | undefined { + if (!creds) return undefined + if (creds.tokenHelper) { + return executeTokenHelper(creds.tokenHelper) + } + if (creds.authToken) { + return `Bearer ${creds.authToken}` + } + if (creds.basicAuth) { + return `Basic ${Buffer.from(`${creds.basicAuth.username}:${creds.basicAuth.password}`, 'utf8').toString('base64')}` + } + return undefined } -function splitKey (key: string): string[] { - const index = key.lastIndexOf(':') - if (index === -1) { - return [key, ''] - } - return [key.slice(0, index), key.slice(index + 1)] -} - -export function loadToken (helperPath: string, settingName: string): string { - if (!path.isAbsolute(helperPath) || !fs.existsSync(helperPath)) { - throw new PnpmError('BAD_TOKEN_HELPER_PATH', `${settingName} must be an absolute path, without arguments`) - } - - const spawnResult = spawnSync(helperPath, { shell: true }) +function executeTokenHelper (tokenHelper: TokenHelper): string { + const [cmd, ...args] = tokenHelper + const spawnResult = spawnSync(cmd, args, { stdio: 'pipe' }) if (spawnResult.status !== 0) { - throw new PnpmError('TOKEN_HELPER_ERROR_STATUS', `Error running "${helperPath}" as a token helper, configured as ${settingName}. Exit code ${spawnResult.status?.toString() ?? ''}`) + throw new PnpmError('TOKEN_HELPER_ERROR_STATUS', `Error running "${cmd}" as a token helper. Exit code ${spawnResult.status?.toString() ?? ''}`) } const token = spawnResult.stdout.toString('utf8').trimEnd() if (!token) { - throw new PnpmError('TOKEN_HELPER_EMPTY_TOKEN', `Token helper "${helperPath}", configured as ${settingName}, returned an empty token`) + throw new PnpmError('TOKEN_HELPER_EMPTY_TOKEN', `Token helper "${cmd}" returned an empty token`) } // If the token already contains an auth scheme (e.g. "Bearer ...", "Basic ..."), // return it as-is. diff --git a/network/auth-header/src/index.ts b/network/auth-header/src/index.ts index 6a86c1edfb..bdaf21ac85 100644 --- a/network/auth-header/src/index.ts +++ b/network/auth-header/src/index.ts @@ -1,22 +1,15 @@ import { nerfDart } from '@pnpm/config.nerf-dart' +import type { RegistryConfig } from '@pnpm/types' -import { getAuthHeadersFromConfig, loadToken } from './getAuthHeadersFromConfig.js' +import { getAuthHeadersFromCreds } from './getAuthHeadersFromConfig.js' import { removePort } from './helpers/removePort.js' -export { - loadToken, -} - export function createGetAuthHeaderByURI ( - opts: { - allSettings: Record - userSettings?: Record - } + configByUri: Record, + defaultRegistry?: string ): (uri: string) => string | undefined { - const authHeaders = getAuthHeadersFromConfig({ - allSettings: opts.allSettings, - userSettings: opts.userSettings ?? {}, - }) + const registry = defaultRegistry ? nerfDart(defaultRegistry) : '//registry.npmjs.org/' + const authHeaders = getAuthHeadersFromCreds(configByUri, registry) if (Object.keys(authHeaders).length === 0) return (uri: string) => basicAuth(new URL(uri)) return getAuthHeaderByURI.bind(null, authHeaders, getMaxParts(Object.keys(authHeaders))) } diff --git a/network/auth-header/test/getAuthHeaderByURI.ts b/network/auth-header/test/getAuthHeaderByURI.ts index d30f179647..b9b923ed7d 100644 --- a/network/auth-header/test/getAuthHeaderByURI.ts +++ b/network/auth-header/test/getAuthHeaderByURI.ts @@ -1,17 +1,14 @@ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header' -const opts = { - allSettings: { - '//reg.com/:_authToken': 'abc123', - '//reg.co/tarballs/:_authToken': 'xxx', - '//reg.gg:8888/:_authToken': '0000', - '//custom.domain.com/artifactory/api/npm/npm-virtual/:_authToken': 'xyz', - }, - userSettings: {}, +const configByUri = { + '//reg.com/': { creds: { authToken: 'abc123' } }, + '//reg.co/tarballs/': { creds: { authToken: 'xxx' } }, + '//reg.gg:8888/': { creds: { authToken: '0000' } }, + '//custom.domain.com/artifactory/api/npm/npm-virtual/': { creds: { authToken: 'xyz' } }, } test('getAuthHeaderByURI()', () => { - const getAuthHeaderByURI = createGetAuthHeaderByURI(opts) + const getAuthHeaderByURI = createGetAuthHeaderByURI(configByUri) expect(getAuthHeaderByURI('https://reg.com/')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://reg.com/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://reg.com:8080/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') @@ -22,9 +19,7 @@ test('getAuthHeaderByURI()', () => { }) test('getAuthHeaderByURI() basic auth without settings', () => { - const getAuthHeaderByURI = createGetAuthHeaderByURI({ - allSettings: {}, - }) + const getAuthHeaderByURI = createGetAuthHeaderByURI({}) expect(getAuthHeaderByURI('https://user:secret@reg.io/')).toBe('Basic ' + btoa('user:secret')) expect(getAuthHeaderByURI('https://user:@reg.io/')).toBe('Basic ' + btoa('user:')) expect(getAuthHeaderByURI('https://:secret@reg.io/')).toBe('Basic ' + btoa(':secret')) @@ -32,7 +27,7 @@ test('getAuthHeaderByURI() basic auth without settings', () => { }) test('getAuthHeaderByURI() basic auth with settings', () => { - const getAuthHeaderByURI = createGetAuthHeaderByURI(opts) + const getAuthHeaderByURI = createGetAuthHeaderByURI(configByUri) expect(getAuthHeaderByURI('https://user:secret@reg.com/')).toBe('Basic ' + btoa('user:secret')) expect(getAuthHeaderByURI('https://user:secret@reg.com/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret')) expect(getAuthHeaderByURI('https://user:secret@reg.com:8080/foo/-/foo-1.0.0.tgz')).toBe('Basic ' + btoa('user:secret')) @@ -43,7 +38,7 @@ test('getAuthHeaderByURI() basic auth with settings', () => { }) test('getAuthHeaderByURI() https port 443 checks', () => { - const getAuthHeaderByURI = createGetAuthHeaderByURI(opts) + const getAuthHeaderByURI = createGetAuthHeaderByURI(configByUri) expect(getAuthHeaderByURI('https://custom.domain.com:443/artifactory/api/npm/npm-virtual/')).toBe('Bearer xyz') expect(getAuthHeaderByURI('https://custom.domain.com:443/artifactory/api/npm/')).toBeUndefined() expect(getAuthHeaderByURI('https://custom.domain.com:443/artifactory/api/npm/-/@platform/device-utils-1.0.0.tgz')).toBeUndefined() @@ -52,33 +47,39 @@ test('getAuthHeaderByURI() https port 443 checks', () => { test('getAuthHeaderByURI() when default ports are specified', () => { const getAuthHeaderByURI = createGetAuthHeaderByURI({ - allSettings: { - '//reg.com/:_authToken': 'abc123', - }, - userSettings: {}, + '//reg.com/': { creds: { authToken: 'abc123' } }, }) expect(getAuthHeaderByURI('https://reg.com:443/')).toBe('Bearer abc123') expect(getAuthHeaderByURI('http://reg.com:80/')).toBe('Bearer abc123') }) test('returns undefined when the auth header is not found', () => { - expect(createGetAuthHeaderByURI({ allSettings: {}, userSettings: {} })('http://reg.com')).toBeUndefined() + expect(createGetAuthHeaderByURI({})('http://reg.com')).toBeUndefined() }) test('getAuthHeaderByURI() when the registry has pathnames', () => { const getAuthHeaderByURI = createGetAuthHeaderByURI({ - allSettings: { - '//npm.pkg.github.com/pnpm/:_authToken': 'abc123', - }, - userSettings: {}, + '//npm.pkg.github.com/pnpm/': { creds: { authToken: 'abc123' } }, }) expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/')).toBe('Bearer abc123') expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') - expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') - expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') - expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') - expect(getAuthHeaderByURI('https://npm.pkg.github.com/pnpm/foo/-/foo-1.0.0.tgz')).toBe('Bearer abc123') +}) + +test('getAuthHeaderByURI() with default registry auth', () => { + const getAuthHeaderByURI = createGetAuthHeaderByURI( + { '': { creds: { authToken: 'default-token' } } }, + 'https://registry.npmjs.org/' + ) + expect(getAuthHeaderByURI('https://registry.npmjs.org/')).toBe('Bearer default-token') + expect(getAuthHeaderByURI('https://registry.npmjs.org/foo/-/foo-1.0.0.tgz')).toBe('Bearer default-token') +}) + +test('getAuthHeaderByURI() with basic auth via basicAuth', () => { + const getAuthHeaderByURI = createGetAuthHeaderByURI({ + '//reg.com/': { creds: { basicAuth: { username: 'user', password: 'pass' } } }, + }) + expect(getAuthHeaderByURI('https://reg.com/')).toBe('Basic ' + btoa('user:pass')) }) diff --git a/network/auth-header/test/getAuthHeadersFromConfig.test.ts b/network/auth-header/test/getAuthHeadersFromConfig.test.ts index 8edcaa2e9f..816a7d392d 100644 --- a/network/auth-header/test/getAuthHeadersFromConfig.test.ts +++ b/network/auth-header/test/getAuthHeadersFromConfig.test.ts @@ -1,9 +1,7 @@ import os from 'node:os' import path from 'node:path' -import { Buffer } from 'safe-buffer' - -import { getAuthHeadersFromConfig } from '../src/getAuthHeadersFromConfig.js' +import { getAuthHeadersFromCreds } from '../src/getAuthHeadersFromConfig.js' const osTokenHelper = { linux: path.join(import.meta.dirname, 'utils/test-exec.js'), @@ -28,126 +26,62 @@ const osErrorTokenHelper = { // Only exception is win32, all others behave like linux const osFamily = os.platform() === 'win32' ? 'win32' : 'linux' -describe('getAuthHeadersFromConfig()', () => { - it('should get settings', () => { - const allSettings = { - '//registry.npmjs.org/:_authToken': 'abc123', - '//registry.foobar.eu/:_password': encodeBase64('foobar'), - '//registry.foobar.eu/:username': 'foobar', - '//registry.hu/:_auth': 'foobar', - '//localhost:3000/:_auth': 'foobar', - } - const userSettings = {} - expect(getAuthHeadersFromConfig({ allSettings, userSettings })).toStrictEqual({ +describe('getAuthHeadersFromCreds()', () => { + it('should convert auth token to Bearer header', () => { + const result = getAuthHeadersFromCreds({ + '//registry.npmjs.org/': { creds: { authToken: 'abc123' } }, + '//registry.hu/': { creds: { authToken: 'def456' } }, + }, '//registry.npmjs.org/') + expect(result).toStrictEqual({ '//registry.npmjs.org/': 'Bearer abc123', + '//registry.hu/': 'Bearer def456', + }) + }) + it('should convert basicAuth to Basic header', () => { + const result = getAuthHeadersFromCreds({ + '//registry.foobar.eu/': { creds: { basicAuth: { username: 'foobar', password: 'foobar' } } }, + }, '//registry.npmjs.org/') + expect(result).toStrictEqual({ '//registry.foobar.eu/': 'Basic Zm9vYmFyOmZvb2Jhcg==', - '//registry.hu/': 'Basic foobar', - '//localhost:3000/': 'Basic foobar', }) }) - describe('should get settings for the default registry', () => { - it('_auth', () => { - const allSettings = { - registry: 'https://reg.com/', - _auth: 'foobar', - } - expect(getAuthHeadersFromConfig({ allSettings, userSettings: {} })).toStrictEqual({ - '//reg.com/': 'Basic foobar', - }) - }) - it('username/_password', () => { - const allSettings = { - registry: 'https://reg.com/', - username: 'foo', - _password: encodeBase64('bar'), - } - expect(getAuthHeadersFromConfig({ allSettings, userSettings: {} })).toStrictEqual({ - '//reg.com/': `Basic ${encodeBase64('foo:bar')}`, - }) - }) - it('tokenHelper', () => { - const allSettings = { - registry: 'https://reg.com/', - } - const userSettings = { - tokenHelper: osTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings, userSettings })).toStrictEqual({ - '//reg.com/': 'Bearer token-from-spawn', - }) - }) - it('only read token helper from user config', () => { - const allSettings = { - registry: 'https://reg.com/', - tokenHelper: osTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings, userSettings: {} })).toStrictEqual({}) + it('should handle default registry auth (empty key)', () => { + const result = getAuthHeadersFromCreds({ + '': { creds: { authToken: 'default-token' } }, + }, '//reg.com/') + expect(result).toStrictEqual({ + '//reg.com/': 'Bearer default-token', }) }) - it('should get tokenHelper', () => { - const userSettings = { - '//registry.foobar.eu/:tokenHelper': osTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings: {}, userSettings })).toStrictEqual({ + it('should execute tokenHelper', () => { + const result = getAuthHeadersFromCreds({ + '//registry.foobar.eu/': { creds: { tokenHelper: [osTokenHelper[osFamily]] } }, + }, '//registry.npmjs.org/') + expect(result).toStrictEqual({ '//registry.foobar.eu/': 'Bearer token-from-spawn', }) }) - it('should throw an error if the token helper is not an absolute path', () => { - expect(() => getAuthHeadersFromConfig({ - allSettings: {}, - userSettings: { - '//reg.com:tokenHelper': './utils/text-exec.js', - }, - })).toThrow('must be an absolute path, without arguments') - }) - it('should throw an error if the token helper is not an absolute path with args', () => { - expect(() => getAuthHeadersFromConfig({ - allSettings: {}, - userSettings: { - '//reg.com:tokenHelper': `${osTokenHelper[osFamily]} arg1`, - }, - })).toThrow('must be an absolute path, without arguments') - }) - it('should throw an error if the token helper fails', () => { - expect(() => getAuthHeadersFromConfig({ - allSettings: {}, - userSettings: { - '//reg.com:tokenHelper': osErrorTokenHelper[osFamily], - }, - })).toThrow('Exit code') - }) - it('only read token helper from user config', () => { - const allSettings = { - '//reg.com:tokenHelper': osTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings, userSettings: {} })).toStrictEqual({}) - }) it('should prepend Bearer to raw token from tokenHelper', () => { - const userSettings = { - '//registry.foobar.eu/:tokenHelper': osRawTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings: {}, userSettings })).toStrictEqual({ + const result = getAuthHeadersFromCreds({ + '//registry.foobar.eu/': { creds: { tokenHelper: [osRawTokenHelper[osFamily]] } }, + }, '//registry.npmjs.org/') + expect(result).toStrictEqual({ '//registry.foobar.eu/': 'Bearer raw-token-no-scheme', }) }) - it('should not modify token that already has an auth scheme', () => { - const userSettings = { - '//registry.foobar.eu/:tokenHelper': osTokenHelper[osFamily], - } - expect(getAuthHeadersFromConfig({ allSettings: {}, userSettings })).toStrictEqual({ - '//registry.foobar.eu/': 'Bearer token-from-spawn', - }) + it('should throw an error if the token helper fails', () => { + expect(() => getAuthHeadersFromCreds({ + '//reg.com/': { creds: { tokenHelper: [osErrorTokenHelper[osFamily]] } }, + }, '//registry.npmjs.org/')).toThrow('Exit code') }) it('should throw an error if the token helper returns an empty token', () => { - expect(() => getAuthHeadersFromConfig({ - allSettings: {}, - userSettings: { - '//reg.com:tokenHelper': osEmptyTokenHelper[osFamily], - }, - })).toThrow('returned an empty token') + expect(() => getAuthHeadersFromCreds({ + '//reg.com/': { creds: { tokenHelper: [osEmptyTokenHelper[osFamily]] } }, + }, '//registry.npmjs.org/')).toThrow('returned an empty token') + }) + it('should return empty object when no auth infos', () => { + const result = getAuthHeadersFromCreds({}, '//registry.npmjs.org/') + expect(result).toStrictEqual({}) }) }) -function encodeBase64 (s: string) { - return Buffer.from(s, 'utf8').toString('base64') -} diff --git a/network/auth-header/tsconfig.json b/network/auth-header/tsconfig.json index 5e5ef544a0..dbf37d88ad 100644 --- a/network/auth-header/tsconfig.json +++ b/network/auth-header/tsconfig.json @@ -11,6 +11,9 @@ "references": [ { "path": "../../core/error" + }, + { + "path": "../../core/types" } ] } diff --git a/network/fetch/src/dispatcher.ts b/network/fetch/src/dispatcher.ts index fe07d669d4..2ddedc7d9c 100644 --- a/network/fetch/src/dispatcher.ts +++ b/network/fetch/src/dispatcher.ts @@ -4,7 +4,7 @@ import { URL } from 'node:url' import { nerfDart } from '@pnpm/config.nerf-dart' import { PnpmError } from '@pnpm/error' -import type { SslConfig } from '@pnpm/types' +import type { TlsConfig } from '@pnpm/types' import { LRUCache } from 'lru-cache' import { SocksClient } from 'socks' import { Agent, type Dispatcher, ProxyAgent, setGlobalDispatcher } from 'undici' @@ -38,6 +38,8 @@ const DISPATCHER_CACHE = new LRUCache({ }, }) +export type ClientCertificates = Record + export interface DispatcherOptions { ca?: string | string[] | Buffer cert?: string | string[] | Buffer @@ -49,7 +51,7 @@ export interface DispatcherOptions { httpProxy?: string httpsProxy?: string noProxy?: boolean | string - clientCertificates?: Record + clientCertificates?: ClientCertificates } /** @@ -78,6 +80,15 @@ export function getDispatcher (uri: string, opts: DispatcherOptions): Dispatcher return getNonProxyDispatcher(parsedUri, opts) } +function hasClientCertificates (certs?: ClientCertificates): boolean { + if (!certs) return false + for (const uri in certs) { + const entry = certs[uri] + if (entry.cert || entry.key || entry.ca) return true + } + return false +} + function needsCustomDispatcher (opts: DispatcherOptions): boolean { return Boolean( opts.httpProxy || @@ -87,7 +98,7 @@ function needsCustomDispatcher (opts: DispatcherOptions): boolean { opts.key || opts.localAddress || opts.strictSsl === false || - opts.clientCertificates || + hasClientCertificates(opts.clientCertificates) || opts.maxSockets ) } diff --git a/network/fetch/src/fetchFromRegistry.ts b/network/fetch/src/fetchFromRegistry.ts index 33bf2ee730..9eb522fa4f 100644 --- a/network/fetch/src/fetchFromRegistry.ts +++ b/network/fetch/src/fetchFromRegistry.ts @@ -1,9 +1,9 @@ import { URL } from 'node:url' import type { FetchFromRegistry } from '@pnpm/fetching.types' -import type { SslConfig } from '@pnpm/types' +import type { RegistryConfig } from '@pnpm/types' -import { type DispatcherOptions, getDispatcher } from './dispatcher.js' +import { type ClientCertificates, type DispatcherOptions, getDispatcher } from './dispatcher.js' import { fetch, isRedirect, type RequestInit } from './fetch.js' const USER_AGENT = 'pnpm' // or maybe make it `${pkg.name}/${pkg.version} (+https://npm.im/${pkg.name})` @@ -35,7 +35,7 @@ export type { DispatcherOptions } export interface CreateFetchFromRegistryOptions extends DispatcherOptions { userAgent?: string - sslConfigs?: Record + configByUri?: Record } export function createFetchFromRegistry (defaultOpts: CreateFetchFromRegistryOptions): FetchFromRegistry { @@ -64,7 +64,7 @@ export function createFetchFromRegistry (defaultOpts: CreateFetchFromRegistryOpt ...defaultOpts, ...opts, strictSsl: defaultOpts.strictSsl ?? true, - clientCertificates: defaultOpts.sslConfigs, + clientCertificates: extractTlsConfigs(defaultOpts.configByUri), } const response = await fetchWithDispatcher(urlObject, { @@ -115,6 +115,18 @@ function getHeaders ( return headers } +function extractTlsConfigs (configByUri?: Record): ClientCertificates | undefined { + if (!configByUri) return undefined + let result: ClientCertificates | undefined + for (const [uri, config] of Object.entries(configByUri)) { + if (config.tls) { + result ??= {} + result[uri] = config.tls + } + } + return result +} + function resolveRedirectUrl (response: Response, currentUrl: URL): URL { const location = response.headers.get('location') if (!location) { diff --git a/network/fetch/test/fetchFromRegistry.test.ts b/network/fetch/test/fetchFromRegistry.test.ts index 5e3ac00b50..fb6da7ebb4 100644 --- a/network/fetch/test/fetchFromRegistry.test.ts +++ b/network/fetch/test/fetchFromRegistry.test.ts @@ -136,16 +136,18 @@ test('fetch from registry with client certificate authentication', async () => { await proxyServer.start() - const sslConfigs = { + const configByUri = { [`//localhost:${randomPort}/`]: { - ca: fs.readFileSync(path.join(CERTS_DIR, 'ca-crt.pem'), 'utf8'), - cert: fs.readFileSync(path.join(CERTS_DIR, 'client-crt.pem'), 'utf8'), - key: fs.readFileSync(path.join(CERTS_DIR, 'client-key.pem'), 'utf8'), + tls: { + ca: fs.readFileSync(path.join(CERTS_DIR, 'ca-crt.pem'), 'utf8'), + cert: fs.readFileSync(path.join(CERTS_DIR, 'client-crt.pem'), 'utf8'), + key: fs.readFileSync(path.join(CERTS_DIR, 'client-key.pem'), 'utf8'), + }, }, } const fetchFromRegistry = createFetchFromRegistry({ - sslConfigs, + configByUri, strictSsl: false, }) diff --git a/patching/commands/test/patch.test.ts b/patching/commands/test/patch.test.ts index 53814381ca..5737fbd9db 100644 --- a/patching/commands/test/patch.test.ts +++ b/patching/commands/test/patch.test.ts @@ -25,9 +25,7 @@ const f = fixtures(import.meta.dirname) const basePatchOption = { pnpmHomeDir: '', - authConfig: { - registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, - }, + configByUri: {}, registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` }, userConfig: {}, virtualStoreDir: 'node_modules/.pnpm', diff --git a/patching/commands/test/utils/index.ts b/patching/commands/test/utils/index.ts index cda250f491..1e13e113af 100644 --- a/patching/commands/test/utils/index.ts +++ b/patching/commands/test/utils/index.ts @@ -38,7 +38,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a07fd0c64..ac04b3d759 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6761,6 +6761,9 @@ importers: '@pnpm/error': specifier: workspace:* version: link:../../core/error + '@pnpm/types': + specifier: workspace:* + version: link:../../core/types devDependencies: '@pnpm/network.auth-header': specifier: workspace:* diff --git a/pnpm/src/checkForUpdates.ts b/pnpm/src/checkForUpdates.ts index 564d999b14..7d290af0ee 100644 --- a/pnpm/src/checkForUpdates.ts +++ b/pnpm/src/checkForUpdates.ts @@ -27,7 +27,7 @@ export async function checkForUpdates (config: Config): Promise { const { resolve } = createResolver({ ...config, - authConfig: config.authConfig, + configByUri: config.configByUri, retry: { retries: 0, }, diff --git a/releasing/commands/src/publish/pack.ts b/releasing/commands/src/publish/pack.ts index cee4f0df4d..756d36a1dc 100644 --- a/releasing/commands/src/publish/pack.ts +++ b/releasing/commands/src/publish/pack.ts @@ -95,7 +95,6 @@ export function help (): string { export type PackOptions = Pick & Pick { - const { registry, auth, ssl } = findAuthSslInfo(manifest, options) + const { registry, config } = findRegistryInfo(manifest, options) + const { creds, tls } = config ?? {} const { access, @@ -118,22 +102,15 @@ async function createPublishOptions (manifest: ExportedManifest, options: Publis // always fall back to prompting the user for an OTP code, even when the user // has no OTP set up. authType: 'web', - ca: ssl?.ca, - cert: Array.isArray(ssl?.cert) ? ssl.cert.join('\n') : ssl?.cert, - key: ssl?.key, + ca: tls?.ca, + cert: tls?.cert, + key: tls?.key, npmCommand: 'publish', - token: auth && extractToken(auth), - username: auth?.authUserPass?.username, - password: auth?.authUserPass?.password, + token: creds && extractToken(creds), + username: creds?.basicAuth?.username, + password: creds?.basicAuth?.password, } - // This is necessary because getNetworkConfigs initialized them as { cert: '', key: '' } - // which may be a problem. - // The real fix is to change the type `SslConfig` into that of partial properties, but that - // is out of scope for now. - removeEmptyStringProperty(publishOptions, 'cert') - removeEmptyStringProperty(publishOptions, 'key') - if (registry) { const oidcTokenProvenance = await fetchTokenAndProvenanceByOidcIfApplicable(publishOptions, manifest.name, registry, options) publishOptions.token ??= oidcTokenProvenance?.authToken @@ -145,26 +122,19 @@ async function createPublishOptions (manifest: ExportedManifest, options: Publis return publishOptions } -interface AuthSslInfo { +interface RegistryInfo { registry: NormalizedRegistryUrl - auth: Pick - ssl: Pick + config: RegistryConfig } /** - * Find auth and ssl information according to {@link https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration}. - * - * The example `.npmrc` demonstrated inheritance. + * Find credentials and SSL info for a package's registry. + * Follows {@link https://docs.npmjs.com/cli/v10/configuring-npm/npmrc#auth-related-configuration}. */ -function findAuthSslInfo ( +function findRegistryInfo ( { name }: ExportedManifest, - { - authInfos, - sslConfigs, - registries, - ...defaultInfos - }: Pick -): Partial { + { configByUri, registries }: Pick +): Partial { // eslint-disable-next-line regexp/no-unused-capturing-group const scopedMatches = /@(?[^/]+)\/(?[^/]+)/.exec(name) @@ -181,45 +151,36 @@ function findAuthSslInfo ( longestConfigKey: initialRegistryConfigKey, } = supportedRegistryInfo - const result: Partial = { registry } - + let creds: Creds | undefined + let tls: RegistryConfig['tls'] = {} for (const registryConfigKey of allRegistryConfigKeys(initialRegistryConfigKey)) { - const auth: Pick | undefined = authInfos[registryConfigKey] - const ssl: Pick | undefined = sslConfigs[registryConfigKey] - - result.auth ??= auth // old auth from longer path collectively overrides new auth from shorter path - - result.ssl = { - ...ssl, - ...result.ssl, // old ssl from longer path individually overrides new ssl from shorter path - } + const entry = configByUri[registryConfigKey] + if (!entry) continue + // Auth from longer path collectively overrides shorter path + creds ??= entry.creds + // TLS from longer path individually overrides shorter path + tls = { ...entry.tls, ...tls } } - if ( - nonNormalizedRegistry !== registries.default && - registry !== registries.default && - registry !== parseSupportedRegistryUrl(registries.default)?.normalizedUrl - ) { - return result + const isDefaultRegistry = + nonNormalizedRegistry === registries.default || + registry === registries.default || + registry === parseSupportedRegistryUrl(registries.default)?.normalizedUrl + + if (isDefaultRegistry) { + creds ??= configByUri['']?.creds } return { registry, - auth: result.auth ?? defaultInfos, // old auth from longer path collectively overrides default auth - ssl: { - ...defaultInfos, - ...result.ssl, // old ssl from longer path individually overrides default ssl - }, + config: { creds, tls }, } } function extractToken ({ authToken, tokenHelper, -}: { - authToken?: string - tokenHelper?: [string, ...string[]] -}): string | undefined { +}: Pick): string | undefined { if (authToken) return authToken if (tokenHelper) { return executeTokenHelper(tokenHelper, { globalWarn }) @@ -341,12 +302,6 @@ function appendAuthOptionsForRegistry (targetPublishOptions: PublishOptions, reg targetPublishOptions[`${registryConfigKey}:_password`] ??= targetPublishOptions.password && btoa(targetPublishOptions.password) } -function removeEmptyStringProperty (object: Partial>, key: Key): void { - if (!object[key]) { - delete object[key] - } -} - function pruneUndefined (object: Record): void { for (const key in object) { if (object[key] === undefined) { diff --git a/releasing/commands/src/publish/recursivePublish.ts b/releasing/commands/src/publish/recursivePublish.ts index f5bb251ba8..653a00d26a 100644 --- a/releasing/commands/src/publish/recursivePublish.ts +++ b/releasing/commands/src/publish/recursivePublish.ts @@ -19,7 +19,7 @@ export type PublishRecursiveOpts = Required> & @@ -51,7 +51,6 @@ Partial> & Partial wsPkg.package) const { resolve } = createResolver({ ...opts, - authConfig: opts.authConfig, - userConfig: opts.userConfig, + configByUri: opts.configByUri, retry: { factor: opts.fetchRetryFactor, maxTimeout: opts.fetchRetryMaxtimeout, diff --git a/releasing/commands/test/deploy/utils/index.ts b/releasing/commands/test/deploy/utils/index.ts index 3e1966faaa..3b1e9c876b 100644 --- a/releasing/commands/test/deploy/utils/index.ts +++ b/releasing/commands/test/deploy/utils/index.ts @@ -40,7 +40,7 @@ export const DEFAULT_OPTS = { pnpmHomeDir: '', preferWorkspacePackages: true, proxy: undefined, - authConfig: { registry: REGISTRY }, + configByUri: {}, registries: { default: REGISTRY }, registry: REGISTRY, rootProjectManifestDir: '', diff --git a/releasing/commands/test/publish/recursivePublish.ts b/releasing/commands/test/publish/recursivePublish.ts index eca235f57d..1f79603490 100644 --- a/releasing/commands/test/publish/recursivePublish.ts +++ b/releasing/commands/test/publish/recursivePublish.ts @@ -299,10 +299,7 @@ test('errors on fake registry', async () => { const promise = publish.handler({ ...DEFAULT_OPTS, ...await filterProjectsBySelectorObjectsFromDir(process.cwd(), []), - authConfig: { - ...DEFAULT_OPTS.authConfig, - registry: fakeRegistry, - }, + configByUri: {}, registries: { ...DEFAULT_OPTS.registries, default: fakeRegistry, diff --git a/releasing/commands/test/publish/utils/index.ts b/releasing/commands/test/publish/utils/index.ts index 76c4700462..1d6270cea6 100644 --- a/releasing/commands/test/publish/utils/index.ts +++ b/releasing/commands/test/publish/utils/index.ts @@ -4,7 +4,7 @@ import { safeExeca as execa } from 'execa' const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` export const DEFAULT_OPTS = { - authInfos: {}, + configByUri: {}, argv: { original: [], }, @@ -36,13 +36,11 @@ export const DEFAULT_OPTS = { pnpmfile: ['./.pnpmfile.cjs'], pnpmHomeDir: '', proxy: undefined, - authConfig: { registry: REGISTRY }, registries: { default: REGISTRY }, registry: REGISTRY, sort: true, cacheDir: '../cache', strictSsl: false, - sslConfigs: {}, userAgent: 'pnpm', userConfig: {}, useRunningStoreServer: false, diff --git a/store/commands/src/inspecting/catIndex.ts b/store/commands/src/inspecting/catIndex.ts index 566b9f6620..93e88c8575 100644 --- a/store/commands/src/inspecting/catIndex.ts +++ b/store/commands/src/inspecting/catIndex.ts @@ -32,14 +32,13 @@ export function help (): string { export type CatIndexCommandOptions = Pick< Config, -| 'authConfig' +| 'configByUri' | 'pnpmHomeDir' | 'storeDir' | 'lockfileDir' | 'dir' | 'registries' | 'cacheDir' -| 'sslConfigs' > export async function handler (opts: CatIndexCommandOptions, params: string[]): Promise { @@ -70,7 +69,7 @@ export async function handler (opts: CatIndexCommandOptions, params: string[]): }) const { resolve } = createResolver({ ...opts, - authConfig: opts.authConfig, + configByUri: opts.configByUri, }) const pkgSnapshot = await resolve( { alias, bareSpecifier }, diff --git a/store/commands/test/store/storeAdd.ts b/store/commands/test/store/storeAdd.ts index 0365324463..1259b19046 100644 --- a/store/commands/test/store/storeAdd.ts +++ b/store/commands/test/store/storeAdd.ts @@ -17,12 +17,9 @@ test('pnpm store add express@4.16.3', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, - }, + configByUri: {}, registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` }, storeDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['add', 'express@4.16.3']) @@ -41,15 +38,12 @@ test('pnpm store add scoped package that uses not the standard registry', async cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: 'https://registry.npmjs.org/', - }, + configByUri: {}, registries: { '@foo': `http://localhost:${REGISTRY_MOCK_PORT}/`, default: 'https://registry.npmjs.org/', }, storeDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['add', '@foo/no-deps@1.0.0']) @@ -71,15 +65,12 @@ test('should fail if some packages can not be added', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: 'https://registry.npmjs.org/', - }, + configByUri: {}, registries: { '@foo': `http://localhost:${REGISTRY_MOCK_PORT}/`, default: 'https://registry.npmjs.org/', }, storeDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['add', '@pnpm/this-does-not-exist']) diff --git a/store/commands/test/store/storePath.ts b/store/commands/test/store/storePath.ts index ac732e261e..592745c385 100644 --- a/store/commands/test/store/storePath.ts +++ b/store/commands/test/store/storePath.ts @@ -15,12 +15,9 @@ test('CLI prints the current store path', async () => { cacheDir: path.resolve('cache'), dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: '/home/example/.pnpm-store', - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['path']) @@ -44,12 +41,9 @@ test('CLI prints the current store path when storeDir is relative', async () => dir: subpackageDir, workspaceDir, pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: relativeStoreDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['path']) diff --git a/store/commands/test/store/storePrune.ts b/store/commands/test/store/storePrune.ts index 5c4417eb87..f5815a6a16 100644 --- a/store/commands/test/store/storePrune.ts +++ b/store/commands/test/store/storePrune.ts @@ -47,13 +47,10 @@ test('remove unreferenced packages', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -72,13 +69,10 @@ test('remove unreferenced packages', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -120,13 +114,10 @@ test('prune outputs total size of removed files', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -158,13 +149,10 @@ test('remove packages that are used by project that no longer exist', async () = cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -206,12 +194,9 @@ test('keep dependencies used by others', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -232,12 +217,9 @@ test('keep dependency used by package', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -256,12 +238,9 @@ test('prune will skip scanning non-directory in storeDir', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -283,13 +262,10 @@ test('prune does not fail if the store contains an unexpected directory', async cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -321,13 +297,10 @@ test('prune removes alien files from the store if the --force flag is used', asy cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir, - userConfig: {}, force: true, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, @@ -352,13 +325,10 @@ describe('prune when store directory is not properly configured', () => { cacheDir: path.resolve('cache'), dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter, storeDir: nonExistentStoreDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: 120, }, ['prune']) @@ -388,13 +358,10 @@ describe('prune when store directory is not properly configured', () => { cacheDir: path.resolve('cache'), dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter: jest.fn(), storeDir: fileInPlaceOfStoreDir, - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: 120, }, ['prune']) @@ -449,13 +416,10 @@ test('prune removes cache directories that outlives dlx-cache-max-age', async () cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, reporter () {}, storeDir, - userConfig: {}, dlxCacheMaxAge: 7, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -514,12 +478,9 @@ describe('global virtual store prune', () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: path.join(storeDir, STORE_VERSION), - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -582,12 +543,9 @@ describe('global virtual store prune', () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: path.join(storeDir, STORE_VERSION), - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -653,12 +611,9 @@ describe('global virtual store prune', () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: path.join(storeDir, STORE_VERSION), - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) @@ -737,12 +692,9 @@ describe('global virtual store prune', () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: { default: REGISTRY }, storeDir: path.join(storeDir, STORE_VERSION), - userConfig: {}, dlxCacheMaxAge: Infinity, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['prune']) diff --git a/store/commands/test/store/storeStatus.ts b/store/commands/test/store/storeStatus.ts index b0db4e3b01..7a0be4ba47 100644 --- a/store/commands/test/store/storeStatus.ts +++ b/store/commands/test/store/storeStatus.ts @@ -41,12 +41,9 @@ test('CLI fails when store status finds modified packages', async () => { cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: modulesState!.registries!, storeDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['status']) @@ -95,12 +92,9 @@ test('CLI does not fail when store status does not find modified packages', asyn cacheDir, dir: process.cwd(), pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: modulesState!.registries!, storeDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['status']) @@ -141,12 +135,9 @@ storeDir: "${relativeStoreDir}" dir: subpackageDir, workspaceDir, pnpmHomeDir: '', - authConfig: { - registry: REGISTRY, - }, + configByUri: {}, registries: modulesState!.registries!, storeDir: relativeStoreDir, - userConfig: {}, dlxCacheMaxAge: 0, virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120, }, ['status']) diff --git a/store/connection-manager/src/createNewStoreController.ts b/store/connection-manager/src/createNewStoreController.ts index 9f4fb2348f..2f30c27273 100644 --- a/store/connection-manager/src/createNewStoreController.ts +++ b/store/connection-manager/src/createNewStoreController.ts @@ -12,7 +12,7 @@ type CreateResolverOptions = Pick & Required> @@ -54,7 +54,7 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick boolean fetchFullMetadata?: boolean -} & Partial> & Pick +} & Partial> & Pick export async function createNewStoreController ( opts: CreateNewStoreControllerOptions @@ -70,7 +70,6 @@ export async function createNewStoreController ( const { resolve, fetchers, clearResolutionCache } = createClient({ customResolvers: opts.hooks?.customResolvers, customFetchers: opts.hooks?.customFetchers, - userConfig: opts.userConfig, unsafePerm: opts.unsafePerm, ca: opts.ca, cacheDir: opts.cacheDir, @@ -89,8 +88,7 @@ export async function createNewStoreController ( noProxy: opts.noProxy, offline: opts.offline, preferOffline: opts.preferOffline, - authConfig: opts.authConfig, - sslConfigs: opts.sslConfigs, + configByUri: opts.configByUri, registries: opts.registries, retry: { factor: opts.fetchRetryFactor, diff --git a/store/controller/test/index.ts b/store/controller/test/index.ts index a70788f521..d774e9d096 100644 --- a/store/controller/test/index.ts +++ b/store/controller/test/index.ts @@ -15,7 +15,7 @@ describe('store.importPackage()', () => { const registry = 'https://registry.npmjs.org/' const storeIndex = new StoreIndex(storeDir) const { resolve, fetchers, clearResolutionCache } = createClient({ - authConfig: {}, + configByUri: {}, cacheDir: path.join(tmp, 'cache'), storeDir: path.join(tmp, 'store'), storeIndex, @@ -59,7 +59,7 @@ describe('store.importPackage()', () => { const registry = 'https://registry.npmjs.org/' const storeIndex = new StoreIndex(storeDir) const { resolve, fetchers, clearResolutionCache } = createClient({ - authConfig: {}, + configByUri: {}, cacheDir: path.join(tmp, 'cache'), storeDir: path.join(tmp, 'store'), storeIndex, diff --git a/testing/temp-store/src/index.ts b/testing/temp-store/src/index.ts index f76bc24574..ded4eb1e40 100644 --- a/testing/temp-store/src/index.ts +++ b/testing/temp-store/src/index.ts @@ -20,12 +20,12 @@ export function createTempStore (opts?: { clientOptions?: Partial storeOptions?: CreatePackageStoreOptions }): CreateTempStoreResult { - const authConfig = { registry } + const configByUri: ClientOptions['configByUri'] = {} const cacheDir = path.resolve('cache') const storeDir = opts?.storeDir ?? path.resolve('.store') const storeIndex = new StoreIndex(storeDir) const { resolve, fetchers, clearResolutionCache } = createClient({ - authConfig, + configByUri, retry: { retries: 4, factor: 10,