|
| 1 | +const types = [ |
| 2 | + {value: 'feat', name: 'feat: A new feature'}, |
| 3 | + {value: 'fix', name: 'fix: A bug fix'}, |
| 4 | + {value: 'docs', name: 'docs: Documentation only changes'}, |
| 5 | + { |
| 6 | + value: 'style', |
| 7 | + name: 'style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)', |
| 8 | + }, |
| 9 | + { |
| 10 | + value: 'refactor', |
| 11 | + name: 'refactor: A code change that neither fixes a bug nor adds a feature', |
| 12 | + }, |
| 13 | + { |
| 14 | + value: 'perf', |
| 15 | + name: 'perf: A code change that improves performance', |
| 16 | + }, |
| 17 | + {value: 'test', name: 'test: Adding missing tests'}, |
| 18 | + { |
| 19 | + value: 'chore', |
| 20 | + name: 'chore: Changes to the build process or auxiliary tools\n and libraries such as documentation generation', |
| 21 | + }, |
| 22 | + {value: 'revert', name: 'revert: Revert to a commit'}, |
| 23 | + {value: 'WIP', name: 'WIP: Work in progress'}, |
| 24 | +]; |
| 25 | + |
| 26 | +const scopes = [ |
| 27 | + {name: 'chore'}, |
| 28 | + {name: 'ci-cd'}, |
| 29 | + {name: 'core'}, |
| 30 | + {name: 'styles'}, |
| 31 | + {name: 'auth'}, |
| 32 | + {name: 'theme'}, |
| 33 | + {name: 'shared'}, |
| 34 | +]; |
| 35 | + |
| 36 | +/** |
| 37 | + * @typedef {{type: string; scope: string; subject: string; body: string; isBreaking: boolean; breakingBody: string; breaking: string; isIssueAffected: boolean; issuesBody: string; issues: string;}} Answers |
| 38 | + */ |
| 39 | + |
| 40 | +/** @type import('cz-format-extension').Config<Answers> */ |
| 41 | +module.exports = { |
| 42 | + questions({inquirer, gitInfo}) { |
| 43 | + let migrationQuestions = getMigrationChanges(gitInfo.staged); |
| 44 | + return [ |
| 45 | + { |
| 46 | + type: 'list', |
| 47 | + name: 'type', |
| 48 | + message: 'Select type', |
| 49 | + choices: types, |
| 50 | + }, |
| 51 | + { |
| 52 | + type: 'list', |
| 53 | + name: 'scope', |
| 54 | + message: 'Denote the SCOPE of this change (optional):\n', |
| 55 | + choices: scopes, |
| 56 | + }, |
| 57 | + { |
| 58 | + type: 'input', |
| 59 | + name: 'subject', |
| 60 | + message: 'Write a SHORT, IMPERATIVE tense description of the change:\n', |
| 61 | + validate: subject => |
| 62 | + subject.length === 0 ? 'subject is required' : true, |
| 63 | + }, |
| 64 | + ...migrationQuestions, |
| 65 | + { |
| 66 | + type: 'input', |
| 67 | + name: 'body', |
| 68 | + message: |
| 69 | + 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n', |
| 70 | + }, |
| 71 | + { |
| 72 | + type: 'input', |
| 73 | + name: 'breaking', |
| 74 | + message: 'List any BREAKING CHANGES (optional):\n', |
| 75 | + }, |
| 76 | + { |
| 77 | + type: 'input', |
| 78 | + name: 'issues', |
| 79 | + message: |
| 80 | + 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n', |
| 81 | + filter: (input, answers) => { |
| 82 | + return input.replace(/^\#/g, 'gh-'); |
| 83 | + }, |
| 84 | + }, |
| 85 | + { |
| 86 | + type: 'expand', |
| 87 | + name: 'confirmCommit', |
| 88 | + choices: [ |
| 89 | + {key: 'y', name: 'Yes', value: 'yes'}, |
| 90 | + {key: 'n', name: 'Abort commit', value: 'no'}, |
| 91 | + ], |
| 92 | + default: 0, |
| 93 | + message(answers) { |
| 94 | + const SEP = |
| 95 | + '###--------------------------------------------------------###'; |
| 96 | + console.log(`\n${SEP}\n${buildCommit({answers, gitInfo})}\n${SEP}\n`); |
| 97 | + return 'Are you sure you want to proceed with the commit above?'; |
| 98 | + }, |
| 99 | + }, |
| 100 | + ]; |
| 101 | + }, |
| 102 | + commitMessage({answers, gitInfo}) { |
| 103 | + if (answers.confirmCommit === 'yes') { |
| 104 | + return buildCommit({answers, gitInfo}); |
| 105 | + } else { |
| 106 | + throw Error('Commit cancelled.'); |
| 107 | + } |
| 108 | + }, |
| 109 | +}; |
| 110 | + |
| 111 | +function buildCommit({answers, gitInfo}) { |
| 112 | + const migrationKeys = Object.keys(answers).filter(q => |
| 113 | + q.includes('migration-'), |
| 114 | + ); |
| 115 | + const migrationAnswers = migrationKeys |
| 116 | + .map(key => `${key}- ${answers[key]}`) |
| 117 | + .join('\n'); |
| 118 | + const migrations = |
| 119 | + migrationAnswers && migrationAnswers.length > 0 |
| 120 | + ? `MIGRATION CHANGE:\n${migrationAnswers}` |
| 121 | + : false; |
| 122 | + const scope = answers.scope ? `(${answers.scope})` : false; |
| 123 | + const head = `${answers.type}${scope}: ${answers.subject}`; |
| 124 | + const body = answers.body ? answers.body : false; |
| 125 | + const breaking = answers.breaking |
| 126 | + ? `BREAKING CHANGE:\n${answers.breaking}` |
| 127 | + : false; |
| 128 | + const issues = answers.issues |
| 129 | + ? answers.issues.split(', ').join('\n').valueOf() |
| 130 | + : false; |
| 131 | + |
| 132 | + return escapeSpecialChars( |
| 133 | + [head, body, migrations, breaking, issues].filter(p => p).join('\n\n'), |
| 134 | + ); |
| 135 | +} |
| 136 | + |
| 137 | +function getMigrationChanges(staged) { |
| 138 | + let migrationPaths = staged |
| 139 | + .map( |
| 140 | + filePath => |
| 141 | + filePath.includes('migrations/') && filePath.split(/\//).pop(), |
| 142 | + ) |
| 143 | + .filter(f => f && f.length > 0); |
| 144 | + let timestamps = {}; |
| 145 | + for (let item of migrationPaths) { |
| 146 | + let match = item.match(/[0-9]{14}/g); |
| 147 | + if (match && match[0]) { |
| 148 | + timestamps[match[0]] = [...(timestamps[match[0]] || []), item]; |
| 149 | + } |
| 150 | + } |
| 151 | + let questions = []; |
| 152 | + Object.keys(timestamps).forEach(timestamp => { |
| 153 | + questions.push({ |
| 154 | + type: 'input', |
| 155 | + name: `migration-${timestamp}`, |
| 156 | + message: `Write a description of the changes in migrations for timestamp - ${timestamp}:\n`, |
| 157 | + validate: description => |
| 158 | + description.length === 0 ? 'description is required' : true, |
| 159 | + }); |
| 160 | + }); |
| 161 | + return questions; |
| 162 | +} |
| 163 | + |
| 164 | +const escapeSpecialChars = result => { |
| 165 | + // eslint-disable-next-line no-useless-escape |
| 166 | + const specialChars = ['`']; |
| 167 | + |
| 168 | + let newResult = result; |
| 169 | + // eslint-disable-next-line array-callback-return |
| 170 | + specialChars.map(item => { |
| 171 | + // If user types "feat: `string`", the commit preview should show "feat: `\string\`". |
| 172 | + // Don't worry. The git log will be "feat: `string`" |
| 173 | + newResult = result.replace(new RegExp(item, 'g'), '\\`'); |
| 174 | + }); |
| 175 | + return newResult; |
| 176 | +}; |
0 commit comments