diff --git a/README.md b/README.md index 5f68efa..552a722 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ Here are the options you can set in your `.cz-config.js`: ] } ``` +* **scopeWrapper**: {function}: This is scope wrapper function. Example: config: `{ scopeWrapper: scope => `[${scope}]` }`, result: `feat[scope]this is a new feature` + * **allowCustomScopes**: {boolean, default false}: adds the option `custom` to scope selection so you can still type a scope if you need. * **allowBreakingChanges**: {Array of Strings: default none}. List of commit types you would like to the question `breaking change` prompted. Eg.: ['feat', 'fix']. * **skipQuestions**: {Array of Strings: default none}. List of questions you want to skip. Eg.: ['body', 'footer']. diff --git a/buildCommit.js b/build-commit.js similarity index 78% rename from buildCommit.js rename to build-commit.js index d17d7ff..e197160 100644 --- a/buildCommit.js +++ b/build-commit.js @@ -16,10 +16,13 @@ const addTicketNumber = (ticketNumber, config) => { }; const addScope = (scope, config) => { - const separator = _.get(config, 'subjectSeparator', defaultSubjectSeparator); + const scopeWrapper = _.get(config, 'scopeWrapper', null); + if (scopeWrapper && typeof scopeWrapper === 'function') { + return scopeWrapper(scope); + } + const separator = _.get(config, 'subjectSeparator', defaultSubjectSeparator); if (!scope) return separator; // it could be type === WIP. So there is no scope - return `(${scope.trim()})${separator}`; }; @@ -46,6 +49,23 @@ const addFooter = (footer, config) => { return `\n\n${footerPrefix} ${addBreaklinesIfNeeded(footer, config.breaklineChar)}`; }; +const addBodys = (answers, config) => { + const bodys = config.messages.bodys || []; + let bodysArr = []; + if (bodys.length > 0) { + bodysArr = _.map(bodys, (b, idx) => { + const str = answers[`body${idx}`] || ''; + return str !== '' ? `${b.msg}\n${answers[`body${idx}`] || ''}\n` : ''; + }); + } + + if (bodysArr.join('') === '') { + return ''; + } + bodysArr.push('\n'); + return bodysArr.join('\n'); +}; + const escapeSpecialChars = result => { // eslint-disable-next-line no-useless-escape const specialChars = ['`']; @@ -80,12 +100,17 @@ module.exports = (answers, config) => { let body = wrap(answers.body, wrapOptions) || ''; body = addBreaklinesIfNeeded(body, config.breaklineChar); + body += addBodys(answers, config); + if (answers.changeId && answers.changeId !== '') { + body += `\n\n${answers.changeId}`; + } + const breaking = wrap(answers.breaking, wrapOptions); const footer = wrap(answers.footer, wrapOptions); - let result = head; + let result = `${head}\n`; if (body) { - result += `\n\n${body}`; + result += `\n${body}`; } if (breaking) { const breakingPrefix = config && config.breakingPrefix ? config.breakingPrefix : 'BREAKING CHANGE:'; diff --git a/index.d.ts b/index.d.ts index c302607..85fa8c2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -25,5 +25,6 @@ declare module "cz-customizable" { breakingPrefix?: string; footerPrefix?: string; subjectLimit?: number; + scopeWrapper?: (scope: string) => string; } } diff --git a/index.js b/index.js index d00e06b..484221d 100755 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -#!/usr/bin/env node +// #!/usr/bin/env node /* eslint-disable import/no-dynamic-require */ /* eslint-disable global-require */ @@ -11,7 +11,7 @@ const temp = require('temp').track(); const fs = require('fs'); const path = require('path'); const log = require('./logger'); -const buildCommit = require('./buildCommit'); +const buildCommit = require('./build-commit'); /* istanbul ignore next */ const readConfigFile = () => { diff --git a/package.json b/package.json index 281cf30..8abc608 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "lint": "node_modules/eslint/bin/eslint.js *.js spec/**.js", "test": "node_modules/jasmine-node/bin/jasmine-node spec/", - "test:coverage": "istanbul cover -x cz-config-EXAMPLE.js node_modules/jasmine-node/bin/jasmine-node spec/", + "test:coverage": "istanbul cover -x cz-config-example.js node_modules/jasmine-node/bin/jasmine-node spec/", "test:check-coverage": "istanbul check-coverage --statements 100 --branches 100 --functions 100 --lines 100", "test:watch": "node_modules/jasmine-node/bin/jasmine-node --color --autotest spec/ --watch .", "report-coverage": "cat ./coverage/lcov.info | codecov", diff --git a/questions.js b/questions.js index b0f0fb6..1a04d27 100644 --- a/questions.js +++ b/questions.js @@ -1,9 +1,11 @@ const fs = require('fs'); const _ = require('lodash'); -const buildCommit = require('./buildCommit'); +const buildCommit = require('./build-commit'); const log = require('./logger'); const isNotWip = answers => answers.type.toLowerCase() !== 'wip'; +const isFixOrFeat = answers => ['fix'].indexOf(answers.type.toLowerCase()) !== -1; +const needBody = answers => ['fix'].indexOf(answers.type.toLowerCase()) === -1; const isValidateTicketNo = (value, config) => { if (!value) { @@ -19,14 +21,30 @@ const isValidateTicketNo = (value, config) => { return true; }; +const getBodyChangeId = () => { + let changeId = ''; + if (fs.existsSync('./.git/COMMIT_EDITMSG')) { + const preparedCommit = fs.readFileSync('./.git/COMMIT_EDITMSG', 'utf-8'); + const match = preparedCommit.match(/(Change-Id:.*)/); + if (match) { + changeId = match[0] || ''; + } + } + return changeId; +}; + const getPreparedCommit = context => { let message = null; if (fs.existsSync('./.git/COMMIT_EDITMSG')) { let preparedCommit = fs.readFileSync('./.git/COMMIT_EDITMSG', 'utf-8'); preparedCommit = preparedCommit + // 过滤 Change-Id + .replace(/Change-Id:.*[\r\n]/g, '') .replace(/^#.*/gm, '') .replace(/^\s*[\r\n]/gm, '') .replace(/[\r\n]$/, '') + // 替换掉现有的scope + .replace(/^(\w*) (?:\|(?: (\w+-\d+)? )?>)? /, '') .split(/\r\n|\r|\n/); if (preparedCommit.length && preparedCommit[0]) { @@ -41,6 +59,7 @@ const getPreparedCommit = context => { }; module.exports = { + getPreparedCommit, getQuestions(config, cz) { // normalize config optional options const scopeOverrides = config.scopeOverrides || {}; @@ -151,6 +170,7 @@ module.exports = { name: 'body', message: messages.body, default: getPreparedCommit('body'), + when: needBody, }, { type: 'input', @@ -173,6 +193,33 @@ module.exports = { message: messages.footer, when: isNotWip, }, + ]; + + const bodys = messages.bodys || []; + if (bodys.length > 0) { + _.forEach(bodys, (b, idx) => { + questions.push({ + type: 'input', + name: `body${idx}`, + message: `填写${b.msg}:\n`, + default: b.default || '', + when: isFixOrFeat, + }); + }); + } + + const changeId = getBodyChangeId() || ''; + if (changeId !== '') { + questions = questions.concat([ + { + type: 'input', + name: 'changeId', + default: changeId, + }, + ]); + } + + questions = questions.concat([ { type: 'expand', name: 'confirmCommit', @@ -188,7 +235,7 @@ module.exports = { return messages.confirmCommit; }, }, - ]; + ]); questions = questions.filter(item => !skipQuestions.includes(item.name)); diff --git a/spec/buildCommitSpec.js b/spec/build-commit-spec.js similarity index 91% rename from spec/buildCommitSpec.js rename to spec/build-commit-spec.js index fc21ebd..62035be 100644 --- a/spec/buildCommitSpec.js +++ b/spec/build-commit-spec.js @@ -1,4 +1,4 @@ -const buildCommit = require('../buildCommit'); +const buildCommit = require('../build-commit'); describe('buildCommit()', () => { const answers = { @@ -26,6 +26,13 @@ describe('buildCommit()', () => { expect(buildCommit(answers, options)).toEqual('feat(app) this is a new feature'); }); + it('subject with custom scope wrapper option', () => { + const options = { + scopeWrapper: scope => `[${scope}] `, + }; + expect(buildCommit(answers, options)).toEqual('feat[app] this is a new feature'); + }); + describe('without scope', () => { it('subject without scope', () => { const answersNoScope = { diff --git a/spec/czCustomizableSpec.js b/spec/cz-customizable-spec.js similarity index 100% rename from spec/czCustomizableSpec.js rename to spec/cz-customizable-spec.js diff --git a/spec/questionsSpec.js b/spec/questions-spec.js similarity index 99% rename from spec/questionsSpec.js rename to spec/questions-spec.js index 2b1b5b9..62573d0 100644 --- a/spec/questionsSpec.js +++ b/spec/questions-spec.js @@ -64,7 +64,7 @@ describe('cz-customizable', () => { // question 5 - SUBJECT expect(getQuestion(5).name).toEqual('subject'); expect(getQuestion(5).type).toEqual('input'); - expect(getQuestion(5).default).toEqual(null); + expect(getQuestion(5).default).toEqual(questions.getPreparedCommit('subject')); expect(getQuestion(5).message).toMatch(/IMPERATIVE tense description/); expect(getQuestion(5).filter('Subject')).toEqual('subject'); expect(getQuestion(5).validate('bad subject that exceed limit for 6 characters')).toEqual('Exceed limit: 40');