diff --git a/packages/parse-cli-args/src/index.ts b/packages/parse-cli-args/src/index.ts index a73b741ffc..40cd78a508 100644 --- a/packages/parse-cli-args/src/index.ts +++ b/packages/parse-cli-args/src/index.ts @@ -3,9 +3,27 @@ import nopt = require('nopt') const RECURSIVE_CMDS = new Set(['recursive', 'multi', 'm']) +export interface ParsedCliArgs { + argv: { + remain: string[], + cooked: string[], + original: string[], + } + cliArgs: string[] + cliConf: { + // tslint:disable-next-line: no-any + [option: string]: any, + } + cmd: string | null + subCmd: string | null + isKnownCommand: boolean + unknownOptions: string[] + workspaceDir?: string +} + export default async function parseCliArgs ( opts: { - getCommandLongName: (commandName: string) => string, + getCommandLongName: (commandName: string) => string | null, getTypesByCommandName: (commandName: string) => object, globalOptionsTypes: Record, isKnownCommand: (commandName: string) => boolean, @@ -13,7 +31,7 @@ export default async function parseCliArgs ( shortHands: Record, }, inputArgv: string[], -) { +): Promise { const noptExploratoryResults = nopt( { filter: [String], @@ -36,7 +54,7 @@ export default async function parseCliArgs ( cliArgs: noptExploratoryResults.argv.remain, cliConf: {}, cmd: 'help', - dir: process.cwd(), + isKnownCommand: true, subCmd: null, unknownOptions: [] as string[], } @@ -67,9 +85,10 @@ export default async function parseCliArgs ( } } - let cmd = opts.getCommandLongName(argv.remain[0]) + let cmd = argv.remain.length > 0 ? opts.getCommandLongName(argv.remain[0]) : null + let isKnownCommand = true if (cmd && !opts.isKnownCommand(cmd) && !RECURSIVE_CMDS.has(cmd)) { - cmd = 'help' + isKnownCommand = false } let subCmd: string | null = argv.remain[1] && opts.getCommandLongName(argv.remain[1]) @@ -77,9 +96,9 @@ export default async function parseCliArgs ( // `pnpm install ""` is going to be just `pnpm install` const cliArgs = argv.remain.slice(1).filter(Boolean) - if (cliConf['recursive'] !== true && (cliConf['filter'] || RECURSIVE_CMDS.has(cmd))) { + if (cliConf['recursive'] !== true && (cliConf['filter'] || RECURSIVE_CMDS.has(cmd!))) { cliConf['recursive'] = true - if (subCmd && RECURSIVE_CMDS.has(cmd)) { + if (subCmd && RECURSIVE_CMDS.has(cmd!)) { cliArgs.shift() argv.remain.shift() cmd = subCmd @@ -119,6 +138,7 @@ export default async function parseCliArgs ( cliArgs, cliConf, cmd, + isKnownCommand, subCmd, unknownOptions, workspaceDir, diff --git a/packages/parse-cli-args/test/index.ts b/packages/parse-cli-args/test/index.ts index d1fd3b2e71..28daeae3f7 100644 --- a/packages/parse-cli-args/test/index.ts +++ b/packages/parse-cli-args/test/index.ts @@ -85,13 +85,14 @@ test('when runnning a global command inside a workspace, the workspace should be t.end() }) -test('help is returned when an unknown command is used', async (t) => { - const { cmd } = await parseCliArgs({ +test('isKnownCommand is false when an unknown command is used', async (t) => { + const { cmd, isKnownCommand } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: {}, isKnownCommand: () => false, }, ['foo']) - t.equal(cmd, 'help') + t.false(isKnownCommand) + t.equal(cmd, 'foo') t.end() }) @@ -166,9 +167,10 @@ test('if a help option is used, set cmd to "help"', async (t) => { }) test('no command', async (t) => { - const { cmd } = await parseCliArgs({ + const { cmd, isKnownCommand } = await parseCliArgs({ ...DEFAULT_OPTS, }, ['--version']) - t.equal(cmd, undefined) + t.equal(cmd, null) + t.true(isKnownCommand) t.end() }) diff --git a/packages/pnpm/src/cmd/index.ts b/packages/pnpm/src/cmd/index.ts index cf3442eb41..11ede45775 100644 --- a/packages/pnpm/src/cmd/index.ts +++ b/packages/pnpm/src/cmd/index.ts @@ -130,8 +130,8 @@ export function getCliOptionsTypes (commandName: string) { return cliOptionsTypesByCommandName[commandName]?.() || {} } -export function getRCOptionsTypes (commandName: string) { - return rcOptionsTypesByCommandName[commandName]?.() || {} +export function getRCOptionsTypes (commandName: string | null) { + return (commandName && rcOptionsTypesByCommandName[commandName]?.()) || {} } export function getCommandFullName (commandName: string) { diff --git a/packages/pnpm/src/main.ts b/packages/pnpm/src/main.ts index dff06a1563..c261464c20 100644 --- a/packages/pnpm/src/main.ts +++ b/packages/pnpm/src/main.ts @@ -30,7 +30,13 @@ import parseCliArgs from './parseCliArgs' import initReporter, { ReporterType } from './reporter' export default async function run (inputArgv: string[]) { - const { argv, cliArgs, cliConf, cmd, subCmd, unknownOptions, workspaceDir } = await parseCliArgs(inputArgv) + const { argv, cliArgs, cliConf, cmd, subCmd, isKnownCommand, unknownOptions, workspaceDir } = await parseCliArgs(inputArgv) + if (!isKnownCommand) { + console.error(`${chalk.bgRed.black('\u2009ERROR\u2009')} ${chalk.red(`Unknown command '${cmd}'`)}`) + console.log(`For help, run: pnpm help`) + process.exit(1) + } + if (unknownOptions.length > 0) { let errorMsg = `${chalk.bgRed.black('\u2009ERROR\u2009')}` if (unknownOptions.length === 1) { @@ -39,7 +45,7 @@ export default async function run (inputArgv: string[]) { errorMsg += ` ${chalk.red(`Unknown options ${unknownOptions.map(unknownOption => `'${unknownOption}'`).join(', ')}`)}` } console.error(errorMsg) - console.log(`For help, run: pnpm help ${cmd}`) + console.log(`For help, run: pnpm help${cmd ? ` ${cmd}` : ''}`) process.exit(1) } process.env['npm_config_argv'] = JSON.stringify(argv) @@ -59,7 +65,7 @@ export default async function run (inputArgv: string[]) { } catch (err) { // Reporting is not initialized at this point, so just printing the error console.error(`${chalk.bgRed.black('\u2009ERROR\u2009')} ${chalk.red(err.message)}`) - console.log(`For help, run: pnpm help ${cmd}`) + console.log(`For help, run: pnpm help${cmd ? ` ${cmd}` : ''}`) process.exit(1) return } diff --git a/packages/pnpm/src/reporter/index.ts b/packages/pnpm/src/reporter/index.ts index 9822196990..e6fc047af4 100644 --- a/packages/pnpm/src/reporter/index.ts +++ b/packages/pnpm/src/reporter/index.ts @@ -8,7 +8,7 @@ export type ReporterType = 'default' | 'ndjson' | 'silent' | 'append-only' export default ( reporterType: ReporterType, opts: { - cmd: string, + cmd: string | null, subCmd: string | null, config: Config, }, @@ -17,7 +17,7 @@ export default ( case 'default': defaultReporter({ context: { - argv: opts.subCmd ? [opts.cmd, opts.subCmd] : [opts.cmd], + argv: opts.cmd ? opts.subCmd ? [opts.cmd, opts.subCmd] : [opts.cmd] : [], config: opts.config, }, reportingOptions: { @@ -30,7 +30,7 @@ export default ( case 'append-only': defaultReporter({ context: { - argv: opts.subCmd ? [opts.cmd, opts.subCmd] : [opts.cmd], + argv: opts.cmd ? opts.subCmd ? [opts.cmd, opts.subCmd] : [opts.cmd] : [], config: opts.config, }, reportingOptions: { diff --git a/packages/pnpm/test/cli.ts b/packages/pnpm/test/cli.ts index 74385f4f5f..a0e9503ba1 100644 --- a/packages/pnpm/test/cli.ts +++ b/packages/pnpm/test/cli.ts @@ -67,7 +67,16 @@ test('pass through to npm with all the args', async t => { t.equal(result.status, 0, 'command was successfull') }) -test('command fails when unsupport flag is used', async (t) => { +test('pnpm fails when an unsupported command is used', async (t) => { + const project = prepare(t) + + const { status, stderr } = execPnpmSync('unsupported-command') + + t.equal(status, 1, 'command failed') + t.ok(stderr.toString().includes("Unknown command 'unsupported-command'")) +}) + +test('command fails when an unsupported flag is used', async (t) => { const project = prepare(t) const { status, stderr } = execPnpmSync('update', '--save-dev')