feat: support -a option in pnpm --help to list all commands (#8628)

close #8624

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
chlorine
2025-10-27 08:31:20 +08:00
committed by GitHub
parent a1a1ab3f90
commit 6e9ad42eda
4 changed files with 368 additions and 171 deletions

View File

@@ -0,0 +1,5 @@
---
"pnpm": minor
---
Support `--all` option in `pnpm --help` to list all commands [#8628](https://github.com/pnpm/pnpm/pull/8628).

View File

@@ -1,187 +1,378 @@
import { packageManager, detectIfCurrentPkgIsExecutable } from '@pnpm/cli-meta'
import { docsUrl } from '@pnpm/cli-utils'
import renderHelp from 'render-help'
import { type CommandDefinition } from './index.js'
export function createHelp (helpByCommandName: Record<string, () => string>): (opts: unknown, params: string[]) => string {
return function (opts: unknown, params: string[]) {
let helpText!: string
if (params.length === 0) {
helpText = getHelpText()
} else if (helpByCommandName[params[0]]) {
helpText = helpByCommandName[params[0]]()
} else {
helpText = `No results for "${params[0]}"`
}
return `Version ${packageManager.version}\
${detectIfCurrentPkgIsExecutable() != null ? ` (compiled to binary; bundled Node.js ${process.version})` : ''}\
\n${helpText}\n`
type HelpByCommandName = Record<string, () => string>
export function createHelp (helpByCommandName: HelpByCommandName): CommandDefinition {
return {
commandNames: ['help'],
shorthands: {
a: '--all',
},
cliOptionsTypes: () => ({
all: Boolean,
}),
rcOptionsTypes: () => ({}),
help: helpHelp,
handler: handler.bind(null, helpByCommandName),
}
}
function getHelpText (): string {
function helpHelp () {
return renderHelp({
description: 'Display help information about pnpm',
descriptionLists: [
{
title: 'Manage your dependencies',
list: [
{
description: 'Install all dependencies for a project',
name: 'install',
shortAlias: 'i',
},
{
description: 'Installs a package and any packages that it depends on. By default, any new package is installed as a prod dependency',
name: 'add',
},
{
description: 'Updates packages to their latest version based on the specified range',
name: 'update',
shortAlias: 'up',
},
{
description: 'Removes packages from node_modules and from the project\'s package.json',
name: 'remove',
shortAlias: 'rm',
},
{
description: 'Connect the local project to another one',
name: 'link',
shortAlias: 'ln',
},
{
description: 'Unlinks a package. Like yarn unlink but pnpm re-installs the dependency after removing the external link',
name: 'unlink',
},
{
description: 'Generates a pnpm-lock.yaml from an npm package-lock.json (or npm-shrinkwrap.json) file',
name: 'import',
},
{
description: 'Runs a pnpm install followed immediately by a pnpm test',
name: 'install-test',
shortAlias: 'it',
},
{
description: 'Rebuild a package',
name: 'rebuild',
shortAlias: 'rb',
},
{
description: 'Removes extraneous packages',
name: 'prune',
},
],
},
{
title: 'Review your dependencies',
list: [
{
description: 'Checks for known security issues with the installed packages',
name: 'audit',
},
{
description: 'Print all the versions of packages that are installed, as well as their dependencies, in a tree-structure',
name: 'list',
shortAlias: 'ls',
},
{
description: 'Check for outdated packages',
name: 'outdated',
},
{
description: 'Check licenses in consumed packages',
name: 'licenses',
},
],
},
{
title: 'Run your scripts',
list: [
{
description: 'Executes a shell command in scope of a project',
name: 'exec',
},
{
description: 'Runs a defined package script',
name: 'run',
},
{
description: 'Runs a package\'s "test" script, if one was provided',
name: 'test',
shortAlias: 't',
},
{
description: 'Runs an arbitrary command specified in the package\'s "start" property of its "scripts" object',
name: 'start',
},
],
},
{
title: 'Other',
list: [
{
description: 'Create a tarball from a package',
name: 'pack',
},
{
description: 'Publishes a package to the registry',
name: 'publish',
},
{
description: 'Prints the effective modules directory',
name: 'root',
},
{
description: 'Prints the index file of a specific package from the store',
name: 'cat-index',
},
{
description: 'Prints the contents of a file based on the hash value stored in the index file',
name: 'cat-file',
},
{
description: 'Experimental! Lists the packages that include the file with the specified hash.',
name: 'find-hash',
},
],
},
{
title: 'Manage your store',
list: [
{
description: 'Adds new packages to the pnpm store directly. Does not modify any projects or files outside the store',
name: 'store add',
},
{
description: 'Prints the path to the active store directory',
name: 'store path',
},
{
description: 'Removes unreferenced (extraneous, orphan) packages from the store',
name: 'store prune',
},
{
description: 'Checks for modified packages in the store',
name: 'store status',
},
],
},
{
title: 'Options',
list: [
{
description: 'Run the command for each project in the workspace.',
name: '--recursive',
shortAlias: '-r',
description: 'Print all the available commands',
name: '--all',
shortAlias: '-a',
},
],
},
],
usages: [],
url: docsUrl('help'),
})
}
function handler (helpByCommandName: HelpByCommandName, opts: { all?: boolean }, params: string[]): string {
let helpText!: string
if (params.length === 0) {
helpText = getHelpText({ all: opts.all ?? false })
} else if (helpByCommandName[params[0]]) {
helpText = helpByCommandName[params[0]]()
} else {
helpText = `No results for "${params[0]}"`
}
return `Version ${packageManager.version}\
${detectIfCurrentPkgIsExecutable() != null ? ` (compiled to binary; bundled Node.js ${process.version})` : ''}\
\n${helpText}\n`
}
interface DescriptionItem {
shortAlias?: string
name: string
description?: string
advanced?: boolean
}
type DescriptionLists = Array<{
title: string
advanced?: boolean
list: DescriptionItem[]
}>
function getHelpText ({ all }: { all: boolean }): string {
const descriptionLists: DescriptionLists = [
{
title: 'Manage your dependencies',
list: [
{
description: 'Install all dependencies for a project',
name: 'install',
shortAlias: 'i',
},
{
description: 'Installs a package and any packages that it depends on. By default, any new package is installed as a prod dependency',
name: 'add',
},
{
description: 'Updates packages to their latest version based on the specified range',
name: 'update',
shortAlias: 'up',
},
{
description: 'Removes packages from node_modules and from the project\'s package.json',
name: 'remove',
shortAlias: 'rm',
},
{
description: 'Connect the local project to another one',
name: 'link',
shortAlias: 'ln',
},
{
description: 'Unlinks a package. Like yarn unlink but pnpm re-installs the dependency after removing the external link',
name: 'unlink',
},
{
description: 'Generates a pnpm-lock.yaml from an npm package-lock.json (or npm-shrinkwrap.json) file',
name: 'import',
advanced: true,
},
{
description: 'Runs a pnpm install followed immediately by a pnpm test',
name: 'install-test',
shortAlias: 'it',
advanced: true,
},
{
description: 'Rebuild a package',
name: 'rebuild',
shortAlias: 'rb',
advanced: true,
},
{
description: 'Removes extraneous packages',
name: 'prune',
advanced: true,
},
{
description: 'Fetch packages from a lockfile into virtual store, package manifest is ignored',
name: 'fetch',
advanced: true,
},
{
description: 'Perform an install removing older dependencies in the lockfile if a newer version can be used',
name: 'dedupe',
advanced: true,
},
],
},
{
title: 'Patch your dependencies',
advanced: true,
list: [
{
description: 'Prepare a package for patching',
name: 'patch',
},
{
description: 'Generate a patch out of a directory',
name: 'patch-commit',
},
{
description: 'Remove existing patch files',
name: 'patch-remove',
},
],
},
{
title: 'Review your dependencies',
list: [
{
description: 'Checks for known security issues with the installed packages',
name: 'audit',
},
{
description: 'Print all the versions of packages that are installed, as well as their dependencies, in a tree-structure',
name: 'list',
shortAlias: 'ls',
},
{
description: 'Check for outdated packages',
name: 'outdated',
},
{
description: 'Check licenses in consumed packages',
name: 'licenses',
advanced: true,
},
{
description: 'Shows all packages that depend on the specified package',
name: 'why',
},
],
},
{
title: 'Run your scripts',
list: [
{
description: 'Executes a shell command in scope of a project',
name: 'exec',
},
{
description: 'Runs a defined package script',
name: 'run',
},
{
description: 'Runs a package\'s "test" script, if one was provided',
name: 'test',
shortAlias: 't',
advanced: true,
},
{
description: 'Runs an arbitrary command specified in the package\'s "start" property of its "scripts" object',
name: 'start',
advanced: true,
},
{
description: 'Fetches a package from the registry without installing it as a dependency, hot loads it, and runs whatever default command binary it exposes',
name: 'dlx',
},
{
description: 'Create a project from a "create-*" or "@foo/create-*" starter kit',
name: 'create',
},
{
description: 'Approve dependencies for running scripts during installation',
name: 'approve-builds',
advanced: true,
},
{
description: 'Print the list of packages with blocked build scripts',
name: 'ignored-builds',
advanced: true,
},
],
},
{
title: 'Other',
list: [
{
description: 'Create a tarball from a package',
name: 'pack',
advanced: true,
},
{
description: 'Publishes a package to the registry',
name: 'publish',
},
{
description: 'Updates pnpm to the latest version',
name: 'self-update',
},
{
description: 'Create a package.json file',
name: 'init',
},
{
description: 'Manage the pnpm configuration files',
name: 'config',
shortAlias: 'c',
},
{
description: 'Prints the effective modules directory',
name: 'root',
advanced: true,
},
{
description: 'Prints the directory into which the executables of dependencies are linked',
name: 'bin',
advanced: true,
},
{
description: 'Deploy a package from a workspace',
name: 'deploy',
advanced: true,
},
{
description: 'Checks for known common issues with pnpm configuration',
name: 'doctor',
advanced: true,
},
],
},
{
title: 'Manage your environments',
advanced: true,
list: [
{
description: 'Manage Node.js versions',
name: 'env ',
},
],
},
{
title: 'Inspect your store',
advanced: true,
list: [
{
description: 'Prints the index file of a specific package from the store',
name: 'cat-index',
},
{
description: 'Prints the contents of a file based on the hash value stored in the index file',
name: 'cat-file',
},
{
description: 'Experimental! Lists the packages that include the file with the specified hash',
name: 'find-hash',
},
],
},
{
title: 'Manage your store',
advanced: true,
list: [
{
description: 'Adds new packages to the pnpm store directly. Does not modify any projects or files outside the store',
name: 'store add',
},
{
description: 'Prints the path to the active store directory',
name: 'store path',
},
{
description: 'Removes unreferenced (extraneous, orphan) packages from the store',
name: 'store prune',
},
{
description: 'Checks for modified packages in the store',
name: 'store status',
},
],
},
{
title: 'Manage your cache',
advanced: true,
list: [
{
description: 'Experimental! Lists the available packages metadata cache. Supports filtering by glob',
name: 'cache list',
},
{
description: 'Experimental! Lists all registries that have their metadata cache locally',
name: 'cache list-registries',
},
{
description: 'Experimental! Views information from the specified package\'s cache',
name: 'cache view',
},
{
description: 'Experimental! Deletes metadata cache for the specified package(s). Supports patterns',
name: 'cache delete',
},
],
},
{
title: 'Options',
list: [
{
description: 'Run the command for each project in the workspace.',
name: '--recursive',
shortAlias: '-r',
},
],
},
]
const filteredDescriptionLists = []
for (const descriptionList of descriptionLists) {
if (!descriptionList.advanced || all) {
filteredDescriptionLists.push({
title: descriptionList.title,
list: descriptionList.list.filter((item) => !item.advanced || all),
})
}
}
return renderHelp({
description: all ? '' : 'These are common pnpm commands used in various situations, use \'pnpm help -a\' to list all commands',
descriptionLists: filteredDescriptionLists,
usages: ['pnpm [command] [flags]', 'pnpm [ -h | --help | -v | --version ]'],
})
}

View File

@@ -108,6 +108,8 @@ export interface CommandDefinition {
skipPackageManagerCheck?: boolean
}
const helpByCommandName: Record<string, () => string> = {}
const commands: CommandDefinition[] = [
add,
approveBuilds,
@@ -159,10 +161,10 @@ const commands: CommandDefinition[] = [
unlink,
update,
why,
createHelp(helpByCommandName),
]
const handlerByCommandName: Record<string, Command> = {}
const helpByCommandName: Record<string, () => string> = {}
const cliOptionsTypesByCommandName: Record<string, () => Record<string, unknown>> = {}
const aliasToFullName = new Map<string, string>()
const completionByCommandName: Record<string, CompletionFunc> = {}
@@ -205,7 +207,6 @@ for (let i = 0; i < commands.length; i++) {
}
}
handlerByCommandName.help = createHelp(helpByCommandName)
handlerByCommandName['completion-server'] = createCompletionServer({
cliOptionsTypesByCommandName,
completionByCommandName,

View File

@@ -2,6 +2,6 @@ import { createHelp } from '../src/cmd/help.js'
test('print an error when help not found', () => {
expect(
createHelp({})({}, ['foo']).split('\n')[1]
(createHelp({}).handler({}, ['foo']) as string).split('\n')[1]
).toBe('No results for "foo"')
})