options.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import process from 'node:process';
  2. import { dirname } from 'node:path';
  3. import { fileURLToPath } from 'node:url';
  4. import { a as readPackageUpSync } from './dependencies.js';
  5. import { joinFlagKeys, decamelizeFlagKey } from './utils.js';
  6. const validateOptions = options => {
  7. const invalidOptionFilters = {
  8. flags: {
  9. keyContainsDashes: {
  10. filter: ([flagKey]) => flagKey.includes('-') && flagKey !== '--',
  11. message: flagKeys => `Flag keys may not contain '-'. Invalid flags: ${joinFlagKeys(flagKeys, '')}`,
  12. },
  13. aliasIsSet: {
  14. filter: ([, flag]) => Object.hasOwn(flag, 'alias'),
  15. message: flagKeys => `The option \`alias\` has been renamed to \`shortFlag\`. The following flags need to be updated: ${joinFlagKeys(flagKeys)}`,
  16. },
  17. choicesNotAnArray: {
  18. filter: ([, flag]) => Object.hasOwn(flag, 'choices') && !Array.isArray(flag.choices),
  19. message: flagKeys => `The option \`choices\` must be an array. Invalid flags: ${joinFlagKeys(flagKeys)}`,
  20. },
  21. choicesNotMatchFlagType: {
  22. filter: ([, flag]) => flag.type && Array.isArray(flag.choices) && flag.choices.some(choice => typeof choice !== flag.type),
  23. message(flagKeys) {
  24. const flagKeysAndTypes = flagKeys.map(flagKey => `(\`${decamelizeFlagKey(flagKey)}\`, type: '${options.flags[flagKey].type}')`);
  25. return `Each value of the option \`choices\` must be of the same type as its flag. Invalid flags: ${flagKeysAndTypes.join(', ')}`;
  26. },
  27. },
  28. defaultNotInChoices: {
  29. filter: ([, flag]) => flag.default && Array.isArray(flag.choices) && ![flag.default].flat().every(value => flag.choices.includes(value)),
  30. message: flagKeys => `Each value of the option \`default\` must exist within the option \`choices\`. Invalid flags: ${joinFlagKeys(flagKeys)}`,
  31. },
  32. },
  33. };
  34. const errorMessages = [];
  35. for (const [optionKey, filters] of Object.entries(invalidOptionFilters)) {
  36. const optionEntries = Object.entries(options[optionKey]);
  37. for (const {filter, message} of Object.values(filters)) {
  38. const invalidOptions = optionEntries.filter(option => filter(option));
  39. const invalidOptionKeys = invalidOptions.map(([key]) => key);
  40. if (invalidOptions.length > 0) {
  41. errorMessages.push(message(invalidOptionKeys));
  42. }
  43. }
  44. }
  45. if (errorMessages.length > 0) {
  46. throw new Error(errorMessages.join('\n'));
  47. }
  48. };
  49. const buildOptions = (helpText, options) => {
  50. if (typeof helpText !== 'string') {
  51. options = helpText;
  52. helpText = '';
  53. }
  54. if (!options.importMeta?.url) {
  55. throw new TypeError('The `importMeta` option is required. Its value must be `import.meta`.');
  56. }
  57. const foundPackage = readPackageUpSync({
  58. cwd: dirname(fileURLToPath(options.importMeta.url)),
  59. normalize: false,
  60. });
  61. const parsedOptions = {
  62. pkg: foundPackage ? foundPackage.packageJson : {},
  63. argv: process.argv.slice(2),
  64. flags: {},
  65. inferType: false,
  66. input: 'string',
  67. help: helpText,
  68. autoHelp: true,
  69. autoVersion: true,
  70. booleanDefault: false,
  71. allowUnknownFlags: true,
  72. allowParentFlags: true,
  73. helpIndent: 2,
  74. ...options,
  75. };
  76. validateOptions(parsedOptions);
  77. return parsedOptions;
  78. };
  79. export { buildOptions };