diff --git a/building/after-install/src/extendBuildOptions.ts b/building/after-install/src/extendBuildOptions.ts index 2f693a2337..7798380c9e 100644 --- a/building/after-install/src/extendBuildOptions.ts +++ b/building/after-install/src/extendBuildOptions.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { DEFAULT_REGISTRIES, normalizeRegistries } from '@pnpm/config.normalize-registries' -import type { Config } from '@pnpm/config.reader' +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' @@ -55,7 +55,7 @@ export type StrictBuildOptions = { } & Pick export type BuildOptions = Partial & -Pick & Pick +Pick & Pick const defaults = async (opts: BuildOptions): Promise => { const packageManager = opts.packageManager ?? diff --git a/building/commands/src/build/rebuild.ts b/building/commands/src/build/rebuild.ts index a18c25fe0f..bee869cfe8 100644 --- a/building/commands/src/build/rebuild.ts +++ b/building/commands/src/build/rebuild.ts @@ -4,7 +4,7 @@ import { } from '@pnpm/building.after-install' import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl, readProjectManifestOnly } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import type { LogBase } from '@pnpm/logger' import { createStoreController, @@ -75,23 +75,24 @@ For options that may be used with `-r`, see "pnpm help recursive"', } export type RebuildCommandOpts = Pick & Pick & CreateStoreControllerOptions & { diff --git a/building/commands/src/build/recursive.ts b/building/commands/src/build/recursive.ts index 60ee363487..d04ef3690a 100755 --- a/building/commands/src/build/recursive.ts +++ b/building/commands/src/build/recursive.ts @@ -8,6 +8,7 @@ import { } from '@pnpm/cli.utils' import { type Config, + type ConfigContext, createProjectConfigRecord, getWorkspaceConcurrency, } from '@pnpm/config.reader' @@ -19,18 +20,19 @@ import pLimit from 'p-limit' type RecursiveRebuildOpts = CreateStoreControllerOptions & Pick & Pick & { pending?: boolean } & Partial> @@ -40,7 +42,7 @@ export async function recursiveRebuild ( params: string[], opts: RecursiveRebuildOpts & { ignoredPackages?: Set - } & Required> + } & Required> & Required> ): Promise { if (allProjects.length === 0) { // It might make sense to throw an exception in this case diff --git a/building/commands/src/policy/approveBuilds.ts b/building/commands/src/policy/approveBuilds.ts index b2e81e10d4..bc44c9b107 100644 --- a/building/commands/src/policy/approveBuilds.ts +++ b/building/commands/src/policy/approveBuilds.ts @@ -1,5 +1,5 @@ import type { CommandHandlerMap } from '@pnpm/cli.command' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { writeSettings } from '@pnpm/config.writer' import { parse } from '@pnpm/deps.path' import { PnpmError } from '@pnpm/error' @@ -14,7 +14,7 @@ import { renderHelp } from 'render-help' import { rebuild, type RebuildCommandOpts } from '../build/index.js' import { getAutomaticallyIgnoredBuilds } from './getAutomaticallyIgnoredBuilds.js' -export type ApproveBuildsCommandOpts = Pick & { all?: boolean, global?: boolean } +export type ApproveBuildsCommandOpts = Pick & Pick & { all?: boolean, global?: boolean } export const commandNames = ['approve-builds'] diff --git a/building/commands/test/policy/approveBuilds.test.ts b/building/commands/test/policy/approveBuilds.test.ts index 3fe4d49482..f7a01b106b 100644 --- a/building/commands/test/policy/approveBuilds.test.ts +++ b/building/commands/test/policy/approveBuilds.test.ts @@ -41,11 +41,13 @@ async function getApproveBuildsConfig () { dir: process.cwd(), registry: `http://localhost:${REGISTRY_MOCK_PORT}`, } + const { config, context } = await getConfig({ + cliOptions, + packageManager: { name: 'pnpm', version: '' }, + }) return { - ...omit(['reporter'], (await getConfig({ - cliOptions, - packageManager: { name: 'pnpm', version: '' }, - })).config), + ...omit(['reporter'], config), + ...context, storeDir: path.resolve('store'), cacheDir: path.resolve('cache'), pnpmfile: [], // this is only needed because the pnpmfile returned by getConfig is string | string[] diff --git a/cache/commands/src/cache.cmd.ts b/cache/commands/src/cache.cmd.ts index 6ffeb85e66..1c3352e7f6 100644 --- a/cache/commands/src/cache.cmd.ts +++ b/cache/commands/src/cache.cmd.ts @@ -7,7 +7,7 @@ import { cacheView, } from '@pnpm/cache.api' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { ABBREVIATED_META_DIR, FULL_FILTERED_META_DIR } from '@pnpm/constants' import { PnpmError } from '@pnpm/error' import { getStorePath } from '@pnpm/store.path' @@ -59,7 +59,7 @@ export function help (): string { }) } -export type CacheCommandOptions = Pick +export type CacheCommandOptions = Pick & Pick export async function handler (opts: CacheCommandOptions, params: string[]): Promise { const cacheType = (opts.resolutionMode === 'time-based' && !opts.registrySupportsTimeField) diff --git a/cli/default-reporter/src/index.ts b/cli/default-reporter/src/index.ts index b5c437e933..65d13ba3a5 100644 --- a/cli/default-reporter/src/index.ts +++ b/cli/default-reporter/src/index.ts @@ -1,4 +1,4 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import type * as logs from '@pnpm/core-loggers' import type { LogLevel, StreamParser } from '@pnpm/logger' import createDiffer from 'ansi-diff' @@ -33,7 +33,7 @@ export function initDefaultReporter ( } context: { argv: string[] - config?: Config + config?: Config & ConfigContext env?: NodeJS.ProcessEnv process?: NodeJS.Process } @@ -111,7 +111,7 @@ export function toOutput$ ( } context: { argv: string[] - config?: Config + config?: Config & ConfigContext env?: NodeJS.ProcessEnv process?: NodeJS.Process } diff --git a/cli/default-reporter/src/reporterForClient/index.ts b/cli/default-reporter/src/reporterForClient/index.ts index 88d6d02f94..ae4c79b2f5 100644 --- a/cli/default-reporter/src/reporterForClient/index.ts +++ b/cli/default-reporter/src/reporterForClient/index.ts @@ -1,4 +1,4 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import type * as logs from '@pnpm/core-loggers' import type { LogLevel } from '@pnpm/logger' import type * as Rx from 'rxjs' @@ -66,7 +66,7 @@ export function reporterForClient ( process: NodeJS.Process isRecursive: boolean logLevel?: LogLevel - pnpmConfig?: Config + pnpmConfig?: Config & ConfigContext streamLifecycleOutput?: boolean aggregateOutput?: boolean throttleProgress?: number diff --git a/cli/default-reporter/src/reporterForClient/reportIgnoredBuilds.ts b/cli/default-reporter/src/reporterForClient/reportIgnoredBuilds.ts index 7afc952c25..13f1f91a76 100644 --- a/cli/default-reporter/src/reporterForClient/reportIgnoredBuilds.ts +++ b/cli/default-reporter/src/reporterForClient/reportIgnoredBuilds.ts @@ -1,4 +1,4 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import type { IgnoredScriptsLog } from '@pnpm/core-loggers' import { lexCompare } from '@pnpm/util.lex-comparator' import boxen from 'boxen' @@ -10,7 +10,7 @@ export function reportIgnoredBuilds ( ignoredScripts: Rx.Observable }, opts: { - pnpmConfig?: Config + pnpmConfig?: Config & ConfigContext // This is used by Bit CLI approveBuildsInstructionText?: string } diff --git a/cli/default-reporter/test/index.ts b/cli/default-reporter/test/index.ts index a569dfb770..f496a997cd 100644 --- a/cli/default-reporter/test/index.ts +++ b/cli/default-reporter/test/index.ts @@ -2,7 +2,7 @@ import path from 'node:path' import { toOutput$ } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { deprecationLogger, hookLogger, @@ -43,7 +43,7 @@ test('prints summary (of current package only)', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -235,7 +235,7 @@ test('prints summary without the filtered out entries', async () => { argv: ['install'], config: { dir: prefix, - } as Config, + } as Config & ConfigContext, }, streamParser: createStreamParser(), filterPkgsDiff: (diff) => diff.name !== 'bar', @@ -304,7 +304,7 @@ test('does not print deprecation message when log level is set to error', async const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { logLevel: 'error', @@ -362,7 +362,7 @@ test('prints summary for global installation', async () => { config: { dir: prefix, global: true, - } as Config, + } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -419,7 +419,7 @@ test('prints added peer dependency', async () => { argv: ['install'], config: { dir: prefix, - } as Config, + } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -460,7 +460,7 @@ test('prints summary correctly when the same package is specified both in option argv: ['install'], config: { dir: prefix, - } as Config, + } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -522,7 +522,7 @@ test('in the installation summary report which dependency types are skipped', as production: true, dev: false, optional: false, - } as Config, + } as Config & ConfigContext, env: { NODE_ENV: 'production', }, @@ -583,7 +583,7 @@ ${h1('devDependencies:')} skipped test('prints summary when some packages fail', async () => { const output$ = toOutput$({ - context: { argv: ['run'], config: { recursive: true } as Config }, + context: { argv: ['run'], config: { recursive: true } as Config & ConfigContext }, streamParser: createStreamParser(), }) @@ -822,7 +822,7 @@ test('prints added/removed stats and warnings during recursive installation', as const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: rootPrefix, recursive: true } as Config, + config: { dir: rootPrefix, recursive: true } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -881,7 +881,7 @@ test('recursive installation: prints only the added stats if nothing was removed const output$ = toOutput$({ context: { argv: ['recursive'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, reportingOptions: { outputMaxWidth: 60 }, streamParser: createStreamParser(), @@ -900,7 +900,7 @@ test('recursive installation: prints only the removed stats if nothing was added const output$ = toOutput$({ context: { argv: ['recursive'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, reportingOptions: { outputMaxWidth: 60 }, streamParser: createStreamParser(), @@ -919,7 +919,7 @@ test('recursive installation: prints at least one remove sign when removed !== 0 const output$ = toOutput$({ context: { argv: ['recursive'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, reportingOptions: { outputMaxWidth: 62 }, streamParser: createStreamParser(), @@ -938,7 +938,7 @@ test('recursive installation: prints at least one add sign when added !== 0', as const output$ = toOutput$({ context: { argv: ['recursive'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, reportingOptions: { outputMaxWidth: 62 }, streamParser: createStreamParser(), @@ -957,7 +957,7 @@ test('recursive uninstall: prints removed packages number', async () => { const output$ = toOutput$({ context: { argv: ['remove'], - config: { dir: '/home/jane/repo', recursive: true } as Config, + config: { dir: '/home/jane/repo', recursive: true } as Config & ConfigContext, }, reportingOptions: { outputMaxWidth: 62 }, streamParser: createStreamParser(), @@ -975,7 +975,7 @@ test('install: print hook message', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -997,7 +997,7 @@ test('recursive: print hook message', async () => { const output$ = toOutput$({ context: { argv: ['recursive'], - config: { dir: '/home/jane/repo' } as Config, + config: { dir: '/home/jane/repo' } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -1020,7 +1020,7 @@ test('prints skipped optional dependency info message', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -1049,7 +1049,7 @@ test('logLevel=default', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -1072,7 +1072,7 @@ test('logLevel=warn', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { logLevel: 'warn', @@ -1097,7 +1097,7 @@ test('logLevel=error', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { logLevel: 'error', @@ -1121,7 +1121,7 @@ test('warnings are collapsed', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { logLevel: 'warn', @@ -1153,7 +1153,7 @@ test('warnings are not collapsed when append-only is true', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { appendOnly: true, diff --git a/cli/default-reporter/test/reportingDeprecations.ts b/cli/default-reporter/test/reportingDeprecations.ts index 65d962653c..8fe0cf2087 100644 --- a/cli/default-reporter/test/reportingDeprecations.ts +++ b/cli/default-reporter/test/reportingDeprecations.ts @@ -1,5 +1,5 @@ import { toOutput$ } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { deprecationLogger, stageLogger, @@ -17,7 +17,7 @@ test('prints summary of deprecated subdependencies', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, streamParser: createStreamParser(), }) diff --git a/cli/default-reporter/test/reportingProgress.ts b/cli/default-reporter/test/reportingProgress.ts index 77f7695349..db625ca8b2 100644 --- a/cli/default-reporter/test/reportingProgress.ts +++ b/cli/default-reporter/test/reportingProgress.ts @@ -1,5 +1,5 @@ import { toOutput$ } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { fetchingProgressLogger, progressLogger, @@ -25,7 +25,7 @@ test('prints progress beginning', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -50,7 +50,7 @@ test('prints progress without added packages stats', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, reportingOptions: { hideAddedPkgsProgress: true, @@ -78,7 +78,7 @@ test('prints all progress stats', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -119,7 +119,7 @@ test('prints progress beginning of node_modules from not cwd', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/projects' } as Config, + config: { dir: '/src/projects' } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -144,7 +144,7 @@ test('prints progress beginning of node_modules from not cwd, when progress pref const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/projects' } as Config, + config: { dir: '/src/projects' } as Config & ConfigContext, }, streamParser: createStreamParser(), reportingOptions: { @@ -172,7 +172,7 @@ test('prints progress beginning when appendOnly is true', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, reportingOptions: { appendOnly: true, @@ -203,7 +203,7 @@ test('prints progress beginning during recursive install', async () => { config: { dir: '/src/project', recursive: true, - } as Config, + } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -230,7 +230,7 @@ test('prints progress on first download', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, reportingOptions: { throttleProgress: 0 }, streamParser: createStreamParser(), @@ -264,7 +264,7 @@ test('moves fixed line to the end', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: prefix } as Config, + config: { dir: prefix } as Config & ConfigContext, }, reportingOptions: { throttleProgress: 0 }, streamParser: createStreamParser(), @@ -326,7 +326,7 @@ test('prints progress of big files download', async () => { const output$ = toOutput$({ context: { argv: ['install'], - config: { dir: '/src/project' } as Config, + config: { dir: '/src/project' } as Config & ConfigContext, }, reportingOptions: { throttleProgress: 0 }, streamParser: createStreamParser(), diff --git a/cli/default-reporter/test/reportingScope.ts b/cli/default-reporter/test/reportingScope.ts index 55b4c66329..283a377d9d 100644 --- a/cli/default-reporter/test/reportingScope.ts +++ b/cli/default-reporter/test/reportingScope.ts @@ -1,7 +1,7 @@ import { setTimeout } from 'node:timers/promises' import { toOutput$ } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { scopeLogger } from '@pnpm/core-loggers' import { createStreamParser } from '@pnpm/logger' import { firstValueFrom } from 'rxjs' @@ -33,7 +33,7 @@ test('prints scope of recursive install in a workspace when not all packages are const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -54,7 +54,7 @@ test('prints scope of recursive install in a workspace when all packages are sel const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -75,7 +75,7 @@ test('prints scope of recursive install not in a workspace when not all packages const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, }, streamParser: createStreamParser(), }) @@ -95,7 +95,7 @@ test('prints scope of recursive install not in a workspace when all packages are const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, }, streamParser: createStreamParser(), }) diff --git a/cli/default-reporter/test/reportingUpdateCheck.ts b/cli/default-reporter/test/reportingUpdateCheck.ts index 632e7607a1..848f797d97 100644 --- a/cli/default-reporter/test/reportingUpdateCheck.ts +++ b/cli/default-reporter/test/reportingUpdateCheck.ts @@ -2,7 +2,7 @@ import { setTimeout } from 'node:timers/promises' import { stripVTControlCharacters as stripAnsi } from 'node:util' import { toOutput$ } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { updateCheckLogger } from '@pnpm/core-loggers' import { createStreamParser } from '@pnpm/logger' import { firstValueFrom } from 'rxjs' @@ -35,7 +35,7 @@ test('print update notification if the latest version is greater than the curren const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, env: {}, }, streamParser: createStreamParser(), @@ -56,7 +56,7 @@ test('print update notification for Corepack if the latest version is greater th const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, env: { COREPACK_ROOT: '/usr/bin/corepack', }, @@ -79,7 +79,7 @@ test('print update notification that suggests to use the standalone scripts for const output$ = toOutput$({ context: { argv: ['install'], - config: { recursive: true } as Config, + config: { recursive: true } as Config & ConfigContext, env: { PNPM_HOME: '/home/user/.local/share/pnpm', }, diff --git a/cli/parse-cli-args/src/index.ts b/cli/parse-cli-args/src/index.ts index e7744925a1..a126dd12fa 100644 --- a/cli/parse-cli-args/src/index.ts +++ b/cli/parse-cli-args/src/index.ts @@ -129,6 +129,22 @@ export async function parseCliArgs ( return [noptExploratoryResults.argv.remain[indexOfRunScriptName]] } + // When "config" is a registered CLI option (e.g. `pnpm add --config`), + // nopt captures --config.xxx=yyy as the "config" flag value instead of + // treating it as the nconf-style config override syntax. Work around this + // by rewriting --config.xxx=yyy to a placeholder before nopt, then restoring. + const hasConfigOption = 'config' in types + const configDotArgs: string[] = [] + const filteredArgv = hasConfigOption + ? inputArgv.map(arg => { + if (arg.startsWith('--config.')) { + configDotArgs.push(arg) + return undefined + } + return arg + }).filter((arg): arg is string => arg !== undefined) + : inputArgv + const { argv, ...options } = nopt( { recursive: Boolean, @@ -138,10 +154,17 @@ export async function parseCliArgs ( ...opts.universalShorthands, ...opts.shorthandsByCommandName[commandName], }, - inputArgv, + filteredArgv, 0, { escapeArgs: getEscapeArgsWithSpecialCases() } ) + + // Re-parse extracted --config.xxx args through nopt so they get proper + // type coercion (e.g. "false" → false for Boolean settings). + if (configDotArgs.length > 0) { + const { argv: _, ...configOptions } = nopt({}, {}, configDotArgs, 0) + Object.assign(options, configOptions) + } const workspaceDir = await getWorkspaceDir(options) // For the run command, it's not clear whether --help should be passed to the diff --git a/config/commands/src/ConfigCommandOptions.ts b/config/commands/src/ConfigCommandOptions.ts index 3b69bafc29..593ff7c8f6 100644 --- a/config/commands/src/ConfigCommandOptions.ts +++ b/config/commands/src/ConfigCommandOptions.ts @@ -1,16 +1,16 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' export type ConfigCommandOptions = Pick & Pick & { + _config: Config + _context: ConfigContext json?: boolean location?: 'global' | 'project' - // The config commands receive the full Config object at runtime - // and read arbitrary typed properties for display. - [key: string]: unknown } diff --git a/config/commands/src/configGet.ts b/config/commands/src/configGet.ts index f9bc929270..60b2d862ee 100644 --- a/config/commands/src/configGet.ts +++ b/config/commands/src/configGet.ts @@ -1,4 +1,4 @@ -import { type Config, isIniConfigKey, types } from '@pnpm/config.reader' +import { isIniConfigKey, types } from '@pnpm/config.reader' import { getObjectValueByPropertyPath } from '@pnpm/object.property-path' import { isCamelCase } from '@pnpm/text.naming-cases' import camelcase from 'camelcase' @@ -28,9 +28,9 @@ function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boo // then fall back to authConfig (for keys like registry set in .npmrc) if (Object.hasOwn(types, kebabKey)) { const camelKey = camelcase(kebabKey, { locale: 'en-US' }) - const explicit = (opts as unknown as Config).explicitlySetKeys + const explicit = opts._context.explicitlySetKeys if (!explicit || explicit.has(camelKey)) { - return { value: (opts as unknown as Record)[camelKey] } + return { value: (opts._config as unknown as Record)[camelKey] } } // Fall back to authConfig for INI keys (registry, ca, etc.) if (kebabKey in opts.authConfig) { @@ -45,7 +45,7 @@ function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boo // For keys not in types (e.g., package-extensions), look up via configToRecord // which excludes internal/sensitive fields. const camelKey = camelcase(key, { locale: 'en-US' }) - const record = configToRecord(opts as unknown as Config) + const record = configToRecord(opts._config, opts._context.explicitlySetKeys) if (Object.hasOwn(record, camelKey)) { return { value: record[camelKey] } } @@ -55,9 +55,9 @@ function lookupConfig (opts: ConfigCommandOptions, key: string, isScopedKey: boo function lookupByPropertyPath (opts: ConfigCommandOptions, propertyPath: string): Found { const parsedPropertyPath = Array.from(parseConfigPropertyPath(propertyPath)) if (parsedPropertyPath.length === 0) { - return { value: configToRecord(opts as unknown as Config) } + return { value: configToRecord(opts._config, opts._context.explicitlySetKeys) } } - const record = configToRecord(opts as unknown as Config) + const record = configToRecord(opts._config, opts._context.explicitlySetKeys) return { value: getObjectValueByPropertyPath(record, parsedPropertyPath), } diff --git a/config/commands/src/configList.ts b/config/commands/src/configList.ts index d2989253a6..fb3b4c8b49 100644 --- a/config/commands/src/configList.ts +++ b/config/commands/src/configList.ts @@ -1,8 +1,6 @@ -import type { Config } from '@pnpm/config.reader' - import type { ConfigCommandOptions } from './ConfigCommandOptions.js' import { configToRecord } from './configToRecord.js' export async function configList (opts: ConfigCommandOptions): Promise { - return JSON.stringify(configToRecord(opts as unknown as Config), undefined, 2) + return JSON.stringify(configToRecord(opts._config, opts._context.explicitlySetKeys), undefined, 2) } diff --git a/config/commands/src/configToRecord.ts b/config/commands/src/configToRecord.ts index 35b8819bdb..3fdd29d064 100644 --- a/config/commands/src/configToRecord.ts +++ b/config/commands/src/configToRecord.ts @@ -4,26 +4,25 @@ import camelcase from 'camelcase' import { censorProtectedSettings } from './protectedSettings.js' -const INTERNAL_CONFIG_KEYS = new Set([ - 'authConfig', 'authInfos', 'rawLocalConfig', 'cliOptions', - 'explicitlySetKeys', - 'hooks', 'finders', 'allProjects', 'selectedProjectsGraph', - 'packageManager', 'wantedPackageManager', 'rootProjectManifest', - 'storeController', 'rootProjectManifestDir', 'sslConfigs', +// Auth-related Config fields that are internal objects, not user settings. +const NON_SETTING_CONFIG_KEYS = new Set([ + 'authConfig', 'authInfos', 'sslConfigs', ]) /** * Convert a Config object to a camelCase record for display. * Only includes explicitly set values (from CLI, env vars, or workspace yaml), * not default values. Auth/registry keys from authConfig are always included. + * + * Accepts a clean Config object (without ConfigContext fields mixed in), + * so no INTERNAL_CONFIG_KEYS exclusion list is needed. */ -export function configToRecord (config: Config): Record { +export function configToRecord (config: Config, explicitlySetKeys: Set): Record { const result: Record = {} - const explicit = config.explicitlySetKeys // Add typed settings (only explicitly set ones if tracking is available) for (const kebabKey of Object.keys(types)) { const camelKey = camelcase(kebabKey, { locale: 'en-US' }) - if (explicit && !explicit.has(camelKey)) continue + if (!explicitlySetKeys.has(camelKey)) continue const value = (config as unknown as Record)[camelKey] if (value !== undefined) { result[camelKey] = value @@ -31,8 +30,8 @@ export function configToRecord (config: Config): Record { } // Add non-types config properties (e.g., packageExtensions, overrides) for (const [key, value] of Object.entries(config)) { - if (value === undefined || INTERNAL_CONFIG_KEYS.has(key)) continue - if (!(key in result) && (!explicit || explicit.has(key))) { + if (value === undefined || NON_SETTING_CONFIG_KEYS.has(key)) continue + if (!(key in result) && explicitlySetKeys.has(key)) { result[key] = value } } diff --git a/config/commands/test/configDelete.test.ts b/config/commands/test/configDelete.test.ts index d526fe4c24..1afc6acdaa 100644 --- a/config/commands/test/configDelete.test.ts +++ b/config/commands/test/configDelete.test.ts @@ -7,19 +7,21 @@ import { readIniFileSync } from 'read-ini-file' import { readYamlFileSync } from 'read-yaml-file' import { writeYamlFileSync } from 'write-yaml-file' +import { createConfigCommandOpts } from './utils/index.js' + test('config delete on registry key not set', async () => { const tmp = tempDir() const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(configDir, 'auth.ini'), '@my-company:registry=https://registry.my-company.example.com/') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'registry']) + }), ['delete', 'registry']) expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({ '@my-company:registry': 'https://registry.my-company.example.com/', @@ -32,13 +34,13 @@ test('config delete on registry key set', async () => { fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(configDir, 'auth.ini'), 'registry=https://registry.my-company.example.com/') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'registry']) + }), ['delete', 'registry']) expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({}) }) @@ -49,13 +51,13 @@ test('config delete on npm-compatible key not set', async () => { fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(configDir, 'auth.ini'), '@my-company:registry=https://registry.my-company.example.com/') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'cafile']) + }), ['delete', 'cafile']) expect(readIniFileSync(path.join(configDir, 'auth.ini'))).toEqual({ '@my-company:registry': 'https://registry.my-company.example.com/', @@ -68,13 +70,13 @@ test('config delete on npm-compatible key set', async () => { fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(configDir, 'auth.ini'), 'cafile=some-cafile') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'cafile']) + }), ['delete', 'cafile']) // NOTE: pnpm currently does not delete empty rc files. // TODO: maybe we should? @@ -89,13 +91,13 @@ test('config delete on pnpm-specific key not set', async () => { cacheDir: '~/cache', }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'store-dir']) + }), ['delete', 'store-dir']) expect(readYamlFileSync(path.join(configDir, 'config.yaml'))).toStrictEqual({ cacheDir: '~/cache', @@ -110,13 +112,13 @@ test('config delete on pnpm-specific key set', async () => { cacheDir: '~/cache', }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', 'cache-dir']) + }), ['delete', 'cache-dir']) expect(fs.readdirSync(configDir)).not.toContain('config.yaml') }) diff --git a/config/commands/test/configGet.test.ts b/config/commands/test/configGet.test.ts index 76fa91f364..72c1c015f4 100644 --- a/config/commands/test/configGet.test.ts +++ b/config/commands/test/configGet.test.ts @@ -1,48 +1,48 @@ import { config } from '@pnpm/config.commands' -import { getOutputString } from './utils/index.js' +import { createConfigCommandOpts, getOutputString } from './utils/index.js' test('config get', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: true, authConfig: {}, storeDir: '~/store', - }, ['get', 'store-dir']) + }), ['get', 'store-dir']) expect(getOutputString(getResult)).toBe('~/store') }) test('config get works with camelCase', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: true, authConfig: {}, storeDir: '~/store', - }, ['get', 'storeDir']) + }), ['get', 'storeDir']) expect(getOutputString(getResult)).toBe('~/store') }) test('config get a boolean should return string format', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: true, authConfig: {}, updateNotifier: true, - }, ['get', 'update-notifier']) + }), ['get', 'update-notifier']) expect(getOutputString(getResult)).toBe('true') }) test('config get on array should return a comma-separated list', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), @@ -52,7 +52,7 @@ test('config get on array should return a comma-separated list', async () => { '*eslint*', '*prettier*', ], - }, ['get', 'public-hoist-pattern']) + }), ['get', 'public-hoist-pattern']) expect(JSON.parse(getOutputString(getResult))).toStrictEqual([ '*eslint*', @@ -61,7 +61,7 @@ test('config get on array should return a comma-separated list', async () => { }) test('config get on object should return a JSON string', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), @@ -70,7 +70,7 @@ test('config get on object should return a JSON string', async () => { catalog: { react: '^19.0.0', }, - }, ['get', 'catalog']) + }), ['get', 'catalog']) expect(JSON.parse(getOutputString(getResult))).toStrictEqual({ react: '^19.0.0' }) }) @@ -80,20 +80,15 @@ test('config get without key show list all settings', async () => { 'store-dir': '~/store', 'fetch-retries': '2', } - const getOutput = await config.handler({ + const baseOpts = { dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), - global: true, authConfig, - }, ['get']) + } + const getOutput = await config.handler(createConfigCommandOpts(baseOpts), ['get']) - const listOutput = await config.handler({ - dir: process.cwd(), - cliOptions: {}, - configDir: process.cwd(), - authConfig, - }, ['list']) + const listOutput = await config.handler(createConfigCommandOpts(baseOpts), ['list']) expect(getOutput).toStrictEqual(listOutput) }) @@ -116,14 +111,14 @@ describe('config get with a property path', () => { trustPolicyExclude: ['foo', 'bar'], packageExtensions, } - const baseOpts = { + const baseOpts = createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: true, authConfig: {}, ...configData, - } + }) describe('anything with --json', () => { test('«»', async () => { @@ -211,7 +206,7 @@ describe('config get with a property path', () => { }) test('config get with scoped registry key (global: false)', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), @@ -219,13 +214,13 @@ test('config get with scoped registry key (global: false)', async () => { authConfig: { '@scope:registry': 'https://custom-registry.example.com/', }, - }, ['get', '@scope:registry']) + }), ['get', '@scope:registry']) expect(getOutputString(getResult)).toBe('https://custom-registry.example.com/') }) test('config get with scoped registry key (global: true)', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), @@ -233,19 +228,19 @@ test('config get with scoped registry key (global: true)', async () => { authConfig: { '@scope:registry': 'https://custom-registry.example.com/', }, - }, ['get', '@scope:registry']) + }), ['get', '@scope:registry']) expect(getOutputString(getResult)).toBe('https://custom-registry.example.com/') }) test('config get with scoped registry key that does not exist', async () => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: false, authConfig: {}, - }, ['get', '@scope:registry']) + }), ['get', '@scope:registry']) expect(getOutputString(getResult)).toBe('undefined') }) @@ -261,13 +256,13 @@ describe('does not traverse the prototype chain (#10296)', () => { 'valueOf', '__proto__', ])('%s', async key => { - const getResult = await config.handler({ + const getResult = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), global: true, authConfig: {}, - }, ['get', key]) + }), ['get', key]) expect(getOutputString(getResult)).toBe('undefined') }) diff --git a/config/commands/test/configList.test.ts b/config/commands/test/configList.test.ts index dc4b86a2fb..3653603f6e 100644 --- a/config/commands/test/configList.test.ts +++ b/config/commands/test/configList.test.ts @@ -1,16 +1,16 @@ import { config } from '@pnpm/config.commands' -import { getOutputString } from './utils/index.js' +import { createConfigCommandOpts, getOutputString } from './utils/index.js' test('config list', async () => { - const output = await config.handler({ + const output = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), authConfig: {}, storeDir: '~/store', fetchRetries: '2', - }, ['list']) + }), ['list']) expect(JSON.parse(getOutputString(output))).toMatchObject({ fetchRetries: '2', @@ -19,7 +19,7 @@ test('config list', async () => { }) test('config list --json', async () => { - const output = await config.handler({ + const output = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), @@ -27,7 +27,7 @@ test('config list --json', async () => { authConfig: {}, storeDir: '~/store', fetchRetries: '2', - }, ['list']) + }), ['list']) const parsed = JSON.parse(output as string) expect(parsed).toMatchObject({ @@ -43,14 +43,14 @@ test('config list censors protected settings', async () => { '//my-org.example.com:username': 'my-username-in-my-org', } - const output = await config.handler({ + const output = await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir: process.cwd(), storeDir: '~/store', fetchRetries: '2', authConfig, - }, ['list']) + }), ['list']) expect(JSON.parse(getOutputString(output))).toMatchObject({ storeDir: '~/store', @@ -68,7 +68,7 @@ test('config list --json censors protected settings', async () => { '//my-org.example.com:username': 'my-username-in-my-org', } - const output = await config.handler({ + const output = await config.handler(createConfigCommandOpts({ dir: process.cwd(), json: true, cliOptions: {}, @@ -76,7 +76,7 @@ test('config list --json censors protected settings', async () => { storeDir: '~/store', fetchRetries: '2', authConfig, - }, ['list']) + }), ['list']) expect(JSON.parse(getOutputString(output))).toMatchObject({ storeDir: '~/store', diff --git a/config/commands/test/configSet.test.ts b/config/commands/test/configSet.test.ts index 4889bff026..9d76d0d6da 100644 --- a/config/commands/test/configSet.test.ts +++ b/config/commands/test/configSet.test.ts @@ -7,7 +7,7 @@ import { tempDir } from '@pnpm/prepare' import { readIniFileSync } from 'read-ini-file' import { readYamlFileSync } from 'read-yaml-file' -import { type ConfigFilesData, readConfigFiles, writeConfigFiles } from './utils/index.js' +import { type ConfigFilesData, createConfigCommandOpts, readConfigFiles, writeConfigFiles } from './utils/index.js' test('config set registry setting using the global option', async () => { const tmp = tempDir() @@ -24,13 +24,13 @@ test('config set registry setting using the global option', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', 'registry', 'https://npm-registry.example.com/']) + }), ['set', 'registry', 'https://npm-registry.example.com/']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -56,13 +56,13 @@ test('config set npm-compatible setting using the global option', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', 'cafile', 'some-cafile']) + }), ['set', 'cafile', 'some-cafile']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -88,13 +88,13 @@ test('config set pnpm-specific key using the global option', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', 'fetch-retries', '1']) + }), ['set', 'fetch-retries', '1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -120,13 +120,13 @@ test('config set using the location=global option', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'global', authConfig: {}, - }, ['set', 'fetchRetries', '1']) + }), ['set', 'fetchRetries', '1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -152,13 +152,13 @@ test('config set pnpm-specific setting using the location=project option', async } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'virtual-store-dir', '.pnpm']) + }), ['set', 'virtual-store-dir', '.pnpm']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -174,25 +174,25 @@ test('config delete with location=project, when delete the last setting from pnp const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'virtual-store-dir', '.pnpm']) + }), ['set', 'virtual-store-dir', '.pnpm']) expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ virtualStoreDir: '.pnpm', }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['delete', 'virtual-store-dir']) + }), ['delete', 'virtual-store-dir']) expect(fs.existsSync(path.join(tmp, 'pnpm-workspace.yaml'))).toBeFalsy() }) @@ -213,13 +213,13 @@ test('config set registry setting using the location=project option', async () = } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'registry', 'https://npm-registry.example.com/']) + }), ['set', 'registry', 'https://npm-registry.example.com/']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -246,13 +246,13 @@ test('config set npm-compatible setting using the location=project option', asyn } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'cafile', 'some-cafile']) + }), ['set', 'cafile', 'some-cafile']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -268,13 +268,13 @@ test('config set saves the setting in the right format to pnpm-workspace.yaml', const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'fetch-timeout', '1000']) + }), ['set', 'fetch-timeout', '1000']) expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ fetchTimeout: 1000, @@ -300,14 +300,14 @@ test('config set registry setting in project .npmrc file', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: false, location: 'project', authConfig: {}, - }, ['set', 'registry', 'https://npm-registry.example.com/']) + }), ['set', 'registry', 'https://npm-registry.example.com/']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -337,14 +337,14 @@ test('config set npm-compatible setting in project .npmrc file', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: false, location: 'project', authConfig: {}, - }, ['set', 'cafile', 'some-cafile']) + }), ['set', 'cafile', 'some-cafile']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -374,14 +374,14 @@ test('config set pnpm-specific setting in project pnpm-workspace.yaml file', asy } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: false, location: 'project', authConfig: {}, - }, ['set', 'fetch-retries', '1']) + }), ['set', 'fetch-retries', '1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -411,13 +411,13 @@ test('config set key=value', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'fetch-retries=1']) + }), ['set', 'fetch-retries=1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -447,13 +447,13 @@ test('config set key=value, when value contains a "="', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'lockfile-dir=foo=bar']) + }), ['set', 'lockfile-dir=foo=bar']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -470,21 +470,21 @@ test('config set or delete throws missing params error', async () => { fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(tmp, '.npmrc'), 'store-dir=~/store') - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config set` requires the config key')) + }), ['set'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config set` requires the config key')) - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['delete'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config delete` requires the config key')) + }), ['delete'])).rejects.toThrow(new PnpmError('CONFIG_NO_PARAMS', '`pnpm config delete` requires the config key')) }) test('config set with dot leading key', async () => { @@ -500,13 +500,13 @@ test('config set with dot leading key', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', '.fetchRetries', '1']) + }), ['set', '.fetchRetries', '1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -530,13 +530,13 @@ test('config set with subscripted key', async () => { } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', '["fetch-retries"]', '1']) + }), ['set', '["fetch-retries"]', '1']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -553,13 +553,13 @@ test('config set rejects complex property path', async () => { fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(configDir, 'auth.ini'), 'store-dir=~/store') - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', '.catalog.react', '19'])).rejects.toMatchObject({ + }), ['set', '.catalog.react', '19'])).rejects.toMatchObject({ code: 'ERR_PNPM_CONFIG_SET_DEEP_KEY', }) }) @@ -569,14 +569,14 @@ test('config set with location=project and json=true', async () => { const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', json: true, authConfig: {}, - }, ['set', 'catalog', '{ "react": "19" }']) + }), ['set', 'catalog', '{ "react": "19" }']) expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toStrictEqual({ catalog: { @@ -584,14 +584,14 @@ test('config set with location=project and json=true', async () => { }, }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', json: true, authConfig: {}, - }, ['set', 'packageExtensions', JSON.stringify({ + }), ['set', 'packageExtensions', JSON.stringify({ '@babel/parser': { peerDependencies: { '@babel/types': '*', @@ -636,26 +636,26 @@ test('config set refuses writing workspace-specific settings to the global confi } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'global', json: true, authConfig: {}, - }, ['set', 'catalog', '{ "react": "19" }'])).rejects.toMatchObject({ + }), ['set', 'catalog', '{ "react": "19" }'])).rejects.toMatchObject({ code: 'ERR_PNPM_CONFIG_SET_UNSUPPORTED_YAML_CONFIG_KEY', key: 'catalog', }) - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'global', json: true, authConfig: {}, - }, ['set', 'packageExtensions', JSON.stringify({ + }), ['set', 'packageExtensions', JSON.stringify({ '@babel/parser': { peerDependencies: { '@babel/types': '*', @@ -671,14 +671,14 @@ test('config set refuses writing workspace-specific settings to the global confi key: 'packageExtensions', }) - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'global', json: true, authConfig: {}, - }, ['set', 'package-extensions', JSON.stringify({ + }), ['set', 'package-extensions', JSON.stringify({ '@babel/parser': { peerDependencies: { '@babel/types': '*', @@ -709,14 +709,14 @@ test('config set writes workspace-specific settings to pnpm-workspace.yaml', asy writeConfigFiles(configDir, tmp, initConfig) const catalog = { react: '19' } - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', json: true, authConfig: {}, - }, ['set', 'catalog', JSON.stringify(catalog)]) + }), ['set', 'catalog', JSON.stringify(catalog)]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, localYaml: { @@ -737,14 +737,14 @@ test('config set writes workspace-specific settings to pnpm-workspace.yaml', asy }, }, } - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', json: true, authConfig: {}, - }, ['set', 'packageExtensions', JSON.stringify(packageExtensions)]) + }), ['set', 'packageExtensions', JSON.stringify(packageExtensions)]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, localYaml: { @@ -760,14 +760,14 @@ test('config set refuses kebab-case workspace-specific settings', async () => { const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', json: true, authConfig: {}, - }, ['set', 'package-extensions', JSON.stringify({ + }), ['set', 'package-extensions', JSON.stringify({ '@babel/parser': { peerDependencies: { '@babel/types': '*', @@ -789,13 +789,13 @@ test('config set registry-specific setting with --location=project should create const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', '//registry.example.com/:_auth', 'test-auth-value']) + }), ['set', '//registry.example.com/:_auth', 'test-auth-value']) expect(readIniFileSync(path.join(tmp, '.npmrc'))).toEqual({ '//registry.example.com/:_auth': 'test-auth-value', @@ -808,13 +808,13 @@ test('config set scoped registry with --location=project should create .npmrc', const configDir = path.join(tmp, 'global-config') fs.mkdirSync(configDir, { recursive: true }) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', '@myorg:registry', 'https://test-registry.example.com/']) + }), ['set', '@myorg:registry', 'https://test-registry.example.com/']) expect(readIniFileSync(path.join(tmp, '.npmrc'))).toEqual({ '@myorg:registry': 'https://test-registry.example.com/', @@ -831,13 +831,13 @@ test('config set when both pnpm-workspace.yaml and .npmrc exist, pnpm-workspace. fs.writeFileSync(path.join(tmp, '.npmrc'), 'store-dir=~/store') fs.writeFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'fetchRetries: 5') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'fetch-timeout', '2000']) + }), ['set', 'fetch-timeout', '2000']) expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ fetchRetries: 5, @@ -856,13 +856,13 @@ test('config set when only pnpm-workspace.yaml exists, writes to it', async () = fs.mkdirSync(configDir, { recursive: true }) fs.writeFileSync(path.join(tmp, 'pnpm-workspace.yaml'), 'fetchRetries: 5') - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: process.cwd(), cliOptions: {}, configDir, location: 'project', authConfig: {}, - }, ['set', 'fetch-timeout', '3000']) + }), ['set', 'fetch-timeout', '3000']) expect(readYamlFileSync(path.join(tmp, 'pnpm-workspace.yaml'))).toEqual({ fetchRetries: 5, diff --git a/config/commands/test/managingAuthSettings.test.ts b/config/commands/test/managingAuthSettings.test.ts index b5088ca51a..2c7279c184 100644 --- a/config/commands/test/managingAuthSettings.test.ts +++ b/config/commands/test/managingAuthSettings.test.ts @@ -3,6 +3,7 @@ import path from 'node:path' import { config } from '@pnpm/config.commands' import { tempDir } from '@pnpm/prepare' +import { createConfigCommandOpts } from './utils/index.js' import { type ConfigFilesData, readConfigFiles, writeConfigFiles } from './utils/index.js' describe.each( @@ -27,13 +28,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', `${key}=123`]) + }), ['set', `${key}=123`]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -51,13 +52,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', key]) + }), ['delete', key]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -78,14 +79,14 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ json: true, dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', key, '"123"']) + }), ['set', key, '"123"']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -103,14 +104,14 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ json: true, dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', key]) + }), ['delete', key]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -136,13 +137,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', `${key}=https://registry.example.com/`]) + }), ['set', `${key}=https://registry.example.com/`]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -160,13 +161,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', key]) + }), ['delete', key]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -189,14 +190,14 @@ describe.each( const tmp = tempDir() const configDir = path.join(tmp, 'global-config') it(`${key} should reject a non-string value`, async () => { - await expect(config.handler({ + await expect(config.handler(createConfigCommandOpts({ json: true, dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', key, '{}'])).rejects.toMatchObject({ + }), ['set', key, '{}'])).rejects.toMatchObject({ code: 'ERR_PNPM_CONFIG_SET_AUTH_NON_STRING', }) }) @@ -219,13 +220,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['set', propertyPath, '123']) + }), ['set', propertyPath, '123']) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, @@ -243,13 +244,13 @@ describe.each( } satisfies ConfigFilesData writeConfigFiles(configDir, tmp, initConfig) - await config.handler({ + await config.handler(createConfigCommandOpts({ dir: tmp, cliOptions: {}, configDir, global: true, authConfig: {}, - }, ['delete', propertyPath]) + }), ['delete', propertyPath]) expect(readConfigFiles(configDir, tmp)).toEqual({ ...initConfig, diff --git a/config/commands/test/utils/index.ts b/config/commands/test/utils/index.ts index de7d770688..b4ec7585a7 100644 --- a/config/commands/test/utils/index.ts +++ b/config/commands/test/utils/index.ts @@ -1,13 +1,46 @@ import fs from 'node:fs' import path from 'node:path' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { readIniFileSync } from 'read-ini-file' import { readYamlFileSync } from 'read-yaml-file' import { writeIniFileSync } from 'write-ini-file' import { writeYamlFileSync } from 'write-yaml-file' +import type { ConfigCommandOptions } from '../../src/ConfigCommandOptions.js' import type { config } from '../../src/index.js' +/** + * Build a {@link ConfigCommandOptions} object for tests. + * + * Accepts the flat shape that tests already use (settings like `storeDir`, + * `authConfig`, etc. mixed into a single object) and builds the `config` + * and `context` properties that the refactored config commands now expect. + */ +export function createConfigCommandOpts ( + opts: Record & { + dir: string + configDir: string + cliOptions: Record + authConfig: Record + global?: boolean + json?: boolean + location?: 'global' | 'project' + } +): ConfigCommandOptions { + return { + ...opts, + _config: opts as unknown as Config, + _context: { + cliOptions: opts.cliOptions ?? {}, + explicitlySetKeys: new Set(Object.keys(opts)), + rawLocalConfig: {}, + rootProjectManifestDir: opts.dir, + packageManager: { name: 'pnpm', version: '0.0.0' }, + } as ConfigContext, + } as ConfigCommandOptions +} + export function getOutputString (result: config.ConfigHandlerResult): string { if (result == null) throw new Error('output is null or undefined') if (typeof result === 'string') return result diff --git a/config/reader/src/Config.ts b/config/reader/src/Config.ts index e1f9a09704..426534ed77 100644 --- a/config/reader/src/Config.ts +++ b/config/reader/src/Config.ts @@ -14,22 +14,50 @@ import type { import type { OptionsFromRootManifest } from './getOptionsFromRootManifest.js' import type { AuthInfo } from './parseAuthInfo.js' -export type UniversalOptions = Pick +export type UniversalOptions = Pick & Pick export type VerifyDepsBeforeRun = 'install' | 'warn' | 'error' | 'prompt' | false -export interface Config extends AuthInfo, OptionsFromRootManifest { +/** + * Runtime state, workspace context, and CLI metadata. + * These fields are NOT user-facing settings — they are computed at startup + * or populated later by the CLI harness (e.g. workspace filtering, hook loading). + */ +export interface ConfigContext { + // -- Runtime state -- + hooks?: Hooks + finders?: Record + + // -- Workspace context -- allProjects?: Project[] selectedProjectsGraph?: ProjectsGraph allProjectsGraph?: ProjectsGraph + rootProjectManifest?: ProjectManifest + rootProjectManifestDir: string + // -- CLI metadata -- + cliOptions: Record // eslint-disable-line + rawLocalConfig: Record // eslint-disable-line + /** Keys explicitly set from workspace yaml, CLI, or env vars (not defaults). */ + explicitlySetKeys: Set + packageManager: { + name: string + version: string + } + wantedPackageManager?: EngineDependency +} + +/** + * User-facing settings + auth/network config. + * Does NOT include runtime state — see {@link ConfigContext} for that. + */ +export interface Config extends AuthInfo, OptionsFromRootManifest { allowNew: boolean autoConfirmAllPrompts?: boolean autoInstallPeers?: boolean bail: boolean color: 'always' | 'auto' | 'never' - cliOptions: Record, // eslint-disable-line useBetaCli: boolean excludeLinksFromLockfile: boolean extraBinPaths: string[] @@ -37,10 +65,7 @@ export interface Config extends AuthInfo, OptionsFromRootManifest { failIfNoMatch: boolean filter: string[] filterProd: string[] - rawLocalConfig: Record, // eslint-disable-line authConfig: Record, // eslint-disable-line - /** Keys explicitly set from workspace yaml, CLI, or env vars (not defaults). */ - explicitlySetKeys: Set dryRun?: boolean // This option might be not supported ever global?: boolean dir: string @@ -86,11 +111,6 @@ export interface Config extends AuthInfo, OptionsFromRootManifest { frozenLockfile?: boolean preferFrozenLockfile?: boolean only?: 'prod' | 'production' | 'dev' | 'development' - packageManager: { - name: string - version: string - } - wantedPackageManager?: EngineDependency preferOffline?: boolean sideEffectsCache?: boolean // for backward compatibility sideEffectsCacheReadonly?: boolean // for backward compatibility @@ -144,8 +164,6 @@ export interface Config extends AuthInfo, OptionsFromRootManifest { ignorePnpmfile?: boolean pnpmfile: string[] | string tryLoadDefaultPnpmfile?: boolean - hooks?: Hooks - finders?: Record packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy' hoistPattern?: string[] publicHoistPattern?: string[] | string @@ -203,8 +221,6 @@ export interface Config extends AuthInfo, OptionsFromRootManifest { testPattern?: string[] changedFilesIgnorePattern?: string[] - rootProjectManifestDir: string - rootProjectManifest?: ProjectManifest userConfig: Record hoist: boolean diff --git a/config/reader/src/auth.test.ts b/config/reader/src/auth.test.ts index e4297b07a8..a4976da763 100644 --- a/config/reader/src/auth.test.ts +++ b/config/reader/src/auth.test.ts @@ -1,40 +1,48 @@ import { inheritAuthConfig } from './auth.js' -import type { InheritableConfig } from './inheritPickedConfig.js' +import type { InheritableConfigPair } from './inheritPickedConfig.js' test('inheritAuthConfig copies only auth keys from source to target', () => { - const target: InheritableConfig = { - bin: 'foo', - cacheDir: '/path/to/cache/dir', - registry: 'https://npmjs.com/registry/', - authConfig: { - 'cache-dir': '/path/to/cache/dir', - registry: 'https://npmjs.com/registry/', - }, - rawLocalConfig: { + const target: InheritableConfigPair = { + config: { bin: 'foo', + cacheDir: '/path/to/cache/dir', registry: 'https://npmjs.com/registry/', + authConfig: { + 'cache-dir': '/path/to/cache/dir', + registry: 'https://npmjs.com/registry/', + }, + } as any, // eslint-disable-line + context: { + rawLocalConfig: { + bin: 'foo', + registry: 'https://npmjs.com/registry/', + }, }, } inheritAuthConfig(target, { - bin: 'bar', - cacheDir: '/path/to/another/cache/dir', - storeDir: '/path/to/custom/store/dir', - registry: 'https://example.com/local-registry/', - authConfig: { - registry: 'https://example.com/global-registry/', - '//example.com/global-registry/:_auth': 'MY_SECRET_GLOBAL_AUTH', - }, - rawLocalConfig: { + config: { bin: 'bar', - 'cache-dir': '/path/to/another/cache/dir', - 'store-dir': '/path/to/custom/store/dir', + cacheDir: '/path/to/another/cache/dir', + storeDir: '/path/to/custom/store/dir', registry: 'https://example.com/local-registry/', - '//example.com/local-registry/:_authToken': 'MY_SECRET_LOCAL_AUTH', + authConfig: { + registry: 'https://example.com/global-registry/', + '//example.com/global-registry/:_auth': 'MY_SECRET_GLOBAL_AUTH', + }, + } as any, // eslint-disable-line + context: { + rawLocalConfig: { + bin: 'bar', + 'cache-dir': '/path/to/another/cache/dir', + 'store-dir': '/path/to/custom/store/dir', + registry: 'https://example.com/local-registry/', + '//example.com/local-registry/:_authToken': 'MY_SECRET_LOCAL_AUTH', + }, }, }) - expect(target).toStrictEqual({ + expect(target.config).toMatchObject({ bin: 'foo', cacheDir: '/path/to/cache/dir', registry: 'https://example.com/local-registry/', @@ -43,10 +51,10 @@ test('inheritAuthConfig copies only auth keys from source to target', () => { registry: 'https://example.com/global-registry/', '//example.com/global-registry/:_auth': 'MY_SECRET_GLOBAL_AUTH', }, - rawLocalConfig: { - bin: 'foo', - registry: 'https://example.com/local-registry/', - '//example.com/local-registry/:_authToken': 'MY_SECRET_LOCAL_AUTH', - }, + }) + expect(target.context.rawLocalConfig).toStrictEqual({ + bin: 'foo', + registry: 'https://example.com/local-registry/', + '//example.com/local-registry/:_authToken': 'MY_SECRET_LOCAL_AUTH', }) }) diff --git a/config/reader/src/auth.ts b/config/reader/src/auth.ts index 4ce50e7588..b6fde03b5f 100644 --- a/config/reader/src/auth.ts +++ b/config/reader/src/auth.ts @@ -1,5 +1,5 @@ import type { Config } from './Config.js' -import { type InheritableConfig, inheritPickedConfig } from './inheritPickedConfig.js' +import { type InheritableConfigPair, inheritPickedConfig } from './inheritPickedConfig.js' import type { types } from './types.js' const RAW_AUTH_CFG_KEYS = [ @@ -83,8 +83,8 @@ function pickAuthConfig (localCfg: Partial): Partial { return result as Partial } -export function inheritAuthConfig (targetCfg: InheritableConfig, authSrcCfg: InheritableConfig): void { - inheritPickedConfig(targetCfg, authSrcCfg, pickAuthConfig, pickRawAuthConfig) +export function inheritAuthConfig (target: InheritableConfigPair, src: InheritableConfigPair): void { + inheritPickedConfig(target, src, pickAuthConfig, pickRawAuthConfig) } /** diff --git a/config/reader/src/index.ts b/config/reader/src/index.ts index 19d9478592..78f0f4bfb7 100644 --- a/config/reader/src/index.ts +++ b/config/reader/src/index.ts @@ -27,6 +27,7 @@ import { checkGlobalBinDir } from './checkGlobalBinDir.js' import { getDefaultWorkspaceConcurrency, getWorkspaceConcurrency } from './concurrency.js' import type { Config, + ConfigContext, ConfigWithDeprecatedSettings, ProjectConfig, UniversalOptions, @@ -63,7 +64,7 @@ export { ProjectConfigUnsupportedFieldError, } from './projectConfig.js' -export type { Config, ProjectConfig, UniversalOptions, VerifyDepsBeforeRun } +export type { Config, ConfigContext, ProjectConfig, UniversalOptions, VerifyDepsBeforeRun } export { isIniConfigKey } from './auth.js' export { type ConfigFileKey, isConfigFileKey } from './configFileKey.js' @@ -89,7 +90,7 @@ export async function getConfig (opts: { env?: Record ignoreNonAuthSettingsFromLocal?: boolean ignoreLocalSettings?: boolean -}): Promise<{ config: Config, warnings: string[] }> { +}): Promise<{ config: Config, context: ConfigContext, warnings: string[] }> { if (opts.ignoreNonAuthSettingsFromLocal) { const { ignoreNonAuthSettingsFromLocal: _, ...authOpts } = opts const globalCfgOpts: typeof authOpts = { @@ -101,7 +102,7 @@ export async function getConfig (opts: { }, } const [final, authSrc] = await Promise.all([getConfig(globalCfgOpts), getConfig(authOpts)]) - inheritAuthConfig(final.config, authSrc.config) + inheritAuthConfig(final, authSrc) final.warnings.push(...authSrc.warnings) return final } @@ -241,7 +242,7 @@ export async function getConfig (opts: { const pnpmConfig = Object.fromEntries( Object.entries(defaultOptions) .map(([key, value]) => [camelcase(key, { locale: 'en-US' }), value]) - ) as unknown as ConfigWithDeprecatedSettings + ) as unknown as (ConfigWithDeprecatedSettings & ConfigContext) for (const [key, value] of Object.entries(npmrcResult.mergedConfig)) { if (Object.hasOwn(types, key)) { @@ -254,6 +255,7 @@ export async function getConfig (opts: { // Track which keys are explicitly set (not defaults) const explicitlySetKeys = new Set(Object.keys(configFromCliOpts)) pnpmConfig.explicitlySetKeys = explicitlySetKeys + pnpmConfig.cliOptions = cliOptions Object.assign(pnpmConfig, configFromCliOpts) // Resolving the current working directory to its actual location is crucial. @@ -377,8 +379,8 @@ export async function getConfig (opts: { } pnpmConfig.packageManager = packageManager + pnpmConfig.rootProjectManifestDir = pnpmConfig.lockfileDir ?? pnpmConfig.workspaceDir ?? pnpmConfig.dir if (!opts.ignoreLocalSettings) { - pnpmConfig.rootProjectManifestDir = pnpmConfig.lockfileDir ?? pnpmConfig.workspaceDir ?? pnpmConfig.dir pnpmConfig.rootProjectManifest = await safeReadProjectManifestOnly(pnpmConfig.rootProjectManifestDir) ?? undefined if (pnpmConfig.rootProjectManifest != null) { if (pnpmConfig.rootProjectManifest.workspaces?.length && !pnpmConfig.workspaceDir) { @@ -622,7 +624,24 @@ export async function getConfig (opts: { } } - return { config: pnpmConfig, warnings } + const { + hooks, finders, + allProjects, selectedProjectsGraph, allProjectsGraph, + rootProjectManifest, rootProjectManifestDir, + cliOptions: ctxCliOptions, rawLocalConfig: ctxRawLocalConfig, + explicitlySetKeys: ctxExplicitlySetKeys, + packageManager: ctxPackageManager, wantedPackageManager, + ...config + } = pnpmConfig as Config & ConfigContext + const context: ConfigContext = { + hooks, finders, + allProjects, selectedProjectsGraph, allProjectsGraph, + rootProjectManifest, rootProjectManifestDir, + cliOptions: ctxCliOptions, rawLocalConfig: ctxRawLocalConfig, + explicitlySetKeys: ctxExplicitlySetKeys, + packageManager: ctxPackageManager, wantedPackageManager, + } + return { config, context, warnings } } function getProcessEnv (env: string): string | undefined { @@ -719,7 +738,7 @@ function getNodeVersionFromEnginesRuntime (manifest: ProjectManifest): string | return undefined } -function addSettingsFromWorkspaceManifestToConfig (pnpmConfig: Config, { +function addSettingsFromWorkspaceManifestToConfig (pnpmConfig: Config & ConfigContext, { configFromCliOpts, projectManifest, workspaceManifest, diff --git a/config/reader/src/inheritPickedConfig.ts b/config/reader/src/inheritPickedConfig.ts index a37a89f416..f65a610623 100644 --- a/config/reader/src/inheritPickedConfig.ts +++ b/config/reader/src/inheritPickedConfig.ts @@ -1,17 +1,20 @@ -import type { Config } from './Config.js' +import type { Config, ConfigContext } from './Config.js' -export type InheritableConfig = Partial & Pick +export interface InheritableConfigPair { + config: Partial & Pick + context: Pick +} export type PickConfig = (cfg: Partial) => Partial export type PickRawConfig = (cfg: Record) => Record export function inheritPickedConfig ( - targetCfg: InheritableConfig, - srcCfg: InheritableConfig, + target: InheritableConfigPair, + src: InheritableConfigPair, pickConfig: PickConfig, pickRawConfig: PickRawConfig, pickRawLocalConfig: PickRawConfig = pickRawConfig ): void { - Object.assign(targetCfg, pickConfig(srcCfg)) - Object.assign(targetCfg.authConfig, pickRawConfig(srcCfg.authConfig)) - Object.assign(targetCfg.rawLocalConfig, pickRawLocalConfig(srcCfg.rawLocalConfig)) + Object.assign(target.config, pickConfig(src.config)) + Object.assign(target.config.authConfig, pickRawConfig(src.config.authConfig)) + Object.assign(target.context.rawLocalConfig, pickRawLocalConfig(src.context.rawLocalConfig)) } diff --git a/config/reader/test/index.ts b/config/reader/test/index.ts index c4b93ee6e1..695115b31d 100644 --- a/config/reader/test/index.ts +++ b/config/reader/test/index.ts @@ -678,7 +678,7 @@ test.skip('rawLocalConfig in a workspace', async () => { fs.writeFileSync('.npmrc', 'hoist-pattern=eslint-*', 'utf8') { - const { config } = await getConfig({ + const { context } = await getConfig({ cliOptions: { 'save-exact': true, }, @@ -689,7 +689,7 @@ test.skip('rawLocalConfig in a workspace', async () => { workspaceDir, }) - expect(config.rawLocalConfig).toStrictEqual({ + expect(context.rawLocalConfig).toStrictEqual({ 'hoist-pattern': 'eslint-*', 'save-exact': true, }) @@ -699,7 +699,7 @@ test.skip('rawLocalConfig in a workspace', async () => { fs.mkdirSync('package2') process.chdir('package2') { - const { config } = await getConfig({ + const { context } = await getConfig({ cliOptions: { 'save-exact': true, }, @@ -710,7 +710,7 @@ test.skip('rawLocalConfig in a workspace', async () => { workspaceDir, }) - expect(config.rawLocalConfig).toStrictEqual({ + expect(context.rawLocalConfig).toStrictEqual({ 'hoist-pattern': '*', 'save-exact': true, }) @@ -722,7 +722,7 @@ test.skip('rawLocalConfig', async () => { fs.writeFileSync('.npmrc', 'modules-dir=modules', 'utf8') - const { config } = await getConfig({ + const { context } = await getConfig({ cliOptions: { 'save-exact': true, }, @@ -732,7 +732,7 @@ test.skip('rawLocalConfig', async () => { }, }) - expect(config.rawLocalConfig).toStrictEqual({ + expect(context.rawLocalConfig).toStrictEqual({ 'modules-dir': 'modules', 'save-exact': true, }) diff --git a/deps/compliance/commands/src/audit/audit.ts b/deps/compliance/commands/src/audit/audit.ts index 41af89fcbc..5f71b3777c 100644 --- a/deps/compliance/commands/src/audit/audit.ts +++ b/deps/compliance/commands/src/audit/audit.ts @@ -1,5 +1,5 @@ import { docsUrl, TABLE_OPTIONS } from '@pnpm/cli.utils' -import { type Config, types as allTypes, type UniversalOptions } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes, type UniversalOptions } from '@pnpm/config.reader' import { WANTED_LOCKFILE } from '@pnpm/constants' import { audit, type AuditAdvisory, type AuditLevelNumber, type AuditLevelString, type AuditReport, type AuditVulnerabilityCounts, type IgnoredAuditVulnerabilityCounts } from '@pnpm/deps.compliance.audit' import { PnpmError } from '@pnpm/error' @@ -166,10 +166,11 @@ export type AuditOptions = Pick & { | 'optional' | 'userConfig' | 'authConfig' -| 'rootProjectManifest' -| 'rootProjectManifestDir' | 'virtualStoreDirMaxLength' | 'workspaceDir' +> & Pick & InstallCommandOptions const DEFAULT_FIX_METHOD = 'override' diff --git a/deps/compliance/commands/src/licenses/licensesList.ts b/deps/compliance/commands/src/licenses/licensesList.ts index 85e220aa72..dbe51fddf1 100644 --- a/deps/compliance/commands/src/licenses/licensesList.ts +++ b/deps/compliance/commands/src/licenses/licensesList.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { readProjectManifestOnly } from '@pnpm/cli.utils' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { WANTED_LOCKFILE } from '@pnpm/constants' import { findDependencyLicenses } from '@pnpm/deps.compliance.license-scanner' import { PnpmError } from '@pnpm/error' @@ -28,11 +28,12 @@ export type LicensesCommandOptions = { | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' +| 'supportedArchitectures' +| 'virtualStoreDirMaxLength' +> & Pick & Partial> diff --git a/deps/compliance/commands/src/sbom/sbom.ts b/deps/compliance/commands/src/sbom/sbom.ts index 46525a43c7..6d52df01b3 100644 --- a/deps/compliance/commands/src/sbom/sbom.ts +++ b/deps/compliance/commands/src/sbom/sbom.ts @@ -1,7 +1,7 @@ import { FILTERING } from '@pnpm/cli.common-cli-options-help' import { packageManager } from '@pnpm/cli.meta' import { docsUrl, readProjectManifestOnly } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { WANTED_LOCKFILE } from '@pnpm/constants' import { collectSbomComponents, @@ -34,10 +34,11 @@ export type SbomCommandOptions = { | 'virtualStoreDir' | 'modulesDir' | 'pnpmHomeDir' - | 'selectedProjectsGraph' - | 'rootProjectManifest' - | 'rootProjectManifestDir' | 'virtualStoreDirMaxLength' +> & Pick & Partial> diff --git a/deps/inspection/commands/src/listing/list.ts b/deps/inspection/commands/src/listing/list.ts index 9af3132dae..9fc6719fa4 100644 --- a/deps/inspection/commands/src/listing/list.ts +++ b/deps/inspection/commands/src/listing/list.ts @@ -1,6 +1,6 @@ import { FILTERING, OPTIONS, UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { list, listForPackages } from '@pnpm/deps.inspection.list' import { listGlobalPackages } from '@pnpm/global.commands' import type { Finder, IncludedDependencies } from '@pnpm/types' @@ -84,16 +84,17 @@ For example: pnpm ls babel-* eslint-*', } export type ListCommandOptions = Pick & Partial> & { +> & Pick & Partial> & { alwaysPrintRootPackage?: boolean depth?: number excludePeers?: boolean diff --git a/deps/inspection/commands/src/outdated/outdated.ts b/deps/inspection/commands/src/outdated/outdated.ts index 19a948ec41..7b60f29b8c 100644 --- a/deps/inspection/commands/src/outdated/outdated.ts +++ b/deps/inspection/commands/src/outdated/outdated.ts @@ -9,7 +9,7 @@ import { TABLE_OPTIONS, } from '@pnpm/cli.utils' import colorizeSemverDiff from '@pnpm/colorize-semver-diff' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { outdatedDepsOfProjects, type OutdatedPackage, @@ -139,7 +139,6 @@ export type OutdatedCommandOptions = { format?: 'table' | 'list' | 'json' sortBy?: 'name' } & Pick & Pick & Partial> export async function handler ( diff --git a/deps/inspection/commands/src/peers.ts b/deps/inspection/commands/src/peers.ts index 2ae2a4e695..034d21ea30 100644 --- a/deps/inspection/commands/src/peers.ts +++ b/deps/inspection/commands/src/peers.ts @@ -1,6 +1,6 @@ import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { checkPeerDependencies } from '@pnpm/deps.inspection.peers-checker' import type { PeerDependencyIssuesByProjects } from '@pnpm/types' import chalk from 'chalk' @@ -66,8 +66,8 @@ export type PeersCommandOptions = Pick & Partial> & { +> & Pick +& Partial> & { json?: boolean lockfileDir?: string lockfileOnly?: boolean diff --git a/deps/inspection/commands/src/view/index.ts b/deps/inspection/commands/src/view/index.ts index 0b5f7662b6..5929598463 100644 --- a/deps/inspection/commands/src/view/index.ts +++ b/deps/inspection/commands/src/view/index.ts @@ -1,5 +1,5 @@ import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header' import { createFetchFromRegistry } from '@pnpm/network.fetch' @@ -49,7 +49,7 @@ export function help (): string { } export async function handler ( - opts: Config & { + opts: Config & ConfigContext & { json?: boolean }, params: string[] diff --git a/deps/inspection/commands/test/view.ts b/deps/inspection/commands/test/view.ts index 8b6d246c28..6d4b7d405c 100644 --- a/deps/inspection/commands/test/view.ts +++ b/deps/inspection/commands/test/view.ts @@ -1,4 +1,4 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { view } from '@pnpm/deps.inspection.commands' import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' @@ -37,79 +37,79 @@ test('view: rcOptionsTypes should return object', () => { test('view: missing package name throws error', async () => { await expect( - view.handler(VIEW_OPTIONS as unknown as Config, []) + view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, []) ).rejects.toMatchObject({ code: 'ERR_PNPM_MISSING_PACKAGE_NAME' }) }) test('view: non-registry spec throws error', async () => { await expect( - view.handler(VIEW_OPTIONS as unknown as Config, ['github:user/repo']) + view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['github:user/repo']) ).rejects.toMatchObject({ code: 'ERR_PNPM_INVALID_PACKAGE_NAME' }) }) test('view: successful lookup of package', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative']) expect(typeof result).toBe('string') expect(result).toContain('is-negative') }) test('view: package not found throws an error', async () => { await expect( - view.handler(VIEW_OPTIONS as unknown as Config, ['not-a-real-package-123456789']) + view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['not-a-real-package-123456789']) ).rejects.toMatchObject({ code: 'ERR_PNPM_FETCH_404' }) }) test('view: no matching version throws an error', async () => { await expect( - view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@99999.0.0']) + view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@99999.0.0']) ).rejects.toMatchObject({ code: 'ERR_PNPM_PACKAGE_NOT_FOUND' }) }) test('view: with --json option', async () => { - const result = await view.handler({ ...VIEW_OPTIONS, json: true } as unknown as Config, ['is-negative']) + const result = await view.handler({ ...VIEW_OPTIONS, json: true } as unknown as Config & ConfigContext, ['is-negative']) expect(typeof result).toBe('string') const parsed = JSON.parse(result as string) expect(parsed.name).toBe('is-negative') }) test('view: accessing a specific field', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative', 'name']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative', 'name']) expect(result).toBe('is-negative') }) test('view: accessing a specific version', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0', 'version']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0', 'version']) expect(result).toBe('1.0.0') }) test('view: accessing multiple fields adds quotes for strings', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0', 'name', 'version']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0', 'name', 'version']) expect(typeof result).toBe('string') expect(result).toContain("name = 'is-negative'") expect(result).toContain("version = '1.0.0'") }) test('view: version range resolves to matching version', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@^1.0.0', 'version']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@^1.0.0', 'version']) expect(typeof result).toBe('string') expect(result).toMatch(/^1\./) }) test('view: dist-tag resolves correctly', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@latest', 'version']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@latest', 'version']) expect(typeof result).toBe('string') expect(result).toMatch(/^\d+\.\d+\.\d+/) }) test('view: nested field selection', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0', 'dist.shasum']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0', 'dist.shasum']) expect(typeof result).toBe('string') expect(result!.length).toBeGreaterThan(0) }) test('view: field selection with --json', async () => { const result = await view.handler( - { ...VIEW_OPTIONS, json: true } as unknown as Config, + { ...VIEW_OPTIONS, json: true } as unknown as Config & ConfigContext, ['is-negative@1.0.0', 'name', 'version'] ) const parsed = JSON.parse(result as string) @@ -118,43 +118,43 @@ test('view: field selection with --json', async () => { }) test('view: text output includes header with name@version', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0']) as string + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0']) as string const firstLine = result.split('\n')[0] expect(firstLine).toContain('is-negative@1.0.0') }) test('view: text output includes dist section', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0']) as string + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0']) as string expect(result).toContain('.tarball:') expect(result).toContain('.shasum:') }) test('view: text output includes dist-tags', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative']) as string + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative']) as string expect(result).toContain('dist-tags:') expect(result).toContain('latest:') }) test('view: text output for package with dependencies shows deps count', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['@pnpm.e2e/pkg-with-1-dep@100.0.0']) as string + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['@pnpm.e2e/pkg-with-1-dep@100.0.0']) as string const firstLine = result.split('\n')[0] expect(firstLine).toContain('deps: ') expect(firstLine).not.toContain('deps: none') }) test('view: text output for package without dependencies shows deps: none', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0']) as string + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0']) as string const firstLine = result.split('\n')[0] expect(firstLine).toContain('deps: none') }) test('view: scoped package lookup', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['@pnpm.e2e/pkg-with-1-dep@100.0.0', 'name']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['@pnpm.e2e/pkg-with-1-dep@100.0.0', 'name']) expect(result).toBe('@pnpm.e2e/pkg-with-1-dep') }) test('view: object field renders as JSON', async () => { - const result = await view.handler(VIEW_OPTIONS as unknown as Config, ['is-negative@1.0.0', 'dist']) + const result = await view.handler(VIEW_OPTIONS as unknown as Config & ConfigContext, ['is-negative@1.0.0', 'dist']) expect(typeof result).toBe('string') const parsed = JSON.parse(result as string) expect(parsed.tarball).toBeDefined() diff --git a/deps/status/src/checkDepsStatus.ts b/deps/status/src/checkDepsStatus.ts index 5ec6a55fcd..07fc265ca0 100644 --- a/deps/status/src/checkDepsStatus.ts +++ b/deps/status/src/checkDepsStatus.ts @@ -3,7 +3,7 @@ import path from 'node:path' import util from 'node:util' import { parseOverrides } from '@pnpm/config.parse-overrides' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { MANIFEST_BASE_NAMES, WANTED_LOCKFILE } from '@pnpm/constants' import { hashObjectNullableWithPrefix } from '@pnpm/crypto.object-hasher' import { PnpmError } from '@pnpm/error' @@ -42,18 +42,14 @@ import { safeStat, safeStatSync } from './safeStat.js' import { statManifestFile } from './statManifestFile.js' export type CheckDepsStatusOptions = Pick & Pick & { ignoreFilteredInstallCache?: boolean ignoredWorkspaceStateSettings?: Array diff --git a/engine/pm/commands/src/self-updater/selfUpdate.ts b/engine/pm/commands/src/self-updater/selfUpdate.ts index e1b28d6547..e94c1bbdf0 100644 --- a/engine/pm/commands/src/self-updater/selfUpdate.ts +++ b/engine/pm/commands/src/self-updater/selfUpdate.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { linkBins } from '@pnpm/bins.linker' import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { createResolver } from '@pnpm/installing.client' import { resolvePackageManagerIntegrities } from '@pnpm/installing.env-installer' @@ -52,6 +52,7 @@ export type SelfUpdateCommandOptions = CreateStoreControllerOptions & Pick & Pick diff --git a/engine/runtime/commands/src/env/node.ts b/engine/runtime/commands/src/env/node.ts index e2b81e05f3..bf8d6b7c65 100644 --- a/engine/runtime/commands/src/env/node.ts +++ b/engine/runtime/commands/src/env/node.ts @@ -1,4 +1,4 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' export type NvmNodeCommandOptions = Pick & Partial> & Partial> & { remote?: boolean } diff --git a/exec/commands/src/exec.ts b/exec/commands/src/exec.ts index 50dba1df27..330f0ad89e 100644 --- a/exec/commands/src/exec.ts +++ b/exec/commands/src/exec.ts @@ -2,7 +2,7 @@ import path from 'node:path' import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl, readProjectManifestOnly, type RecursiveSummary, throwOnCommandFail } from '@pnpm/cli.utils' -import { type Config, getWorkspaceConcurrency, types } from '@pnpm/config.reader' +import { type Config, type ConfigContext, getWorkspaceConcurrency, types } from '@pnpm/config.reader' import { lifecycleLogger, type LifecycleMessage } from '@pnpm/core-loggers' import type { CheckDepsStatusOptions } from '@pnpm/deps.status' import { PnpmError } from '@pnpm/error' @@ -136,7 +136,7 @@ export function getExecutionDuration (start: [number, number]): number { return (end[0] * 1e9 + end[1]) / 1e6 } -export type ExecOpts = Required> & { +export type ExecOpts = Required> & { bail?: boolean unsafePerm?: boolean reverse?: boolean @@ -148,7 +148,6 @@ export type ExecOpts = Required> & { implicitlyFellbackFromRun?: boolean } & Pick> & { | 'userAgent' | 'verifyDepsBeforeRun' | 'workspaceDir' -> & CheckDepsStatusOptions +> & Pick & CheckDepsStatusOptions export async function handler ( opts: ExecOpts, diff --git a/exec/commands/src/run.ts b/exec/commands/src/run.ts index 33925f19c1..5199b3b6ed 100644 --- a/exec/commands/src/run.ts +++ b/exec/commands/src/run.ts @@ -7,7 +7,7 @@ import { readProjectManifestOnly, tryReadProjectManifest, } from '@pnpm/cli.utils' -import { type Config, getWorkspaceConcurrency, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, getWorkspaceConcurrency, types as allTypes } from '@pnpm/config.reader' import type { CheckDepsStatusOptions } from '@pnpm/deps.status' import { PnpmError } from '@pnpm/error' import { @@ -161,7 +161,6 @@ export type RunOpts = & { recursive?: boolean } & Pick + & Pick & ( - | { recursive?: false } & Partial> - | { recursive: true } & Required> + | { recursive?: false } & Partial & Pick> + | { recursive: true } & Required & Pick> ) & { argv?: { diff --git a/exec/commands/src/runRecursive.ts b/exec/commands/src/runRecursive.ts index 9572fef3e8..6623102a81 100644 --- a/exec/commands/src/runRecursive.ts +++ b/exec/commands/src/runRecursive.ts @@ -3,7 +3,7 @@ import path from 'node:path' import util from 'node:util' import { throwOnCommandFail } from '@pnpm/cli.utils' -import { type Config, getWorkspaceConcurrency } from '@pnpm/config.reader' +import { type Config, type ConfigContext, getWorkspaceConcurrency } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { makeNodeRequireOption, @@ -28,14 +28,13 @@ export type RecursiveRunOpts = Pick & Required> & +> & Pick & Required & Pick> & Partial> & { ifPresent?: boolean diff --git a/installing/commands/src/fetch.ts b/installing/commands/src/fetch.ts index 3c6293165a..6f71478e03 100644 --- a/installing/commands/src/fetch.ts +++ b/installing/commands/src/fetch.ts @@ -1,6 +1,6 @@ import { UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl } from '@pnpm/cli.utils' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { type InstallOptions, mutateModulesInSingleProject } from '@pnpm/installing.deps-installer' import { createStoreController, type CreateStoreControllerOptions } from '@pnpm/store.connection-manager' import type { ProjectRootDir } from '@pnpm/types' @@ -46,7 +46,7 @@ export function help (): string { }) } -type FetchCommandOptions = Pick & CreateStoreControllerOptions +type FetchCommandOptions = Pick & Pick & CreateStoreControllerOptions export async function handler (opts: FetchCommandOptions): Promise { const store = await createStoreController(opts) diff --git a/installing/commands/src/import/index.ts b/installing/commands/src/import/index.ts index 61f9a9bfa2..fe72240020 100644 --- a/installing/commands/src/import/index.ts +++ b/installing/commands/src/import/index.ts @@ -2,7 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { docsUrl } from '@pnpm/cli.utils' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { WANTED_LOCKFILE } from '@pnpm/constants' import { PnpmError } from '@pnpm/error' import gfs from '@pnpm/fs.graceful-fs' @@ -99,14 +99,15 @@ export const commandNames = ['import'] export const recursiveByDefault = true export type ImportCommandOptions = Pick & Pick & CreateStoreControllerOptions & Omit diff --git a/installing/commands/src/install.ts b/installing/commands/src/install.ts index c463151a41..5e0ef1c9da 100644 --- a/installing/commands/src/install.ts +++ b/installing/commands/src/install.ts @@ -1,7 +1,7 @@ import type { CommandHandlerMap } from '@pnpm/cli.command' import { FILTERING, OPTIONS, OUTPUT_OPTIONS, UNIVERSAL_OPTIONS } from '@pnpm/cli.common-cli-options-help' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { WANTED_LOCKFILE } from '@pnpm/constants' import { PnpmError } from '@pnpm/error' import type { CreateStoreControllerOptions } from '@pnpm/store.connection-manager' @@ -265,12 +265,10 @@ Install all optionalDependencies even when they don\'t satisfy the current envir } export type InstallCommandOptions = Pick & Pick & CreateStoreControllerOptions & Partial> & { argv: { original: string[] diff --git a/installing/commands/src/installDeps.ts b/installing/commands/src/installDeps.ts index e7b62df235..c776cfbbad 100644 --- a/installing/commands/src/installDeps.ts +++ b/installing/commands/src/installDeps.ts @@ -6,7 +6,7 @@ import { readProjectManifestOnly, tryReadProjectManifest, } from '@pnpm/cli.utils' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { checkDepsStatus } from '@pnpm/deps.status' import { PnpmError } from '@pnpm/error' import { arrayOfWorkspacePackagesToMap } from '@pnpm/installing.context' @@ -57,15 +57,12 @@ const OVERWRITE_UPDATE_OPTIONS = { } export type InstallDepsOptions = Pick & Pick & CreateStoreControllerOptions & { argv: { original: string[] diff --git a/installing/commands/src/link.ts b/installing/commands/src/link.ts index 470904c689..e26c487523 100644 --- a/installing/commands/src/link.ts +++ b/installing/commands/src/link.ts @@ -5,7 +5,7 @@ import { docsUrl, tryReadProjectManifest, } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { writeSettings } from '@pnpm/config.writer' import { PnpmError } from '@pnpm/error' import { arrayOfWorkspacePackagesToMap } from '@pnpm/installing.context' @@ -29,10 +29,7 @@ const isFilespec = isWindows ? /^(?:[./\\]|~\/|[a-z]:)/i : /^(?:[./]|~\/|[a-z]:) type LinkOpts = Pick & Pick & Partial> & install.InstallCommandOptions export const rcOptionsTypes = cliOptionsTypes diff --git a/installing/commands/src/recursive.ts b/installing/commands/src/recursive.ts index a48fb45da6..a5e7067047 100755 --- a/installing/commands/src/recursive.ts +++ b/installing/commands/src/recursive.ts @@ -10,6 +10,7 @@ import { import { createMatcherWithIndex } from '@pnpm/config.matcher' import { type Config, + type ConfigContext, createProjectConfigRecord, getWorkspaceConcurrency, type OptionsFromRootManifest, @@ -63,7 +64,6 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick & Pick & { rebuildHandler?: CommandHandler include?: IncludedDependencies diff --git a/installing/commands/src/remove.ts b/installing/commands/src/remove.ts index e4488507d6..b016e1e421 100644 --- a/installing/commands/src/remove.ts +++ b/installing/commands/src/remove.ts @@ -5,7 +5,7 @@ import { readDepNameCompletions, readProjectManifest, } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { handleGlobalRemove } from '@pnpm/global.commands' import { arrayOfWorkspacePackagesToMap } from '@pnpm/installing.context' @@ -126,32 +126,33 @@ export const completion: CompletionFunc = async (cliOpts) => { export async function handler ( opts: CreateStoreControllerOptions & Pick & Pick & { recursive?: boolean pnpmfile: string[] diff --git a/patching/commands/src/patch.ts b/patching/commands/src/patch.ts index 4644f0f10b..a9cb01b928 100644 --- a/patching/commands/src/patch.ts +++ b/patching/commands/src/patch.ts @@ -2,7 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import type { LogBase } from '@pnpm/logger' import { applyPatchToDir } from '@pnpm/patching.apply-patch' @@ -63,11 +63,12 @@ export type PatchCommandOptions = Pick & Pick & CreateStoreControllerOptions & { editDir?: string reporter?: (logObj: LogBase) => void diff --git a/patching/commands/src/patchCommit.ts b/patching/commands/src/patchCommit.ts index dbb961343d..5ab04df89e 100644 --- a/patching/commands/src/patchCommit.ts +++ b/patching/commands/src/patchCommit.ts @@ -2,7 +2,7 @@ import fs from 'node:fs' import path from 'node:path' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { createShortHash } from '@pnpm/crypto.hash' import { PnpmError } from '@pnpm/error' import { packlist } from '@pnpm/fs.packlist' @@ -52,7 +52,7 @@ export function help (): string { }) } -type PatchCommitCommandOptions = install.InstallCommandOptions & Pick +type PatchCommitCommandOptions = install.InstallCommandOptions & Pick & Pick export async function handler (opts: PatchCommitCommandOptions, params: string[]): Promise { const userDir = params[0] diff --git a/patching/commands/src/patchRemove.ts b/patching/commands/src/patchRemove.ts index d20c9543c2..8b17f398bc 100644 --- a/patching/commands/src/patchRemove.ts +++ b/patching/commands/src/patchRemove.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { install } from '@pnpm/installing.commands' import enquirer from 'enquirer' @@ -31,7 +31,7 @@ export function help (): string { }) } -export type PatchRemoveCommandOptions = install.InstallCommandOptions & Pick +export type PatchRemoveCommandOptions = install.InstallCommandOptions & Pick & Pick export async function handler (opts: PatchRemoveCommandOptions, params: string[]): Promise { let patchesToRemove = params diff --git a/pnpm/src/getConfig.ts b/pnpm/src/getConfig.ts index 21a7c94de5..fa1ff5120f 100644 --- a/pnpm/src/getConfig.ts +++ b/pnpm/src/getConfig.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { formatWarn } from '@pnpm/cli.default-reporter' import { packageManager } from '@pnpm/cli.meta' -import { type CliOptions, type Config, getConfig as _getConfig } from '@pnpm/config.reader' +import { type CliOptions, type Config, type ConfigContext, getConfig as _getConfig } from '@pnpm/config.reader' import { requireHooks } from '@pnpm/hooks.pnpmfile' import { resolveAndInstallConfigDeps } from '@pnpm/installing.env-installer' import { createStoreController } from '@pnpm/store.connection-manager' @@ -18,15 +18,15 @@ export async function getConfig ( workspaceDir: string | undefined ignoreNonAuthSettingsFromLocal?: boolean } -): Promise { - let { config, warnings } = await _getConfig({ +): Promise<{ config: Config, context: ConfigContext }> { + const { config, context, warnings } = await _getConfig({ cliOptions, globalDirShouldAllowWrite: opts.globalDirShouldAllowWrite, packageManager, workspaceDir: opts.workspaceDir, ignoreNonAuthSettingsFromLocal: opts.ignoreNonAuthSettingsFromLocal, }) - config.cliOptions = cliOptions + context.cliOptions = cliOptions applyDerivedConfig(config) if (opts.excludeReporter) { @@ -37,18 +37,22 @@ export async function getConfig ( console.warn(warnings.map((warning) => formatWarn(warning)).join('\n')) } - return config + return { config, context } } -export async function installConfigDepsAndLoadHooks (config: Config): Promise { +export async function installConfigDepsAndLoadHooks ( + config: Config, + context: ConfigContext +): Promise<{ config: Config, context: ConfigContext }> { if (config.configDependencies) { - const store = await createStoreController(config) + const store = await createStoreController({ ...config, ...context }) try { await resolveAndInstallConfigDeps(config.configDependencies, { ...config, + ...context, store: store.ctrl, storeDir: store.dir, - rootDir: config.lockfileDir ?? config.rootProjectManifestDir, + rootDir: config.lockfileDir ?? context.rootProjectManifestDir, frozenLockfile: config.frozenLockfile, }) } finally { @@ -59,7 +63,7 @@ export async function installConfigDepsAndLoadHooks (config: Config): Promise { diff --git a/pnpm/src/main.ts b/pnpm/src/main.ts index 928374b8e1..ff07427aa4 100644 --- a/pnpm/src/main.ts +++ b/pnpm/src/main.ts @@ -10,7 +10,7 @@ import path from 'node:path' import { stripVTControlCharacters as stripAnsi } from 'node:util' import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { executionTimeLogger, scopeLogger } from '@pnpm/core-loggers' import { PnpmError } from '@pnpm/error' import { globalWarn, logger } from '@pnpm/logger' @@ -87,6 +87,7 @@ export async function main (inputArgv: string[]): Promise { parseable?: boolean json?: boolean } + let context: ConfigContext try { // When we just want to print the location of the global bin directory, // we don't need the write permission to it. Related issue: #2700 @@ -95,16 +96,16 @@ export async function main (inputArgv: string[]): Promise { if (cmd === 'link' && cliParams.length === 0) { cliOptions.global = true } - config = await getConfig(cliOptions, { + ;({ config, context } = await getConfig(cliOptions, { excludeReporter: false, globalDirShouldAllowWrite, workspaceDir, ignoreNonAuthSettingsFromLocal: isDlxOrCreateCommand, - }) as typeof config - if (!isExecutedByCorepack() && cmd !== 'setup' && config.wantedPackageManager != null) { - const pm = config.wantedPackageManager + }) as { config: typeof config, context: ConfigContext }) + if (!isExecutedByCorepack() && cmd !== 'setup' && context.wantedPackageManager != null) { + const pm = context.wantedPackageManager if (pm.onFail === 'download' && pm.name === 'pnpm' && cmd !== 'self-update') { - await switchCliVersion(config) + await switchCliVersion(config, context) } else if (pm.onFail !== 'ignore' && (!cmd || !skipPackageManagerCheckForCommand.has(cmd))) { if (cliOptions.global) { globalWarn('Using --global skips the package manager check for this project') @@ -113,7 +114,7 @@ export async function main (inputArgv: string[]): Promise { } } } - config = await installConfigDepsAndLoadHooks(config) as typeof config + ;({ config, context } = await installConfigDepsAndLoadHooks(config, context) as { config: typeof config, context: ConfigContext }) if (isDlxOrCreateCommand || cmd === 'sbom') { config.useStderr = true } @@ -167,7 +168,7 @@ export async function main (inputArgv: string[]): Promise { if (printLogs) { initReporter(reporterType, { cmd, - config, + config: { ...config, ...context }, }) global[REPORTER_INITIALIZED] = reporterType } @@ -176,8 +177,8 @@ export async function main (inputArgv: string[]): Promise { // script with the same name, run the script instead of the built-in command. const typedCommandName = argv.remain[0] if (cmd != null && !builtInCommandForced && overridableByScriptCommands.has(typedCommandName) && !cliOptions.global) { - const currentDirManifest = config.dir === config.rootProjectManifestDir - ? config.rootProjectManifest + const currentDirManifest = config.dir === context.rootProjectManifestDir + ? context.rootProjectManifest : await safeReadProjectManifestOnly(config.dir) if (currentDirManifest?.scripts?.[typedCommandName]) { // Redirect to "pnpm run " @@ -191,8 +192,8 @@ export async function main (inputArgv: string[]): Promise { } } else if ( workspaceDir && - config.dir !== config.rootProjectManifestDir && - config.rootProjectManifest?.scripts?.[typedCommandName] + config.dir !== context.rootProjectManifestDir && + context.rootProjectManifest?.scripts?.[typedCommandName] ) { throw new PnpmError( 'SCRIPT_OVERRIDE_IN_WORKSPACE_ROOT', @@ -261,9 +262,9 @@ export async function main (inputArgv: string[]): Promise { process.exitCode = config.failIfNoMatch ? 1 : 0 return } - config.allProjectsGraph = filterResults.allProjectsGraph - config.selectedProjectsGraph = filterResults.selectedProjectsGraph - if (isEmpty(config.selectedProjectsGraph)) { + context.allProjectsGraph = filterResults.allProjectsGraph + context.selectedProjectsGraph = filterResults.selectedProjectsGraph + if (isEmpty(context.selectedProjectsGraph)) { if (printLogs) { console.log(`No projects matched the filters in "${wsDir}"`) } @@ -279,7 +280,7 @@ export async function main (inputArgv: string[]): Promise { if (filterResults.unmatchedFilters.length !== 0 && printLogs) { console.log(`No projects matched the filters "${filterResults.unmatchedFilters.join(', ')}" in "${wsDir}"`) } - config.allProjects = filterResults.allProjects + context.allProjects = filterResults.allProjects config.workspaceDir = wsDir } @@ -313,16 +314,18 @@ export async function main (inputArgv: string[]): Promise { !cliOptions['recursive'] ? { selected: 1 } : { - selected: Object.keys(config.selectedProjectsGraph!).length, - total: config.allProjects!.length, + selected: Object.keys(context.selectedProjectsGraph!).length, + total: context.allProjects!.length, } ), ...(workspaceDir ? { workspacePrefix: workspaceDir } : {}), }) let result = pnpmCmds[cmd ?? 'help']( - // TypeScript doesn't currently infer that the type of config - // is `Omit` after the `delete config.reporter` statement - config as Omit, + // Spread config (settings) and context (runtime state) into a single + // options object for command handlers. The original split objects are + // also passed for handlers that need them separated (e.g. config commands). + // Named "_config"/"_context" to avoid clashing with the "--config" CLI option. + { ...config, ...context, _config: config, _context: context } as Omit, cliParams, pnpmCmds ) diff --git a/pnpm/src/reporter/index.ts b/pnpm/src/reporter/index.ts index d739ef9d23..cc71fe44af 100644 --- a/pnpm/src/reporter/index.ts +++ b/pnpm/src/reporter/index.ts @@ -1,5 +1,5 @@ import { initDefaultReporter } from '@pnpm/cli.default-reporter' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import type { Log } from '@pnpm/core-loggers' import { type LogLevel, type StreamParser, streamParser, writeToConsole } from '@pnpm/logger' @@ -11,7 +11,7 @@ export function initReporter ( reporterType: ReporterType, opts: { cmd: string | null - config: Config + config: Config & ConfigContext } ): void { switch (reporterType) { diff --git a/pnpm/src/switchCliVersion.ts b/pnpm/src/switchCliVersion.ts index 7998354840..1aa4732301 100644 --- a/pnpm/src/switchCliVersion.ts +++ b/pnpm/src/switchCliVersion.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { packageManager } from '@pnpm/cli.meta' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { installPnpmToStore } from '@pnpm/engine.pm.commands' import { PnpmError } from '@pnpm/error' import { isPackageManagerResolved, resolvePackageManagerIntegrities } from '@pnpm/installing.env-installer' @@ -12,22 +12,22 @@ import { createStoreController } from '@pnpm/store.connection-manager' import spawn from 'cross-spawn' import semver from 'semver' -export async function switchCliVersion (config: Config): Promise { - const pm = config.wantedPackageManager +export async function switchCliVersion (config: Config, context: ConfigContext): Promise { + const pm = context.wantedPackageManager if (pm == null || pm.name !== 'pnpm' || pm.version == null) return - let envLockfile = await readEnvLockfile(config.rootProjectManifestDir) ?? undefined + let envLockfile = await readEnvLockfile(context.rootProjectManifestDir) ?? undefined let storeToUse: Awaited> | undefined // Check if the env lockfile already has a resolved version that satisfies the wanted version/range. let pmVersion = envLockfile?.importers['.'].packageManagerDependencies?.['pnpm']?.version if (!pmVersion || !semver.satisfies(pmVersion, pm.version, { includePrerelease: true })) { // Resolve to an exact version from the registry. - storeToUse = await createStoreController(config) + storeToUse = await createStoreController({ ...config, ...context }) envLockfile = await resolvePackageManagerIntegrities(pm.version, { envLockfile, registries: config.registries, - rootDir: config.rootProjectManifestDir, + rootDir: context.rootProjectManifestDir, storeController: storeToUse.ctrl, storeDir: storeToUse.dir, }) @@ -38,11 +38,11 @@ export async function switchCliVersion (config: Config): Promise { return } } else if (!isPackageManagerResolved(envLockfile, pmVersion)) { - storeToUse = await createStoreController(config) + storeToUse = await createStoreController({ ...config, ...context }) envLockfile = await resolvePackageManagerIntegrities(pmVersion, { envLockfile, registries: config.registries, - rootDir: config.rootProjectManifestDir, + rootDir: context.rootProjectManifestDir, storeController: storeToUse.ctrl, storeDir: storeToUse.dir, }) @@ -57,7 +57,7 @@ export async function switchCliVersion (config: Config): Promise { // We need a store controller to install pnpm. If it wasn't created during // integrity resolution (because integrities were already cached), create it now. if (!storeToUse) { - storeToUse = await createStoreController(config) + storeToUse = await createStoreController({ ...config, ...context }) } if (!envLockfile) { diff --git a/pnpm/src/types.ts b/pnpm/src/types.ts index b0483fdbe0..81500c0c49 100644 --- a/pnpm/src/types.ts +++ b/pnpm/src/types.ts @@ -1,10 +1,10 @@ -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import type { LogBase, ReadPackageHook, } from '@pnpm/types' -export type PnpmOptions = Omit & { +export type PnpmOptions = Omit & { argv: { cooked: string[] original: string[] diff --git a/pnpm/test/getConfig.test.ts b/pnpm/test/getConfig.test.ts index 04905e1276..e529f8c1a8 100644 --- a/pnpm/test/getConfig.test.ts +++ b/pnpm/test/getConfig.test.ts @@ -64,7 +64,7 @@ describe('calcPnpmfilePathsOfPluginDeps', () => { test('hoist: false removes hoistPattern', async () => { prepare() - const config = await getConfig({ + const { config } = await getConfig({ hoist: false, }, { workspaceDir: '.', diff --git a/releasing/commands/src/publish/pack.ts b/releasing/commands/src/publish/pack.ts index 89c52cfa46..cee4f0df4d 100644 --- a/releasing/commands/src/publish/pack.ts +++ b/releasing/commands/src/publish/pack.ts @@ -6,7 +6,7 @@ import { getBinsFromPackageManifest } from '@pnpm/bins.resolver' import type { Catalogs } from '@pnpm/catalogs.types' import { FILTERING } from '@pnpm/cli.common-cli-options-help' import { readProjectManifest } from '@pnpm/cli.utils' -import { type Config, getDefaultWorkspaceConcurrency, getWorkspaceConcurrency, types as allTypes, type UniversalOptions } from '@pnpm/config.reader' +import { type Config, type ConfigContext, getDefaultWorkspaceConcurrency, getWorkspaceConcurrency, types as allTypes, type UniversalOptions } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { packlist } from '@pnpm/fs.packlist' import type { Hooks } from '@pnpm/hooks.pnpmfile' @@ -102,11 +102,12 @@ export type PackOptions = Pick & Pick & Partial> & Partial> & { argv: { original: string[] diff --git a/releasing/commands/src/publish/publish.ts b/releasing/commands/src/publish/publish.ts index 47c44875e3..3f54457ec5 100644 --- a/releasing/commands/src/publish/publish.ts +++ b/releasing/commands/src/publish/publish.ts @@ -2,7 +2,7 @@ import path from 'node:path' import { FILTERING } from '@pnpm/cli.common-cli-options-help' import { docsUrl, readProjectManifest } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { runLifecycleHook, type RunLifecycleHookOptions } from '@pnpm/exec.lifecycle' import { getCurrentBranch, isGitRepo, isRemoteHistoryClean, isWorkingTreeClean } from '@pnpm/network.git-utils' @@ -122,7 +122,8 @@ export async function handler ( engineStrict?: boolean recursive?: boolean workspaceDir?: string - } & Pick, + } & Pick + & Pick, params: string[] ): Promise<{ exitCode?: number } | undefined> { const result = await publish(opts, params) @@ -143,7 +144,8 @@ export async function publish ( engineStrict?: boolean recursive?: boolean workspaceDir?: string - } & Pick, + } & Pick + & Pick, params: string[] ): Promise { if (opts.gitChecks !== false && await isGitRepo()) { diff --git a/releasing/commands/src/publish/recursivePublish.ts b/releasing/commands/src/publish/recursivePublish.ts index 2682a8c18f..f5bb251ba8 100644 --- a/releasing/commands/src/publish/recursivePublish.ts +++ b/releasing/commands/src/publish/recursivePublish.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { createResolver } from '@pnpm/installing.client' import { logger } from '@pnpm/logger' import type { ResolveFunction } from '@pnpm/resolving.resolver-base' @@ -17,13 +17,15 @@ import type { PublishPackedPkgOptions } from './publishPackedPkg.js' export type PublishRecursiveOpts = Required> & +Required> & Partial> & +Partial> & { access?: 'public' | 'restricted' argv: { @@ -61,7 +65,7 @@ Partial> + opts: PublishRecursiveOpts & Required> ): Promise<{ exitCode: number }> { const pkgs = Object.values(opts.selectedProjectsGraph).map((wsPkg) => wsPkg.package) const { resolve } = createResolver({ diff --git a/store/connection-manager/src/createNewStoreController.ts b/store/connection-manager/src/createNewStoreController.ts index b57e6ef81c..9f4fb2348f 100644 --- a/store/connection-manager/src/createNewStoreController.ts +++ b/store/connection-manager/src/createNewStoreController.ts @@ -1,7 +1,7 @@ import { promises as fs } from 'node:fs' import { packageManager } from '@pnpm/cli.meta' -import type { Config } from '@pnpm/config.reader' +import type { Config, ConfigContext } from '@pnpm/config.reader' import { type ClientOptions, createClient } from '@pnpm/installing.client' import { type CafsLocker, createPackageStore, type StoreController } from '@pnpm/store.controller' import { StoreIndex } from '@pnpm/store.index' @@ -28,7 +28,6 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick & { +> & Pick & { cafsLocker?: CafsLocker ignoreFile?: (filename: string) => boolean fetchFullMetadata?: boolean diff --git a/workspace/commands/src/init.ts b/workspace/commands/src/init.ts index dc328d8528..976ed98959 100644 --- a/workspace/commands/src/init.ts +++ b/workspace/commands/src/init.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { packageManager } from '@pnpm/cli.meta' import { docsUrl } from '@pnpm/cli.utils' -import { type Config, types as allTypes } from '@pnpm/config.reader' +import { type Config, type ConfigContext, types as allTypes } from '@pnpm/config.reader' import { PnpmError } from '@pnpm/error' import { sortKeysByPriority } from '@pnpm/object.key-sorting' import type { ProjectManifest } from '@pnpm/types' @@ -55,7 +55,7 @@ export function help (): string { } export type InitOptions = - & Pick + & Pick & Partial