From 6e9ad42edae705b919023f4e571a5aaa01faf5ca Mon Sep 17 00:00:00 2001 From: chlorine Date: Mon, 27 Oct 2025 08:31:20 +0800 Subject: [PATCH] feat: support `-a` option in `pnpm --help` to list all commands (#8628) close #8624 --------- Co-authored-by: Zoltan Kochan --- .changeset/popular-suits-repair.md | 5 + pnpm/src/cmd/help.ts | 527 ++++++++++++++++++++--------- pnpm/src/cmd/index.ts | 5 +- pnpm/test/help.spec.ts | 2 +- 4 files changed, 368 insertions(+), 171 deletions(-) create mode 100644 .changeset/popular-suits-repair.md diff --git a/.changeset/popular-suits-repair.md b/.changeset/popular-suits-repair.md new file mode 100644 index 0000000000..65c09dc9de --- /dev/null +++ b/.changeset/popular-suits-repair.md @@ -0,0 +1,5 @@ +--- +"pnpm": minor +--- + +Support `--all` option in `pnpm --help` to list all commands [#8628](https://github.com/pnpm/pnpm/pull/8628). diff --git a/pnpm/src/cmd/help.ts b/pnpm/src/cmd/help.ts index 1c862246c0..6d36c35cff 100644 --- a/pnpm/src/cmd/help.ts +++ b/pnpm/src/cmd/help.ts @@ -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>): (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> + +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 ]'], }) } diff --git a/pnpm/src/cmd/index.ts b/pnpm/src/cmd/index.ts index c00090f260..ce3869d918 100644 --- a/pnpm/src/cmd/index.ts +++ b/pnpm/src/cmd/index.ts @@ -108,6 +108,8 @@ export interface CommandDefinition { skipPackageManagerCheck?: boolean } +const helpByCommandName: Record string> = {} + const commands: CommandDefinition[] = [ add, approveBuilds, @@ -159,10 +161,10 @@ const commands: CommandDefinition[] = [ unlink, update, why, + createHelp(helpByCommandName), ] const handlerByCommandName: Record = {} -const helpByCommandName: Record string> = {} const cliOptionsTypesByCommandName: Record Record> = {} const aliasToFullName = new Map() const completionByCommandName: Record = {} @@ -205,7 +207,6 @@ for (let i = 0; i < commands.length; i++) { } } -handlerByCommandName.help = createHelp(helpByCommandName) handlerByCommandName['completion-server'] = createCompletionServer({ cliOptionsTypesByCommandName, completionByCommandName, diff --git a/pnpm/test/help.spec.ts b/pnpm/test/help.spec.ts index 50ecfaffab..2e119ed1f8 100644 --- a/pnpm/test/help.spec.ts +++ b/pnpm/test/help.spec.ts @@ -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"') })