format-commit 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -10
- package/lib/ai-service.js +272 -0
- package/lib/commit.js +419 -148
- package/lib/create-branch.js +108 -0
- package/lib/default-config.json +23 -18
- package/lib/env-utils.js +109 -0
- package/lib/index.js +47 -34
- package/lib/options.json +60 -29
- package/lib/setup.js +260 -110
- package/lib/utils.js +146 -75
- package/package.json +14 -10
package/lib/commit.js
CHANGED
|
@@ -1,170 +1,441 @@
|
|
|
1
|
-
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import kleur from 'kleur';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import * as utils from './utils.js';
|
|
2
8
|
|
|
3
|
-
const prompts = require('prompts');
|
|
4
|
-
const utils = require('./utils');
|
|
5
|
-
const options = require('./options.json');
|
|
6
9
|
|
|
10
|
+
const { gray } = kleur;
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
utils.log('new commit');
|
|
14
|
+
const options = JSON.parse(
|
|
15
|
+
readFileSync(join(__dirname, 'options.json'), 'utf-8')
|
|
16
|
+
);
|
|
13
17
|
|
|
14
|
-
if (testMode) {
|
|
15
|
-
utils.log('test mode enabled - commit will not be performed', 'warning');
|
|
16
|
-
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const currentBranch = utils.getCurrentBranch();
|
|
22
|
-
const askForVersion = utils.askForVersion(config, currentBranch);
|
|
19
|
+
/** Parse and validate commit title format, auto-correct case */
|
|
20
|
+
const parseAndNormalizeCommitTitle = (title, config) => {
|
|
21
|
+
let type, scope, message, detectedFormatGroup;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
// Try different format patterns
|
|
24
|
+
const format7_8 = /^([^(]+)\(([^)]+)\):\s*(.+)$/; // type(scope): message
|
|
25
|
+
const format5_6 = /^([^(]+)\(([^)]+)\)\s+(.+)$/; // type(scope) message
|
|
26
|
+
const format3_4 = /^([^:]+):\s*(.+)$/; // type: message
|
|
27
|
+
const format1_2 = /^\(([^)]+)\)\s+(.+)$/; // (type) message
|
|
28
|
+
|
|
29
|
+
let match;
|
|
30
|
+
|
|
31
|
+
if ((match = title.match(format7_8))) {
|
|
32
|
+
[, type, scope, message] = match;
|
|
33
|
+
detectedFormatGroup = 'type(scope):';
|
|
34
|
+
} else if ((match = title.match(format5_6))) {
|
|
35
|
+
[, type, scope, message] = match;
|
|
36
|
+
detectedFormatGroup = 'type(scope)';
|
|
37
|
+
} else if ((match = title.match(format3_4))) {
|
|
38
|
+
[, type, message] = match;
|
|
39
|
+
detectedFormatGroup = 'type:';
|
|
40
|
+
} else if ((match = title.match(format1_2))) {
|
|
41
|
+
[, type, message] = match;
|
|
42
|
+
detectedFormatGroup = '(type)';
|
|
43
|
+
} else {
|
|
44
|
+
return { error: 'Invalid commit format. Expected format with type prefix.' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Verify format matches config
|
|
48
|
+
let expectedFormatGroup;
|
|
49
|
+
if (config.format >= 7) {
|
|
50
|
+
expectedFormatGroup = 'type(scope):';
|
|
51
|
+
} else if (config.format >= 5) {
|
|
52
|
+
expectedFormatGroup = 'type(scope)';
|
|
53
|
+
} else if (config.format >= 3) {
|
|
54
|
+
expectedFormatGroup = 'type:';
|
|
55
|
+
} else {
|
|
56
|
+
expectedFormatGroup = '(type)';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (detectedFormatGroup !== expectedFormatGroup) {
|
|
60
|
+
const exampleTitle = utils.formatCommitTitle(
|
|
61
|
+
config.types[0].value,
|
|
62
|
+
'name',
|
|
63
|
+
config.format,
|
|
64
|
+
config.scopes?.[0]?.value
|
|
65
|
+
);
|
|
66
|
+
return { error: `Wrong format. Expected: "${exampleTitle}"` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type = type.trim();
|
|
70
|
+
message = message.trim();
|
|
71
|
+
if (scope) {
|
|
72
|
+
scope = scope.trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate type exists (case-insensitive)
|
|
76
|
+
const validType = config.types.find(t => t.value.toLowerCase() === type.toLowerCase());
|
|
77
|
+
if (!validType) {
|
|
78
|
+
const validTypes = config.types.map(t => t.value).join(', ');
|
|
79
|
+
return { error: `Invalid type "${type}". Valid types: ${validTypes}` };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate scope if present (case-insensitive)
|
|
83
|
+
let validScope = scope;
|
|
84
|
+
if (scope) {
|
|
85
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
86
|
+
return { error: 'Scope not allowed in current format configuration' };
|
|
87
|
+
}
|
|
88
|
+
const foundScope = config.scopes.find(s => s.value.toLowerCase() === scope.toLowerCase());
|
|
89
|
+
if (!foundScope) {
|
|
90
|
+
const validScopes = config.scopes.map(s => s.value).join(', ');
|
|
91
|
+
return { error: `Invalid scope "${scope}". Valid scopes: ${validScopes}` };
|
|
28
92
|
}
|
|
93
|
+
validScope = foundScope.value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Re-format with correct case
|
|
97
|
+
const normalized = utils.formatCommitTitle(validType.value, message, config.format, validScope);
|
|
98
|
+
|
|
99
|
+
return { normalized };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Prompt with pre-filled text (editable) using readline */
|
|
103
|
+
const promptWithPrefill = (message, prefill, validate) => {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const askQuestion = (text) => {
|
|
106
|
+
const rl = readline.createInterface({
|
|
107
|
+
input: process.stdin,
|
|
108
|
+
output: process.stdout
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const questionText = `${kleur.bold(message)}\n› `;
|
|
112
|
+
|
|
113
|
+
// Handle Ctrl+C to cancel
|
|
114
|
+
rl.on('SIGINT', () => {
|
|
115
|
+
rl.close();
|
|
116
|
+
console.log('');
|
|
117
|
+
reject(new Error('cancelled'));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
rl.question(questionText, (answer) => {
|
|
121
|
+
rl.close();
|
|
122
|
+
|
|
123
|
+
const validation = validate ? validate(answer) : true;
|
|
124
|
+
if (validation !== true) {
|
|
125
|
+
utils.log(validation, 'error');
|
|
126
|
+
// Ask again with current answer as prefill
|
|
127
|
+
askQuestion(answer);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve(answer);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
rl.write(text);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
askQuestion(prefill);
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
/** Execute commit, handle version bump, push and show status */
|
|
143
|
+
const finalizeCommit = async (title, description, commitData, currentBranch, testMode) => {
|
|
144
|
+
utils.log('commit changes...');
|
|
145
|
+
|
|
146
|
+
if (testMode) {
|
|
147
|
+
utils.log(title, 'warning');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const commitRes = utils.handleCmdExec(`git commit -m "${title}" -m "${description}"`);
|
|
152
|
+
if (!commitRes) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
utils.log('commit successfully completed', 'success');
|
|
156
|
+
console.log(commitRes);
|
|
157
|
+
|
|
158
|
+
let newVersion = null;
|
|
159
|
+
if (commitData.version === 'prerelease') {
|
|
160
|
+
const preRelease = await prompts([
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
name: 'tag',
|
|
164
|
+
message: 'Pre-release tag?',
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
utils.log('update version...');
|
|
168
|
+
newVersion = utils.handleCmdExec(`npm version ${commitData.version} --preid=${preRelease.tag}`);
|
|
169
|
+
} else if (commitData.version) {
|
|
170
|
+
utils.log('update version...');
|
|
171
|
+
const version = commitData.customVersion ? commitData.customVersion : commitData.version;
|
|
172
|
+
newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (newVersion) {
|
|
176
|
+
utils.log(`package updated to ${newVersion}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (commitData.pushAfterCommit) {
|
|
180
|
+
utils.log('push changes...');
|
|
181
|
+
const gitPush = utils.handleCmdExec(`git push -u origin ${currentBranch}`);
|
|
182
|
+
console.log(gitPush);
|
|
183
|
+
}
|
|
184
|
+
const gitStatus = utils.handleCmdExec('git status');
|
|
185
|
+
console.log(gitStatus);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
export default async function commit(config, testMode) {
|
|
190
|
+
if (!config) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
utils.log('new commit');
|
|
194
|
+
|
|
195
|
+
if (!testMode && !utils.hasStagedChanges()) {
|
|
196
|
+
utils.log('No staged changes found - stage your changes with git add', 'error');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (testMode) {
|
|
201
|
+
utils.log('test mode enabled - commit will not be performed', 'warning');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Get current git branch for version change option "only on release branch"
|
|
205
|
+
const currentBranch = utils.getCurrentBranch();
|
|
206
|
+
const askForVersion = utils.askForVersion(config, currentBranch);
|
|
207
|
+
const ignoreVersion = config.changeVersion === 'ignore';
|
|
29
208
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
209
|
+
const noType = !config.types || (config.types && config.types.length === 0);
|
|
210
|
+
if (noType) {
|
|
211
|
+
utils.log('no types defined - please update config', 'error');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const noScope = !config.scopes || (config.scopes && config.scopes.length === 0);
|
|
216
|
+
if (config.format >= 5 && noScope) {
|
|
217
|
+
utils.log('no scopes defined - update config or format option', 'error');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let commitTitle = null;
|
|
222
|
+
let useAISuggestion = false;
|
|
223
|
+
|
|
224
|
+
// Try to generate AI suggestions if enabled
|
|
225
|
+
if (config.ai?.enabled) {
|
|
226
|
+
utils.log('generating suggestions...');
|
|
227
|
+
const aiService = await import('./ai-service.js');
|
|
228
|
+
const suggestions = await aiService.generateCommitSuggestions(config, testMode);
|
|
229
|
+
|
|
230
|
+
if (suggestions && suggestions.length === 4) {
|
|
231
|
+
let aiCancelled = false;
|
|
232
|
+
const aiChoice = await prompts({
|
|
233
|
+
type: 'select',
|
|
234
|
+
name: 'selectedTitle',
|
|
235
|
+
message: 'Start with a suggested title?',
|
|
236
|
+
choices: [
|
|
237
|
+
...suggestions.map(s => ({ title: s, value: s })),
|
|
238
|
+
{ title: gray('Custom (enter manually)'), value: '__custom__' },
|
|
239
|
+
],
|
|
240
|
+
}, {
|
|
241
|
+
onCancel: () => {
|
|
242
|
+
aiCancelled = true;
|
|
243
|
+
return false;
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (aiCancelled) {
|
|
248
|
+
utils.log('commit cancelled', 'error');
|
|
33
249
|
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (aiChoice.selectedTitle && aiChoice.selectedTitle !== '__custom__') {
|
|
253
|
+
try {
|
|
254
|
+
const rawTitle = await promptWithPrefill(
|
|
255
|
+
'Edit commit title or continue:',
|
|
256
|
+
aiChoice.selectedTitle,
|
|
257
|
+
val => {
|
|
258
|
+
if (!val || val.trim().length === 0) {
|
|
259
|
+
return 'Commit title cannot be empty';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Parse and validate format, type, and scope
|
|
263
|
+
const result = parseAndNormalizeCommitTitle(val, config);
|
|
264
|
+
if (result.error) {
|
|
265
|
+
return result.error;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate length of normalized title
|
|
269
|
+
const lengthCheck = utils.validCommitTitle(result.normalized, config.minLength, config.maxLength);
|
|
270
|
+
if (lengthCheck !== true) {
|
|
271
|
+
return lengthCheck;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Normalize the final title (correct case)
|
|
279
|
+
const result = parseAndNormalizeCommitTitle(rawTitle, config);
|
|
280
|
+
commitTitle = result.normalized;
|
|
281
|
+
useAISuggestion = true;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (err.message === 'cancelled') {
|
|
284
|
+
utils.log('commit cancelled', 'error');
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
utils.log('AI suggestions failed, using manual input', 'warning');
|
|
34
291
|
}
|
|
292
|
+
}
|
|
35
293
|
|
|
36
|
-
|
|
294
|
+
let cancelled = false;
|
|
295
|
+
|
|
296
|
+
// If AI suggestion was accepted, only ask for description, version, and push
|
|
297
|
+
if (useAISuggestion) {
|
|
37
298
|
const commit = await prompts([
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
name: 'version',
|
|
71
|
-
message: 'Type of version change',
|
|
72
|
-
/**
|
|
73
|
-
* Display only some npm version options or all depending on config
|
|
74
|
-
*/
|
|
75
|
-
choices: config.showAllVersionTypes
|
|
76
|
-
? [...options.versionTypes, ...options.allVersionTypes]
|
|
77
|
-
: options.versionTypes,
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
type: prev => prev === 'custom' ? 'text' : null,
|
|
81
|
-
name: 'customVersion',
|
|
82
|
-
message: 'Version?',
|
|
83
|
-
validate: val => utils.validVersion(val),
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
type: 'confirm',
|
|
87
|
-
name: 'pushAfterCommit',
|
|
88
|
-
message: 'Push changes?',
|
|
89
|
-
initial: false,
|
|
90
|
-
},
|
|
299
|
+
{
|
|
300
|
+
type: 'text',
|
|
301
|
+
name: 'description',
|
|
302
|
+
message: 'Commit description?',
|
|
303
|
+
validate: val => val.length > 255 ? 'Commit description too long' : true,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
type: ignoreVersion ? null : (askForVersion ? null : 'confirm'),
|
|
307
|
+
name: 'changeVersion',
|
|
308
|
+
message: 'Change package version?',
|
|
309
|
+
initial: false,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: prev => ignoreVersion ? null : (askForVersion | prev ? 'select' : null),
|
|
313
|
+
name: 'version',
|
|
314
|
+
message: 'Type of version change',
|
|
315
|
+
choices: config.showAllVersionTypes
|
|
316
|
+
? [...options.versionTypes, ...options.allVersionTypes]
|
|
317
|
+
: options.versionTypes,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: prev => prev === 'custom' ? 'text' : null,
|
|
321
|
+
name: 'customVersion',
|
|
322
|
+
message: 'Version?',
|
|
323
|
+
validate: val => utils.validVersion(val),
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
type: 'confirm',
|
|
327
|
+
name: 'pushAfterCommit',
|
|
328
|
+
message: 'Push changes?',
|
|
329
|
+
initial: false,
|
|
330
|
+
},
|
|
91
331
|
], {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
332
|
+
onCancel: () => {
|
|
333
|
+
cancelled = true;
|
|
334
|
+
return false;
|
|
335
|
+
},
|
|
96
336
|
});
|
|
97
337
|
|
|
98
|
-
/**
|
|
99
|
-
* Handle prompt cancellation and stop commit execution
|
|
100
|
-
*/
|
|
101
338
|
if (cancelled) {
|
|
102
|
-
|
|
103
|
-
|
|
339
|
+
utils.log('commit cancelled', 'error');
|
|
340
|
+
return;
|
|
104
341
|
}
|
|
105
342
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
utils.log('commit changes...');
|
|
110
|
-
if (config.stageAllChanges) {
|
|
111
|
-
utils.handleCmdExec('git add -A');
|
|
112
|
-
}
|
|
113
|
-
const commitTitle = utils.formatCommitTitle(
|
|
114
|
-
commit.type,
|
|
115
|
-
commit.title,
|
|
116
|
-
config.format,
|
|
117
|
-
commit.scope
|
|
118
|
-
);
|
|
343
|
+
await finalizeCommit(commitTitle, commit.description, commit, currentBranch, testMode);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
119
346
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
347
|
+
// Classic flow: ask for type and scope first
|
|
348
|
+
const typeScope = await prompts([
|
|
349
|
+
{
|
|
350
|
+
type: 'select',
|
|
351
|
+
name: 'type',
|
|
352
|
+
message: 'Type of changes',
|
|
353
|
+
choices: config.types,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: config.format >= 5 ? 'select' : null,
|
|
357
|
+
name: 'scope',
|
|
358
|
+
message: 'Scope',
|
|
359
|
+
choices: config.scopes,
|
|
360
|
+
},
|
|
361
|
+
], {
|
|
362
|
+
onCancel: () => {
|
|
363
|
+
cancelled = true;
|
|
364
|
+
return false;
|
|
365
|
+
},
|
|
366
|
+
});
|
|
124
367
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
utils.log('commit successfully completed', 'success');
|
|
130
|
-
console.log(commitRes);
|
|
131
|
-
|
|
132
|
-
let newVersion = null;
|
|
133
|
-
if (commit.version === 'prerelease') {
|
|
134
|
-
/**
|
|
135
|
-
* Ask tag if new version is a prerelease and update it
|
|
136
|
-
*/
|
|
137
|
-
const preRelease = await prompts([
|
|
138
|
-
{
|
|
139
|
-
type: 'text',
|
|
140
|
-
name: 'tag',
|
|
141
|
-
message: 'Pre-release tag?',
|
|
142
|
-
},
|
|
143
|
-
]);
|
|
144
|
-
utils.log('update version...');
|
|
145
|
-
newVersion = utils.handleCmdExec(`npm version ${commit.version} --preid=${preRelease.tag}`);
|
|
146
|
-
|
|
147
|
-
} else if (commit.version) {
|
|
148
|
-
/**
|
|
149
|
-
* Ask version if custom option selected and update it
|
|
150
|
-
*/
|
|
151
|
-
utils.log('update version...');
|
|
152
|
-
const version = commit.customVersion ? commit.customVersion : commit.version;
|
|
153
|
-
newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
|
|
154
|
-
}
|
|
368
|
+
if (cancelled) {
|
|
369
|
+
utils.log('commit cancelled', 'error');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
155
372
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
373
|
+
// Ask for title with full formatted length validation, then description, version and push
|
|
374
|
+
const commit = await prompts([
|
|
375
|
+
{
|
|
376
|
+
type: 'text',
|
|
377
|
+
name: 'title',
|
|
378
|
+
message: 'Commit title?',
|
|
379
|
+
validate: val => {
|
|
380
|
+
if (!val || val.trim().length === 0) {
|
|
381
|
+
return 'Commit title cannot be empty';
|
|
382
|
+
}
|
|
383
|
+
const fullTitle = utils.formatCommitTitle(typeScope.type, val, config.format, typeScope.scope);
|
|
384
|
+
return utils.validCommitTitle(fullTitle, config.minLength, config.maxLength);
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
type: 'text',
|
|
389
|
+
name: 'description',
|
|
390
|
+
message: 'Commit description?',
|
|
391
|
+
validate: val => val.length > 255 ? 'Commit description too long' : true,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: ignoreVersion ? null : (askForVersion ? null : 'confirm'),
|
|
395
|
+
name: 'changeVersion',
|
|
396
|
+
message: 'Change package version?',
|
|
397
|
+
initial: false,
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
type: prev => ignoreVersion ? null : (askForVersion | prev ? 'select' : null),
|
|
401
|
+
name: 'version',
|
|
402
|
+
message: 'Type of version change',
|
|
403
|
+
// Display only some npm version options or all depending on config
|
|
404
|
+
choices: config.showAllVersionTypes
|
|
405
|
+
? [...options.versionTypes, ...options.allVersionTypes]
|
|
406
|
+
: options.versionTypes,
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
type: prev => prev === 'custom' ? 'text' : null,
|
|
410
|
+
name: 'customVersion',
|
|
411
|
+
message: 'Version?',
|
|
412
|
+
validate: val => utils.validVersion(val),
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
type: 'confirm',
|
|
416
|
+
name: 'pushAfterCommit',
|
|
417
|
+
message: 'Push changes?',
|
|
418
|
+
initial: false,
|
|
419
|
+
},
|
|
420
|
+
], {
|
|
421
|
+
onCancel: () => {
|
|
422
|
+
cancelled = true;
|
|
423
|
+
return false;
|
|
424
|
+
},
|
|
425
|
+
});
|
|
159
426
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
427
|
+
if (cancelled) {
|
|
428
|
+
utils.log('commit cancelled', 'error');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Format changes message and commit it
|
|
433
|
+
commitTitle = utils.formatCommitTitle(
|
|
434
|
+
typeScope.type,
|
|
435
|
+
commit.title,
|
|
436
|
+
config.format,
|
|
437
|
+
typeScope.scope
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
await finalizeCommit(commitTitle, commit.description, commit, currentBranch, testMode);
|
|
441
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import * as utils from './utils.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export default async function createBranch(config, testMode) {
|
|
6
|
+
if (!config) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
utils.log('new branch');
|
|
10
|
+
|
|
11
|
+
if (testMode) {
|
|
12
|
+
utils.log('test mode enabled - branch will not be created', 'warning');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!config.branchFormat) {
|
|
16
|
+
utils.log('no branch format defined - please update config', 'error');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const noType = !config.types || (config.types && config.types.length === 0);
|
|
21
|
+
if (noType) {
|
|
22
|
+
utils.log('no types defined - please update config', 'error');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const noScope = !config.scopes || (config.scopes && config.scopes.length === 0);
|
|
27
|
+
if (config.branchFormat === 2 && noScope) {
|
|
28
|
+
utils.log('no scopes defined - update config or branch format option', 'error');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
const branch = await prompts([
|
|
34
|
+
{
|
|
35
|
+
type: 'select',
|
|
36
|
+
name: 'type',
|
|
37
|
+
message: 'Type of branch',
|
|
38
|
+
choices: config.types,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: config.branchFormat === 2 ? 'select' : null,
|
|
42
|
+
name: 'scope',
|
|
43
|
+
message: 'Scope',
|
|
44
|
+
choices: config.scopes,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'text',
|
|
48
|
+
name: 'description',
|
|
49
|
+
message: 'Branch description?',
|
|
50
|
+
validate: val => utils.validBranchDescription(val, config.maxLength),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
name: 'checkoutAfterCreate',
|
|
55
|
+
message: 'Switch to the new branch after creation?',
|
|
56
|
+
initial: true,
|
|
57
|
+
},
|
|
58
|
+
], {
|
|
59
|
+
onCancel: () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
return false;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (cancelled) {
|
|
66
|
+
utils.log('branch creation cancelled', 'error');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Format branch name and create it
|
|
71
|
+
utils.log('create branch...');
|
|
72
|
+
const branchName = utils.formatBranchName(
|
|
73
|
+
branch.type,
|
|
74
|
+
branch.description,
|
|
75
|
+
config.branchFormat,
|
|
76
|
+
branch.scope
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (testMode) {
|
|
80
|
+
utils.log(`Branch name: ${branchName}`, 'warning');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const branchExists = utils.checkBranchExists(branchName);
|
|
85
|
+
if (branchExists) {
|
|
86
|
+
utils.log(`branch "${branchName}" already exists`, 'error');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const createCommand = branch.checkoutAfterCreate
|
|
91
|
+
? `git checkout -b ${branchName}`
|
|
92
|
+
: `git branch ${branchName}`;
|
|
93
|
+
|
|
94
|
+
const createRes = utils.handleCmdExec(createCommand);
|
|
95
|
+
if (!createRes) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const successMessage = branch.checkoutAfterCreate
|
|
100
|
+
? `branch "${branchName}" successfully created and checked out`
|
|
101
|
+
: `branch "${branchName}" successfully created`;
|
|
102
|
+
|
|
103
|
+
utils.log(successMessage, 'success');
|
|
104
|
+
console.log(createRes);
|
|
105
|
+
|
|
106
|
+
const gitStatus = utils.handleCmdExec('git status');
|
|
107
|
+
console.log(gitStatus);
|
|
108
|
+
}
|