├── .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 | 
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: '