├── .circleci └── config.yml ├── .eslintrc.json ├── .git-cz.json ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── bin └── git-cz.js ├── build ├── readme.js └── readme.md ├── docs └── example.png ├── index.js ├── lib ├── LimitedInputPrompt.js ├── cli.js ├── createQuestions.js ├── createState.js ├── cz.js ├── defaults.js ├── formatCommitMessage.js ├── getConfig.js ├── parseArgs.js ├── questions │ ├── body.js │ ├── breaking.js │ ├── issues.js │ ├── lerna.js │ ├── scope.js │ ├── subject.js │ └── type.js ├── runInteractiveQuestions.js ├── runNonInteractiveMode.js └── util │ ├── getGitDir.js │ ├── getGitRootDir.js │ └── lerna.js ├── package.json ├── renovate.json ├── run.js ├── test ├── .eslintrc.json ├── __snapshots__ │ └── cli.test.js.snap ├── cli.test.js ├── cz.test.js ├── formatCommitMessage.test.js └── testUtils.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | refs: 4 | container: &container 5 | docker: 6 | - image: node:16 7 | working_directory: ~/repo 8 | steps: 9 | - &Versions 10 | run: 11 | name: Versions 12 | command: node -v && npm -v && yarn -v 13 | - &Install 14 | run: 15 | name: Install Dependencies 16 | command: yarn install --pure-lockfile 17 | - &Lint 18 | run: 19 | name: Lint 20 | command: yarn lint 21 | - &Build 22 | run: 23 | name: Build 24 | command: yarn build 25 | - &Build_binaries 26 | run: 27 | name: Build binaries 28 | command: yarn build:binaries 29 | - &Test 30 | run: 31 | name: Test 32 | command: yarn test 33 | - &Post_to_dev_null 34 | run: 35 | name: 'Post to Slack #dev-null' 36 | command: npx ci-scripts slack --channel="dev-null" 37 | 38 | jobs: 39 | all: 40 | <<: *container 41 | steps: 42 | - checkout 43 | - *Versions 44 | - *Install 45 | - *Lint 46 | - *Build 47 | - *Build_binaries 48 | - *Test 49 | - *Post_to_dev_null 50 | 51 | master: 52 | <<: *container 53 | steps: 54 | - checkout 55 | - *Versions 56 | - *Install 57 | - *Lint 58 | - *Build 59 | - *Build_binaries 60 | - *Test 61 | - *Post_to_dev_null 62 | - run: 63 | name: Release 64 | command: yarn release 65 | - *Post_to_dev_null 66 | 67 | nightly: 68 | <<: *container 69 | steps: 70 | - checkout 71 | - *Versions 72 | - *Install 73 | - *Lint 74 | - *Build 75 | - *Build_binaries 76 | - *Test 77 | - *Post_to_dev_null 78 | - run: 79 | name: Post to Slack on FAILURE 80 | command: npx ci slack --channel="dev" --text="** nightly build failed :scream:" --icon_emoji=tired_face 81 | when: on_fail 82 | 83 | workflows: 84 | version: 2 85 | all: 86 | jobs: 87 | - all: 88 | context: common-env-vars 89 | filters: 90 | branches: 91 | ignore: 92 | - master 93 | - gh-pages 94 | master: 95 | jobs: 96 | - master: 97 | context: common-env-vars 98 | filters: 99 | branches: 100 | only: master 101 | nightly: 102 | triggers: 103 | - schedule: 104 | cron: '0 1 * * *' 105 | filters: 106 | branches: 107 | only: master 108 | jobs: 109 | - nightly: 110 | context: common-env-vars 111 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "globalReturn": false, 12 | "impliedStrict": true, 13 | "jsx": true 14 | }, 15 | "ecmaVersion": 2017, 16 | "sourceType": "module" 17 | }, 18 | "plugins": [ 19 | "no-use-extend-native", 20 | "babel", 21 | "import", 22 | "jsdoc", 23 | "promise", 24 | "filenames", 25 | "unicorn" 26 | ], 27 | "rules": { 28 | "accessor-pairs": 2, 29 | "array-bracket-newline": 0, 30 | "array-bracket-spacing": [2, "never"], 31 | "array-callback-return": 2, 32 | "array-element-newline": 0, 33 | "arrow-body-style": [2, "as-needed"], 34 | "arrow-parens": [2, "always"], 35 | "arrow-spacing": [ 36 | 2, 37 | { 38 | "after": true, 39 | "before": true 40 | } 41 | ], 42 | "babel/new-cap": 0, 43 | "babel/no-invalid-this": 2, 44 | "babel/object-curly-spacing": [2, "never"], 45 | "block-scoped-var": 2, 46 | "block-spacing": [2, "always"], 47 | "brace-style": [ 48 | 2, 49 | "1tbs", 50 | { 51 | "allowSingleLine": false 52 | } 53 | ], 54 | "callback-return": 2, 55 | "camelcase": 0, 56 | "capitalized-comments": 0, 57 | "class-methods-use-this": 2, 58 | "comma-dangle": [2, "never"], 59 | "comma-spacing": [ 60 | 2, 61 | { 62 | "after": true, 63 | "before": false 64 | } 65 | ], 66 | "comma-style": [2, "last"], 67 | "complexity": [1, 10], 68 | "computed-property-spacing": [2, "never"], 69 | "consistent-return": 2, 70 | "consistent-this": [2, "self"], 71 | "constructor-super": 2, 72 | "curly": 2, 73 | "default-case": 0, 74 | "dot-location": [2, "property"], 75 | "dot-notation": 2, 76 | "eol-last": 2, 77 | "eqeqeq": 2, 78 | "filenames/match-exported": 2, 79 | "filenames/match-regex": [ 80 | 2, 81 | "^[A-Z]?[a-z]+(?:[A-Z][a-z]+)*(\\.[a-z]+)*$", 82 | false 83 | ], 84 | "filenames/no-index": 0, 85 | "for-direction": 2, 86 | "func-call-spacing": [2, "never"], 87 | "func-name-matching": 2, 88 | "func-names": 0, 89 | "func-style": [2, "expression"], 90 | "generator-star-spacing": [ 91 | 2, 92 | { 93 | "after": false, 94 | "before": true 95 | } 96 | ], 97 | "global-require": 2, 98 | "guard-for-in": 2, 99 | "handle-callback-err": 2, 100 | "id-length": [ 101 | 1, 102 | { 103 | "exceptions": ["P", "R", "$", "_"], 104 | "max": 50, 105 | "min": 2 106 | } 107 | ], 108 | "id-match": [ 109 | 2, 110 | "(^[A-Za-z]+(?:[A-Z][a-z]*)*\\d*$)|(^[A-Z]+(_[A-Z]+)*(_\\d$)*$)|(^(_|\\$)$)", 111 | { 112 | "onlyDeclarations": true, 113 | "properties": true 114 | } 115 | ], 116 | "import/default": 2, 117 | "import/export": 2, 118 | "import/extensions": [ 119 | 2, 120 | "never", 121 | { 122 | "json": "always" 123 | } 124 | ], 125 | "import/first": [2, "absolute-first"], 126 | "import/max-dependencies": 0, 127 | "import/named": 2, 128 | "import/namespace": 2, 129 | "import/newline-after-import": 2, 130 | "import/no-absolute-path": 2, 131 | "import/no-amd": 2, 132 | "import/no-anonymous-default-export": [ 133 | 2, 134 | { 135 | "allowAnonymousClass": false, 136 | "allowAnonymousFunction": false, 137 | "allowArray": false, 138 | "allowArrowFunction": false, 139 | "allowLiteral": false, 140 | "allowObject": false 141 | } 142 | ], 143 | "import/no-commonjs": 2, 144 | "import/no-deprecated": 1, 145 | "import/no-duplicates": 0, 146 | "import/no-dynamic-require": 2, 147 | "import/no-extraneous-dependencies": [ 148 | 2, 149 | { 150 | "devDependencies": true, 151 | "optionalDependencies": true, 152 | "peerDependencies": true 153 | } 154 | ], 155 | "import/no-internal-modules": 0, 156 | "import/no-mutable-exports": 2, 157 | "import/no-named-as-default": 2, 158 | "import/no-named-as-default-member": 2, 159 | "import/no-named-default": 2, 160 | "import/no-namespace": 0, 161 | "import/no-nodejs-modules": 0, 162 | "import/no-restricted-paths": 0, 163 | "import/no-unassigned-import": 2, 164 | "import/no-unresolved": 2, 165 | "import/no-webpack-loader-syntax": 2, 166 | "import/order": [ 167 | 2, 168 | { 169 | "groups": [ 170 | "builtin", 171 | "external", 172 | "internal", 173 | "parent", 174 | "sibling", 175 | "index" 176 | ], 177 | "newlines-between": "never" 178 | } 179 | ], 180 | "import/prefer-default-export": 0, 181 | "import/unambiguous": 1, 182 | "indent": [2, 2], 183 | "init-declarations": 0, 184 | "jsdoc/check-param-names": 1, 185 | "jsdoc/check-tag-names": 1, 186 | "jsdoc/check-types": 1, 187 | "jsdoc/newline-after-description": [1, "always"], 188 | "jsdoc/require-description-complete-sentence": 1, 189 | "jsdoc/require-hyphen-before-description": 0, 190 | "jsdoc/require-param": 0, 191 | "jsdoc/require-param-description": 0, 192 | "jsdoc/require-param-type": 0, 193 | "jsdoc/require-returns-description": 0, 194 | "jsdoc/require-returns-type": 0, 195 | "jsx-quotes": [2, "prefer-single"], 196 | "key-spacing": [ 197 | 2, 198 | { 199 | "afterColon": true, 200 | "beforeColon": false 201 | } 202 | ], 203 | "keyword-spacing": [ 204 | 2, 205 | { 206 | "after": true, 207 | "before": true 208 | } 209 | ], 210 | "line-comment-position": [ 211 | 2, 212 | { 213 | "position": "above" 214 | } 215 | ], 216 | "linebreak-style": [2, "unix"], 217 | "lines-around-comment": [ 218 | 2, 219 | { 220 | "allowArrayEnd": true, 221 | "allowArrayStart": true, 222 | "allowBlockEnd": true, 223 | "allowBlockStart": true, 224 | "allowObjectEnd": true, 225 | "allowObjectStart": true, 226 | "beforeBlockComment": true, 227 | "beforeLineComment": true 228 | } 229 | ], 230 | "lines-around-directive": [2, "always"], 231 | "max-len": [ 232 | 1, 233 | { 234 | "code": 160 235 | } 236 | ], 237 | "max-nested-callbacks": [1, 3], 238 | "max-statements-per-line": [ 239 | 2, 240 | { 241 | "max": 1 242 | } 243 | ], 244 | "multiline-ternary": 0, 245 | "new-cap": [ 246 | 0, 247 | { 248 | "capIsNew": false, 249 | "newIsCap": true 250 | } 251 | ], 252 | "new-parens": 2, 253 | "newline-before-return": 2, 254 | "newline-per-chained-call": 0, 255 | "no-alert": 2, 256 | "no-array-constructor": 2, 257 | "no-await-in-loop": 0, 258 | "no-buffer-constructor": 2, 259 | "no-caller": 2, 260 | "no-case-declarations": 2, 261 | "no-catch-shadow": 2, 262 | "no-class-assign": 2, 263 | "no-compare-neg-zero": 2, 264 | "no-cond-assign": 2, 265 | "no-confusing-arrow": 2, 266 | "no-console": 2, 267 | "no-const-assign": 2, 268 | "no-constant-condition": 1, 269 | "no-continue": 2, 270 | "no-control-regex": 2, 271 | "no-debugger": 1, 272 | "no-delete-var": 2, 273 | "no-div-regex": 2, 274 | "no-dupe-args": 2, 275 | "no-dupe-class-members": 2, 276 | "no-dupe-keys": 2, 277 | "no-duplicate-case": 2, 278 | "no-duplicate-imports": 2, 279 | "no-else-return": 0, 280 | "no-empty": 2, 281 | "no-empty-character-class": 2, 282 | "no-empty-pattern": 2, 283 | "no-eq-null": 2, 284 | "no-eval": 2, 285 | "no-ex-assign": 2, 286 | "no-extend-native": 2, 287 | "no-extra-bind": 2, 288 | "no-extra-boolean-cast": 0, 289 | "no-extra-parens": 2, 290 | "no-extra-semi": 2, 291 | "no-fallthrough": 2, 292 | "no-floating-decimal": 2, 293 | "no-func-assign": 2, 294 | "no-global-assign": 2, 295 | "no-implicit-coercion": 2, 296 | "no-implicit-globals": 0, 297 | "no-implied-eval": 2, 298 | "no-inline-comments": 2, 299 | "no-inner-declarations": 2, 300 | "no-invalid-regexp": 2, 301 | "no-invalid-this": 0, 302 | "no-irregular-whitespace": 2, 303 | "no-iterator": 2, 304 | "no-label-var": 2, 305 | "no-labels": 2, 306 | "no-lone-blocks": 2, 307 | "no-lonely-if": 2, 308 | "no-loop-func": 2, 309 | "no-magic-numbers": 0, 310 | "no-mixed-requires": 0, 311 | "no-mixed-spaces-and-tabs": 2, 312 | "no-multi-spaces": 2, 313 | "no-multi-str": 2, 314 | "no-multiple-empty-lines": [ 315 | 2, 316 | { 317 | "max": 1, 318 | "maxBOF": 0, 319 | "maxEOF": 1 320 | } 321 | ], 322 | "no-native-reassign": 2, 323 | "no-negated-condition": 2, 324 | "no-negated-in-lhs": 2, 325 | "no-nested-ternary": 2, 326 | "no-new": 2, 327 | "no-new-func": 2, 328 | "no-new-object": 2, 329 | "no-new-require": 2, 330 | "no-new-symbol": 2, 331 | "no-new-wrappers": 2, 332 | "no-obj-calls": 2, 333 | "no-octal": 2, 334 | "no-octal-escape": 2, 335 | "no-param-reassign": [ 336 | 2, 337 | { 338 | "props": false 339 | } 340 | ], 341 | "no-path-concat": 2, 342 | "no-process-env": 2, 343 | "no-process-exit": 2, 344 | "no-proto": 2, 345 | "no-redeclare": [ 346 | 2, 347 | { 348 | "builtinGlobals": true 349 | } 350 | ], 351 | "no-regex-spaces": 2, 352 | "no-restricted-globals": 0, 353 | "no-restricted-modules": 0, 354 | "no-restricted-properties": 0, 355 | "no-restricted-syntax": 0, 356 | "no-return-assign": 2, 357 | "no-return-await": 2, 358 | "no-script-url": 2, 359 | "no-self-assign": 2, 360 | "no-self-compare": 2, 361 | "no-sequences": 2, 362 | "no-shadow": [ 363 | 2, 364 | { 365 | "builtinGlobals": false, 366 | "hoist": "all" 367 | } 368 | ], 369 | "no-shadow-restricted-names": 2, 370 | "no-spaced-func": 2, 371 | "no-sparse-arrays": 2, 372 | "no-sync": 0, 373 | "no-tabs": 2, 374 | "no-template-curly-in-string": 2, 375 | "no-ternary": 0, 376 | "no-this-before-super": 2, 377 | "no-throw-literal": 2, 378 | "no-trailing-spaces": 2, 379 | "no-undef": 2, 380 | "no-undef-init": 2, 381 | "no-undefined": 0, 382 | "no-underscore-dangle": 0, 383 | "no-unexpected-multiline": 2, 384 | "no-unmodified-loop-condition": 2, 385 | "no-unneeded-ternary": 2, 386 | "no-unreachable": 1, 387 | "no-unsafe-finally": 2, 388 | "no-unsafe-negation": 2, 389 | "no-unused-expressions": 2, 390 | "no-unused-vars": 2, 391 | "no-use-before-define": 2, 392 | "no-use-extend-native/no-use-extend-native": 2, 393 | "no-useless-call": 2, 394 | "no-useless-computed-key": 2, 395 | "no-useless-concat": 2, 396 | "no-useless-constructor": 2, 397 | "no-useless-escape": 2, 398 | "no-useless-rename": [ 399 | 2, 400 | { 401 | "ignoreDestructuring": false, 402 | "ignoreExport": false, 403 | "ignoreImport": false 404 | } 405 | ], 406 | "no-useless-return": 2, 407 | "no-var": 2, 408 | "no-void": 2, 409 | "no-warning-comments": [ 410 | 1, 411 | { 412 | "location": "start", 413 | "terms": ["todo", "@toto"] 414 | } 415 | ], 416 | "no-whitespace-before-property": 2, 417 | "no-with": 2, 418 | "nonblock-statement-body-position": [2, "below"], 419 | "object-curly-spacing": [0, "never"], 420 | "object-property-newline": [ 421 | 2, 422 | { 423 | "allowMultiplePropertiesPerLine": false 424 | } 425 | ], 426 | "object-shorthand": [2, "always"], 427 | "one-var": [2, "never"], 428 | "one-var-declaration-per-line": 2, 429 | "operator-assignment": [2, "always"], 430 | "operator-linebreak": [2, "after"], 431 | "padded-blocks": [2, "never"], 432 | "padding-line-between-statements": 0, 433 | "prefer-arrow-callback": 2, 434 | "prefer-const": 2, 435 | "prefer-destructuring": 0, 436 | "prefer-numeric-literals": 2, 437 | "prefer-promise-reject-errors": 2, 438 | "prefer-reflect": 0, 439 | "prefer-rest-params": 2, 440 | "prefer-spread": 2, 441 | "prefer-template": 0, 442 | "promise/always-return": 2, 443 | "promise/avoid-new": 0, 444 | "promise/catch-or-return": 2, 445 | "promise/no-callback-in-promise": 0, 446 | "promise/no-native": 0, 447 | "promise/no-nesting": 0, 448 | "promise/no-promise-in-callback": 0, 449 | "promise/no-return-wrap": 2, 450 | "promise/param-names": 2, 451 | "promise/prefer-await-to-callbacks": 1, 452 | "promise/prefer-await-to-then": 1, 453 | "quote-props": [2, "as-needed"], 454 | "quotes": [2, "single"], 455 | "radix": 2, 456 | "require-await": 1, 457 | "require-jsdoc": 0, 458 | "require-yield": 2, 459 | "semi": [2, "always"], 460 | "semi-spacing": [ 461 | 2, 462 | { 463 | "after": true, 464 | "before": false 465 | } 466 | ], 467 | "semi-style": [2, "last"], 468 | "sort-keys": [ 469 | 2, 470 | "asc", 471 | { 472 | "caseSensitive": false, 473 | "natural": true 474 | } 475 | ], 476 | "sort-vars": 2, 477 | "space-before-blocks": [2, "always"], 478 | "space-before-function-paren": [2, "always"], 479 | "space-in-parens": [2, "never"], 480 | "space-infix-ops": 2, 481 | "space-unary-ops": [ 482 | 2, 483 | { 484 | "nonwords": false, 485 | "words": true 486 | } 487 | ], 488 | "spaced-comment": [2, "always"], 489 | "strict": [2, "never"], 490 | "switch-colon-spacing": [ 491 | 2, 492 | { 493 | "after": true, 494 | "before": false 495 | } 496 | ], 497 | "symbol-description": 2, 498 | "template-tag-spacing": [2, "never"], 499 | "unicode-bom": [2, "never"], 500 | "unicorn/catch-error-name": [ 501 | "error", 502 | { 503 | "name": "error" 504 | } 505 | ], 506 | "unicorn/custom-error-definition": 0, 507 | "unicorn/escape-case": 2, 508 | "unicorn/explicit-length-check": 0, 509 | "unicorn/filename-case": 0, 510 | "unicorn/no-abusive-eslint-disable": 2, 511 | "unicorn/no-array-instanceof": 2, 512 | "unicorn/no-hex-escape": 2, 513 | "unicorn/no-new-buffer": 2, 514 | "unicorn/no-process-exit": 0, 515 | "unicorn/number-literal-case": 2, 516 | "unicorn/prefer-starts-ends-with": 2, 517 | "unicorn/prefer-type-error": 2, 518 | "unicorn/throw-new-error": 2, 519 | "use-isnan": 2, 520 | "valid-jsdoc": 0, 521 | "valid-typeof": 2, 522 | "vars-on-top": 2, 523 | "wrap-iife": [2, "inside"], 524 | "wrap-regex": 0, 525 | "yoda": 0, 526 | 527 | "import/unambiguous": "off", 528 | "import/no-commonjs": "off", 529 | "promise/prefer-await-to-then": "off", 530 | "strict": "off" 531 | }, 532 | "settings": { 533 | "import/extensions": [".js"] 534 | } 535 | } 536 | -------------------------------------------------------------------------------- /.git-cz.json: -------------------------------------------------------------------------------- 1 | { 2 | "disableEmoji": false, 3 | "format": "{type}{scope}: {emoji}{subject}" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swo 3 | *.swp 4 | .DS_Store 5 | .idea/ 6 | node_modules/ 7 | npm-debug.log 8 | .vscode/ 9 | yarn-error.log 10 | dist/ 11 | package-lock.json 12 | /binaries/ 13 | coverage/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.swo 3 | *.swp 4 | .DS_Store 5 | .idea/ 6 | node_modules/ 7 | npm-debug.log 8 | yarn.lock 9 | .vscode/ 10 | yarn-error.log 11 | lib/ 12 | .eslintrs.json 13 | .nvmrc 14 | .travis.yml 15 | index.js 16 | package-lock.json 17 | run.js 18 | yarn.lock 19 | /test/ 20 | /build/ 21 | /docs/ 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": false, 4 | "endOfLine": "auto", 5 | "printWidth": 85, 6 | "semi": true, 7 | "singleQuote": true, 8 | "tabWidth": 2, 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [4.9.0](https://github.com/streamich/git-cz/compare/v4.8.1...v4.9.0) (2022-05-14) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * 🐛 Read git-cz config from package.json ([c72cfac](https://github.com/streamich/git-cz/commit/c72cfac5e730734c409ce4e28a3fc8c4d2d7554c)) 7 | 8 | 9 | ### Features 10 | 11 | * 🎸 support cjs config files ([4f2ebc5](https://github.com/streamich/git-cz/commit/4f2ebc5c5f9986e73184bd9d30698129cd561ae7)) 12 | 13 | ## [4.8.1](https://github.com/streamich/git-cz/compare/v4.8.0...v4.8.1) (2022-05-14) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * 🐛 Fix COMMIT_EDITMSG in bare repositories ([008ee31](https://github.com/streamich/git-cz/commit/008ee3171c82563220417453b47ad520b5c2c114)) 19 | 20 | # [4.8.0](https://github.com/streamich/git-cz/compare/v4.7.6...v4.8.0) (2021-10-13) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * 🐛 give default value to prevent convert error ([c16df5d](https://github.com/streamich/git-cz/commit/c16df5dbaa6b8452d10877ff56c018419ec76fc6)), closes [#227](https://github.com/streamich/git-cz/issues/227) 26 | * better support for workspaces ([215b6c3](https://github.com/streamich/git-cz/commit/215b6c3e15f417512ed5f7fd5b47b29f440c85a3)) 27 | 28 | 29 | ### Features 30 | 31 | * 🎸 add "format" field to customize subject in commit msg [#81](https://github.com/streamich/git-cz/issues/81) ([5e998cf](https://github.com/streamich/git-cz/commit/5e998cf03193ab50d7c2530ee6268a2ed72ba44a)) 32 | * 🎸️ format (custom message) ([6f0c828](https://github.com/streamich/git-cz/commit/6f0c828120adac11cb1bd80282b168f35b98d0b3)) 33 | 34 | ## [4.7.6](https://github.com/streamich/git-cz/compare/v4.7.5...v4.7.6) (2020-12-07) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * 🐛 move check for git folder to top ([cdf142c](https://github.com/streamich/git-cz/commit/cdf142cb8bbdc2186c04be31a443e79a377565d8)) 40 | 41 | ## [4.7.5](https://github.com/streamich/git-cz/compare/v4.7.4...v4.7.5) (2020-11-30) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * 🐛 pad-right ([1891a62](https://github.com/streamich/git-cz/commit/1891a62641fd978c3f9b46dedfcc1123d5490fde)) 47 | * 🐛 pad-right ([94b063c](https://github.com/streamich/git-cz/commit/94b063c69cef87b3f0a5dc688a6a0c18f1daaa96)) 48 | 49 | ## [4.7.4](https://github.com/streamich/git-cz/compare/v4.7.3...v4.7.4) (2020-11-18) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * 🐛 add config option to change closed issue message ([#218](https://github.com/streamich/git-cz/issues/218)) ([dd88ce9](https://github.com/streamich/git-cz/commit/dd88ce967abed710c0fd3b784085567e5a2e0f4b)), closes [#215](https://github.com/streamich/git-cz/issues/215) 55 | 56 | ## [4.7.3](https://github.com/streamich/git-cz/compare/v4.7.2...v4.7.3) (2020-11-11) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * 🐛 multiple lines on Windows ([#210](https://github.com/streamich/git-cz/issues/210)) ([838d47b](https://github.com/streamich/git-cz/commit/838d47b6c4fedf0d19d50ecf0e48a3afd22ba308)), closes [#188](https://github.com/streamich/git-cz/issues/188) [#197](https://github.com/streamich/git-cz/issues/197) 62 | 63 | ## [4.7.2](https://github.com/streamich/git-cz/compare/v4.7.1...v4.7.2) (2020-11-11) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * 🐛 disable-emoji config being overwritten by default ([#211](https://github.com/streamich/git-cz/issues/211)) ([eb9eb06](https://github.com/streamich/git-cz/commit/eb9eb06004579a0f73eaa7852c22e790414e3ddb)), closes [#207](https://github.com/streamich/git-cz/issues/207) 69 | 70 | ## [4.7.1](https://github.com/streamich/git-cz/compare/v4.7.0...v4.7.1) (2020-08-26) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * 🐛 ignore "staged files check" when -a or --amend is passed ([206274f](https://github.com/streamich/git-cz/commit/206274ff1cfab9180fa3298f8cb9408e4971feca)), closes [#189](https://github.com/streamich/git-cz/issues/189) 76 | 77 | # [4.7.0](https://github.com/streamich/git-cz/compare/v4.6.2...v4.7.0) (2020-06-18) 78 | 79 | 80 | ### Features 81 | 82 | * 🎸 exit process when no files staged ([9fb4844](https://github.com/streamich/git-cz/commit/9fb4844758226798444ee74e16a0df1f3d9bc25b)) 83 | 84 | ## [4.6.2](https://github.com/streamich/git-cz/compare/v4.6.1...v4.6.2) (2020-05-28) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * 🐛 check for staged files ([78dec95](https://github.com/streamich/git-cz/commit/78dec9516b56cda86727534c76cf4f20f4f008c3)) 90 | * 🐛 failing test (execSync not defined) ([b9b6969](https://github.com/streamich/git-cz/commit/b9b6969c05fe5d1dfc2687fa471190bde2a84c83)) 91 | 92 | ## [4.6.1](https://github.com/streamich/git-cz/compare/v4.6.0...v4.6.1) (2020-05-27) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * release revert of checking for staged files ([8a6ac6e](https://github.com/streamich/git-cz/commit/8a6ac6e3df1411bf910bb79360742aa34b1bc2a2)) 98 | 99 | # [4.6.0](https://github.com/streamich/git-cz/compare/v4.5.0...v4.6.0) (2020-05-25) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * 🐛 test when in --dry-run mode ([1697c56](https://github.com/streamich/git-cz/commit/1697c561e8edc613d6087ab2ec84ab7617c0c1e5)) 105 | 106 | 107 | ### Features 108 | 109 | * 🎸 check for staged files ([c283ad3](https://github.com/streamich/git-cz/commit/c283ad3fac13eaf00a899b49da474c7608a61708)) 110 | 111 | # [4.5.0](https://github.com/streamich/git-cz/compare/v4.4.1...v4.5.0) (2020-05-16) 112 | 113 | 114 | ### Features 115 | 116 | * 🎸 manually bump version ([fb05bfb](https://github.com/streamich/git-cz/commit/fb05bfb03ee428ef97b397873c9e88da2902212a)) 117 | * 🎸 recursively search parent folders for config file ([ce04676](https://github.com/streamich/git-cz/commit/ce0467639c56de19f0c9f227d86ef06b570f6790)), closes [#60](https://github.com/streamich/git-cz/issues/60) 118 | 119 | ## [4.4.1](https://github.com/streamich/git-cz/compare/v4.4.0...v4.4.1) (2020-05-16) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * manually update version ([811025c](https://github.com/streamich/git-cz/commit/811025c962eab12af792c1d071438f26785266fb)) 125 | 126 | # [4.4.0](https://github.com/streamich/git-cz/compare/v4.3.1...v4.4.0) (2020-05-16) 127 | 128 | 129 | ### Features 130 | 131 | * 🎸 add --disable-emoji to --help and parse from CLI ([82dd0c9](https://github.com/streamich/git-cz/commit/82dd0c94ba13c9694d258a9c710f7c94409fa327)) 132 | * 🎸 add disable emoji flag ([52a43d9](https://github.com/streamich/git-cz/commit/52a43d95d66a5e0d4a1e1fd92993bf11de102a35)) 133 | 134 | ## [4.3.1](https://github.com/streamich/git-cz/compare/v4.3.0...v4.3.1) (2020-02-03) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * 🐛 fix git commit error ([b116ba0](https://github.com/streamich/git-cz/commit/b116ba0ed4206a173dfb63206ddf7c058e2046ba)) 140 | 141 | # [4.3.0](https://github.com/streamich/git-cz/compare/v4.2.0...v4.3.0) (2020-02-02) 142 | 143 | 144 | ### Features 145 | 146 | * 🎸 add help & version flags ([799fff2](https://github.com/streamich/git-cz/commit/799fff2d9da4ec04ad7ee85b01172a038020ae89)) 147 | * 🎸 improve help screen ([1838c1c](https://github.com/streamich/git-cz/commit/1838c1c5cb96d37b116234bb1ebe06721035ca46)) 148 | 149 | # [4.2.0](https://github.com/streamich/git-cz/compare/v4.1.0...v4.2.0) (2020-01-20) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * 🐛 do not format body as undefined ([28d6e77](https://github.com/streamich/git-cz/commit/28d6e77ce7592d03c411b3f3c9cc69d1ea7c0e53)) 155 | 156 | 157 | ### Features 158 | 159 | * 🎸 Allow emojis in non-interactive and set defaults ([08cf19c](https://github.com/streamich/git-cz/commit/08cf19c36de6c34b4502435d9b12097474db5829)) 160 | * 🎸 can set answers through CLI in default mode ([99238c2](https://github.com/streamich/git-cz/commit/99238c2c1d2c6ed2f5ee209261c297ef4feed712)) 161 | * 🎸 non-interactive mode ([61b40db](https://github.com/streamich/git-cz/commit/61b40db85d668d1a7aa62588f18ba2ec15ba4667)) 162 | 163 | # [4.1.0](https://github.com/streamich/git-cz/compare/v4.0.0...v4.1.0) (2020-01-18) 164 | 165 | 166 | ### Features 167 | 168 | * use fuzzy search for scopes and types ([e6d615f](https://github.com/streamich/git-cz/commit/e6d615f6d1c3934c3b94a0126e32b777c5d4ae8f)) 169 | 170 | # [4.0.0](https://github.com/streamich/git-cz/compare/v3.2.1...v4.0.0) (2020-01-16) 171 | 172 | 173 | ### Bug Fixes 174 | 175 | * 🐛 Adhere to lerna settings for workspaces directory ([4151235](https://github.com/streamich/git-cz/commit/415123502b5f00e3988fc49b4643c945f91185e3)), closes [#85](https://github.com/streamich/git-cz/issues/85) 176 | * 🐛 fixes autocomplete prompt import ([91226ba](https://github.com/streamich/git-cz/commit/91226ba829723ebd15afec52eaf53bb8cc64e210)) 177 | * 🐛 fixes scope's empty string answer issue ([53dd466](https://github.com/streamich/git-cz/commit/53dd4667be95b1f28e13a8aeb203b1a2c8762ccb)) 178 | * 🐛 make semantic-release publish to NPM ([6f5c836](https://github.com/streamich/git-cz/commit/6f5c836256bc893b3e2a7e3b141842e03a790c4e)) 179 | * 🐛 simplify semantic-release config ([3872978](https://github.com/streamich/git-cz/commit/387297890a597fd1ad2456e8948ac12fabc4bcb9)) 180 | * remove emojis when disableEmoji is true ([62915be](https://github.com/streamich/git-cz/commit/62915be714fc9628c4dba06e37c59f4212e5a532)) 181 | 182 | 183 | ### Continuous Integration 184 | 185 | * 🎡 remove NPM semantic-release plugin for on release ([a9f23eb](https://github.com/streamich/git-cz/commit/a9f23eb96e45f8dba124a674bdf1c742fe51385e)) 186 | 187 | 188 | ### Features 189 | 190 | * 🎸 adds a feature to search for types ([f8c3452](https://github.com/streamich/git-cz/commit/f8c34521228460ffa72912012585acdbb6e40286)) 191 | * 🎸 adds git hooks support ([80176cd](https://github.com/streamich/git-cz/commit/80176cd3735c6a8988335964cfb6dbbaccce4703)), closes [#79](https://github.com/streamich/git-cz/issues/79) 192 | * 🎸 adds scope search in scopes question ([70bf18b](https://github.com/streamich/git-cz/commit/70bf18bb02881e2c566cfa8a1cb1af20d59b2af2)) 193 | 194 | 195 | ### BREAKING CHANGES 196 | 197 | * 🧨 Release new major 198 | 199 | ## [3.2.1](https://github.com/streamich/git-cz/compare/v3.2.0...v3.2.1) (2019-07-01) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * trigger again ([ee4cf18](https://github.com/streamich/git-cz/commit/ee4cf18)) 205 | * trigger new release ([5457be9](https://github.com/streamich/git-cz/commit/5457be9)) 206 | 207 | # [3.2.0](https://github.com/streamich/git-cz/compare/v3.1.1...v3.2.0) (2019-07-01) 208 | 209 | 210 | ### Bug Fixes 211 | 212 | * 🐛 don't add emoji to head only when "disableEmoji" is true ([45489c8](https://github.com/streamich/git-cz/commit/45489c8)) 213 | 214 | 215 | ### Features 216 | 217 | * 🎸 added disableEmojis on config ([448873e](https://github.com/streamich/git-cz/commit/448873e)) 218 | 219 | ## [3.1.1](https://github.com/streamich/git-cz/compare/v3.1.0...v3.1.1) (2019-04-26) 220 | 221 | 222 | ### Bug Fixes 223 | 224 | * build binaries on Travis ([10194a8](https://github.com/streamich/git-cz/commit/10194a8)) 225 | 226 | # [3.1.0](https://github.com/streamich/git-cz/compare/v3.0.1...v3.1.0) (2019-04-26) 227 | 228 | 229 | ### Features 230 | 231 | * 🎸 build binaries ([0a64804](https://github.com/streamich/git-cz/commit/0a64804)) 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 2 | 3 | # git-cz 4 | 5 | ![image](https://user-images.githubusercontent.com/9773803/49760520-fa6c6f00-fcc4-11e8-84c4-80727f071487.png) 6 | 7 | ### Without installation 8 | 9 | ```shell 10 | npx git-cz 11 | # or 12 | npx git-cz -e 13 | ``` 14 | 15 | ### Install globally standalone 16 | 17 | ```shell 18 | npm install -g git-cz 19 | git-cz 20 | # or 21 | git-cz -e 22 | ``` 23 | 24 | ### Install locally with Commitizen 25 | 26 | ```shell 27 | npm install -g commitizen 28 | npm install --save-dev git-cz 29 | ``` 30 | 31 | `package.json`: 32 | 33 | ```json 34 | { 35 | "config": { 36 | "commitizen": { 37 | "path": "git-cz" 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | run: 44 | 45 | ```shell 46 | git cz 47 | ``` 48 | 49 | ### Install globally with Commitizen 50 | 51 | ```shell 52 | npm install -g commitizen git-cz 53 | commitizen init git-cz --save-dev --save-exact 54 | ``` 55 | 56 | run: 57 | 58 | ```shell 59 | git cz 60 | ``` 61 | 62 | ## Custom config 63 | 64 | You can provide a custom configuration in a `changelog.config.js` file in your repo, or in any parent folder. 65 | git-cz will search for the closest config file. 66 | Below is default config: 67 | 68 | ```js 69 | module.exports = { 70 | disableEmoji: false, 71 | format: '{type}{scope}: {emoji}{subject}', 72 | list: ['test', 'feat', 'fix', 'chore', 'docs', 'refactor', 'style', 'ci', 'perf'], 73 | maxMessageLength: 64, 74 | minMessageLength: 3, 75 | questions: ['type', 'scope', 'subject', 'body', 'breaking', 'issues', 'lerna'], 76 | scopes: [], 77 | types: { 78 | chore: { 79 | description: 'Build process or auxiliary tool changes', 80 | emoji: '🤖', 81 | value: 'chore' 82 | }, 83 | ci: { 84 | description: 'CI related changes', 85 | emoji: '🎡', 86 | value: 'ci' 87 | }, 88 | docs: { 89 | description: 'Documentation only changes', 90 | emoji: '✏️', 91 | value: 'docs' 92 | }, 93 | feat: { 94 | description: 'A new feature', 95 | emoji: '🎸', 96 | value: 'feat' 97 | }, 98 | fix: { 99 | description: 'A bug fix', 100 | emoji: '🐛', 101 | value: 'fix' 102 | }, 103 | perf: { 104 | description: 'A code change that improves performance', 105 | emoji: '⚡️', 106 | value: 'perf' 107 | }, 108 | refactor: { 109 | description: 'A code change that neither fixes a bug or adds a feature', 110 | emoji: '💡', 111 | value: 'refactor' 112 | }, 113 | release: { 114 | description: 'Create a release commit', 115 | emoji: '🏹', 116 | value: 'release' 117 | }, 118 | style: { 119 | description: 'Markup, white-space, formatting, missing semi-colons...', 120 | emoji: '💄', 121 | value: 'style' 122 | }, 123 | test: { 124 | description: 'Adding missing tests', 125 | emoji: '💍', 126 | value: 'test' 127 | }, 128 | messages: { 129 | type: 'Select the type of change that you\'re committing:', 130 | customScope: 'Select the scope this component affects:', 131 | subject: 'Write a short, imperative mood description of the change:\n', 132 | body: 'Provide a longer description of the change:\n ', 133 | breaking: 'List any breaking changes:\n', 134 | footer: 'Issues this commit closes, e.g #123:', 135 | confirmCommit: 'The packages that this commit has affected\n', 136 | }, 137 | } 138 | }; 139 | ``` 140 | 141 | ## Non-interactive mode 142 | 143 | Using `--non-interactive` flag you can run `git-cz` non-interactive mode. 144 | 145 | For example: 146 | 147 | ```bash 148 | git-cz --non-interactive --type=feat --subject="add onClick prop to component" 149 | ``` 150 | 151 | CLI parameters: 152 | 153 | - `--type` 154 | - `--subject` 155 | - `--scope` 156 | - `--body` 157 | - `--breaking` 158 | - `--issues` 159 | - `--lerna` 160 | 161 | ## Disable Emoji 162 | 163 | Using `--disable-emoji` flag will disable emoji. 164 | 165 | For example: 166 | 167 | ```bash 168 | git-cz --disable-emoji 169 | ``` 170 | 171 | ## Commit message format 172 | 173 | - A commit message consists of a **header**, **body** and **footer**. 174 | - The header has a **type** and a **subject**: 175 | 176 | ```bash 177 | [()]: 178 | [BLANK LINE] 179 | [body] 180 | [BLANK LINE] 181 | [breaking changes] 182 | [BLANK LINE] 183 | [footer] 184 | ``` 185 | 186 | The **header** is the only mandatory part of the commit message. 187 | 188 | The first line (type + subject) is limited to 50 characters **[enforced]** 189 | 190 | Any other line should be limited to 72 character **[automatic wrapping]** 191 | 192 | This allows the message to be easier to read on GitHub as well as in various git tools. 193 | 194 | ### Format 195 | 196 | By default the subject format is: `{type}{scope}: {subject}` 197 | 198 | Configuring the `format` field in `.git-cz.json` you can customize your own: 199 | 200 | - `{type}{scope}: {emoji}{subject}` 201 | - `{emoji}{scope} {subject}` 202 | 203 | ### Type 204 | 205 | Must be one of the following: 206 | 207 | - `test` — Adding missing tests 208 | - `feat` — A new feature 209 | - `fix` — A bug fix 210 | - `chore` — Build process or auxiliary tool changes 211 | - `docs` — Documentation only changes 212 | - `refactor` — A code change that neither fixes a bug or adds a feature 213 | - `style` — Markup, white-space, formatting, missing semi-colons... 214 | - `ci` — CI related changes 215 | - `perf` — A code change that improves performance 216 | 217 | ### Subject 218 | 219 | The subject contains succinct description of the change: 220 | 221 | - Use the imperative, present tense: "change" not "changed" nor "changes" 222 | - No dot (.) at the end. 223 | 224 | ### Body 225 | 226 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". 227 | The body should include the motivation for the change and contrast this with previous behavior. 228 | 229 | #### Affects [only on [lerna](https://lernajs.io/) environments] 230 | 231 | Select the packages the commit affected. 232 | 233 | ### Breaking Changes 234 | 235 | **Breaking Changes** must start with the words `BREAKING CHANGE: `. 236 | 237 | ### Footer 238 | 239 | The footer is the place to reference any tasks related to this commit. 240 | 241 | ## Why this Fork? 242 | 243 | ```bash 244 | npm i -g git-cz 245 | added 1 package in 0.612s 246 | ``` 247 | 248 | Installs in 0.6s vs 31.1s. 249 | 250 | ```bash 251 | npm i -g mol-conventional-changelog 252 | added 345 packages in 31.076s 253 | ``` 254 | -------------------------------------------------------------------------------- /bin/git-cz.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line import/no-unassigned-import, filenames/match-regex 4 | require('../dist/cli'); 5 | -------------------------------------------------------------------------------- /build/readme.js: -------------------------------------------------------------------------------- 1 | const defaults = require('../lib/defaults'); 2 | 3 | exports.types = () => { 4 | let str = ''; 5 | 6 | for (const type of defaults.list) { 7 | str += `- \`${type}\` — ${defaults.types[type].description}\n`; 8 | } 9 | 10 | return str; 11 | }; 12 | 13 | exports.config = () => 14 | `\`\`\`js 15 | module.exports = ${JSON.stringify(defaults, null, 2)}; 16 | \`\`\``; 17 | -------------------------------------------------------------------------------- /build/readme.md: -------------------------------------------------------------------------------- 1 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 2 | 3 | # git-cz 4 | 5 | 6 | ### Without installation 7 | 8 | ```shell 9 | npx git-cz 10 | ``` 11 | 12 | 13 | ### Install globally standalone 14 | 15 | ```shell 16 | npm install -g git-cz 17 | git-cz 18 | ``` 19 | 20 | 21 | ### Install locally with Commitizen 22 | 23 | ```shell 24 | npm install -g commitizen 25 | npm install --save-dev git-cz 26 | ``` 27 | 28 | `package.json`: 29 | 30 | ```json 31 | { 32 | "config": { 33 | "commitizen": { 34 | "path": "git-cz" 35 | } 36 | }, 37 | } 38 | ``` 39 | 40 | run: 41 | 42 | ```shell 43 | git cz 44 | ``` 45 | 46 | 47 | ### Install globally with Commitizen 48 | 49 | ```shell 50 | npm install -g commitizen git-cz 51 | commitizen init git-cz --save-dev --save-exact 52 | ``` 53 | 54 | run: 55 | 56 | ```shell 57 | git cz 58 | ``` 59 | 60 | 61 | ## Example 62 | 63 | ![](./docs/example.png) 64 | 65 | 66 | ## Custom config 67 | 68 | You can provide custom configuration in `changelog.congfig.js` file 69 | in your repo. Below is default config: 70 | 71 | ```mmd 72 | return scripts.config(); 73 | ``` 74 | 75 | 76 | ## Commit Message Format 77 | 78 | * A commit message consists of a **header**, **body** and **footer**. 79 | * The header has a **type** and a **subject**: 80 | 81 | ``` 82 | [()]: 83 | [BLANK LINE] 84 | [body] 85 | [BLANK LINE] 86 | [breaking changes] 87 | [BLANK LINE] 88 | [footer] 89 | ``` 90 | 91 | The **header** is the only mandatory part of the commit message. 92 | 93 | The first line (type + subject) is limited to 50 characters **[enforced]** 94 | 95 | Any other line should be limited to 72 character **[automatic wrapping]** 96 | 97 | This allows the message to be easier to read on GitHub as well as in various git tools. 98 | 99 | ### Type 100 | 101 | Must be one of the following: 102 | 103 | ```mmd 104 | return scripts.types(); 105 | ``` 106 | 107 | ### Subject 108 | 109 | The subject contains succinct description of the change: 110 | 111 | * Use the imperative, present tense: "change" not "changed" nor "changes" 112 | * No dot (.) at the end. 113 | 114 | ### Body 115 | 116 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". 117 | The body should include the motivation for the change and contrast this with previous behavior. 118 | 119 | #### Affects [only on [lerna](https://lernajs.io/) environments] 120 | 121 | Select the packages the commit affected. 122 | 123 | ### Breaking Changes 124 | 125 | **Breaking Changes** must start with the words `BREAKING CHANGE: `. 126 | 127 | ### Footer 128 | 129 | The footer is the place to reference any tasks related to this commit. 130 | 131 | 132 | 133 | ## Why this Fork? 134 | 135 | ``` 136 | npm i -g git-cz 137 | added 1 package in 0.612s 138 | ``` 139 | 140 | Installs in 0.6s vs 31.1s. 141 | 142 | ``` 143 | npm i -g mol-conventional-changelog 144 | added 345 packages in 31.076s 145 | ``` 146 | -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamich/git-cz/31e9ffdea8b482a80400c0590f25179a219bb71b/docs/example.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/cz'); 2 | -------------------------------------------------------------------------------- /lib/LimitedInputPrompt.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const InputPrompt = require('inquirer/lib/prompts/input'); 3 | 4 | class LimitedInputPrompt extends InputPrompt { 5 | constructor (...args) { 6 | super(...args); 7 | 8 | if (!this.opt.maxLength) { 9 | this.throwParamError('maxLength'); 10 | } 11 | this.originalMessage = this.opt.message; 12 | this.spacer = new Array(this.opt.maxLength).fill('-').join(''); 13 | 14 | if (this.opt.leadingLabel) { 15 | if (typeof this.opt.leadingLabel === 'function') { 16 | this.leadingLabel = ' ' + this.opt.leadingLabel(this.answers); 17 | } else { 18 | this.leadingLabel = ' ' + this.opt.leadingLabel; 19 | } 20 | } else { 21 | this.leadingLabel = ''; 22 | } 23 | 24 | this.leadingLength = this.leadingLabel.length; 25 | } 26 | 27 | remainingChar () { 28 | return this.opt.maxLength - this.leadingLength - this.rl.line.length; 29 | } 30 | 31 | onKeypress () { 32 | if (this.rl.line.length > this.opt.maxLength - this.leadingLength) { 33 | this.rl.line = this.rl.line.slice(0, this.opt.maxLength - this.leadingLength); 34 | this.rl.cursor--; 35 | } 36 | 37 | this.render(); 38 | } 39 | 40 | getCharsLeftText () { 41 | const chars = this.remainingChar(); 42 | 43 | if (chars > 15) { 44 | return chalk.green(`${chars} chars left`); 45 | } else if (chars > 5) { 46 | return chalk.yellow(`${chars} chars left`); 47 | } else { 48 | return chalk.red(`${chars} chars left`); 49 | } 50 | } 51 | 52 | render (error) { 53 | let bottomContent = ''; 54 | let message = this.getQuestion(); 55 | let appendContent = ''; 56 | const isFinal = this.status === 'answered'; 57 | 58 | if (isFinal) { 59 | appendContent = this.answer; 60 | } else { 61 | appendContent = this.rl.line; 62 | } 63 | 64 | message = `${message} 65 | [${this.spacer}] ${this.getCharsLeftText()} 66 | ${this.leadingLabel} ${appendContent}`; 67 | 68 | if (error) { 69 | bottomContent = chalk.red('>> ') + error; 70 | } 71 | 72 | this.screen.render(message, bottomContent); 73 | } 74 | } 75 | 76 | module.exports = LimitedInputPrompt; 77 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | const {spawn, execSync} = require('child_process'); 2 | const fs = require('fs'); 3 | const {join} = require('path'); 4 | const shellescape = require('any-shell-escape'); 5 | const signale = require('signale'); 6 | const parseArgs = require('./parseArgs'); 7 | const createState = require('./createState'); 8 | const runInteractiveQuestions = require('./runInteractiveQuestions'); 9 | const runNonInteractiveMode = require('./runNonInteractiveMode'); 10 | const formatCommitMessage = require('./formatCommitMessage'); 11 | const getGitDir = require('./util/getGitDir'); 12 | 13 | // eslint-disable-next-line no-process-env 14 | const executeCommand = (command, env = process.env) => { 15 | const proc = spawn(command, [], { 16 | env, 17 | shell: true, 18 | stdio: [0, 1, 2] 19 | }); 20 | 21 | proc.on('close', (code) => { 22 | // eslint-disable-next-line no-process-exit 23 | process.exit(code); 24 | }); 25 | }; 26 | 27 | // eslint-disable-next-line complexity 28 | const main = async () => { 29 | try { 30 | const {cliAnswers, cliOptions, passThroughParams} = parseArgs(); 31 | 32 | let state = null; 33 | 34 | if (cliOptions.disableEmoji) { 35 | state = createState({disableEmoji: cliOptions.disableEmoji}); 36 | } else { 37 | state = createState(); 38 | } 39 | 40 | if (cliOptions.dryRun) { 41 | // eslint-disable-next-line no-console 42 | console.log('Running in dry mode.'); 43 | } else if ( 44 | !passThroughParams['allow-empty'] && 45 | !passThroughParams.a && 46 | !passThroughParams.amend 47 | ) { 48 | try { 49 | /** 50 | * @author https://github.com/rodrigograca31 51 | * @see https://github.com/streamich/git-cz/issues/177 52 | * 53 | * Exits with 1 if there are differences and 0 if no differences. 54 | */ 55 | execSync('git diff HEAD --staged --quiet --exit-code'); 56 | 57 | // Executes the following line only if the one above didn't crash (exit code: 0) 58 | signale.error('No files staged!'); 59 | 60 | // eslint-disable-next-line no-process-exit 61 | process.exit(0); 62 | } catch (error) { 63 | // eslint-disable no-empty 64 | } 65 | } 66 | 67 | if (cliOptions.nonInteractive) { 68 | await runNonInteractiveMode(state, cliAnswers); 69 | } else { 70 | await runInteractiveQuestions(state, cliAnswers); 71 | } 72 | 73 | const message = formatCommitMessage(state); 74 | 75 | const appendedArgs = []; 76 | 77 | // eslint-disable-next-line guard-for-in 78 | for (const key in passThroughParams) { 79 | const value = passThroughParams[key]; 80 | 81 | if (key.length === 1) { 82 | appendedArgs.push('-' + key); 83 | } else { 84 | appendedArgs.push('--' + key); 85 | } 86 | 87 | if (value !== true) { 88 | appendedArgs.push(value); 89 | } 90 | } 91 | 92 | const commitMsgFile = join(getGitDir(), 'COMMIT_EDITMSG'); 93 | 94 | const command = shellescape([ 95 | 'git', 96 | 'commit', 97 | '--file', 98 | commitMsgFile, 99 | ...appendedArgs 100 | ]); 101 | 102 | if (cliOptions.dryRun) { 103 | // eslint-disable-next-line no-console 104 | console.log('Will execute command:'); 105 | 106 | // The full path is replaced with a relative path to make the test pass on every machine 107 | // eslint-disable-next-line no-console 108 | console.log(command.replace(commitMsgFile, '.git/COMMIT_EDITMSG')); 109 | // eslint-disable-next-line no-console 110 | console.log('Message:'); 111 | // eslint-disable-next-line no-console 112 | console.log(message); 113 | } else { 114 | fs.writeFileSync(commitMsgFile, message); 115 | 116 | /** 117 | * @author https://github.com/oxyii 118 | * @see https://github.com/streamich/git-cz/issues/79 119 | */ 120 | if (cliOptions.hook) { 121 | // eslint-disable-next-line no-process-exit 122 | process.exit(0); 123 | } 124 | 125 | executeCommand(command); 126 | } 127 | } catch (error) { 128 | signale.fatal(error); 129 | } 130 | }; 131 | 132 | main(); 133 | -------------------------------------------------------------------------------- /lib/createQuestions.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require, global-require */ 2 | const qBody = require('./questions/body'); 3 | const qBreaking = require('./questions/breaking'); 4 | const qIssues = require('./questions/issues'); 5 | const qLerna = require('./questions/lerna'); 6 | const qScope = require('./questions/scope'); 7 | const qSubject = require('./questions/subject'); 8 | const qType = require('./questions/type'); 9 | 10 | const creators = { 11 | body: qBody, 12 | breaking: qBreaking, 13 | issues: qIssues, 14 | lerna: qLerna, 15 | scope: qScope, 16 | subject: qSubject, 17 | type: qType 18 | }; 19 | 20 | const createQuestions = (state, cliAnswers) => { 21 | const questions = state.config.questions 22 | .filter((name) => cliAnswers[name] === undefined) 23 | .map((name) => { 24 | const question = creators[name].createQuestion(state); 25 | 26 | if (state.config.messages && state.config.messages[name]) { 27 | question.message = state.config.messages[name]; 28 | } 29 | 30 | return question; 31 | }); 32 | 33 | return questions.filter(Boolean); 34 | }; 35 | 36 | module.exports = createQuestions; 37 | -------------------------------------------------------------------------------- /lib/createState.js: -------------------------------------------------------------------------------- 1 | const getGitRootDir = require('./util/getGitRootDir'); 2 | const getConfig = require('./getConfig'); 3 | 4 | const createState = (config = {}) => { 5 | let root; 6 | 7 | try { 8 | root = getGitRootDir(); 9 | } catch (error) { 10 | throw new Error('Could not find Git root folder.'); 11 | } 12 | 13 | const state = { 14 | answers: { 15 | body: '', 16 | breaking: '', 17 | issues: '', 18 | lerna: '', 19 | scope: '', 20 | subject: '', 21 | type: '' 22 | }, 23 | config: { 24 | ...getConfig(root), 25 | ...config 26 | }, 27 | root 28 | }; 29 | 30 | return state; 31 | }; 32 | 33 | module.exports = createState; 34 | -------------------------------------------------------------------------------- /lib/cz.js: -------------------------------------------------------------------------------- 1 | const createState = require('./createState'); 2 | const runInteractiveQuestions = require('./runInteractiveQuestions'); 3 | const formatCommitMessage = require('./formatCommitMessage'); 4 | 5 | exports.prompter = (cz, commit) => { 6 | const run = async () => { 7 | const state = createState(); 8 | 9 | await runInteractiveQuestions(state); 10 | 11 | const message = formatCommitMessage(state); 12 | 13 | return commit(message); 14 | }; 15 | 16 | run(); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | const format = '{type}{scope}: {emoji}{subject}'; 2 | 3 | const types = { 4 | chore: { 5 | description: 'Build process or auxiliary tool changes', 6 | emoji: '🤖', 7 | value: 'chore' 8 | }, 9 | ci: { 10 | description: 'CI related changes', 11 | emoji: '🎡', 12 | value: 'ci' 13 | }, 14 | docs: { 15 | description: 'Documentation only changes', 16 | emoji: '✏️', 17 | value: 'docs' 18 | }, 19 | feat: { 20 | description: 'A new feature', 21 | emoji: '🎸', 22 | value: 'feat' 23 | }, 24 | fix: { 25 | description: 'A bug fix', 26 | emoji: '🐛', 27 | value: 'fix' 28 | }, 29 | perf: { 30 | description: 'A code change that improves performance', 31 | emoji: '⚡️', 32 | value: 'perf' 33 | }, 34 | refactor: { 35 | description: 'A code change that neither fixes a bug or adds a feature', 36 | emoji: '💡', 37 | value: 'refactor' 38 | }, 39 | release: { 40 | description: 'Create a release commit', 41 | emoji: '🏹', 42 | value: 'release' 43 | }, 44 | style: { 45 | description: 'Markup, white-space, formatting, missing semi-colons...', 46 | emoji: '💄', 47 | value: 'style' 48 | }, 49 | test: { 50 | description: 'Adding missing tests', 51 | emoji: '💍', 52 | value: 'test' 53 | } 54 | }; 55 | 56 | // https://github.com/angular/angular/blob/master/CONTRIBUTING.md#type 57 | const list = [ 58 | 'test', 59 | 'feat', 60 | 'fix', 61 | 'chore', 62 | 'docs', 63 | 'refactor', 64 | 'style', 65 | 'ci', 66 | 'perf' 67 | ]; 68 | 69 | // https://github.com/angular/angular/blob/master/CONTRIBUTING.md#scope 70 | const scopes = []; 71 | 72 | const questions = [ 73 | 'type', 74 | 'scope', 75 | 'subject', 76 | 'body', 77 | 'breaking', 78 | 'issues', 79 | 'lerna' 80 | ]; 81 | 82 | module.exports = { 83 | breakingChangePrefix: '🧨 ', 84 | closedIssueMessage: 'Closes: ', 85 | closedIssuePrefix: '✅ ', 86 | format, 87 | list, 88 | maxMessageLength: 64, 89 | minMessageLength: 3, 90 | questions, 91 | scopes, 92 | types 93 | }; 94 | -------------------------------------------------------------------------------- /lib/formatCommitMessage.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable complexity */ 2 | 3 | const wrap = require('word-wrap'); 4 | 5 | const MAX_LINE_WIDTH = 72; 6 | 7 | const makeAffectsLine = function (answers) { 8 | const selectedPackages = answers.packages; 9 | 10 | if (selectedPackages && selectedPackages.length) { 11 | return `\naffects: ${selectedPackages.join(', ')}`; 12 | } 13 | 14 | return ''; 15 | }; 16 | 17 | const formatCommitMessage = (state) => { 18 | const {config, answers} = state; 19 | const wrapOptions = { 20 | indent: '', 21 | trim: true, 22 | width: MAX_LINE_WIDTH 23 | }; 24 | 25 | const emoji = config.types[answers.type].emoji; 26 | const scope = answers.scope ? '(' + answers.scope.trim() + ')' : ''; 27 | const subject = answers.subject.trim(); 28 | const type = answers.type; 29 | 30 | const format = config.format || '{type}{scope}: {emoji}{subject}'; 31 | 32 | const affectsLine = makeAffectsLine(answers); 33 | 34 | // Wrap these lines at MAX_LINE_WIDTH character 35 | const body = wrap((answers.body || '') + affectsLine, wrapOptions); 36 | const breaking = wrap(answers.breaking, wrapOptions); 37 | const issues = wrap(answers.issues, wrapOptions); 38 | 39 | // @note(emoji) Add space after emoji (breakingChangePrefix/closedIssueEmoji) 40 | const head = format 41 | .replace(/\{emoji\}/g, config.disableEmoji ? '' : emoji + ' ') 42 | .replace(/\{scope\}/g, scope) 43 | .replace(/\{subject\}/g, subject) 44 | .replace(/\{type\}/g, type); 45 | 46 | let msg = head; 47 | 48 | if (body) { 49 | msg += '\n\n' + body; 50 | } 51 | 52 | if (breaking) { 53 | const breakingEmoji = config.disableEmoji ? '' : config.breakingChangePrefix; 54 | 55 | msg += '\n\nBREAKING CHANGE: ' + breakingEmoji + breaking; 56 | } 57 | 58 | if (issues) { 59 | const closedIssueEmoji = config.disableEmoji ? '' : config.closedIssuePrefix; 60 | 61 | msg += '\n\n' + closedIssueEmoji + config.closedIssueMessage + issues; 62 | } 63 | 64 | return msg; 65 | }; 66 | 67 | module.exports = formatCommitMessage; 68 | -------------------------------------------------------------------------------- /lib/getConfig.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require, import/no-dynamic-require */ 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const signale = require('signale'); 5 | const defaults = require('./defaults'); 6 | 7 | const configFiles = [ 8 | '.git-cz.json', 9 | 'changelog.config.js', 10 | 'changelog.config.cjs', 11 | 'changelog.config.json' 12 | ]; 13 | 14 | const findOverrides = (root) => { 15 | const dir = root || process.cwd(); 16 | 17 | for (const file of configFiles) { 18 | const filename = path.resolve(dir, file); 19 | 20 | if (fs.existsSync(filename) && fs.statSync(filename).isFile()) { 21 | return require(filename); 22 | } 23 | } 24 | 25 | const parent = path.resolve(dir, '..'); 26 | 27 | const pkgFilename = path.join(dir, 'package.json'); 28 | 29 | if (fs.existsSync(pkgFilename)) { 30 | try { 31 | const changelog = require(pkgFilename).config.commitizen.changelog; 32 | 33 | if (changelog) { 34 | return changelog; 35 | } 36 | // eslint-disable-next-line no-empty 37 | } catch (error) {} 38 | } 39 | 40 | if (parent !== dir) { 41 | return findOverrides(parent); 42 | } 43 | 44 | return {}; 45 | }; 46 | 47 | const getConfig = (root) => { 48 | const overrides = findOverrides(root); 49 | 50 | if (typeof overrides !== 'object') { 51 | signale.fatal(new TypeError('Expected changelog config to be an object.')); 52 | 53 | // eslint-disable-next-line no-process-exit 54 | process.exit(1); 55 | } 56 | 57 | return { 58 | ...defaults, 59 | ...overrides 60 | }; 61 | }; 62 | 63 | module.exports = getConfig; 64 | -------------------------------------------------------------------------------- /lib/parseArgs.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-process-exit */ 2 | /* eslint-disable no-console */ 3 | /* eslint-disable id-length */ 4 | const minimist = require('minimist'); 5 | const pkg = require('../package.json'); 6 | 7 | const helpScreen = ` 8 | ${pkg.description} 9 | 10 | Usage: git-cz [options] 11 | 12 | options: 13 | -h, --help show usage information 14 | -v, --version print version info and exit 15 | --disable-emoji don't add emoji to commit title 16 | --format custom formatting options for subject 17 | --non-interactive run git-cz in non-interactive mode 18 | 19 | non-interactive mode options: 20 | --type type of the commit, defaults to "chore" 21 | --subject message of the commit, defaults to "automated commit" 22 | --scope semantic commit scope 23 | --body extended description of the commit 24 | --breaking description of breaking changes, if any 25 | --issues GitHub issues this commit closed, e.g "#123" 26 | --lerna Lerna mono-repo packages this commit affects 27 | `; 28 | 29 | const parseArgs = () => { 30 | const { 31 | // eslint-disable-next-line no-unused-vars 32 | _: inputs, 33 | 'dry-run': dryRun, 34 | hook, 35 | 'disable-emoji': disableEmoji, 36 | format, 37 | 'non-interactive': nonInteractive, 38 | body, 39 | breaking, 40 | issues, 41 | lerna, 42 | scope, 43 | subject, 44 | type, 45 | help, 46 | h, 47 | version, 48 | v, 49 | ...passThroughParams 50 | } = minimist(process.argv.slice(2), { 51 | alias: { 52 | h: 'help', 53 | v: 'version' 54 | }, 55 | boolean: [ 56 | 'version', 57 | 'help', 58 | 'disable-emoji', 59 | 'non-interactive', 60 | 'hook', 61 | 'dry-run' 62 | ], 63 | string: [ 64 | 'format', 65 | 'type', 66 | 'subject', 67 | 'scope', 68 | 'body', 69 | 'breaking', 70 | 'issues', 71 | 'learna' 72 | ] 73 | }); 74 | 75 | if (help || h) { 76 | console.log(helpScreen); 77 | process.exit(); 78 | } 79 | 80 | if (version || v) { 81 | console.log(pkg.version); 82 | process.exit(); 83 | } 84 | 85 | const cliOptions = { 86 | disableEmoji, 87 | dryRun, 88 | format, 89 | help, 90 | hook, 91 | nonInteractive, 92 | version 93 | }; 94 | 95 | const cliAnswers = { 96 | body, 97 | breaking, 98 | issues, 99 | lerna, 100 | scope, 101 | subject, 102 | type 103 | }; 104 | 105 | return { 106 | cliAnswers, 107 | cliOptions, 108 | passThroughParams 109 | }; 110 | }; 111 | 112 | module.exports = parseArgs; 113 | -------------------------------------------------------------------------------- /lib/questions/body.js: -------------------------------------------------------------------------------- 1 | exports.createQuestion = () => { 2 | const question = { 3 | message: 'Provide a longer description of the change:\n ', 4 | name: 'body', 5 | type: 'input' 6 | }; 7 | 8 | return question; 9 | }; 10 | -------------------------------------------------------------------------------- /lib/questions/breaking.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | exports.createQuestion = () => { 4 | const question = { 5 | message: `List any breaking changes\n ${chalk.red('BREAKING CHANGE')}:`, 6 | name: 'breaking', 7 | type: 'input' 8 | }; 9 | 10 | return question; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/questions/issues.js: -------------------------------------------------------------------------------- 1 | exports.createQuestion = () => ({ 2 | message: 'Issues this commit closes, e.g #123:', 3 | name: 'issues', 4 | type: 'input' 5 | }); 6 | -------------------------------------------------------------------------------- /lib/questions/lerna.js: -------------------------------------------------------------------------------- 1 | const {getAllPackages, getChangedPackages, isLerna} = require('../util/lerna'); 2 | 3 | exports.createQuestion = (state) => { 4 | if (!isLerna(state)) { 5 | return null; 6 | } 7 | 8 | const changedPackages = getChangedPackages(state); 9 | const allPackages = getAllPackages(state); 10 | 11 | const question = { 12 | choices: allPackages, 13 | default: changedPackages, 14 | message: `The packages that this commit has affected (${changedPackages.length} detected)\n`, 15 | name: 'packages', 16 | type: 'checkbox' 17 | }; 18 | 19 | return question; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/questions/scope.js: -------------------------------------------------------------------------------- 1 | const fuzzy = require('fuzzy'); 2 | 3 | /** 4 | * Searches for the scopes containing the given substring. 5 | * 6 | * @param {string} substring Substring to search with. 7 | * @param {string[]} scopes Scopes list. 8 | */ 9 | const findScope = function (substring, scopes) { 10 | return Promise.resolve(fuzzy.filter(substring || '', scopes).map(({original: scope}) => scope)); 11 | }; 12 | 13 | exports.createQuestion = (state) => { 14 | const {scopes} = state.config; 15 | 16 | if (!scopes) { 17 | return null; 18 | } 19 | 20 | if (!Array.isArray(scopes)) { 21 | throw new TypeError('scopes must be an array of strings.'); 22 | } 23 | 24 | if (scopes.length < 1) { 25 | return null; 26 | } 27 | 28 | const question = { 29 | message: 'Select the scope this component affects:', 30 | name: 'scope', 31 | source: (_answers, input) => findScope(input, scopes), 32 | type: 'autocomplete' 33 | }; 34 | 35 | return question; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/questions/subject.js: -------------------------------------------------------------------------------- 1 | exports.createQuestion = (state) => { 2 | const {config} = state; 3 | const minTitleLengthErrorMessage = `The subject must have at least ${config.minMessageLength} characters`; 4 | const question = { 5 | filter: (input) => { 6 | let subject; 7 | 8 | subject = input.trim(); 9 | while (subject.endsWith('.')) { 10 | subject = subject.substr(0, subject.length - 1).trim(); 11 | } 12 | 13 | return subject; 14 | }, 15 | leadingLabel: (answers) => { 16 | let scope = ''; 17 | 18 | if (answers.scope && answers.scope !== 'none') { 19 | scope = `(${answers.scope})`; 20 | } 21 | 22 | return `${state.answers.type || answers.type}${scope}:`; 23 | }, 24 | 25 | // Minus 3 chars are for emoji + space. 26 | maxLength: config.maxMessageLength - 3, 27 | message: 'Write a short, imperative mood description of the change:', 28 | name: 'subject', 29 | type: 'limitedInput', 30 | validate: (input) => input.length >= config.minMessageLength || minTitleLengthErrorMessage 31 | }; 32 | 33 | return question; 34 | }; 35 | -------------------------------------------------------------------------------- /lib/questions/type.js: -------------------------------------------------------------------------------- 1 | const fuzzy = require('fuzzy'); 2 | 3 | const typeToListItem = ({types, disableEmoji}, type) => { 4 | const {description, emoji, value} = types[type]; 5 | const prefix = emoji && !disableEmoji ? emoji + ' ' : ''; 6 | 7 | return { 8 | name: prefix + (value + ':').padEnd(12, ' ') + description, 9 | value 10 | }; 11 | }; 12 | 13 | /** 14 | * Searches for the type that includes the given substring. 15 | * 16 | * @param {string} substring Substring to search with. 17 | * @param {string[]} config The whole config. 18 | */ 19 | const findType = function (substring, config) { 20 | const types = config.list; 21 | 22 | return Promise.resolve(fuzzy.filter(substring || '', types).map(({original: type}) => typeToListItem(config, type))); 23 | }; 24 | 25 | exports.createQuestion = (state) => { 26 | const {config} = state; 27 | const question = { 28 | message: 'Select the type of change that you\'re committing:', 29 | name: 'type', 30 | source: (_answers, input) => findType(input, config), 31 | type: 'autocomplete' 32 | }; 33 | 34 | return question; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/runInteractiveQuestions.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const AutocompletePrompt = require('inquirer-list-search-prompt'); 3 | const LimitedInputPrompt = require('./LimitedInputPrompt'); 4 | const createQuestions = require('./createQuestions'); 5 | 6 | inquirer.registerPrompt('limitedInput', LimitedInputPrompt); 7 | inquirer.registerPrompt('autocomplete', AutocompletePrompt); 8 | 9 | // if (IS_LERNA_PROJECT) { 10 | // const allPackages = getAllPackages().map((pkg) => pkg.name); 11 | // const changedPackages = getChangedPackages(); 12 | // 13 | // promptQuestions = promptQuestions.concat(createPackagesQuestion(allPackages, changedPackages)); 14 | // } 15 | 16 | const runInteractiveQuestions = async (state, cliAnswers = {}) => { 17 | Object.keys(cliAnswers).forEach((key) => { 18 | state.answers[key] = cliAnswers[key]; 19 | }); 20 | 21 | const questions = createQuestions(state, cliAnswers); 22 | const answers = await inquirer.prompt(questions); 23 | 24 | Object.keys(state.answers).forEach((key) => { 25 | if (answers[key]) { 26 | state.answers[key] = answers[key]; 27 | } 28 | }); 29 | 30 | return answers; 31 | }; 32 | 33 | module.exports = runInteractiveQuestions; 34 | -------------------------------------------------------------------------------- /lib/runNonInteractiveMode.js: -------------------------------------------------------------------------------- 1 | const runNonInteractiveMode = (state, {type = 'chore', subject = 'automated commit', ...restAnswers}) => { 2 | const answers = { 3 | subject, 4 | type, 5 | ...restAnswers 6 | }; 7 | 8 | Object.keys(state.answers).forEach((key) => { 9 | if (answers[key]) { 10 | state.answers[key] = answers[key]; 11 | delete answers[key]; 12 | } 13 | }); 14 | }; 15 | 16 | module.exports = runNonInteractiveMode; 17 | -------------------------------------------------------------------------------- /lib/util/getGitDir.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | 3 | const getGitDir = () => { 4 | const devNull = process.platform === 'win32' ? ' nul' : '/dev/null'; 5 | const dir = execSync('git rev-parse --absolute-git-dir 2>' + devNull) 6 | .toString() 7 | .trim(); 8 | 9 | return dir; 10 | }; 11 | 12 | module.exports = getGitDir; 13 | -------------------------------------------------------------------------------- /lib/util/getGitRootDir.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | 3 | const getGitRootDir = () => { 4 | const devNull = process.platform === 'win32' ? ' nul' : '/dev/null'; 5 | const dir = execSync('git rev-parse --show-toplevel 2>' + devNull) 6 | .toString() 7 | .trim(); 8 | 9 | return dir; 10 | }; 11 | 12 | module.exports = getGitRootDir; 13 | -------------------------------------------------------------------------------- /lib/util/lerna.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const isLerna = (state) => 6 | fs.existsSync(path.join(state.root, 'lerna.json')); 7 | 8 | const isDir = (root) => (name) => { 9 | const filepath = path.join(root, name); 10 | 11 | try { 12 | const stats = fs.statSync(filepath); 13 | 14 | return stats.isDirectory(); 15 | } catch (error) { 16 | return false; 17 | } 18 | }; 19 | 20 | const removeLastDirectoryPartOf = (url) => url.substring(0, url.lastIndexOf('/')); 21 | 22 | const getPackageDirectories = (state) => { 23 | const pkgFilename = path.join(state.root, 'package.json'); 24 | 25 | if (fs.existsSync(pkgFilename)) { 26 | try { 27 | const workspacesConfig = require(String(pkgFilename)).workspaces; 28 | const workspacePackages = Array.isArray(workspacesConfig) ? workspacesConfig : workspacesConfig.packages; 29 | 30 | if (workspacePackages && workspacePackages.length) { 31 | return workspacePackages 32 | .filter((workspacePackage) => workspacePackage.endsWith('*')) 33 | .map((workspacePackage) => 34 | removeLastDirectoryPartOf(String(workspacePackage)) 35 | 36 | // else { 37 | // TODO: support paths that do not end with '*', in that case the package it self is the directory so we don't need to look at inner directories 38 | // return workspacePackage 39 | // } 40 | ); 41 | 42 | // Remove the /* on the tail 43 | } 44 | // eslint-disable-next-line no-empty 45 | } catch (error) { 46 | } 47 | } 48 | 49 | return 'packages'; 50 | }; 51 | 52 | const getAllPackages = (state) => { 53 | try { 54 | const dirs = getPackageDirectories(state).map((dir) => path.join(state.root, dir)); 55 | 56 | return dirs.flatMap((dir) => fs.readdirSync(dir).filter(isDir(dir))); 57 | } catch (error) { 58 | return []; 59 | } 60 | }; 61 | 62 | const getChangedFiles = () => { 63 | const devNull = process.platform === 'win32' ? ' nul' : '/dev/null'; 64 | 65 | return execSync('git diff --cached --name-only 2>' + devNull) 66 | .toString() 67 | .trim() 68 | .split('\n'); 69 | }; 70 | 71 | const getChangedPackages = (state) => { 72 | const unique = {}; 73 | const changedFiles = getChangedFiles(); 74 | const regex = new RegExp('^' + getPackageDirectories(state) + '\/([^/]+)\/', 'is'); 75 | 76 | for (const filename of changedFiles) { 77 | const matches = filename.match(regex); 78 | 79 | if (matches) { 80 | unique[matches[1]] = 1; 81 | } 82 | } 83 | 84 | return Object.keys(unique); 85 | }; 86 | 87 | module.exports = { 88 | getAllPackages, 89 | getChangedPackages, 90 | isLerna 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-cz", 3 | "version": "4.8.0", 4 | "description": "Semantic emojified git commit, git-cz.", 5 | "main": "dist/cz.js", 6 | "bin": { 7 | "git-cz": "./bin/git-cz.js", 8 | "gitcz": "./bin/git-cz.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/streamich/git-cz.git" 13 | }, 14 | "homepage": "https://github.com/streamich/git-cz", 15 | "license": "Unlicense", 16 | "scripts": { 17 | "lint": "yarn eslint", 18 | "clean": "rimraf dist binaries", 19 | "build": "yarn build:cli && yarn build:cz", 20 | "build:cli": "browserify --node -o dist/cli.js lib/cli.js", 21 | "build:cz": "browserify --node -o dist/cz.js --standalone prompter lib/cz.js", 22 | "build:readme": "mmarkdown", 23 | "build:binaries": "mkdirp binaries && pkg lib/cli.js --out-path binaries", 24 | "test": "jest --maxWorkers 2", 25 | "test:dev": "jest --watch", 26 | "test:coverage": "jest --coverage", 27 | "release": "semantic-release", 28 | "eslint": "eslint lib/*.js" 29 | }, 30 | "devDependencies": { 31 | "@semantic-release/changelog": "6.0.3", 32 | "@semantic-release/git": "10.0.1", 33 | "any-shell-escape": "0.1.1", 34 | "browserify": "17.0.1", 35 | "chai": "4.5.0", 36 | "chalk": "4.1.2", 37 | "commitizen": "4.3.1", 38 | "eslint": "7.32.0", 39 | "eslint-plugin-babel": "5.3.1", 40 | "eslint-plugin-filenames": "1.3.2", 41 | "eslint-plugin-import": "2.31.0", 42 | "eslint-plugin-jest": "27.9.0", 43 | "eslint-plugin-jsdoc": "41.1.2", 44 | "eslint-plugin-no-use-extend-native": "0.5.0", 45 | "eslint-plugin-promise": "6.6.0", 46 | "eslint-plugin-unicorn": "23.0.0", 47 | "fuzzy": "0.1.3", 48 | "global": "4.4.0", 49 | "husky": "8.0.3", 50 | "inquirer": "8.2.6", 51 | "inquirer-list-search-prompt": "1.0.2", 52 | "jest": "28.1.3", 53 | "minimist": "1.2.8", 54 | "mocha": "10.7.3", 55 | "pkg": "5.8.1", 56 | "rimraf": "5.0.10", 57 | "semantic-release": "19.0.5", 58 | "signale": "1.4.0", 59 | "spawncommand": "2.2.0", 60 | "word-wrap": "1.2.5" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "yarn lint", 65 | "prepare-commit-msg": "exec < /dev/tty && node ./lib/cli.js --hook || true" 66 | } 67 | }, 68 | "config": { 69 | "commitizen": { 70 | "path": "./dist/cz.js" 71 | } 72 | }, 73 | "mmarkdown": { 74 | "src": "./build/readme.md", 75 | "out": "./README.md", 76 | "scripts": "./build/readme.js", 77 | "backup": false 78 | }, 79 | "release": { 80 | "verifyConditions": [ 81 | "@semantic-release/changelog", 82 | "@semantic-release/git" 83 | ], 84 | "prepare": [ 85 | "@semantic-release/changelog", 86 | "@semantic-release/git" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true, 6 | "pinVersions": false, 7 | "major": { 8 | "automerge": false 9 | }, 10 | "devDependencies": { 11 | "automerge": true, 12 | "pinVersions": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | const {prompter} = require('.'); 2 | 3 | prompter(); 4 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "error", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error", 12 | "jest/valid-expect": "error" 13 | } 14 | } -------------------------------------------------------------------------------- /test/__snapshots__/cli.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`git-cz --help 1`] = ` 4 | " 5 | Semantic emojified git commit, git-cz. 6 | 7 | Usage: git-cz [options] 8 | 9 | options: 10 | -h, --help show usage information 11 | -v, --version print version info and exit 12 | --disable-emoji don't add emoji to commit title 13 | --format custom formatting options for subject 14 | --non-interactive run git-cz in non-interactive mode 15 | 16 | non-interactive mode options: 17 | --type type of the commit, defaults to \\"chore\\" 18 | --subject message of the commit, defaults to \\"automated commit\\" 19 | --scope semantic commit scope 20 | --body extended description of the commit 21 | --breaking description of breaking changes, if any 22 | --issues GitHub issues this commit closed, e.g \\"#123\\" 23 | --lerna Lerna mono-repo packages this commit affects 24 | 25 | " 26 | `; 27 | 28 | exports[`git-cz --non-interactive 1`] = ` 29 | "Running in dry mode. 30 | Will execute command: 31 | git commit --file '.git/COMMIT_EDITMSG' 32 | Message: 33 | chore: 🤖 automated commit 34 | " 35 | `; 36 | -------------------------------------------------------------------------------- /test/cli.test.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package.json'); 2 | const {runCLI} = require('./testUtils'); 3 | 4 | test('git-cz --help', async () => { 5 | const {getResult} = runCLI(['--help']); 6 | 7 | const result = await getResult(); 8 | 9 | expect(result).toMatchSnapshot(); 10 | }); 11 | 12 | test('git-cz --version', async () => { 13 | const {getResult} = runCLI(['--version']); 14 | 15 | const result = await getResult(); 16 | 17 | expect(result.trim()).toBe(pkg.version); 18 | }); 19 | 20 | test('git-cz --non-interactive', async () => { 21 | const {getResult} = runCLI(['--non-interactive', '--dry-run']); 22 | 23 | const result = await getResult(); 24 | 25 | expect(result).toMatchSnapshot(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/cz.test.js: -------------------------------------------------------------------------------- 1 | const cz = require('../lib/cz'); 2 | 3 | describe('commitizen', () => { 4 | it('exports prompter function', () => { 5 | if (typeof cz.prompter !== 'function') { 6 | throw new TypeError('Expected to export "prompter" function for Commitizen.'); 7 | } 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/formatCommitMessage.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | const {expect} = require('chai'); 3 | const formatCommitMessage = require('../lib/formatCommitMessage'); 4 | 5 | const defaultConfig = { 6 | disableEmoji: false, 7 | format: '{type}{scope}: {emoji}{subject}', 8 | breakingChangePrefix: '🧨 ', 9 | closedIssuePrefix: '✅ ', 10 | closedIssueMessage: 'Closes: ', 11 | commitMessageFormat: '<(scope)>: ', 12 | list: ['test', 'feat', 'fix', 'chore', 'docs', 'refactor', 'style', 'ci', 'perf'], 13 | maxMessageLength: 64, 14 | minMessageLength: 3, 15 | questions: ['type', 'scope', 'subject', 'body', 'breaking', 'issues', 'lerna'], 16 | scopes: [], 17 | types: { 18 | chore: { 19 | description: 'Build process or auxiliary tool changes', 20 | emoji: '🤖', 21 | value: 'chore' 22 | }, 23 | ci: { 24 | description: 'CI related changes', 25 | emoji: '🎡', 26 | value: 'ci' 27 | }, 28 | docs: { 29 | description: 'Documentation only changes', 30 | emoji: '✏️', 31 | value: 'docs' 32 | }, 33 | feat: { 34 | description: 'A new feature', 35 | emoji: '🎸', 36 | value: 'feat' 37 | }, 38 | fix: { 39 | description: 'A bug fix', 40 | emoji: '🐛', 41 | value: 'fix' 42 | }, 43 | perf: { 44 | description: 'A code change that improves performance', 45 | emoji: '⚡️', 46 | value: 'perf' 47 | }, 48 | refactor: { 49 | description: 'A code change that neither fixes a bug or adds a feature', 50 | emoji: '💡', 51 | value: 'refactor' 52 | }, 53 | release: { 54 | description: 'Create a release commit', 55 | emoji: '🏹', 56 | value: 'release' 57 | }, 58 | style: { 59 | description: 'Markup, white-space, formatting, missing semi-colons...', 60 | emoji: '💄', 61 | value: 'style' 62 | }, 63 | test: { 64 | description: 'Adding missing tests', 65 | emoji: '💍', 66 | value: 'test' 67 | } 68 | } 69 | }; 70 | 71 | const defaultState = { 72 | answers: { 73 | body: '', 74 | breaking: '', 75 | issues: '', 76 | lerna: '', 77 | scope: '', 78 | subject: 'First commit', 79 | type: 'feat' 80 | }, 81 | config: defaultConfig, 82 | root: '/Users/vad/dev/git-cz' 83 | }; 84 | 85 | describe('formatCommitMessage()', () => { 86 | it('does not include emoji, if emojis disabled in config (no scope)', () => { 87 | const message = formatCommitMessage({ 88 | ...defaultState, 89 | config: { 90 | ...defaultConfig, 91 | disableEmoji: true 92 | } 93 | }); 94 | 95 | expect(message).equal('feat: First commit'); 96 | }); 97 | 98 | it('does not include emoji, if emojis disabled in config (with scope)', () => { 99 | const message = formatCommitMessage({ 100 | ...defaultState, 101 | answers: { 102 | ...defaultState.answers, 103 | scope: 'init' 104 | }, 105 | config: { 106 | ...defaultConfig, 107 | disableEmoji: true 108 | } 109 | }); 110 | 111 | expect(message).equal('feat(init): First commit'); 112 | }); 113 | 114 | it('does not include emoji, if emojis disabled in config (custom)', () => { 115 | const message = formatCommitMessage({ 116 | ...defaultState, 117 | answers: { 118 | ...defaultState.answers, 119 | scope: 'init' 120 | }, 121 | config: { 122 | ...defaultConfig, 123 | format: '{subject} :{scope}{type}', 124 | disableEmoji: true 125 | } 126 | }); 127 | 128 | expect(message).equal('First commit :(init)feat'); 129 | }); 130 | 131 | it('does not include emoji, if emojis disabled in config (dynamic custom)', () => { 132 | const isDynamic = true; 133 | const message = formatCommitMessage({ 134 | ...defaultState, 135 | answers: { 136 | ...defaultState.answers, 137 | scope: 'init' 138 | }, 139 | config: { 140 | ...defaultConfig, 141 | format: `{subject} :{scope}{type}${isDynamic && ' [skip ci]'}`, 142 | disableEmoji: true 143 | } 144 | }); 145 | 146 | expect(message).equal('First commit :(init)feat [skip ci]'); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /test/testUtils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const spawn = require('spawncommand'); 3 | 4 | exports.keys = { 5 | down: '\u001B\u005B\u0042', 6 | enter: '\r', 7 | up: '\u001B\u005B\u0041' 8 | }; 9 | 10 | exports.runCLI = (args = []) => { 11 | const CLI_PATH = path.join(__dirname, '/../lib/cli'); 12 | 13 | const {promise, stdin} = spawn('node', [ 14 | CLI_PATH, 15 | ...args 16 | ]); 17 | 18 | const getResult = async () => { 19 | const {stdout} = await promise; 20 | 21 | return stdout; 22 | }; 23 | 24 | const delay = () => new Promise((resolve) => setTimeout(resolve, 500)); 25 | 26 | const write = async (inputs = []) => { 27 | for (const input of inputs) { 28 | stdin.write(input); 29 | await delay(); 30 | } 31 | 32 | stdin.end(); 33 | }; 34 | 35 | return { 36 | getResult, 37 | write 38 | }; 39 | }; 40 | --------------------------------------------------------------------------------