├── .babelrc ├── .czrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmrc ├── changelog.md ├── contributing.md ├── cz-conventional-changelog-lint.gif ├── package.json ├── readme.md └── source ├── get-input.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015-node4" 4 | ], 5 | "plugins": [ 6 | "transform-do-expressions", 7 | "transform-function-bind", 8 | "transform-object-rest-spread", 9 | "transform-async-to-generator", 10 | "add-module-exports" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "distribution/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | trim_trailing_whitespace = true 5 | indent_style = tab 6 | 7 | [{.*rc,*.yml,*.md,package.json,*.svg}] 8 | indent_style = space 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["xo/esnext"], 3 | "plugins": ["require-jsdoc"], 4 | "rules": { 5 | "require-jsdoc": 2, 6 | "valid-jsdoc": 2 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node modules 2 | node_modules 3 | 4 | # npm debug log 5 | npm-debug.log 6 | 7 | # distribution folder 8 | distribution 9 | 10 | # package 11 | react-jogwheel-*.tgz 12 | 13 | # vim files 14 | *.swp 15 | *.swo 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | spin = false 2 | save-exact = true 3 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | 2 | ## [0.1.3](https://github.com/marionebl/cz-conventional-changelog/compare/v0.1.2...v0.1.3) (2016-03-13) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * correct dependency type for babel-polyfill ([08123f0](https://github.com/marionebl/cz-conventional-changelog/commit/08123f0)) 8 | 9 | 10 | 11 | 12 | ## [0.1.2](https://github.com/marionebl/cz-conventional-changelog/compare/v0.1.1...v0.1.2) (2016-03-03) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * **system:** publish from distribution ([1e98d3c](https://github.com/marionebl/cz-conventional-changelog/commit/1e98d3c)) 18 | 19 | 20 | 21 | 22 | ## [0.1.1](https://github.com/marionebl/cz-conventional-changelog/compare/v0.1.0...v0.1.1) (2016-03-03) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * **system:** publish from distribution ([57b0eb9](https://github.com/marionebl/cz-conventional-changelog/commit/57b0eb9)) 28 | 29 | 30 | 31 | 32 | # 0.1.0 (2016-03-03) 33 | 34 | 35 | ### Features 36 | 37 | * basic project setup ([897aba4](https://github.com/marionebl/cz-conventional-changelog/commit/897aba4)) 38 | 39 | 40 | 41 | --- 42 | 43 | `cz-conventional-changelog-lint` is built by Mario Nebl and [contributors](./graphs/contributors) 44 | with :heart: and released under the [MIT License](./license.md). 45 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # cz-conventional-changelog-lint 2 | 3 | Yeay! You want to contribute to cz-conventional-changelog-lint. That's amazing! 4 | To smoothen everyone's experience involved with the project 5 | please take note of the following guidelines and rules. 6 | 7 | ## I Found an Issue 8 | 9 | Thank you for reporting any issues you find. We do our best to test and make 10 | cz-conventional-changelog-lint as solid as possible, 11 | but any reported issue is a real help. 12 | 13 | > cz-conventional-changelog-lint issues 14 | 15 | Please follow these guidelines when reporting issues: 16 | 17 | * Provide a title in the format of ` when ` 18 | 19 | * Tag your issue with the tag `bug` 20 | 21 | * Provide a short summary of what you are trying to do 22 | 23 | * Provide the log of the encountered error if applicable 24 | 25 | * Provide the exact version of cz-conventional-changelog-lint. 26 | Check `npm ls cz-conventional-changelog-lint` when in doubt 27 | 28 | * Be awesome and consider contributing a [pull request](#want-to-contribute) 29 | 30 | ## I want to contribute 31 | 32 | You consider contributing changes to cz-conventional-changelog-lint – 33 | we dig that! 34 | Please consider these guidelines when filing a pull request: 35 | 36 | * Follow the [Coding Rules](#coding-rules) 37 | * Follow the [Commit Rules](#commit-rules) 38 | * Make sure you rebased the current master branch when filing the pull request 39 | * Squash your commits when filing the pull request 40 | * Provide a short title with a maximum of 72 characters 41 | 42 | ## Coding Rules 43 | 44 | To keep the code base of cz-conventional-changelog-lint 45 | neat and tidy the following rules apply to every change 46 | 47 | > Coding standards 48 | 49 | * [Happiness](/sindresorhus/xo) enforced via eslint 50 | * Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra) 51 | * Coverage never drops below 90% 52 | * No change may lower coverage by more than 5% 53 | * Be awesome 54 | 55 | ## Commit Rules 56 | 57 | To help everyone with understanding the commit history of 58 | cz-conventional-changelog-lint the following commit rules are enforced. 59 | To make your life easier cz-conventional-changelog-lint 60 | is commitizen-friendly and provides the npm run-script `commit`. 61 | 62 | > Commit standards 63 | 64 | * [conventional-changelog](/commitizen/cz-conventional-changelog) 65 | * husky commit message hook available 66 | * present tense 67 | * maximum of 100 characters 68 | * message format of `$type($scope): $message` 69 | 70 | --- 71 | 72 | Copyright 2016 by [Mario Nebl](https://github.com/marionebl) 73 | and [contributors](./graphs/contributors). 74 | Released under the [MIT license]('./license.md'). 75 | -------------------------------------------------------------------------------- /cz-conventional-changelog-lint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marionebl/cz-conventional-changelog-lint/a8b6984f94f22b10b94309b8b4c7b5f914e6b1c1/cz-conventional-changelog-lint.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cz-conventional-changelog-lint", 3 | "version": "0.1.3", 4 | "description": "Let an interactive command line interface help you with creating commit messages matching your conventional-changelog-lint configuration.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm run watch", 8 | "execute": "conventional-changelog -", 9 | "clean": "rm -rf distribution", 10 | "prepare": "mkdir -p distribution", 11 | "copy": "cp *.{json,md} distribution", 12 | "typings": "cat source/index.js | react2dts --name $npm_package_name > distribution/index.d.ts", 13 | "prebuild": "parallelshell 'npm run test' 'npm run clean && npm run prepare'", 14 | "build": "babel source --out-dir distribution", 15 | "postbuild": "npm run typings && npm run copy && npm run notify", 16 | "test": "(eslint source/**/*.js && remark *.md -u remark-lint) && echo '' || notify -t $npm_package_name -m 'Linting failed! 😢'", 17 | "watch": "npm run build -- --watch", 18 | "serve": "serve() {\nbeefy \"distribution/examples/${1}.js\"\n}\n serve", 19 | "notify": "echo 'Build ready, happy hacking! ✊' && notify -t $npm_package_name -m 'Build ready, happy hacking! ✊'", 20 | "commit": "git-cz", 21 | "commitmsg": "conventional-changelog-lint -e", 22 | "changelog": "conventional-changelog --preset angular --infile changelog.md --same-file --output-unreleased", 23 | "push": "git push && git push --tags && hub release create \"v$(cat .git/RELEASE_VERSION.tmp)\" --message=\"v$(cat .git/RELEASE_VERSION.tmp)\n$(cat .git/COMMITMSG.tmp)\" && cd ./distribution && npm publish && cd ../ && rm .git/RELEASE_VERSION.tmp && rm .git/COMMITMSG.tmp", 24 | "release": "npm version $(conventional-recommended-bump -p angular)", 25 | "preversion": "npm run build && npm test", 26 | "version": "npm run changelog && git add . && echo \"$(conventional-changelog -p angular)\" > .git/COMMITMSG.tmp", 27 | "postversion": "echo $(git log -1 --pretty=%B HEAD^..HEAD) > .git/RELEASE_VERSION.tmp && git tag -d v$(cat .git/RELEASE_VERSION.tmp) && git commit --amend -m \"chore(release): $(cat .git/RELEASE_VERSION.tmp)\n$(cat .git/COMMITMSG.tmp)\" && git tag -a v$(cat .git/RELEASE_VERSION.tmp) -m \"$(cat .git/COMMITMSG.tmp)\" && npm run postbuild" 28 | }, 29 | "config": { 30 | "commitizen": { 31 | "path": "./distribution/" 32 | } 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/marionebl/cz-conventional-changelog.git" 37 | }, 38 | "keywords": [ 39 | "commitizen", 40 | "conventional-changelog-lint", 41 | "angular", 42 | "commit" 43 | ], 44 | "author": "Mario Nebl ", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/marionebl/cz-conventional-changelog/issues" 48 | }, 49 | "homepage": "https://github.com/marionebl/cz-conventional-changelog#readme", 50 | "devDependencies": { 51 | "babel-cli": "6.6.0", 52 | "babel-eslint": "5.0.0", 53 | "babel-plugin-add-module-exports": "0.1.2", 54 | "babel-plugin-transform-async-to-generator": "6.5.0", 55 | "babel-plugin-transform-do-expressions": "6.5.0", 56 | "babel-plugin-transform-function-bind": "6.5.2", 57 | "babel-plugin-transform-object-rest-spread": "6.5.0", 58 | "babel-preset-es2015-node4": "2.0.3", 59 | "commitizen": "2.5.0", 60 | "conventional-changelog-cli": "1.1.1", 61 | "conventional-changelog-lint": "0.3.1", 62 | "conventional-recommended-bump": "0.1.1", 63 | "cz-conventional-changelog": "1.1.5", 64 | "eslint": "1.10.3", 65 | "eslint-config-xo": "0.9.1", 66 | "eslint-plugin-babel": "3.1.0", 67 | "eslint-plugin-react": "3.15.0", 68 | "eslint-plugin-require-jsdoc": "1.0.4", 69 | "husky": "0.11.1", 70 | "node-notifier": "4.4.0", 71 | "parallelshell": "2.0.0", 72 | "react-to-typescript-definitions": "0.10.0", 73 | "remark": "4.1.1", 74 | "remark-lint": "3.0.0", 75 | "watch": "0.17.1" 76 | }, 77 | "dependencies": { 78 | "babel-polyfill": "6.7.2", 79 | "chalk": "1.1.1", 80 | "conventional-changelog-lint": "0.3.2", 81 | "inquirer": "0.12.0", 82 | "lodash": "4.5.1", 83 | "throat": "2.0.2", 84 | "vorpal": "1.10.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | > reads your [conventional-changelog-lint](https://github.com/marionebl/conventional-changelog-lint) 2 | > config and automatically creates matching 3 | > [commitizen](https://github.com/commitizen/cz-cli) cli helpers for you. 4 | 5 | # cz-conventional-changelog-lint 6 | 7 | Let an interactive command line interface help you with 8 | creating commit messages matching your 9 | [conventional-changelog-lint](https://github.com/marionebl/conventional-changelog-lint) configuration. 10 | 11 | ## Installation 12 | 13 | Fetch `cz-conventional-changelog-lint` via npm, install `peerDependencies` 14 | 15 | ```bash 16 | npm install --save cz-conventional-changelog-lint commitizen 17 | ``` 18 | 19 | ## Configuration 20 | 21 | `cz-conventional-changelog-lint` is designed as `commitizen` adapter. 22 | To use it with [commitizen](https://github.com/commitizen/cz-cli) 23 | specify it as shared config: 24 | 25 | ```json 26 | { 27 | "config": { 28 | "commitizen": { 29 | "path": "./node_modules/cz-conventional-changelog-lint" 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Usage 36 | 37 | Use the [commitizen](https://github.com/commitizen/cz-cli) command line 38 | interface to start `cz-conventional-changelog-lint`. 39 | 40 | ```bash 41 | # do stuff in your project … 42 | 43 | # … then stage your changes 44 | git add 45 | 46 | # Execute the commitizen cli 47 | git-cz 48 | ``` 49 | 50 | ![cz-conventional-changelog-lint demo](./cz-conventional-changelog-lint.gif) 51 | 52 | ## History 53 | 54 | `cz-conventional-changelog-lint` maintains full transparency over all changes. 55 | 56 | * Every release on npm has a corresponding Github release 57 | * A full changelog is automatically generated on release 58 | 59 | --- 60 | 61 | ⇨ See [changelog.md](./changelog.md) for a full list of changes 62 | 63 | ## Contributing 64 | 65 | You dig `cz-conventional-changelog-lint` and want to submit a pull request? 66 | Awesome! Be sure to read the [contribution guide](./contributing.md) 67 | and you should be good to go. 68 | Here are some notes to get you coding real quick. 69 | 70 | ```bash 71 | git clone git@github.com:marionebl/cz-conventional-changelog-lint.git 72 | cd cz-conventional-changelog-lint 73 | npm install 74 | npm start 75 | ``` 76 | 77 | --- 78 | 79 | `cz-conventional-changelog-lint` is built by Mario Nebl and [contributors](./documentation/contributors.md) 80 | with :heart: and released under the [MIT License](./license.md). 81 | -------------------------------------------------------------------------------- /source/get-input.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import {getPreset, getConfiguration} from 'conventional-changelog-lint'; 3 | import {merge} from 'lodash'; 4 | import vorpal from 'vorpal'; 5 | 6 | /** 7 | * Get prefix for a given rule id 8 | * @param {string} id of the rule 9 | * @return {string} prefix of the rule 10 | */ 11 | function getRulePrefix(id) { 12 | const fragments = id.split('-'); 13 | const [prefix] = fragments; 14 | return fragments.length > 1 ? 15 | prefix : 16 | null; 17 | } 18 | 19 | /** 20 | * Get name for a given rule id 21 | * @param {string} id of the rule 22 | * @return {[type]} name of the rule 23 | */ 24 | function getRuleName(id) { 25 | const fragments = id.split('-'); 26 | return fragments.length > 1 ? 27 | fragments.slice(1).join('-') : 28 | fragments[0]; 29 | } 30 | 31 | /** 32 | * Get a predecate matching rule definitions with a given prefix 33 | * @param {[type]} name [description] 34 | * @return {[type]} [description] 35 | */ 36 | function getHasPrefix(name) { 37 | return rule => getRulePrefix(rule[0]) === name; 38 | } 39 | 40 | /** 41 | * Get a predecate matching rule definitions with a given name 42 | * @param {[type]} name [description] 43 | * @return {[type]} [description] 44 | */ 45 | function getHasName(name) { 46 | return rule => getRuleName(rule[0]) === name; 47 | } 48 | 49 | /** 50 | * Get rules for a given prefix 51 | * @param {string} prefix to search in rule names 52 | * @param {object} rules rules to search in 53 | * @return {object} rules matching the prefix search 54 | */ 55 | function getRules(prefix, rules) { 56 | return Object.entries(rules).filter(getHasPrefix(prefix)); 57 | } 58 | 59 | /** 60 | * Check if a rule definition is active 61 | * @param {object} rule to check 62 | * @return {boolean} if the rule definition is active 63 | */ 64 | function ruleIsActive(rule) { 65 | const [, [severity]] = rule; 66 | return severity > 0; 67 | } 68 | 69 | /** 70 | * Check if a rule definition is applicable 71 | * @param {object} rule to check 72 | * @return {boolean} if the rule definition is appliable 73 | */ 74 | function ruleIsApplicable(rule) { 75 | const [, [, applicable]] = rule; 76 | return applicable === 'always'; 77 | } 78 | 79 | /** 80 | * [enumRuleIsActive description] 81 | * @param {[type]} rule [description] 82 | * @return {[type]} [description] 83 | */ 84 | function enumRuleIsActive(rule) { 85 | const [, [, , value]] = rule; 86 | return ruleIsActive(rule) && 87 | ruleIsApplicable(rule) && 88 | value.length > 0; 89 | } 90 | 91 | /** 92 | * Get formatted meta hints for configuration 93 | * @param {object} settings dictionary to parse 94 | * @return {string} formatted meta information 95 | */ 96 | function meta(settings) { 97 | return chalk.grey(Object.entries(settings) 98 | .filter(item => item[1]) 99 | .map(item => { 100 | const [name, value] = item; 101 | return typeof value === 'boolean' ? 102 | `[${name}]` : 103 | `[${name}=${value}]`; 104 | }) 105 | .join(' ')); 106 | } 107 | 108 | /** 109 | * Get forced case for rule 110 | * @param {object} rule to parse 111 | * @return {string|null} transform function applying the enforced case 112 | */ 113 | function getForcedCase(rule) { 114 | if (!rule) { 115 | return null; 116 | } 117 | 118 | const [, [severity, applicable, value]] = rule; 119 | const negated = applicable === 'never'; 120 | 121 | if (severity === 0) { 122 | return null; 123 | } 124 | 125 | if (negated) { 126 | return value === 'lowerCase' ? 127 | 'upperCase' : 128 | 'lowerCase'; 129 | } 130 | 131 | return value === 'lowerCase' ? 132 | 'lowerCase' : 133 | 'upperCase'; 134 | } 135 | 136 | /** 137 | * Get forced case for rule 138 | * @param {object} rule to parse 139 | * @return {fn} transform function applying the enforced case 140 | */ 141 | function getForcedCaseFn(rule) { 142 | const noop = input => input; 143 | const lowerCase = input => String.prototype.toLowerCase.call(input); 144 | const upperCase = input => String.prototype.toUpperCase.call(input); 145 | 146 | if (!rule) { 147 | return noop; 148 | } 149 | 150 | // const case = getForcedCase(rule); 151 | const forcedCase = getForcedCase(rule); 152 | 153 | if (forcedCase === null) { 154 | return noop; 155 | } 156 | 157 | return forcedCase === 'lowerCase' ? 158 | lowerCase : 159 | upperCase; 160 | } 161 | 162 | /** 163 | * Get forced leading for rule 164 | * @param {object} rule to parse 165 | * @return {boolean|null} transform function applying the leading 166 | */ 167 | function getForcedLeading(rule) { 168 | if (!rule) { 169 | return null; 170 | } 171 | 172 | const [, [severity, applicable]] = rule; 173 | const negated = applicable === 'never'; 174 | 175 | if (severity === 0) { 176 | return null; 177 | } 178 | 179 | return !negated; 180 | } 181 | 182 | /** 183 | * Get forced leading for rule 184 | * @param {object} rule to parse 185 | * @return {fn} transform function applying the leading 186 | */ 187 | function getForcedLeadingFn(rule) { 188 | const noop = input => input; 189 | const remove = input => { 190 | const fragments = input.split('\n'); 191 | return fragments[0] === '' ? 192 | fragments.slice(1).join('\n') : 193 | input; 194 | }; 195 | const lead = input => { 196 | const fragments = input.split('\n'); 197 | return fragments[0] === '' ? 198 | input : 199 | ['', ...fragments].join('\n'); 200 | }; 201 | 202 | if (!rule) { 203 | return noop; 204 | } 205 | 206 | const leading = getForcedLeading(rule); 207 | 208 | if (leading === null) { 209 | return noop; 210 | } 211 | 212 | return leading ? 213 | lead : 214 | remove; 215 | } 216 | 217 | /** 218 | * get a cli prompt based on rule configuration 219 | * @param {string} type type of the data to gather 220 | * @param {array} rules rules to parse 221 | * @param {object} settings = {} additional display settings 222 | * @param {object} results = {} results to display for live editing 223 | * @return {object} prompt instance 224 | */ 225 | function getPrompt(type, rules, settings = {}, results = {}) { 226 | const prompt = vorpal(); 227 | 228 | const enumRule = rules 229 | .filter(getHasName('enum')) 230 | .filter(enumRuleIsActive)[0]; 231 | 232 | const emptyRule = rules 233 | .filter(getHasName('empty'))[0]; 234 | 235 | const mustBeEmpty = emptyRule ? 236 | emptyRule[1][0] > 0 && 237 | emptyRule[1][1] === 'always' : 238 | false; 239 | 240 | const mayNotBeEmpty = emptyRule ? 241 | emptyRule[1][0] > 0 && 242 | emptyRule[1][1] === 'never' : 243 | false; 244 | 245 | const mayBeEmpty = !mayNotBeEmpty; 246 | 247 | if (mustBeEmpty) { 248 | prompt.removeAllListeners('keypress'); 249 | prompt.removeAllListeners('client_prompt_submit'); 250 | prompt.ui.redraw.done(); 251 | return Promise.resolve(); 252 | } 253 | 254 | const caseRule = rules 255 | .filter(getHasName('case'))[0]; 256 | 257 | const forcedCase = getForcedCase(caseRule); 258 | const forceCaseFn = getForcedCaseFn(caseRule); 259 | 260 | const leadingBlankRule = rules 261 | .filter(getHasName('leading-blank'))[0]; 262 | 263 | const forceLeadingBlankFn = getForcedLeadingFn(leadingBlankRule); 264 | 265 | const maxLenghtRule = rules 266 | .filter(getHasName('max-length'))[0]; 267 | 268 | const hasMaxLength = maxLenghtRule && maxLenghtRule[1][0] > 0; 269 | 270 | const inputMaxLength = hasMaxLength ? 271 | maxLenghtRule[1][1] : 272 | Infinity; 273 | 274 | const headerLength = settings.header ? 275 | settings.header.length : 276 | Infinity; 277 | 278 | const remainingHeaderLength = headerLength ? 279 | headerLength - [ 280 | results.type, 281 | results.scope, 282 | results.scope ? '()' : '', 283 | results.type && results.scope ? ':' : '', 284 | results.subject 285 | ].join('').length : 286 | Infinity; 287 | 288 | const maxLength = Math.min(inputMaxLength, remainingHeaderLength); 289 | 290 | return new Promise(resolve => { 291 | // Add the defined enums as sub commands if applicable 292 | if (enumRule) { 293 | const [, [, , enums]] = enumRule; 294 | 295 | enums.forEach(enumerable => { 296 | const enumSettings = (settings.enumerables || {})[enumerable] || {}; 297 | prompt 298 | .command(enumerable) 299 | .description(enumSettings.description || '') 300 | .action(() => { 301 | prompt.removeAllListeners(); 302 | prompt.ui.redraw.done(); 303 | return resolve(forceLeadingBlankFn(forceCaseFn(enumerable))); 304 | }); 305 | }); 306 | } else { 307 | prompt 308 | .catch('[text...]') 309 | .action(parameters => { 310 | const {text} = parameters; 311 | prompt.removeAllListeners(); 312 | prompt.ui.redraw.done(); 313 | return resolve(forceLeadingBlankFn(forceCaseFn(text.join(' ')))); 314 | }); 315 | } 316 | 317 | if (mayBeEmpty) { 318 | // Add an easy exit command 319 | prompt 320 | .command(':skip') 321 | .description('Skip the input if possible.') 322 | .action(() => { 323 | prompt.removeAllListeners(); 324 | prompt.ui.redraw.done(); 325 | resolve(''); 326 | }); 327 | } 328 | 329 | // Handle empty input 330 | const onSubmit = input => { 331 | if (input.length > 0) { 332 | return; 333 | } 334 | 335 | // Show help if enum is defined and input may not be empty 336 | if (mayNotBeEmpty) { 337 | prompt.ui.log(chalk.yellow(`⚠ ${chalk.bold(type)} may not be empty.`)); 338 | } 339 | 340 | if (mayBeEmpty) { 341 | prompt.ui.log(chalk.blue(`ℹ Enter ${chalk.bold(':skip')} to omit ${chalk.bold(type)}.`)); 342 | } 343 | 344 | if (enumRule) { 345 | prompt.exec('help'); 346 | } 347 | }; 348 | 349 | const drawRemaining = length => { 350 | if (length < Infinity) { 351 | const colors = [ 352 | { 353 | threshold: 5, 354 | color: 'red' 355 | }, 356 | { 357 | threshold: 10, 358 | color: 'yellow' 359 | }, 360 | { 361 | threshold: Infinity, 362 | color: 'grey' 363 | } 364 | ]; 365 | 366 | const color = colors 367 | .filter(item => { 368 | return item.threshold >= length; 369 | }) 370 | .map(item => item.color)[0]; 371 | 372 | prompt.ui.redraw(chalk[color](`${length} characters left`)); 373 | } 374 | }; 375 | 376 | const onKey = event => { 377 | const sanitized = forceCaseFn(event.value); 378 | const cropped = sanitized.slice(0, maxLength); 379 | 380 | // We **could** do live editing, but there are some quirks to solve 381 | /* const live = merge({}, results, { 382 | [type]: cropped 383 | }); 384 | prompt.ui.redraw(`\n\n${format(live, true)}\n\n`); */ 385 | 386 | if (maxLength) { 387 | drawRemaining(maxLength - cropped.length); 388 | } 389 | prompt.ui.input(cropped); 390 | }; 391 | 392 | prompt.addListener('keypress', onKey); 393 | prompt.addListener('client_prompt_submit', onSubmit); 394 | 395 | prompt.log(`\n\nPlease enter a ${chalk.bold(type)}: ${meta({ 396 | 'optional': !mayNotBeEmpty, 397 | 'required': mayNotBeEmpty, 398 | 'tab-completion': typeof enumRule !== 'undefined', 399 | 'header': typeof settings.header !== 'undefined', 400 | 'case': forcedCase, 401 | 'multi-line': settings.multiline 402 | })}`); 403 | 404 | if (settings.description) { 405 | prompt.log(chalk.grey(`${settings.description}\n`)); 406 | } 407 | 408 | prompt.log(`\n\n${format(results, true)}\n\n`); 409 | 410 | drawRemaining(maxLength); 411 | 412 | prompt 413 | .delimiter(`❯ ${type}:`) 414 | .show(); 415 | }); 416 | } 417 | 418 | const settings = { 419 | type: { 420 | description: ' holds information about the goal of a change.', 421 | enumerables: { 422 | feat: { 423 | description: 'Adds a new feature.' 424 | }, 425 | fix: { 426 | description: 'Solves a bug.' 427 | }, 428 | docs: { 429 | description: 'Adds or alters documentation.' 430 | }, 431 | style: { 432 | description: 'Improves formatting, white-space.' 433 | }, 434 | refactor: { 435 | description: 'Rewrites code without feature, performance or bug changes.' 436 | }, 437 | perf: { 438 | description: 'Improves performance.' 439 | }, 440 | test: { 441 | description: 'Adds or modifies tests.' 442 | }, 443 | chore: { 444 | description: 'Change build process, tooling or dependencies.' 445 | } 446 | } 447 | }, 448 | scope: { 449 | description: ' marks which sub-component of the project is affected' 450 | }, 451 | subject: { 452 | description: ' is a short, high-level description of the change' 453 | }, 454 | body: { 455 | description: ' holds additional information about the change', 456 | multline: true 457 | }, 458 | footer: { 459 | description: '