mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-30 21:11:55 -04:00
feat: add pnpm pm prefix to force built-in commands (#11147)
- Added `pnpm pm <command>` syntax that always runs the built-in pnpm command, bypassing any same-named script in `package.json` - When a project defines a script like `"clean": "rm -rf dist"`, `pnpm clean` runs that script, but `pnpm pm clean` runs the built-in clean command - This applies to all overridable commands: `clean`, `purge`, `rebuild`, `deploy`, `setup`
This commit is contained in:
5
.changeset/pnpm-pm-builtin-prefix.md
Normal file
5
.changeset/pnpm-pm-builtin-prefix.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Added support for `pnpm pm <command>` to force running the built-in pnpm command, bypassing any same-named script in package.json. For example, `pnpm pm clean` always runs the built-in clean command even if a "clean" script exists. Note that `pm` is now effectively reserved as a leading token; if you have a script named `pm`, run it explicitly with `pnpm run pm`.
|
||||
@@ -10,7 +10,6 @@ import path from 'node:path'
|
||||
import { stripVTControlCharacters as stripAnsi } from 'node:util'
|
||||
|
||||
import { isExecutedByCorepack, packageManager } from '@pnpm/cli.meta'
|
||||
import type { ParsedCliArgs } from '@pnpm/cli.parse-cli-args'
|
||||
import type { Config } from '@pnpm/config.reader'
|
||||
import { executionTimeLogger, scopeLogger } from '@pnpm/core-loggers'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
@@ -28,6 +27,7 @@ import { checkForUpdates } from './checkForUpdates.js'
|
||||
import { NOT_IMPLEMENTED_COMMAND_SET, overridableByScriptCommands, pnpmCmds, rcOptionsTypes, recursiveByDefaultCommands, skipPackageManagerCheckForCommand } from './cmd/index.js'
|
||||
import { formatUnknownOptionsError } from './formatError.js'
|
||||
import { getConfig, installConfigDepsAndLoadHooks } from './getConfig.js'
|
||||
import type { ParsedCliArgsWithBuiltIn } from './parseCliArgs.js'
|
||||
import { parseCliArgs } from './parseCliArgs.js'
|
||||
import { initReporter, type ReporterType } from './reporter/index.js'
|
||||
import { switchCliVersion } from './switchCliVersion.js'
|
||||
@@ -56,7 +56,7 @@ const DEPRECATED_OPTIONS = new Set([
|
||||
])
|
||||
|
||||
export async function main (inputArgv: string[]): Promise<void> {
|
||||
let parsedCliArgs!: ParsedCliArgs
|
||||
let parsedCliArgs!: ParsedCliArgsWithBuiltIn
|
||||
try {
|
||||
parsedCliArgs = await parseCliArgs(inputArgv)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
@@ -71,6 +71,7 @@ export async function main (inputArgv: string[]): Promise<void> {
|
||||
options: cliOptions,
|
||||
cmd,
|
||||
fallbackCommandUsed,
|
||||
builtInCommandForced,
|
||||
unknownOptions,
|
||||
workspaceDir,
|
||||
} = parsedCliArgs
|
||||
@@ -194,7 +195,7 @@ export async function main (inputArgv: string[]): Promise<void> {
|
||||
// Commands with scriptOverride: if the current project's package.json has a
|
||||
// script with the same name, run the script instead of the built-in command.
|
||||
const typedCommandName = argv.remain[0]
|
||||
if (cmd != null && overridableByScriptCommands.has(typedCommandName) && !cliOptions.global) {
|
||||
if (cmd != null && !builtInCommandForced && overridableByScriptCommands.has(typedCommandName) && !cliOptions.global) {
|
||||
const currentDirManifest = config.dir === config.rootProjectManifestDir
|
||||
? config.rootProjectManifest
|
||||
: await safeReadProjectManifestOnly(config.dir)
|
||||
|
||||
@@ -13,8 +13,14 @@ const RENAMED_OPTIONS = {
|
||||
store: 'store-dir',
|
||||
}
|
||||
|
||||
export async function parseCliArgs (inputArgv: string[]): Promise<ParsedCliArgs> {
|
||||
return parseCliArgsLib({
|
||||
export type ParsedCliArgsWithBuiltIn = ParsedCliArgs & { builtInCommandForced: boolean }
|
||||
|
||||
export async function parseCliArgs (inputArgv: string[]): Promise<ParsedCliArgsWithBuiltIn> {
|
||||
const builtInCommandForced = inputArgv[0] === 'pm'
|
||||
if (builtInCommandForced) {
|
||||
inputArgv.splice(0, 1)
|
||||
}
|
||||
const result = await parseCliArgsLib({
|
||||
fallbackCommand: 'run',
|
||||
escapeArgs: ['create', 'exec', 'test'],
|
||||
getCommandLongName: getCommandFullName,
|
||||
@@ -24,4 +30,5 @@ export async function parseCliArgs (inputArgv: string[]): Promise<ParsedCliArgs>
|
||||
universalOptionsTypes: GLOBAL_OPTIONS,
|
||||
universalShorthands,
|
||||
}, inputArgv)
|
||||
return { ...result, builtInCommandForced }
|
||||
}
|
||||
|
||||
@@ -240,6 +240,38 @@ test('pnpm clean errors in workspace subdir when root has clean script', () => {
|
||||
expect(output).toContain('ERR_PNPM_SCRIPT_OVERRIDE_IN_WORKSPACE_ROOT')
|
||||
})
|
||||
|
||||
test('pnpm pm clean runs the built-in command even when a clean script exists', () => {
|
||||
tempDir()
|
||||
writeJsonFile('package.json', {
|
||||
name: 'has-clean-script',
|
||||
scripts: { clean: 'echo "script-clean-ran"' },
|
||||
})
|
||||
fs.mkdirSync('node_modules/.pnpm', { recursive: true })
|
||||
|
||||
const result = execPnpmSync(['pm', 'clean'])
|
||||
expect(result.status).toBe(0)
|
||||
expect(result.stdout.toString()).not.toContain('script-clean-ran')
|
||||
expect(fs.existsSync('node_modules/.pnpm')).toBe(false)
|
||||
})
|
||||
|
||||
test('pnpm pm clean does not error in workspace subdir when root has clean script', () => {
|
||||
preparePackages([
|
||||
{ name: 'project-a', version: '1.0.0' },
|
||||
])
|
||||
|
||||
writeJsonFile('package.json', {
|
||||
name: 'root',
|
||||
version: '1.0.0',
|
||||
scripts: { clean: 'echo "root-clean"' },
|
||||
})
|
||||
writeYamlFileSync('pnpm-workspace.yaml', { packages: ['*'] })
|
||||
fs.mkdirSync(path.join('project-a', 'node_modules', '.pnpm'), { recursive: true })
|
||||
|
||||
const result = execPnpmSync(['pm', 'clean'], { cwd: path.resolve('project-a') })
|
||||
expect(result.status).toBe(0)
|
||||
expect(fs.existsSync(path.join('project-a', 'node_modules', '.pnpm'))).toBe(false)
|
||||
})
|
||||
|
||||
test('pnpm clean runs built-in in workspace subdir when root has no clean script', () => {
|
||||
preparePackages([
|
||||
{ name: 'project-a', version: '1.0.0' },
|
||||
|
||||
Reference in New Issue
Block a user