cli.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. const load_1 = __importDefault(require("@commitlint/load"));
  7. const lint_1 = __importDefault(require("@commitlint/lint"));
  8. const read_1 = __importDefault(require("@commitlint/read"));
  9. const isFunction_1 = __importDefault(require("lodash/isFunction"));
  10. const get_stdin_1 = __importDefault(require("get-stdin"));
  11. const resolve_from_1 = __importDefault(require("resolve-from"));
  12. const resolve_global_1 = __importDefault(require("resolve-global"));
  13. const yargs_1 = __importDefault(require("yargs"));
  14. const cli_error_1 = require("./cli-error");
  15. const pkg = require('../package');
  16. const cli = yargs_1.default
  17. .options({
  18. color: {
  19. alias: 'c',
  20. default: true,
  21. description: 'toggle colored output',
  22. type: 'boolean',
  23. },
  24. config: {
  25. alias: 'g',
  26. description: 'path to the config file',
  27. type: 'string',
  28. },
  29. cwd: {
  30. alias: 'd',
  31. default: process.cwd(),
  32. defaultDescription: '(Working Directory)',
  33. description: 'directory to execute in',
  34. type: 'string',
  35. },
  36. edit: {
  37. alias: 'e',
  38. description: 'read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG',
  39. type: 'string',
  40. },
  41. env: {
  42. alias: 'E',
  43. description: 'check message in the file at path given by environment variable value',
  44. type: 'string',
  45. },
  46. extends: {
  47. alias: 'x',
  48. description: 'array of shareable configurations to extend',
  49. type: 'array',
  50. },
  51. 'help-url': {
  52. alias: 'H',
  53. type: 'string',
  54. description: 'help url in error message',
  55. },
  56. from: {
  57. alias: 'f',
  58. description: 'lower end of the commit range to lint; applies if edit=false',
  59. type: 'string',
  60. },
  61. format: {
  62. alias: 'o',
  63. description: 'output format of the results',
  64. type: 'string',
  65. },
  66. 'parser-preset': {
  67. alias: 'p',
  68. description: 'configuration preset to use for conventional-commits-parser',
  69. type: 'string',
  70. },
  71. quiet: {
  72. alias: 'q',
  73. default: false,
  74. description: 'toggle console output',
  75. type: 'boolean',
  76. },
  77. to: {
  78. alias: 't',
  79. description: 'upper end of the commit range to lint; applies if edit=false',
  80. type: 'string',
  81. },
  82. verbose: {
  83. alias: 'V',
  84. type: 'boolean',
  85. description: 'enable verbose output for reports without problems',
  86. },
  87. })
  88. .version('version', 'display version information', `${pkg.name}@${pkg.version}`)
  89. .alias('v', 'version')
  90. .help('help')
  91. .alias('h', 'help')
  92. .usage(`${pkg.name}@${pkg.version} - ${pkg.description}\n`)
  93. .usage(`[input] reads from stdin if --edit, --env, --from and --to are omitted`)
  94. .strict();
  95. main(Object.assign({ edit: false }, cli.argv)).catch((err) => {
  96. setTimeout(() => {
  97. if (err.type === pkg.name) {
  98. process.exit(1);
  99. }
  100. throw err;
  101. }, 0);
  102. });
  103. async function main(options) {
  104. const raw = options._;
  105. const flags = normalizeFlags(options);
  106. const fromStdin = checkFromStdin(raw, flags);
  107. const input = await (fromStdin
  108. ? get_stdin_1.default()
  109. : read_1.default({
  110. to: flags.to,
  111. from: flags.from,
  112. edit: flags.edit,
  113. cwd: flags.cwd,
  114. }));
  115. const messages = (Array.isArray(input) ? input : [input])
  116. .filter((message) => typeof message === 'string')
  117. .filter((message) => message.trim() !== '')
  118. .filter(Boolean);
  119. if (messages.length === 0 && !checkFromRepository(flags)) {
  120. const err = new cli_error_1.CliError('[input] is required: supply via stdin, or --env or --edit or --from and --to', pkg.name);
  121. yargs_1.default.showHelp('log');
  122. console.log(err.message);
  123. throw err;
  124. }
  125. const loadOpts = { cwd: flags.cwd, file: flags.config };
  126. const loaded = await load_1.default(getSeed(flags), loadOpts);
  127. const parserOpts = selectParserOpts(loaded.parserPreset);
  128. const opts = {
  129. parserOpts: {},
  130. plugins: {},
  131. ignores: [],
  132. defaultIgnores: true,
  133. };
  134. if (parserOpts) {
  135. opts.parserOpts = parserOpts;
  136. }
  137. if (loaded.plugins) {
  138. opts.plugins = loaded.plugins;
  139. }
  140. if (loaded.ignores) {
  141. opts.ignores = loaded.ignores;
  142. }
  143. if (loaded.defaultIgnores === false) {
  144. opts.defaultIgnores = false;
  145. }
  146. const format = loadFormatter(loaded, flags);
  147. // Strip comments if reading from `.git/COMMIT_EDIT_MSG`
  148. if (flags.edit) {
  149. opts.parserOpts.commentChar = '#';
  150. }
  151. const results = await Promise.all(messages.map((message) => lint_1.default(message, loaded.rules, opts)));
  152. if (Object.keys(loaded.rules).length === 0) {
  153. let input = '';
  154. if (results.length !== 0) {
  155. input = results[0].input;
  156. }
  157. results.splice(0, results.length, {
  158. valid: false,
  159. errors: [
  160. {
  161. level: 2,
  162. valid: false,
  163. name: 'empty-rules',
  164. message: [
  165. 'Please add rules to your `commitlint.config.js`',
  166. ' - Getting started guide: https://git.io/fhHij',
  167. ' - Example config: https://git.io/fhHip',
  168. ].join('\n'),
  169. },
  170. ],
  171. warnings: [],
  172. input,
  173. });
  174. }
  175. const report = results.reduce((info, result) => {
  176. info.valid = result.valid ? info.valid : false;
  177. info.errorCount += result.errors.length;
  178. info.warningCount += result.warnings.length;
  179. info.results.push(result);
  180. return info;
  181. }, {
  182. valid: true,
  183. errorCount: 0,
  184. warningCount: 0,
  185. results: [],
  186. });
  187. const output = format(report, {
  188. color: flags.color,
  189. verbose: flags.verbose,
  190. helpUrl: flags['help-url']
  191. ? flags['help-url'].trim()
  192. : 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
  193. });
  194. if (!flags.quiet && output !== '') {
  195. console.log(output);
  196. }
  197. if (!report.valid) {
  198. throw new cli_error_1.CliError(output, pkg.name);
  199. }
  200. }
  201. function checkFromStdin(input, flags) {
  202. return input.length === 0 && !checkFromRepository(flags);
  203. }
  204. function checkFromRepository(flags) {
  205. return checkFromHistory(flags) || checkFromEdit(flags);
  206. }
  207. function checkFromEdit(flags) {
  208. return Boolean(flags.edit) || flags.env;
  209. }
  210. function checkFromHistory(flags) {
  211. return typeof flags.from === 'string' || typeof flags.to === 'string';
  212. }
  213. function normalizeFlags(flags) {
  214. const edit = getEditValue(flags);
  215. return Object.assign(Object.assign({}, flags), { edit });
  216. }
  217. function getEditValue(flags) {
  218. if (flags.env) {
  219. if (!(flags.env in process.env)) {
  220. throw new Error(`Recieved '${flags.env}' as value for -E | --env, but environment variable '${flags.env}' is not available globally`);
  221. }
  222. return process.env[flags.env];
  223. }
  224. const { edit } = flags;
  225. // If the edit flag is set but empty (i.e '-e') we default
  226. // to .git/COMMIT_EDITMSG
  227. if (edit === '') {
  228. return true;
  229. }
  230. if (typeof edit === 'boolean') {
  231. return edit;
  232. }
  233. // The recommended method to specify -e with husky was `commitlint -e $HUSKY_GIT_PARAMS`
  234. // This does not work properly with win32 systems, where env variable declarations
  235. // use a different syntax
  236. // See https://github.com/conventional-changelog/commitlint/issues/103 for details
  237. // This has been superceded by the `-E GIT_PARAMS` / `-E HUSKY_GIT_PARAMS`
  238. const isGitParams = edit === '$GIT_PARAMS' || edit === '%GIT_PARAMS%';
  239. const isHuskyParams = edit === '$HUSKY_GIT_PARAMS' || edit === '%HUSKY_GIT_PARAMS%';
  240. if (isGitParams || isHuskyParams) {
  241. console.warn(`Using environment variable syntax (${edit}) in -e |\
  242. --edit is deprecated. Use '{-E|--env} HUSKY_GIT_PARAMS instead'`);
  243. if (isGitParams && 'GIT_PARAMS' in process.env) {
  244. return process.env.GIT_PARAMS;
  245. }
  246. if ('HUSKY_GIT_PARAMS' in process.env) {
  247. return process.env.HUSKY_GIT_PARAMS;
  248. }
  249. throw new Error(`Received ${edit} as value for -e | --edit, but GIT_PARAMS or HUSKY_GIT_PARAMS are not available globally.`);
  250. }
  251. return edit;
  252. }
  253. function getSeed(flags) {
  254. const n = (flags.extends || []).filter((i) => typeof i === 'string');
  255. return n.length > 0
  256. ? { extends: n, parserPreset: flags['parser-preset'] }
  257. : { parserPreset: flags['parser-preset'] };
  258. }
  259. function selectParserOpts(parserPreset) {
  260. if (typeof parserPreset !== 'object') {
  261. return undefined;
  262. }
  263. if (typeof parserPreset.parserOpts !== 'object') {
  264. return undefined;
  265. }
  266. return parserPreset.parserOpts;
  267. }
  268. function loadFormatter(config, flags) {
  269. const moduleName = flags.format || config.formatter || '@commitlint/format';
  270. const modulePath = resolve_from_1.default.silent(__dirname, moduleName) ||
  271. resolve_from_1.default.silent(flags.cwd, moduleName) ||
  272. resolve_global_1.default.silent(moduleName);
  273. if (modulePath) {
  274. const moduleInstance = require(modulePath);
  275. if (isFunction_1.default(moduleInstance.default)) {
  276. return moduleInstance.default;
  277. }
  278. return moduleInstance;
  279. }
  280. throw new Error(`Using format ${moduleName}, but cannot find the module.`);
  281. }
  282. // Catch unhandled rejections globally
  283. process.on('unhandledRejection', (reason, promise) => {
  284. console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason);
  285. throw reason;
  286. });
  287. //# sourceMappingURL=cli.js.map