123 lines
3.5 KiB
JavaScript
123 lines
3.5 KiB
JavaScript
import process from 'node:process';
|
|
import { decamelizeFlagKey } from './utils.js';
|
|
|
|
const validateFlags = (flags, options) => {
|
|
for (const [flagKey, flagValue] of Object.entries(options.flags)) {
|
|
if (flagKey !== '--' && !flagValue.isMultiple && Array.isArray(flags[flagKey])) {
|
|
throw new Error(`The flag --${flagKey} can only be set once.`);
|
|
}
|
|
}
|
|
};
|
|
|
|
const validateChoicesByFlag = (flagKey, flagValue, receivedInput) => {
|
|
const {choices, isRequired} = flagValue;
|
|
|
|
if (!choices) {
|
|
return;
|
|
}
|
|
|
|
const valueMustBeOneOf = `Value must be one of: [\`${choices.join('`, `')}\`]`;
|
|
|
|
if (!receivedInput) {
|
|
if (isRequired) {
|
|
return `Flag \`${decamelizeFlagKey(flagKey)}\` has no value. ${valueMustBeOneOf}`;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(receivedInput)) {
|
|
const unknownValues = receivedInput.filter(index => !choices.includes(index));
|
|
|
|
if (unknownValues.length > 0) {
|
|
const valuesText = unknownValues.length > 1 ? 'values' : 'value';
|
|
|
|
return `Unknown ${valuesText} for flag \`${decamelizeFlagKey(flagKey)}\`: \`${unknownValues.join('`, `')}\`. ${valueMustBeOneOf}`;
|
|
}
|
|
} else if (!choices.includes(receivedInput)) {
|
|
return `Unknown value for flag \`${decamelizeFlagKey(flagKey)}\`: \`${receivedInput}\`. ${valueMustBeOneOf}`;
|
|
}
|
|
};
|
|
|
|
const validateChoices = (flags, receivedFlags) => {
|
|
const errors = [];
|
|
|
|
for (const [flagKey, flagValue] of Object.entries(flags)) {
|
|
const receivedInput = receivedFlags[flagKey];
|
|
const errorMessage = validateChoicesByFlag(flagKey, flagValue, receivedInput);
|
|
|
|
if (errorMessage) {
|
|
errors.push(errorMessage);
|
|
}
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
throw new Error(`${errors.join('\n')}`);
|
|
}
|
|
};
|
|
|
|
const validate = (flags, options) => {
|
|
validateFlags(flags, options);
|
|
validateChoices(options.flags, flags);
|
|
};
|
|
|
|
const reportUnknownFlags = unknownFlags => {
|
|
console.error([
|
|
`Unknown flag${unknownFlags.length > 1 ? 's' : ''}`,
|
|
...unknownFlags,
|
|
].join('\n'));
|
|
};
|
|
|
|
const checkUnknownFlags = input => {
|
|
const unknownFlags = input.filter(item => typeof item === 'string' && item.startsWith('-'));
|
|
if (unknownFlags.length > 0) {
|
|
reportUnknownFlags(unknownFlags);
|
|
process.exit(2);
|
|
}
|
|
};
|
|
|
|
const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => {
|
|
const flag = definedFlags[flagName];
|
|
let isFlagRequired = true;
|
|
|
|
if (typeof flag.isRequired === 'function') {
|
|
isFlagRequired = flag.isRequired(receivedFlags, input);
|
|
if (typeof isFlagRequired !== 'boolean') {
|
|
throw new TypeError(`Return value for isRequired callback should be of type boolean, but ${typeof isFlagRequired} was returned.`);
|
|
}
|
|
}
|
|
|
|
if (receivedFlags[flagName] === undefined) {
|
|
return isFlagRequired;
|
|
}
|
|
|
|
return flag.isMultiple && receivedFlags[flagName].length === 0 && isFlagRequired;
|
|
};
|
|
|
|
const reportMissingRequiredFlags = missingRequiredFlags => {
|
|
console.error(`Missing required flag${missingRequiredFlags.length > 1 ? 's' : ''}`);
|
|
for (const flag of missingRequiredFlags) {
|
|
console.error(`\t${decamelizeFlagKey(flag.key)}${flag.shortFlag ? `, -${flag.shortFlag}` : ''}`);
|
|
}
|
|
};
|
|
|
|
const checkMissingRequiredFlags = (flags, receivedFlags, input) => {
|
|
const missingRequiredFlags = [];
|
|
if (flags === undefined) {
|
|
return [];
|
|
}
|
|
|
|
for (const flagName of Object.keys(flags)) {
|
|
if (flags[flagName].isRequired && isFlagMissing(flagName, flags, receivedFlags, input)) {
|
|
missingRequiredFlags.push({key: flagName, ...flags[flagName]});
|
|
}
|
|
}
|
|
|
|
if (missingRequiredFlags.length > 0) {
|
|
reportMissingRequiredFlags(missingRequiredFlags);
|
|
process.exit(2);
|
|
}
|
|
};
|
|
|
|
export { checkMissingRequiredFlags, checkUnknownFlags, validate };
|