index.js 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. const path = require('path')
  2. const microargs = require('microargs')
  3. const { get, difference, isEmpty, padEnd, forEach, capitalize, omit, isString } = require('lodash')
  4. class CLIError extends Error { }
  5. function optionToString (optionName) {
  6. return optionName.length === 1 ? `-${optionName}` : `--${optionName}`
  7. }
  8. function optionsToString (optionsKeys) {
  9. return optionsKeys.map(optionToString).join(' ')
  10. }
  11. function printHelp (scriptName, annotations, logger) {
  12. if (isEmpty(annotations)) {
  13. logger.log('Documentation not found')
  14. return null
  15. }
  16. const { description, params, options } = annotations
  17. const extra = omit(annotations, ['description', 'params', 'options'])
  18. const usageOptions = isEmpty(options) ? '' : '[options]'
  19. const usageParams = isEmpty(params) ? '' : `[${params.join(' ')}]`
  20. logger.log(`Usage: ${path.basename(scriptName)} ${usageOptions} ${usageParams}\n`)
  21. if (description) {
  22. logger.log(`${description}\n`)
  23. }
  24. if (!isEmpty(options)) {
  25. logger.log('Options:\n')
  26. forEach(options, (value, key) => {
  27. logger.log(` ${padEnd(optionToString(key), 12)}${value}`)
  28. })
  29. }
  30. forEach(extra, (value, key) => {
  31. logger.log(`\n${capitalize(key)}:\n`)
  32. logger.log(`${value}\n`)
  33. })
  34. }
  35. module.exports = (argv, annotations = {}, help, logger = console) => {
  36. help = help || printHelp
  37. return (callback) => {
  38. const { params, options } = microargs(argv.slice(2))
  39. const scriptName = path.basename(argv[1])
  40. if (isString(annotations)) {
  41. annotations = {
  42. description: annotations
  43. }
  44. }
  45. if (options.help) {
  46. return help(scriptName, annotations, logger)
  47. }
  48. const annotatedOptionsKeys = (get(annotations, 'options') && Object.keys(annotations.options)) || []
  49. const optionsKeys = Object.keys(options)
  50. const illegalOptionsKeys = difference(optionsKeys, annotatedOptionsKeys)
  51. if (annotatedOptionsKeys.length && illegalOptionsKeys.length) {
  52. const msg = `Illegal option: ${optionsToString(illegalOptionsKeys)}\n` +
  53. `Available options: ${optionsToString(annotatedOptionsKeys)}\n` +
  54. `Type "${scriptName} --help" for more information`
  55. throw new CLIError(msg)
  56. }
  57. return callback(options, ...params)
  58. }
  59. }
  60. module.exports.CLIError = CLIError