Files
pnpm/cli/parse-cli-args/src/index.ts
Zoltan Kochan 4a36b9a110 refactor: rename internal packages to @pnpm/<domain>.<leaf> convention (#10997)
## Summary

Rename all internal packages so their npm names follow the `@pnpm/<domain>.<leaf>` convention, matching their directory structure. Also rename directories to remove redundancy and improve clarity.

### Bulk rename (94 packages)

All `@pnpm/` packages now derive their name from their directory path using dot-separated segments. Exceptions: `packages/`, `__utils__/`, and `pnpm/artifacts/` keep leaf names only.

### Directory renames (removing redundant prefixes)

- `cli/cli-meta` → `cli/meta`, `cli/cli-utils` → `cli/utils`
- `config/config` → `config/reader`, `config/config-writer` → `config/writer`
- `fetching/fetching-types` → `fetching/types`
- `lockfile/lockfile-to-pnp` → `lockfile/to-pnp`
- `store/store-connection-manager` → `store/connection-manager`
- `store/store-controller-types` → `store/controller-types`
- `store/store-path` → `store/path`

### Targeted renames (clarity improvements)

- `deps/dependency-path` → `deps/path` (`@pnpm/deps.path`)
- `deps/calc-dep-state` → `deps/graph-hasher` (`@pnpm/deps.graph-hasher`)
- `deps/inspection/dependencies-hierarchy` → `deps/inspection/tree-builder` (`@pnpm/deps.inspection.tree-builder`)
- `bins/link-bins` → `bins/linker`, `bins/remove-bins` → `bins/remover`, `bins/package-bins` → `bins/resolver`
- `installing/get-context` → `installing/context`
- `store/package-store` → `store/controller`
- `pkg-manifest/manifest-utils` → `pkg-manifest/utils`

### Manifest reader/writer renames

- `workspace/read-project-manifest` → `workspace/project-manifest-reader` (`@pnpm/workspace.project-manifest-reader`)
- `workspace/write-project-manifest` → `workspace/project-manifest-writer` (`@pnpm/workspace.project-manifest-writer`)
- `workspace/read-manifest` → `workspace/workspace-manifest-reader` (`@pnpm/workspace.workspace-manifest-reader`)
- `workspace/manifest-writer` → `workspace/workspace-manifest-writer` (`@pnpm/workspace.workspace-manifest-writer`)

### Workspace package renames

- `workspace/find-packages` → `workspace/projects-reader`
- `workspace/find-workspace-dir` → `workspace/root-finder`
- `workspace/resolve-workspace-range` → `workspace/range-resolver`
- `workspace/filter-packages-from-dir` merged into `workspace/filter-workspace-packages` → `workspace/projects-filter`

### Domain moves

- `pkg-manifest/read-project-manifest` → `workspace/project-manifest-reader`
- `pkg-manifest/write-project-manifest` → `workspace/project-manifest-writer`
- `pkg-manifest/exportable-manifest` → `releasing/exportable-manifest`

### Scope

- 1206 files changed
- Updated: package.json names/deps, TypeScript imports, tsconfig references, changeset files, renovate.json, test fixtures, import ordering
2026-03-17 21:50:40 +01:00

251 lines
8.0 KiB
TypeScript

import { PnpmError } from '@pnpm/error'
import nopt from '@pnpm/nopt'
import { findWorkspaceDir } from '@pnpm/workspace.root-finder'
import didYouMean, { ReturnTypeEnums } from 'didyoumean2'
const RECURSIVE_CMDS = new Set(['recursive', 'multi', 'm'])
const SPECIALLY_ESCAPED_CMDS = new Set(['run', 'dlx'])
export interface ParsedCliArgs {
argv: {
remain: string[]
cooked: string[]
original: string[]
}
params: string[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options: Record<string, any>
cmd: string | null
unknownOptions: Map<string, string[]>
fallbackCommandUsed: boolean
workspaceDir: string | undefined
}
export async function parseCliArgs (
opts: {
escapeArgs?: string[]
fallbackCommand?: string
getCommandLongName: (commandName: string) => string | null
getTypesByCommandName: (commandName: string) => object
renamedOptions?: Record<string, string>
shorthandsByCommandName: Record<string, Record<string, string | string[]>>
universalOptionsTypes: Record<string, unknown>
universalShorthands: Record<string, string | string[]>
},
inputArgv: string[]
): Promise<ParsedCliArgs> {
const noptExploratoryResults = nopt(
{
filter: [String],
help: Boolean,
recursive: Boolean,
...opts.universalOptionsTypes,
...opts.getTypesByCommandName('add'),
...opts.getTypesByCommandName('install'),
},
{
r: '--recursive',
...opts.universalShorthands,
},
inputArgv,
0,
{ escapeArgs: opts.escapeArgs }
)
const recursiveCommandUsed = RECURSIVE_CMDS.has(noptExploratoryResults.argv.remain[0])
let commandName = getCommandName(noptExploratoryResults.argv.remain)
let cmd = commandName ? opts.getCommandLongName(commandName) : null
const fallbackCommandUsed = Boolean(commandName && !cmd && opts.fallbackCommand)
if (fallbackCommandUsed) {
cmd = opts.fallbackCommand!
commandName = opts.fallbackCommand!
inputArgv.unshift(opts.fallbackCommand!)
// The run command has special casing for --help and is handled further below.
} else if (!SPECIALLY_ESCAPED_CMDS.has(cmd!)) {
if (noptExploratoryResults['help']) {
return {
...getParsedArgsForHelp(),
workspaceDir: await getWorkspaceDir(noptExploratoryResults),
}
}
if (noptExploratoryResults['version'] || noptExploratoryResults['v']) {
return {
argv: noptExploratoryResults.argv,
cmd: null,
options: {
version: true,
},
params: noptExploratoryResults.argv.remain,
unknownOptions: new Map(),
fallbackCommandUsed: false,
workspaceDir: await getWorkspaceDir(noptExploratoryResults),
}
}
}
function getParsedArgsForHelp (): Omit<ParsedCliArgs, 'workspaceDir'> {
return {
argv: noptExploratoryResults.argv,
cmd: 'help',
options: {},
params: noptExploratoryResults.argv.remain,
unknownOptions: new Map(),
fallbackCommandUsed: false,
}
}
const types = {
...opts.universalOptionsTypes,
...opts.getTypesByCommandName(commandName),
} as any // eslint-disable-line @typescript-eslint/no-explicit-any
function getCommandName (args: string[]): string {
if (recursiveCommandUsed) {
args = args.slice(1)
}
if (opts.getCommandLongName(args[0]) !== 'install' || args.length === 1) {
return args[0]
}
return 'add'
}
function getEscapeArgsWithSpecialCases (): string[] | undefined {
if (!SPECIALLY_ESCAPED_CMDS.has(cmd!)) {
return opts.escapeArgs
}
// We'd like everything after the run script's name to be passed to the
// script's argv itself. For example, "pnpm run echo --test" should pass
// "--test" to the "echo" script. This requires determining the script's
// name and declaring it as the "escape arg".
//
// The name of the run script is normally the second argument (ex: pnpm
// run foo), but can be pushed back by recursive commands (ex: pnpm
// recursive run foo) or becomes the first argument when the fallback
// command (ex: pnpm foo) is set to 'run'.
const indexOfRunScriptName = 1 +
(recursiveCommandUsed ? 1 : 0) +
(fallbackCommandUsed && opts.fallbackCommand === 'run' ? -1 : 0)
return [noptExploratoryResults.argv.remain[indexOfRunScriptName]]
}
const { argv, ...options } = nopt(
{
recursive: Boolean,
...types,
},
{
...opts.universalShorthands,
...opts.shorthandsByCommandName[commandName],
},
inputArgv,
0,
{ escapeArgs: getEscapeArgsWithSpecialCases() }
)
const workspaceDir = await getWorkspaceDir(options)
// For the run command, it's not clear whether --help should be passed to the
// underlying script or invoke pnpm's help text until an additional nopt call.
if (SPECIALLY_ESCAPED_CMDS.has(cmd!) && options['help']) {
return {
...getParsedArgsForHelp(),
workspaceDir,
}
}
if (opts.renamedOptions != null) {
for (const [cliOption, optionValue] of Object.entries(options)) {
if (opts.renamedOptions[cliOption]) {
options[opts.renamedOptions[cliOption]] = optionValue
delete options[cliOption]
}
}
}
const params = argv.remain.slice(1)
if (options['recursive'] !== true && (options['filter'] || options['filter-prod'] || recursiveCommandUsed)) {
options['recursive'] = true
const subCmd: string | null = argv.remain[1] && opts.getCommandLongName(argv.remain[1])
if (subCmd && recursiveCommandUsed) {
params.shift()
argv.remain.shift()
cmd = subCmd
}
}
if (options['workspace-root']) {
if (options['global']) {
throw new PnpmError('OPTIONS_CONFLICT', '--workspace-root may not be used with --global')
}
if (!workspaceDir) {
throw new PnpmError('NOT_IN_WORKSPACE', '--workspace-root may only be used inside a workspace')
}
options['dir'] = workspaceDir
}
if (cmd === 'install' && params.length > 0) {
cmd = 'add'
} else if (!cmd && options['recursive']) {
cmd = 'recursive'
}
const knownOptions = new Set(Object.keys(types))
return {
argv,
cmd,
params,
workspaceDir,
fallbackCommandUsed,
...normalizeOptions(options, knownOptions),
}
}
const CUSTOM_OPTION_PREFIX = 'config.'
interface NormalizeOptionsResult {
options: Record<string, unknown>
unknownOptions: Map<string, string[]>
}
function normalizeOptions (options: Record<string, unknown>, knownOptions: Set<string>): NormalizeOptionsResult {
const standardOptionNames = []
const normalizedOptions: Record<string, unknown> = {}
for (const [optionName, optionValue] of Object.entries(options)) {
if (optionName.startsWith(CUSTOM_OPTION_PREFIX)) {
normalizedOptions[optionName.substring(CUSTOM_OPTION_PREFIX.length)] = optionValue
continue
}
normalizedOptions[optionName] = optionValue
standardOptionNames.push(optionName)
}
const unknownOptions = getUnknownOptions(standardOptionNames, knownOptions)
return { options: normalizedOptions, unknownOptions }
}
function getUnknownOptions (usedOptions: string[], knownOptions: Set<string>): Map<string, string[]> {
const unknownOptions = new Map<string, string[]>()
const closestMatches = getClosestOptionMatches.bind(null, Array.from(knownOptions))
for (const usedOption of usedOptions) {
if (knownOptions.has(usedOption) || usedOption.startsWith('//') || isScopeRegistryOption(usedOption)) continue
unknownOptions.set(usedOption, closestMatches(usedOption))
}
return unknownOptions
}
function isScopeRegistryOption (optionName: string): boolean {
return /^@[a-z0-9][\w.-]*:registry$/.test(optionName)
}
function getClosestOptionMatches (knownOptions: string[], option: string): string[] {
return didYouMean(option, knownOptions, {
returnType: ReturnTypeEnums.ALL_CLOSEST_MATCHES,
})
}
async function getWorkspaceDir (parsedOpts: Record<string, unknown>): Promise<string | undefined> {
if (parsedOpts['global'] || parsedOpts['ignore-workspace']) return undefined
const dir = parsedOpts['dir'] ?? process.cwd()
return findWorkspaceDir(dir as string)
}