Skip to content

Commit

Permalink
fix: unknown options should be checked against original cli options
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Nov 26, 2018
1 parent e51fe6e commit b51adbe
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 48 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"devDependencies": {
"@types/execa": "^0.9.0",
"@types/jest": "^23.3.9",
"@types/minimist": "^1.2.0",
"cz-conventional-changelog": "^2.1.0",
"eslint-config-rem": "^3.0.0",
"execa": "^1.0.0",
Expand All @@ -38,7 +39,7 @@
"typescript": "^3.1.6"
},
"dependencies": {
"minimost": "^1.2.0"
"minimist": "^1.2.0"
},
"release": {
"branch": "master"
Expand Down
10 changes: 7 additions & 3 deletions src/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ export default class Command {
return this
}

/**
* Check if the parsed options contain any unknown options
* Exit and output error when true
* @param options Original options, i.e. not camelCased one
* @param globalCommand
*/
checkUnknownOptions(options: { [k: string]: any }, globalCommand: Command) {
if (!this.config.allowUnknownOptions) {
for (const name of Object.keys(options)) {
Expand All @@ -225,12 +231,10 @@ export default class Command {
name.length > 1 ? `--${name}` : `-${name}`
}\``
)
process.exitCode = 1
return true
process.exit(1)
}
}
}
return false
}
}

Expand Down
112 changes: 78 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { EventEmitter } from 'events'
import path from 'path'
import minimost, { Opts as MinimostOpts } from 'minimost'
import minimist, { Opts as MinimistOpts } from 'minimist'
import Command, { HelpCallback, CommandExample } from './Command'
import { OptionConfig } from './Option'
import { getMinimostOptions } from './utils'
import { getMinimistOptions, camelcase } from './utils'

interface ParsedArgv {
args: string[]
Expand All @@ -12,6 +12,16 @@ interface ParsedArgv {
}
}

interface MinimistResult extends ParsedArgv {
args: string[]
options: {
[k: string]: any
}
originalOptions: {
[k: string]: any
}
}

const NAME_OF_GLOBAL_COMMAND = 'this-does-not-matter'

class CAC extends EventEmitter {
Expand All @@ -28,7 +38,7 @@ class CAC extends EventEmitter {
*/
args: string[]
/**
* Parsed CLI options
* Parsed CLI options, camelCased
*/
options: { [k: string]: any }

Expand Down Expand Up @@ -106,75 +116,110 @@ class CAC extends EventEmitter {
this.bin = argv[1] ? path.basename(argv[1]) : 'cli'

for (const command of this.commands) {
const minimostOptions = getMinimostOptions([
const minimistOptions = getMinimistOptions([
...this.globalCommand.options,
...command.options
])
const parsed = this.minimost(argv.slice(2), minimostOptions)
if (command.isMatched(parsed.args[0])) {
const { args, options, originalOptions } = this.minimist(
argv.slice(2),
minimistOptions
)
if (command.isMatched(args[0])) {
this.matchedCommand = command
this.args = parsed.args
this.options = parsed.options
this.emit(`command:${parsed.args[0]}`, command)
this.runCommandAction(command, this.globalCommand, parsed)
return parsed
this.args = args
this.options = options
this.emit(`command:${args[0]}`, command)
this.runCommandAction(command, this.globalCommand, {
args,
options,
originalOptions
})
return { args, options }
}
}

// Try the default command
for (const command of this.commands) {
if (command.name === '') {
const minimostOptions = getMinimostOptions([
const minimistOptions = getMinimistOptions([
...this.globalCommand.options,
...command.options
])
const parsed = this.minimost(argv.slice(2), minimostOptions)
this.args = parsed.args
this.options = parsed.options
const { args, options, originalOptions } = this.minimist(
argv.slice(2),
minimistOptions
)
this.matchedCommand = command
this.args = args
this.options = options
this.emit(`command:!`, command)
this.runCommandAction(command, this.globalCommand, parsed)
return parsed
this.runCommandAction(command, this.globalCommand, {
args,
options,
originalOptions
})
return { args, options }
}
}

const globalMinimostOptions = getMinimostOptions(this.globalCommand.options)
const parsed = this.minimost(argv.slice(2), globalMinimostOptions)
this.args = parsed.args
this.options = parsed.options
const globalMinimistOptions = getMinimistOptions(this.globalCommand.options)
const { args, options } = this.minimist(
argv.slice(2),
globalMinimistOptions
)
this.args = args
this.options = options

if (parsed.options.help && this.globalCommand.hasOption('help')) {
if (options.help && this.globalCommand.hasOption('help')) {
this.outputHelp()
return parsed
}

if (
parsed.options.version &&
options.version &&
this.globalCommand.hasOption('version') &&
this.globalCommand.versionNumber
) {
this.outputVersion()
return parsed
}

this.emit('command:*')

return parsed
return { args, options }
}

minimost(argv: string[], minimostOptions: MinimostOpts) {
const { input: args, flags: options } = minimost(argv, minimostOptions)
private minimist(
argv: string[],
minimistOptions: MinimistOpts
): MinimistResult {
const parsed = minimist(
argv,
Object.assign(
{
'--': true
},
minimistOptions
)
)

const args = parsed._
delete parsed._

const options: { [k: string]: any } = {}
for (const key of Object.keys(parsed)) {
options[camelcase(key)] = parsed[key]
}

return {
args,
options
options,
originalOptions: parsed
}
}

runCommandAction(
private runCommandAction(
command: Command,
globalCommand: Command,
{ args, options }: ParsedArgv
{ args, options, originalOptions }: MinimistResult
) {
if (options.help && globalCommand.hasOption('help')) {
return this.outputHelp(true)
Expand All @@ -186,7 +231,7 @@ class CAC extends EventEmitter {

if (!command.commandAction) return

if (command.checkUnknownOptions(options, globalCommand)) return
command.checkUnknownOptions(originalOptions, globalCommand)

// The first one is command name
if (!command.isDefaultCommand) {
Expand All @@ -199,8 +244,7 @@ class CAC extends EventEmitter {
console.error(
`error: missing required args for command "${command.rawName}"`
)
process.exitCode = 1
return
process.exit(1)
}

const actionArgs: any[] = []
Expand Down
8 changes: 7 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const findAllBrackets = (v: string) => {
return res
}

export const getMinimostOptions = (options: Option[]) => {
export const getMinimistOptions = (options: Option[]) => {
return {
default: options.reduce((res: { [k: string]: any }, option) => {
if (option.config.default !== undefined) {
Expand Down Expand Up @@ -68,3 +68,9 @@ export const findLongest = (arr: string[]) => {
export const padRight = (str: string, length: number) => {
return str.length >= length ? str : `${str}${' '.repeat(length - str.length)}`
}

export const camelcase = (input: string) => {
return input.replace(/([a-z])-([a-z])/g, (_, p1, p2) => {
return p1 + p2.toUpperCase()
})
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
"module": "commonjs",
"outDir": "dist"
},
"include": ["src"]
"include": ["src", "declarations.d.ts"]
}
8 changes: 0 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4337,14 +4337,6 @@ minimist@~0.0.1:
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=

minimost@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/minimost/-/minimost-1.2.0.tgz#a37f91d60395fc003180d208ca9e0316bcc4e3a2"
integrity sha512-/+eWyOtXw41WIUV9rBgrXna11bxbqymebSeW2arsfp/MCGCwe+2czzsOueEtLZgH4xb4QXhje5H9MLCsCPibLA==
dependencies:
"@types/minimist" "^1.2.0"
minimist "^1.2.0"

minipass@^2.2.1, minipass@^2.3.3, minipass@^2.3.4:
version "2.3.5"
resolved "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
Expand Down

0 comments on commit b51adbe

Please sign in to comment.