123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- 'use strict';
- const EOL = require('os').EOL;
- const levenshtein = require('fastest-levenshtein');
- const { red, cyan } = require('picocolors');
- /**
- * @param {{ [key: string]: { alias?: string } }} allowedOptions
- * @return {string[]}
- */
- const buildAllowedOptions = (allowedOptions) => {
- const options = Object.keys(allowedOptions);
- for (const { alias } of Object.values(allowedOptions)) {
- if (alias) {
- options.push(alias);
- }
- }
- 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;
- };
- /**
- * Converts a string to kebab case.
- * For example, `kebabCase('oneTwoThree') === 'one-two-three'`.
- * @param {string} opt
- * @returns {string}
- */
- const kebabCase = (opt) => {
- const matches = opt.match(/[A-Z]?[a-z]+|[A-Z]|[0-9]+/g);
- if (matches) {
- return matches.map((s) => s.toLowerCase()).join('-');
- }
- return '';
- };
- /**
- * @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 ${red(cliOption(invalid))}.`;
- if (suggestion) {
- line += ` Did you mean ${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((opt) => kebabCase(opt))
- .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);
- }, '');
- };
|