You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
2.0 KiB
92 lines
2.0 KiB
'use strict'; |
|
|
|
const _ = require('lodash'); |
|
const chalk = require('chalk'); |
|
const EOL = require('os').EOL; |
|
const levenshtein = require('fastest-levenshtein'); |
|
|
|
/** |
|
* @param {{ [key: string]: { alias?: string } }} allowedOptions |
|
* @return {string[]} |
|
*/ |
|
const buildAllowedOptions = (allowedOptions) => { |
|
let options = Object.keys(allowedOptions); |
|
|
|
options = options.reduce((opts, opt) => { |
|
const alias = allowedOptions[opt].alias; |
|
|
|
if (alias) { |
|
opts.push(alias); |
|
} |
|
|
|
return opts; |
|
}, options); |
|
options.sort(); |
|
|
|
return options; |
|
}; |
|
|
|
/** |
|
* @param {string[]} all |
|
* @param {string} invalid |
|
* @return {null|string} |
|
*/ |
|
const suggest = (all, invalid) => { |
|
const maxThreshold = 10; |
|
|
|
for (let threshold = 1; threshold <= maxThreshold; threshold++) { |
|
const suggestion = all.find((option) => levenshtein.distance(option, invalid) <= threshold); |
|
|
|
if (suggestion) { |
|
return suggestion; |
|
} |
|
} |
|
|
|
return null; |
|
}; |
|
|
|
/** |
|
* @param {string} opt |
|
* @return {string} |
|
*/ |
|
const cliOption = (opt) => { |
|
if (opt.length === 1) { |
|
return `"-${opt}"`; |
|
} |
|
|
|
return `"--${_.kebabCase(opt)}"`; |
|
}; |
|
|
|
/** |
|
* @param {string} invalid |
|
* @param {string|null} suggestion |
|
* @return {string} |
|
*/ |
|
const buildMessageLine = (invalid, suggestion) => { |
|
let line = `Invalid option ${chalk.red(cliOption(invalid))}.`; |
|
|
|
if (suggestion) { |
|
line += ` Did you mean ${chalk.cyan(cliOption(suggestion))}?`; |
|
} |
|
|
|
return line + EOL; |
|
}; |
|
|
|
/** |
|
* @param {{ [key: string]: any }} allowedOptions |
|
* @param {{ [key: string]: any }} inputOptions |
|
* @return {string} |
|
*/ |
|
module.exports = function checkInvalidCLIOptions(allowedOptions, inputOptions) { |
|
const allOptions = buildAllowedOptions(allowedOptions); |
|
|
|
return Object.keys(inputOptions) |
|
.filter((opt) => !allOptions.includes(opt)) |
|
.map(_.kebabCase) |
|
.reduce((msg, invalid) => { |
|
// NOTE: No suggestion for shortcut options because it's too difficult |
|
const suggestion = invalid.length >= 2 ? suggest(allOptions, invalid) : null; |
|
|
|
return msg + buildMessageLine(invalid, suggestion); |
|
}, ''); |
|
};
|
|
|