mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 01:59:43 -04:00
refactor(auth): unify auth/SSL into structured configByUri (#11201)
Replaces the dual `authConfig` (raw .npmrc) + `authInfos` (parsed auth) + `sslConfigs` (parsed SSL) pattern with a single structured `configByUri: Record<string, RegistryConfig>` 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<string, RegistryConfig>` 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
This commit is contained in:
@@ -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<string, RegistryConfig>
|
||||
userConfig: Record<string, string>
|
||||
userAgent: string
|
||||
packageManager: {
|
||||
@@ -52,7 +52,7 @@ export type StrictBuildOptions = {
|
||||
peersSuffixMaxLength: number
|
||||
strictStorePkgContentCheck: boolean
|
||||
fetchFullMetadata?: boolean
|
||||
} & Pick<Config, 'sslConfigs' | 'allowBuilds'>
|
||||
} & Pick<Config, 'allowBuilds'>
|
||||
|
||||
export type BuildOptions = Partial<StrictBuildOptions> &
|
||||
Pick<StrictBuildOptions, 'storeDir' | 'storeController'> & Pick<ConfigContext, 'rootProjectManifest' | 'rootProjectManifestDir'>
|
||||
@@ -73,7 +73,7 @@ const defaults = async (opts: BuildOptions): Promise<StrictBuildOptions> => {
|
||||
packageManager,
|
||||
pending: false,
|
||||
production: true,
|
||||
authConfig: {},
|
||||
configByUri: {},
|
||||
registries: DEFAULT_REGISTRIES,
|
||||
scriptsPrependNodePath: false,
|
||||
shamefullyHoist: false,
|
||||
|
||||
@@ -35,7 +35,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmfile: ['./.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -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',
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Config, 'color' | 'dir' | 'authConfig'>
|
||||
|
||||
@@ -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<string, AuthInfo>
|
||||
sslConfigs: Record<string, SslConfig>
|
||||
configByUri: Record<string, RegistryConfig>
|
||||
ignoreWorkspaceRootCheck: boolean
|
||||
workspaceRoot: boolean
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ const RAW_AUTH_CFG_KEY_SUFFIXES = [
|
||||
const AUTH_CFG_KEYS = [
|
||||
'ca',
|
||||
'cert',
|
||||
'configByUri',
|
||||
'key',
|
||||
'localAddress',
|
||||
'gitShallowHosts',
|
||||
|
||||
@@ -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<string, AuthInfo> // TODO: remove optional from here, this means that tests would have to be updated.
|
||||
sslConfigs: Record<string, SslConfig>
|
||||
configByUri?: Record<string, RegistryConfig> // TODO: remove optional from here, this means that tests would have to be updated.
|
||||
registries: Record<string, string>
|
||||
}
|
||||
|
||||
export function getNetworkConfigs (rawConfig: Record<string, unknown>): NetworkConfigs {
|
||||
const authInfoInputs: Record<string, AuthInfoInput> = {}
|
||||
const sslConfigs: Record<string, SslConfig> = {}
|
||||
const rawCredsMap: Record<string, RawCreds> = {}
|
||||
const registries: Record<string, string> = {}
|
||||
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<string, unknown>): AuthInfo | undefined {
|
||||
const input: AuthInfoInput = {}
|
||||
export function getDefaultCreds (rawConfig: Record<string, unknown>): 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<string, unknown>): AuthInf
|
||||
input[key] = value
|
||||
}
|
||||
}
|
||||
return parseAuthInfo(input)
|
||||
return parseCreds(input)
|
||||
}
|
||||
|
||||
const AUTH_SUFFIX_RE = /:(?<key>_auth|_authToken|_password|username|tokenHelper)$/
|
||||
const AUTH_SUFFIX_KEY_MAP: Record<string, keyof AuthInfoInput> = {
|
||||
const AUTH_SUFFIX_KEY_MAP: Record<string, keyof RawCreds> = {
|
||||
_auth: 'authPairBase64',
|
||||
_authToken: 'authToken',
|
||||
_password: 'authPassword',
|
||||
@@ -88,41 +73,41 @@ const AUTH_SUFFIX_KEY_MAP: Record<string, keyof AuthInfoInput> = {
|
||||
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 = /:(?<id>cert|key|ca)(?<kind>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 }
|
||||
}
|
||||
|
||||
@@ -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<string, string>
|
||||
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) {
|
||||
|
||||
@@ -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<AuthInfoInput, 'authPairBase64' | 'authUsername' | 'authPassword'>): AuthUserPass | undefined {
|
||||
}: Pick<RawCreds, 'authPairBase64' | 'authUsername' | 'authPassword'>): 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(['$', '%', '`', '"', "'"])
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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("'"))
|
||||
})
|
||||
@@ -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<DepPath | ProjectId, Record<string, 'public' | 'private'>>
|
||||
|
||||
export type PkgResolutionId = string & { __brand: 'PkgResolutionId' }
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"corepack",
|
||||
"corge",
|
||||
"cowsay",
|
||||
"Creds",
|
||||
"cves",
|
||||
"cwsay",
|
||||
"cyclonedx",
|
||||
|
||||
5
deps/compliance/commands/src/audit/audit.ts
vendored
5
deps/compliance/commands/src/audit/audit.ts
vendored
@@ -164,8 +164,7 @@ export type AuditOptions = Pick<UniversalOptions, 'dir'> & {
|
||||
| 'dev'
|
||||
| 'overrides'
|
||||
| 'optional'
|
||||
| 'userConfig'
|
||||
| 'authConfig'
|
||||
| 'configByUri'
|
||||
| 'virtualStoreDirMaxLength'
|
||||
| 'workspaceDir'
|
||||
> & Pick<ConfigContext,
|
||||
@@ -188,7 +187,7 @@ export async function handler (opts: AuditOptions): Promise<{ exitCode: number,
|
||||
optionalDependencies: opts.optional !== false,
|
||||
}
|
||||
let auditReport!: AuditReport
|
||||
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.authConfig, userSettings: opts.userConfig })
|
||||
const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri, opts.registries?.default)
|
||||
try {
|
||||
auditReport = await audit(lockfile, getAuthHeader, {
|
||||
dispatcherOptions: {
|
||||
|
||||
5
deps/compliance/commands/test/audit/index.ts
vendored
5
deps/compliance/commands/test/audit/index.ts
vendored
@@ -201,9 +201,8 @@ describe('plugin-commands-audit', () => {
|
||||
...AUDIT_REGISTRY_OPTS,
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
authConfig: {
|
||||
registry: AUDIT_REGISTRY,
|
||||
[`${AUDIT_REGISTRY.replace(/^https?:/, '')}:_authToken`]: '123',
|
||||
configByUri: {
|
||||
'//audit.registry/': { creds: { authToken: '123' } },
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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: {},
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
rootProjectManifestDir: '',
|
||||
// registry: REGISTRY,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
rootProjectManifestDir: '',
|
||||
sort: true,
|
||||
|
||||
@@ -164,7 +164,7 @@ export type OutdatedCommandOptions = {
|
||||
| 'offline'
|
||||
| 'optional'
|
||||
| 'production'
|
||||
| 'authConfig'
|
||||
| 'configByUri'
|
||||
| 'registries'
|
||||
| 'strictSsl'
|
||||
| 'tag'
|
||||
|
||||
2
deps/inspection/commands/src/view/index.ts
vendored
2
deps/inspection/commands/src/view/index.ts
vendored
@@ -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,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
proxy: undefined,
|
||||
preferWorkspacePackages: true,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -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/' },
|
||||
})
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -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<ClientOptions, 'authConfig' | 'minimumReleaseAgeExclude' | 'storeIndex'>
|
||||
export type ManifestGetterOptions = Omit<ClientOptions, 'configByUri' | 'minimumReleaseAgeExclude' | 'storeIndex'>
|
||||
& GetManifestOpts
|
||||
& { fullMetadata: boolean, authConfig: Record<string, string> }
|
||||
& { fullMetadata: boolean, configByUri: Record<string, RegistryConfig> }
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
@@ -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<ResolveFunction>(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)
|
||||
|
||||
@@ -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: '' },
|
||||
|
||||
@@ -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 }, {
|
||||
|
||||
@@ -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,
|
||||
|
||||
3
engine/runtime/commands/src/env/node.ts
vendored
3
engine/runtime/commands/src/env/node.ts
vendored
@@ -17,14 +17,13 @@ export type NvmNodeCommandOptions = Pick<Config,
|
||||
| 'localAddress'
|
||||
| 'noProxy'
|
||||
| 'nodeDownloadMirrors'
|
||||
| 'authConfig'
|
||||
| 'configByUri'
|
||||
| 'strictSsl'
|
||||
| 'storeDir'
|
||||
| 'pnpmHomeDir'
|
||||
> & Partial<Pick<Config,
|
||||
| 'cacheDir'
|
||||
| 'configDir'
|
||||
| 'sslConfigs'
|
||||
// Fields needed to forward opts to add.handler for env use
|
||||
| 'registries'
|
||||
| 'lockfileDir'
|
||||
|
||||
10
engine/runtime/commands/test/env/env.test.ts
vendored
10
engine/runtime/commands/test/env/env.test.ts
vendored
@@ -18,7 +18,7 @@ test('env use calls pnpm add with the correct arguments', async () => {
|
||||
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 <version>" can only be used with the "--global" option currently'))
|
||||
|
||||
@@ -78,7 +78,7 @@ test('fail if there is no global bin directory', async () => {
|
||||
bin: undefined,
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
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'))
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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<string, string>
|
||||
configByUri: Record<string, RegistryConfig>
|
||||
customResolvers?: CustomResolver[]
|
||||
customFetchers?: CustomFetcher[]
|
||||
ignoreScripts?: boolean
|
||||
sslConfigs?: Record<string, SslConfig>
|
||||
retry?: RetryTimeoutOptions
|
||||
storeIndex: StoreIndex
|
||||
timeout?: number
|
||||
nodeDownloadMirrors?: Record<string, string>
|
||||
unsafePerm?: boolean
|
||||
userAgent?: string
|
||||
userConfig?: Record<string, string>
|
||||
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<ClientOptions, 'storeIndex'>): { 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 })
|
||||
}
|
||||
|
||||
@@ -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/',
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ const DEFAULT_OPTIONS = {
|
||||
preferWorkspacePackages: true,
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
|
||||
preferWorkspacePackages: true,
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ const DEFAULT_OPTS = {
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
pnpmHomeDir: '',
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_OPTS = {
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
pnpmHomeDir: '',
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -28,7 +28,7 @@ const DEFAULT_OPTIONS = {
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_OPTIONS = {
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -35,7 +35,7 @@ const DEFAULT_OPTIONS = {
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ const DEFAULT_OPTIONS = {
|
||||
pnpmfile: ['.pnpmfile.cjs'],
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
authConfig: { registry: REGISTRY_URL },
|
||||
configByUri: {},
|
||||
registries: {
|
||||
default: REGISTRY_URL,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -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<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
configByUri: Record<string, RegistryConfig>
|
||||
verifyStoreIntegrity: boolean
|
||||
engineStrict: boolean
|
||||
allowBuilds?: Record<string, boolean | string>
|
||||
@@ -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')
|
||||
|
||||
@@ -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<string, RegistryConfig> = {
|
||||
[`//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<string, RegistryConfig> = {
|
||||
[`//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<string, RegistryConfig> = {
|
||||
'': { 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<string, RegistryConfig> = {
|
||||
[`//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<string, RegistryConfig> = {
|
||||
[`//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<string, RegistryConfig> = {
|
||||
[`//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<string, RegistryConfig> = {
|
||||
[`//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)
|
||||
|
||||
|
||||
@@ -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<string, RegistryConfig>
|
||||
unsafePerm: boolean
|
||||
userAgent: string
|
||||
registries: Registries
|
||||
@@ -234,7 +235,7 @@ export async function headlessInstall (opts: HeadlessOptions): Promise<Installat
|
||||
extraNodePaths: opts.extraNodePaths,
|
||||
preferSymlinkedExecutables: opts.preferSymlinkedExecutables,
|
||||
extraEnv: opts.extraEnv,
|
||||
authConfig: opts.authConfig,
|
||||
configByUri: opts.configByUri,
|
||||
resolveSymlinksInInjectedDirs: opts.resolveSymlinksInInjectedDirs,
|
||||
scriptsPrependNodePath: opts.scriptsPrependNodePath,
|
||||
scriptShell: opts.scriptShell,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { toLockfileResolution } from '@pnpm/lockfile.utils'
|
||||
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 type { ConfigDependencies } from '@pnpm/types'
|
||||
import type { ConfigDependencies, RegistryConfig } from '@pnpm/types'
|
||||
import getNpmTarballUrl from 'get-npm-tarball-url'
|
||||
|
||||
import { installConfigDeps, type InstallConfigDepsOpts } from './installConfigDeps.js'
|
||||
@@ -19,7 +19,7 @@ import { pruneEnvLockfile } from './pruneEnvLockfile.js'
|
||||
|
||||
export type ResolveAndInstallConfigDepsOpts = CreateFetchFromRegistryOptions & ResolverFactoryOptions & InstallConfigDepsOpts & {
|
||||
rootDir: string
|
||||
userConfig?: Record<string, string>
|
||||
configByUri?: Record<string, RegistryConfig>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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<string, string>
|
||||
configByUri?: Record<string, RegistryConfig>
|
||||
}
|
||||
|
||||
export async function resolveConfigDeps (configDeps: string[], opts: ResolveConfigDepsOpts): Promise<void> {
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:*",
|
||||
|
||||
@@ -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<string, string>
|
||||
userSettings: Record<string, string>
|
||||
}
|
||||
export function getAuthHeadersFromCreds (
|
||||
configByUri: Record<string, RegistryConfig>,
|
||||
defaultRegistry: string
|
||||
): Record<string, string> {
|
||||
const authHeaderValueByURI: Record<string, string> = {}
|
||||
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.
|
||||
|
||||
@@ -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<string, string>
|
||||
userSettings?: Record<string, string>
|
||||
}
|
||||
configByUri: Record<string, RegistryConfig>,
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -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'))
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"references": [
|
||||
{
|
||||
"path": "../../core/error"
|
||||
},
|
||||
{
|
||||
"path": "../../core/types"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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<string, Dispatcher>({
|
||||
},
|
||||
})
|
||||
|
||||
export type ClientCertificates = Record<string, TlsConfig>
|
||||
|
||||
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<string, SslConfig>
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<string, SslConfig>
|
||||
configByUri?: Record<string, RegistryConfig>
|
||||
}
|
||||
|
||||
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<string, RegistryConfig>): 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) {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -38,7 +38,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -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:*
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function checkForUpdates (config: Config): Promise<void> {
|
||||
|
||||
const { resolve } = createResolver({
|
||||
...config,
|
||||
authConfig: config.authConfig,
|
||||
configByUri: config.configByUri,
|
||||
retry: {
|
||||
retries: 0,
|
||||
},
|
||||
|
||||
@@ -95,7 +95,6 @@ export function help (): string {
|
||||
|
||||
export type PackOptions = Pick<UniversalOptions, 'dir'> & Pick<Config, 'catalogs'
|
||||
| 'ignoreScripts'
|
||||
| 'authConfig'
|
||||
| 'embedReadme'
|
||||
| 'packGzipLevel'
|
||||
| 'nodeLinker'
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Config } from '@pnpm/config.reader'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { globalInfo, globalWarn } from '@pnpm/logger'
|
||||
import type { ExportedManifest } from '@pnpm/releasing.exportable-manifest'
|
||||
import type { Creds, RegistryConfig } from '@pnpm/types'
|
||||
import type { PublishOptions } from 'libnpmpublish'
|
||||
|
||||
import { displayError } from './displayError.js'
|
||||
@@ -16,26 +17,8 @@ import { publishWithOtpHandling } from './otp.js'
|
||||
import type { PackResult } from './pack.js'
|
||||
import { allRegistryConfigKeys, type NormalizedRegistryUrl, parseSupportedRegistryUrl } from './registryConfigKeys.js'
|
||||
|
||||
type AuthConfigKey =
|
||||
| 'authToken'
|
||||
| 'authUserPass'
|
||||
| 'tokenHelper'
|
||||
|
||||
type SslConfigKey =
|
||||
| 'ca'
|
||||
| 'cert'
|
||||
| 'key'
|
||||
|
||||
type AuthSslConfigKey =
|
||||
// default registry
|
||||
| AuthConfigKey
|
||||
| SslConfigKey
|
||||
// other registries
|
||||
| 'authInfos'
|
||||
| 'sslConfigs'
|
||||
|
||||
export type PublishPackedPkgOptions = Pick<Config,
|
||||
| AuthSslConfigKey
|
||||
| 'configByUri'
|
||||
| 'dryRun'
|
||||
| 'fetchRetries'
|
||||
| 'fetchRetryFactor'
|
||||
@@ -76,7 +59,8 @@ export async function publishPackedPkg (
|
||||
}
|
||||
|
||||
async function createPublishOptions (manifest: ExportedManifest, options: PublishPackedPkgOptions): Promise<PublishOptions> {
|
||||
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<Config, AuthConfigKey>
|
||||
ssl: Pick<Config, SslConfigKey>
|
||||
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<Config, AuthSslConfigKey | 'registries'>
|
||||
): Partial<AuthSslInfo> {
|
||||
{ configByUri, registries }: Pick<Config, 'configByUri' | 'registries'>
|
||||
): Partial<RegistryInfo> {
|
||||
// eslint-disable-next-line regexp/no-unused-capturing-group
|
||||
const scopedMatches = /@(?<scope>[^/]+)\/(?<slug>[^/]+)/.exec(name)
|
||||
|
||||
@@ -181,45 +151,36 @@ function findAuthSslInfo (
|
||||
longestConfigKey: initialRegistryConfigKey,
|
||||
} = supportedRegistryInfo
|
||||
|
||||
const result: Partial<AuthSslInfo> = { registry }
|
||||
|
||||
let creds: Creds | undefined
|
||||
let tls: RegistryConfig['tls'] = {}
|
||||
for (const registryConfigKey of allRegistryConfigKeys(initialRegistryConfigKey)) {
|
||||
const auth: Pick<Config, AuthConfigKey> | undefined = authInfos[registryConfigKey]
|
||||
const ssl: Pick<Config, SslConfigKey> | 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<Creds, 'authToken' | 'tokenHelper'>): 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<Key extends string> (object: Partial<Record<Key, string>>, key: Key): void {
|
||||
if (!object[key]) {
|
||||
delete object[key]
|
||||
}
|
||||
}
|
||||
|
||||
function pruneUndefined (object: Record<string, unknown>): void {
|
||||
for (const key in object) {
|
||||
if (object[key] === undefined) {
|
||||
|
||||
@@ -19,7 +19,7 @@ export type PublishRecursiveOpts = Required<Pick<Config,
|
||||
| 'cacheDir'
|
||||
| 'dir'
|
||||
| 'pnpmHomeDir'
|
||||
| 'authConfig'
|
||||
| 'configByUri'
|
||||
| 'registries'
|
||||
| 'workspaceDir'
|
||||
>> &
|
||||
@@ -51,7 +51,6 @@ Partial<Pick<Config,
|
||||
| 'strictSsl'
|
||||
| 'unsafePerm'
|
||||
| 'userAgent'
|
||||
| 'userConfig'
|
||||
| 'verifyStoreIntegrity'
|
||||
>> &
|
||||
Partial<Pick<ConfigContext,
|
||||
@@ -70,8 +69,7 @@ export async function recursivePublish (
|
||||
const pkgs = Object.values(opts.selectedProjectsGraph).map((wsPkg) => wsPkg.package)
|
||||
const { resolve } = createResolver({
|
||||
...opts,
|
||||
authConfig: opts.authConfig,
|
||||
userConfig: opts.userConfig,
|
||||
configByUri: opts.configByUri,
|
||||
retry: {
|
||||
factor: opts.fetchRetryFactor,
|
||||
maxTimeout: opts.fetchRetryMaxtimeout,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DEFAULT_OPTS = {
|
||||
pnpmHomeDir: '',
|
||||
preferWorkspacePackages: true,
|
||||
proxy: undefined,
|
||||
authConfig: { registry: REGISTRY },
|
||||
configByUri: {},
|
||||
registries: { default: REGISTRY },
|
||||
registry: REGISTRY,
|
||||
rootProjectManifestDir: '',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string> {
|
||||
@@ -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 },
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -12,7 +12,7 @@ type CreateResolverOptions = Pick<Config,
|
||||
| 'fetchRetryMaxtimeout'
|
||||
| 'fetchRetryMintimeout'
|
||||
| 'offline'
|
||||
| 'authConfig'
|
||||
| 'configByUri'
|
||||
| 'verifyStoreIntegrity'
|
||||
> & Required<Pick<Config, 'cacheDir' | 'storeDir'>>
|
||||
|
||||
@@ -54,7 +54,7 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick<Confi
|
||||
cafsLocker?: CafsLocker
|
||||
ignoreFile?: (filename: string) => boolean
|
||||
fetchFullMetadata?: boolean
|
||||
} & Partial<Pick<Config, 'userConfig' | 'deployAllFiles' | 'sslConfigs' | 'strictStorePkgContentCheck'>> & Pick<ClientOptions, 'resolveSymlinksInInjectedDirs'>
|
||||
} & Partial<Pick<Config, 'deployAllFiles' | 'strictStorePkgContentCheck'>> & Pick<ClientOptions, 'resolveSymlinksInInjectedDirs'>
|
||||
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -20,12 +20,12 @@ export function createTempStore (opts?: {
|
||||
clientOptions?: Partial<ClientOptions>
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user