cli.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. "use strict";
  2. var __asyncValues = (this && this.__asyncValues) || function (o) {
  3. if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
  4. var m = o[Symbol.asyncIterator], i;
  5. return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
  6. function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
  7. function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
  8. };
  9. var __importDefault = (this && this.__importDefault) || function (mod) {
  10. return (mod && mod.__esModule) ? mod : { "default": mod };
  11. };
  12. Object.defineProperty(exports, "__esModule", { value: true });
  13. const execa_1 = __importDefault(require("execa"));
  14. const load_1 = __importDefault(require("@commitlint/load"));
  15. const lint_1 = __importDefault(require("@commitlint/lint"));
  16. const read_1 = __importDefault(require("@commitlint/read"));
  17. const lodash_isfunction_1 = __importDefault(require("lodash.isfunction"));
  18. const resolve_from_1 = __importDefault(require("resolve-from"));
  19. const resolve_global_1 = __importDefault(require("resolve-global"));
  20. const yargs_1 = __importDefault(require("yargs"));
  21. const util_1 = __importDefault(require("util"));
  22. const cli_error_1 = require("./cli-error");
  23. const pkg = require('../package');
  24. const gitDefaultCommentChar = '#';
  25. const cli = yargs_1.default
  26. .options({
  27. color: {
  28. alias: 'c',
  29. default: true,
  30. description: 'toggle colored output',
  31. type: 'boolean',
  32. },
  33. config: {
  34. alias: 'g',
  35. description: 'path to the config file',
  36. type: 'string',
  37. },
  38. 'print-config': {
  39. type: 'boolean',
  40. default: false,
  41. description: 'print resolved config',
  42. },
  43. cwd: {
  44. alias: 'd',
  45. default: process.cwd(),
  46. defaultDescription: '(Working Directory)',
  47. description: 'directory to execute in',
  48. type: 'string',
  49. },
  50. edit: {
  51. alias: 'e',
  52. description: 'read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG',
  53. type: 'string',
  54. },
  55. env: {
  56. alias: 'E',
  57. description: 'check message in the file at path given by environment variable value',
  58. type: 'string',
  59. },
  60. extends: {
  61. alias: 'x',
  62. description: 'array of shareable configurations to extend',
  63. type: 'array',
  64. },
  65. 'help-url': {
  66. alias: 'H',
  67. type: 'string',
  68. description: 'help url in error message',
  69. },
  70. from: {
  71. alias: 'f',
  72. description: 'lower end of the commit range to lint; applies if edit=false',
  73. type: 'string',
  74. },
  75. 'git-log-args': {
  76. description: "addditional git log arguments as space separated string, example '--first-parent --cherry-pick'",
  77. type: 'string',
  78. },
  79. format: {
  80. alias: 'o',
  81. description: 'output format of the results',
  82. type: 'string',
  83. },
  84. 'parser-preset': {
  85. alias: 'p',
  86. description: 'configuration preset to use for conventional-commits-parser',
  87. type: 'string',
  88. },
  89. quiet: {
  90. alias: 'q',
  91. default: false,
  92. description: 'toggle console output',
  93. type: 'boolean',
  94. },
  95. to: {
  96. alias: 't',
  97. description: 'upper end of the commit range to lint; applies if edit=false',
  98. type: 'string',
  99. },
  100. verbose: {
  101. alias: 'V',
  102. type: 'boolean',
  103. description: 'enable verbose output for reports without problems',
  104. },
  105. strict: {
  106. alias: 's',
  107. type: 'boolean',
  108. description: 'enable strict mode; result code 2 for warnings, 3 for errors',
  109. },
  110. })
  111. .version('version', 'display version information', `${pkg.name}@${pkg.version}`)
  112. .alias('v', 'version')
  113. .help('help')
  114. .alias('h', 'help')
  115. .usage(`${pkg.name}@${pkg.version} - ${pkg.description}\n`)
  116. .usage(`[input] reads from stdin if --edit, --env, --from and --to are omitted`)
  117. .strict();
  118. main(cli.argv).catch((err) => {
  119. setTimeout(() => {
  120. if (err.type === pkg.name) {
  121. process.exit(err.error_code);
  122. }
  123. throw err;
  124. }, 0);
  125. });
  126. async function stdin() {
  127. var _a, e_1, _b, _c;
  128. let result = '';
  129. if (process.stdin.isTTY) {
  130. return result;
  131. }
  132. process.stdin.setEncoding('utf8');
  133. try {
  134. for (var _d = true, _e = __asyncValues(process.stdin), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
  135. _c = _f.value;
  136. _d = false;
  137. const chunk = _c;
  138. result += chunk;
  139. }
  140. }
  141. catch (e_1_1) { e_1 = { error: e_1_1 }; }
  142. finally {
  143. try {
  144. if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
  145. }
  146. finally { if (e_1) throw e_1.error; }
  147. }
  148. return result;
  149. }
  150. async function resolveArgs(args) {
  151. return typeof args.then === 'function' ? await args : args;
  152. }
  153. async function main(args) {
  154. var _a;
  155. const options = await resolveArgs(args);
  156. if (typeof options.edit === 'undefined') {
  157. options.edit = false;
  158. }
  159. const raw = options._;
  160. const flags = normalizeFlags(options);
  161. if (flags['print-config']) {
  162. const loaded = await (0, load_1.default)(getSeed(flags), {
  163. cwd: flags.cwd,
  164. file: flags.config,
  165. });
  166. console.log(util_1.default.inspect(loaded, false, null, options.color));
  167. return;
  168. }
  169. const fromStdin = checkFromStdin(raw, flags);
  170. const input = await (fromStdin
  171. ? stdin()
  172. : (0, read_1.default)({
  173. to: flags.to,
  174. from: flags.from,
  175. edit: flags.edit,
  176. cwd: flags.cwd,
  177. gitLogArgs: flags['git-log-args'],
  178. }));
  179. const messages = (Array.isArray(input) ? input : [input])
  180. .filter((message) => typeof message === 'string')
  181. .filter((message) => message.trim() !== '')
  182. .filter(Boolean);
  183. if (messages.length === 0 && !checkFromRepository(flags)) {
  184. const err = new cli_error_1.CliError('[input] is required: supply via stdin, or --env or --edit or --from and --to', pkg.name);
  185. yargs_1.default.showHelp('log');
  186. console.log(err.message);
  187. throw err;
  188. }
  189. const loaded = await (0, load_1.default)(getSeed(flags), {
  190. cwd: flags.cwd,
  191. file: flags.config,
  192. });
  193. const parserOpts = selectParserOpts(loaded.parserPreset);
  194. const opts = {
  195. parserOpts: {},
  196. plugins: {},
  197. ignores: [],
  198. defaultIgnores: true,
  199. };
  200. if (parserOpts) {
  201. opts.parserOpts = parserOpts;
  202. }
  203. if (loaded.plugins) {
  204. opts.plugins = loaded.plugins;
  205. }
  206. if (loaded.ignores) {
  207. opts.ignores = loaded.ignores;
  208. }
  209. if (loaded.defaultIgnores === false) {
  210. opts.defaultIgnores = false;
  211. }
  212. const format = loadFormatter(loaded, flags);
  213. // If reading from `.git/COMMIT_EDIT_MSG`, strip comments using
  214. // core.commentChar from git configuration, falling back to '#'.
  215. if (flags.edit) {
  216. try {
  217. const { stdout } = await (0, execa_1.default)('git', ['config', 'core.commentChar']);
  218. opts.parserOpts.commentChar = stdout.trim() || gitDefaultCommentChar;
  219. }
  220. catch (e) {
  221. const execaError = e;
  222. // git config returns exit code 1 when the setting is unset,
  223. // don't warn in this case.
  224. if (!execaError.failed || execaError.exitCode !== 1) {
  225. console.warn('Could not determine core.commentChar git configuration', e);
  226. }
  227. opts.parserOpts.commentChar = gitDefaultCommentChar;
  228. }
  229. }
  230. const results = await Promise.all(messages.map((message) => (0, lint_1.default)(message, loaded.rules, opts)));
  231. if (Object.keys(loaded.rules).length === 0) {
  232. let input = '';
  233. if (results.length !== 0) {
  234. input = results[0].input;
  235. }
  236. results.splice(0, results.length, {
  237. valid: false,
  238. errors: [
  239. {
  240. level: 2,
  241. valid: false,
  242. name: 'empty-rules',
  243. message: [
  244. 'Please add rules to your `commitlint.config.js`',
  245. ' - Getting started guide: https://commitlint.js.org/#/?id=getting-started',
  246. ' - Example config: https://github.com/conventional-changelog/commitlint/blob/master/%40commitlint/config-conventional/index.js',
  247. ].join('\n'),
  248. },
  249. ],
  250. warnings: [],
  251. input,
  252. });
  253. }
  254. const report = results.reduce((info, result) => {
  255. info.valid = result.valid ? info.valid : false;
  256. info.errorCount += result.errors.length;
  257. info.warningCount += result.warnings.length;
  258. info.results.push(result);
  259. return info;
  260. }, {
  261. valid: true,
  262. errorCount: 0,
  263. warningCount: 0,
  264. results: [],
  265. });
  266. const helpUrl = ((_a = flags['help-url']) === null || _a === void 0 ? void 0 : _a.trim()) || loaded.helpUrl;
  267. const output = format(report, {
  268. color: flags.color,
  269. verbose: flags.verbose,
  270. helpUrl,
  271. });
  272. if (!flags.quiet && output !== '') {
  273. console.log(output);
  274. }
  275. if (flags.strict) {
  276. if (report.errorCount > 0) {
  277. throw new cli_error_1.CliError(output, pkg.name, 3);
  278. }
  279. if (report.warningCount > 0) {
  280. throw new cli_error_1.CliError(output, pkg.name, 2);
  281. }
  282. }
  283. if (!report.valid) {
  284. throw new cli_error_1.CliError(output, pkg.name);
  285. }
  286. }
  287. function checkFromStdin(input, flags) {
  288. return input.length === 0 && !checkFromRepository(flags);
  289. }
  290. function checkFromRepository(flags) {
  291. return checkFromHistory(flags) || checkFromEdit(flags);
  292. }
  293. function checkFromEdit(flags) {
  294. return Boolean(flags.edit) || Boolean(flags.env);
  295. }
  296. function checkFromHistory(flags) {
  297. return typeof flags.from === 'string' || typeof flags.to === 'string';
  298. }
  299. function normalizeFlags(flags) {
  300. const edit = getEditValue(flags);
  301. return Object.assign(Object.assign({}, flags), { edit });
  302. }
  303. function getEditValue(flags) {
  304. if (flags.env) {
  305. if (!(flags.env in process.env)) {
  306. throw new Error(`Received '${flags.env}' as value for -E | --env, but environment variable '${flags.env}' is not available globally`);
  307. }
  308. return process.env[flags.env];
  309. }
  310. const { edit } = flags;
  311. // If the edit flag is set but empty (i.e '-e') we default
  312. // to .git/COMMIT_EDITMSG
  313. if (edit === '') {
  314. return true;
  315. }
  316. if (typeof edit === 'boolean') {
  317. return edit;
  318. }
  319. // The recommended method to specify -e with husky was `commitlint -e $HUSKY_GIT_PARAMS`
  320. // This does not work properly with win32 systems, where env variable declarations
  321. // use a different syntax
  322. // See https://github.com/conventional-changelog/commitlint/issues/103 for details
  323. // This has been superceded by the `-E GIT_PARAMS` / `-E HUSKY_GIT_PARAMS`
  324. const isGitParams = edit === '$GIT_PARAMS' || edit === '%GIT_PARAMS%';
  325. const isHuskyParams = edit === '$HUSKY_GIT_PARAMS' || edit === '%HUSKY_GIT_PARAMS%';
  326. if (isGitParams || isHuskyParams) {
  327. console.warn(`Using environment variable syntax (${edit}) in -e |\
  328. --edit is deprecated. Use '{-E|--env} HUSKY_GIT_PARAMS instead'`);
  329. if (isGitParams && 'GIT_PARAMS' in process.env) {
  330. return process.env.GIT_PARAMS;
  331. }
  332. if ('HUSKY_GIT_PARAMS' in process.env) {
  333. return process.env.HUSKY_GIT_PARAMS;
  334. }
  335. throw new Error(`Received ${edit} as value for -e | --edit, but GIT_PARAMS or HUSKY_GIT_PARAMS are not available globally.`);
  336. }
  337. return edit;
  338. }
  339. function getSeed(flags) {
  340. const n = (flags.extends || []).filter((i) => typeof i === 'string');
  341. return n.length > 0
  342. ? { extends: n, parserPreset: flags['parser-preset'] }
  343. : { parserPreset: flags['parser-preset'] };
  344. }
  345. function selectParserOpts(parserPreset) {
  346. if (typeof parserPreset !== 'object') {
  347. return undefined;
  348. }
  349. if (typeof parserPreset.parserOpts !== 'object') {
  350. return undefined;
  351. }
  352. return parserPreset.parserOpts;
  353. }
  354. function loadFormatter(config, flags) {
  355. const moduleName = flags.format || config.formatter || '@commitlint/format';
  356. const modulePath = resolve_from_1.default.silent(__dirname, moduleName) ||
  357. resolve_from_1.default.silent(flags.cwd, moduleName) ||
  358. resolve_global_1.default.silent(moduleName);
  359. if (modulePath) {
  360. const moduleInstance = require(modulePath);
  361. if ((0, lodash_isfunction_1.default)(moduleInstance.default)) {
  362. return moduleInstance.default;
  363. }
  364. return moduleInstance;
  365. }
  366. throw new Error(`Using format ${moduleName}, but cannot find the module.`);
  367. }
  368. // Catch unhandled rejections globally
  369. process.on('unhandledRejection', (reason, promise) => {
  370. console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason);
  371. throw reason;
  372. });
  373. //# sourceMappingURL=cli.js.map