Files
pnpm/cli/cli-utils/src/getConfig.ts
Zoltan Kochan 821b36ac20 feat: install config dependencies into the global virtual store (#10910)
* feat: install config dependencies into the global virtual store

Config dependencies are now imported into {storeDir}/links/ following the
same path convention as regular packages (@scope/name/version/hash), then
symlinked into node_modules/.pnpm-config/. When the package already exists
in the GVS, the fetch and import are skipped entirely.

* refactor: extract shared GVS path computation into @pnpm/calc-dep-state

Move the leaf node hash computation from config deps-installer into
calcLeafGlobalVirtualStorePath in @pnpm/calc-dep-state to avoid
duplicating the hash logic.
2026-03-08 13:49:26 +01:00

127 lines
4.4 KiB
TypeScript

import fs from 'fs'
import path from 'path'
import { packageManager } from '@pnpm/cli-meta'
import { getConfig as _getConfig, type CliOptions, type Config } from '@pnpm/config'
import { formatWarn } from '@pnpm/default-reporter'
import { createStoreController } from '@pnpm/store-connection-manager'
import { installConfigDeps } from '@pnpm/config.deps-installer'
import { requireHooks } from '@pnpm/pnpmfile'
import type { ConfigDependencies } from '@pnpm/types'
import { lexCompare } from '@pnpm/util.lex-comparator'
export async function getConfig (
cliOptions: CliOptions,
opts: {
excludeReporter: boolean
globalDirShouldAllowWrite?: boolean
rcOptionsTypes: Record<string, unknown>
workspaceDir: string | undefined
checkUnknownSetting?: boolean
ignoreNonAuthSettingsFromLocal?: boolean
}
): Promise<Config> {
let { config, warnings } = await _getConfig({
cliOptions,
globalDirShouldAllowWrite: opts.globalDirShouldAllowWrite,
packageManager,
rcOptionsTypes: opts.rcOptionsTypes,
workspaceDir: opts.workspaceDir,
checkUnknownSetting: opts.checkUnknownSetting,
ignoreNonAuthSettingsFromLocal: opts.ignoreNonAuthSettingsFromLocal,
})
config.cliOptions = cliOptions
if (config.configDependencies) {
const store = await createStoreController(config)
await installConfigDeps(config.configDependencies, {
registries: config.registries,
rootDir: config.lockfileDir ?? config.rootProjectManifestDir,
store: store.ctrl,
storeDir: store.dir,
})
}
if (!config.ignorePnpmfile) {
config.tryLoadDefaultPnpmfile = config.pnpmfile == null
const pnpmfiles = config.pnpmfile == null ? [] : Array.isArray(config.pnpmfile) ? config.pnpmfile : [config.pnpmfile]
if (config.configDependencies) {
const configModulesDir = path.join(config.lockfileDir ?? config.rootProjectManifestDir, 'node_modules/.pnpm-config')
pnpmfiles.unshift(...calcPnpmfilePathsOfPluginDeps(configModulesDir, config.configDependencies))
}
const { hooks, finders, resolvedPnpmfilePaths } = await requireHooks(config.lockfileDir ?? config.dir, {
globalPnpmfile: config.globalPnpmfile,
pnpmfiles,
tryLoadDefaultPnpmfile: config.tryLoadDefaultPnpmfile,
})
config.hooks = hooks
config.finders = finders
config.pnpmfile = resolvedPnpmfilePaths
if (config.hooks?.updateConfig) {
for (const updateConfig of config.hooks.updateConfig) {
const updateConfigResult = updateConfig(config)
config = updateConfigResult instanceof Promise ? await updateConfigResult : updateConfigResult // eslint-disable-line no-await-in-loop
}
}
}
applyDerivedConfig(config)
if (opts.excludeReporter) {
delete config.reporter // This is a silly workaround because @pnpm/core expects a function as opts.reporter
}
if (warnings.length > 0) {
console.warn(warnings.map((warning) => formatWarn(warning)).join('\n'))
}
return config
}
export function * calcPnpmfilePathsOfPluginDeps (configModulesDir: string, configDependencies: ConfigDependencies): Generator<string> {
for (const configDepName of Object.keys(configDependencies).sort(lexCompare)) {
if (isPluginName(configDepName)) {
const mjsPath = path.join(configModulesDir, configDepName, 'pnpmfile.mjs')
if (fs.existsSync(mjsPath)) {
yield mjsPath
} else {
yield path.join(configModulesDir, configDepName, 'pnpmfile.cjs')
}
}
}
}
function isPluginName (configDepName: string): boolean {
if (configDepName.startsWith('pnpm-plugin-')) return true
if (configDepName[0] !== '@') return false
return configDepName.startsWith('@pnpm/plugin-') || configDepName.includes('/pnpm-plugin-')
}
// Apply derived config settings (hoist, shamefullyHoist, symlink)
function applyDerivedConfig (config: Config): void {
if (config.hoist === false) {
delete config.hoistPattern
}
switch (config.shamefullyHoist) {
case false:
delete config.publicHoistPattern
break
case true:
config.publicHoistPattern = ['*']
break
default:
if (
(config.publicHoistPattern == null) ||
(config.publicHoistPattern === '') ||
(
Array.isArray(config.publicHoistPattern) &&
config.publicHoistPattern.length === 1 &&
config.publicHoistPattern[0] === ''
)
) {
delete config.publicHoistPattern
}
break
}
if (!config.symlink) {
delete config.hoistPattern
delete config.publicHoistPattern
}
}