UNPKG

6.73 kBJavaScriptView Raw
1const fs = require('fs');
2const _ = require('lodash');
3const buildCommit = require('./build-commit');
4const log = require('./logger');
5
6const isNotWip = answers => answers.type.toLowerCase() !== 'wip';
7
8const isValidateTicketNo = (value, config) => {
9 if (!value) {
10 return !config.isTicketNumberRequired;
11 }
12 if (!config.ticketNumberRegExp) {
13 return true;
14 }
15 const reg = new RegExp(config.ticketNumberRegExp);
16 if (value.replace(reg, '') !== '') {
17 return false;
18 }
19 return true;
20};
21
22const getPreparedCommit = context => {
23 let message = null;
24 if (fs.existsSync('./.git/COMMIT_EDITMSG')) {
25 let preparedCommit = fs.readFileSync('./.git/COMMIT_EDITMSG', 'utf-8');
26 preparedCommit = preparedCommit
27 .replace(/^#.*/gm, '')
28 .replace(/^\s*[\r\n]/gm, '')
29 .replace(/[\r\n]$/, '')
30 .split(/\r\n|\r|\n/);
31
32 if (preparedCommit.length && preparedCommit[0]) {
33 if (context === 'subject') [message] = preparedCommit;
34 else if (context === 'body' && preparedCommit.length > 1) {
35 preparedCommit.shift();
36 message = preparedCommit.join('|');
37 }
38 }
39 }
40
41 return message;
42};
43
44module.exports = {
45 getQuestions(config, cz) {
46 // normalize config optional options
47 const scopeOverrides = config.scopeOverrides || {};
48 const messages = config.messages || {};
49 const skipQuestions = config.skipQuestions || [];
50 const skipEmptyScopes = config.skipEmptyScopes || false;
51
52 messages.type = messages.type || "Select the type of change that you're committing:";
53 messages.scope = messages.scope || '\nDenote the SCOPE of this change (optional):';
54 messages.customScope = messages.customScope || 'Denote the SCOPE of this change:';
55 if (!messages.ticketNumber) {
56 if (config.ticketNumberRegExp) {
57 messages.ticketNumber =
58 messages.ticketNumberPattern ||
59 `Enter the ticket number following this pattern (${config.ticketNumberRegExp})\n`;
60 } else {
61 messages.ticketNumber = 'Enter the ticket number:\n';
62 }
63 }
64 messages.subject = messages.subject || 'Write a SHORT, IMPERATIVE tense description of the change:\n';
65 messages.body =
66 messages.body || 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n';
67 messages.breaking = messages.breaking || 'List any BREAKING CHANGES (optional):\n';
68 messages.footer = messages.footer || 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n';
69 messages.confirmCommit = messages.confirmCommit || 'Are you sure you want to proceed with the commit above?';
70
71 let questions = [
72 {
73 type: 'list',
74 name: 'type',
75 message: messages.type,
76 choices: config.types,
77 },
78 {
79 type: 'list',
80 name: 'scope',
81 message: messages.scope,
82 choices(answers) {
83 let scopes = [];
84 if (scopeOverrides[answers.type]) {
85 scopes = scopes.concat(scopeOverrides[answers.type]);
86 } else {
87 scopes = scopes.concat(config.scopes);
88 }
89 if (config.allowCustomScopes || scopes.length === 0) {
90 scopes = scopes.concat([
91 new cz.Separator(),
92 { name: 'empty', value: false },
93 { name: 'custom', value: 'custom' },
94 ]);
95 }
96 return scopes;
97 },
98 when(answers) {
99 let hasScope = false;
100 if (scopeOverrides[answers.type]) {
101 hasScope = !!(scopeOverrides[answers.type].length > 0);
102 } else {
103 hasScope = !!(config.scopes && config.scopes.length > 0);
104 }
105 if (!hasScope) {
106 // TODO: Fix when possible
107 // eslint-disable-next-line no-param-reassign
108 answers.scope = skipEmptyScopes ? '' : 'custom';
109 return false;
110 }
111 return isNotWip(answers);
112 },
113 },
114 {
115 type: 'input',
116 name: 'scope',
117 message: messages.customScope,
118 when(answers) {
119 return answers.scope === 'custom';
120 },
121 },
122 {
123 type: 'input',
124 name: 'ticketNumber',
125 message: messages.ticketNumber,
126 when() {
127 return !!config.allowTicketNumber; // no ticket numbers allowed unless specifed
128 },
129 validate(value) {
130 return isValidateTicketNo(value, config);
131 },
132 },
133 {
134 type: 'input',
135 name: 'subject',
136 message: messages.subject,
137 default: getPreparedCommit('subject'),
138 validate(value) {
139 const limit = config.subjectLimit || 100;
140 if (value.length > limit) {
141 return `Exceed limit: ${limit}`;
142 }
143 return true;
144 },
145 filter(value) {
146 const upperCaseSubject = config.upperCaseSubject || false;
147
148 return (upperCaseSubject ? value.charAt(0).toUpperCase() : value.charAt(0).toLowerCase()) + value.slice(1);
149 },
150 },
151 {
152 type: 'input',
153 name: 'body',
154 message: messages.body,
155 default: getPreparedCommit('body'),
156 },
157 {
158 type: 'input',
159 name: 'breaking',
160 message: messages.breaking,
161 when(answers) {
162 // eslint-disable-next-line max-len
163 if (
164 config.askForBreakingChangeFirst ||
165 (config.allowBreakingChanges && config.allowBreakingChanges.indexOf(answers.type.toLowerCase()) >= 0)
166 ) {
167 return true;
168 }
169 return false; // no breaking changes allowed unless specifed
170 },
171 },
172 {
173 type: 'input',
174 name: 'footer',
175 message: messages.footer,
176 when: isNotWip,
177 },
178 {
179 type: 'expand',
180 name: 'confirmCommit',
181 choices: [
182 { key: 'y', name: 'Yes', value: 'yes' },
183 { key: 'n', name: 'Abort commit', value: 'no' },
184 { key: 'e', name: 'Edit message', value: 'edit' },
185 ],
186 default: 0,
187 message(answers) {
188 const SEP = '###--------------------------------------------------------###';
189 log.info(`\n${SEP}\n${buildCommit(answers, config)}\n${SEP}\n`);
190 return messages.confirmCommit;
191 },
192 },
193 ];
194
195 questions = questions.filter(item => !skipQuestions.includes(item.name));
196
197 if (config.askForBreakingChangeFirst) {
198 const isBreaking = oneQuestion => oneQuestion.name === 'breaking';
199
200 const breakingQuestion = _.filter(questions, isBreaking);
201 const questionWithoutBreaking = _.reject(questions, isBreaking);
202
203 questions = _.concat(breakingQuestion, questionWithoutBreaking);
204 }
205
206 return questions;
207 },
208};