format-commit 1.0.0 → 1.1.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 +101 -33
- package/lib/ai-service.js +124 -103
- package/lib/commit.js +75 -106
- package/lib/create-branch.js +35 -5
- package/lib/index.js +6 -5
- package/lib/options.json +8 -8
- package/lib/setup.js +33 -6
- package/lib/utils.js +397 -13
- package/package.json +1 -1
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import kleur from 'kleur';
|
|
3
|
+
import { magenta } from 'kleur/colors';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
const { gray, bold, red, green, yellow } = kleur;
|
|
@@ -53,7 +54,199 @@ const validVersion = (version) => {
|
|
|
53
54
|
return true;
|
|
54
55
|
};
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
+
const applyCasing = (value, casing) => {
|
|
58
|
+
switch (casing) {
|
|
59
|
+
case 'lower': return value.toLowerCase();
|
|
60
|
+
case 'upper': return value.toUpperCase();
|
|
61
|
+
case 'capitalize': return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
|
62
|
+
default: return value;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const detectCasing = (word) => {
|
|
67
|
+
if (word === word.toLowerCase()) { return 'lower'; }
|
|
68
|
+
if (word === word.toUpperCase()) { return 'upper'; }
|
|
69
|
+
return 'capitalize';
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const parseCustomFormat = (pattern) => {
|
|
73
|
+
const regex = /\{([^}]+)\}|\b(type|scope|description)\b/gi;
|
|
74
|
+
const segments = [];
|
|
75
|
+
let lastIndex = 0;
|
|
76
|
+
let match;
|
|
77
|
+
|
|
78
|
+
while ((match = regex.exec(pattern)) !== null) {
|
|
79
|
+
// Add literal text before this match
|
|
80
|
+
if (match.index > lastIndex) {
|
|
81
|
+
segments.push({ type: 'literal', value: pattern.slice(lastIndex, match.index) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (match[1] !== undefined) {
|
|
85
|
+
// {field} placeholder
|
|
86
|
+
segments.push({ type: 'field', label: match[1] });
|
|
87
|
+
} else {
|
|
88
|
+
// keyword (type, scope, description)
|
|
89
|
+
segments.push({
|
|
90
|
+
type: 'keyword',
|
|
91
|
+
keyword: match[2].toLowerCase(),
|
|
92
|
+
case: detectCasing(match[2]),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
lastIndex = match.index + match[0].length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Add trailing literal text
|
|
100
|
+
if (lastIndex < pattern.length) {
|
|
101
|
+
segments.push({ type: 'literal', value: pattern.slice(lastIndex) });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return segments;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const customFormatHasScope = (pattern) => /\bscope\b/i.test(pattern);
|
|
108
|
+
|
|
109
|
+
const customBranchFormatHasScope = (pattern) => customFormatHasScope(pattern);
|
|
110
|
+
|
|
111
|
+
const getCustomFields = (pattern) => {
|
|
112
|
+
const fields = [];
|
|
113
|
+
const regex = /\{([^}]+)\}/g;
|
|
114
|
+
let match;
|
|
115
|
+
while ((match = regex.exec(pattern)) !== null) {
|
|
116
|
+
fields.push(match[1]);
|
|
117
|
+
}
|
|
118
|
+
return fields;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const validateCustomFormatPattern = (pattern) => {
|
|
122
|
+
if (!pattern || !pattern.trim()) {
|
|
123
|
+
return 'Pattern cannot be empty';
|
|
124
|
+
}
|
|
125
|
+
if (!/\btype\b/i.test(pattern)) {
|
|
126
|
+
return 'Pattern must contain the "type" keyword';
|
|
127
|
+
}
|
|
128
|
+
if (!/\bdescription\b/i.test(pattern)) {
|
|
129
|
+
return 'Pattern must contain the "description" keyword';
|
|
130
|
+
}
|
|
131
|
+
// Check balanced braces
|
|
132
|
+
let depth = 0;
|
|
133
|
+
for (const ch of pattern) {
|
|
134
|
+
if (ch === '{') { depth++; }
|
|
135
|
+
if (ch === '}') { depth--; }
|
|
136
|
+
if (depth < 0) { return 'Unbalanced braces in pattern'; }
|
|
137
|
+
}
|
|
138
|
+
if (depth !== 0) { return 'Unbalanced braces in pattern'; }
|
|
139
|
+
return true;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const parseCustomBranchFormat = (pattern) => {
|
|
143
|
+
const regex = /\{([^}]+)\}|\b(type|scope|description)\b/gi;
|
|
144
|
+
const segments = [];
|
|
145
|
+
let lastIndex = 0;
|
|
146
|
+
let match;
|
|
147
|
+
|
|
148
|
+
while ((match = regex.exec(pattern)) !== null) {
|
|
149
|
+
if (match.index > lastIndex) {
|
|
150
|
+
segments.push({ type: 'literal', value: pattern.slice(lastIndex, match.index) });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (match[1] !== undefined) {
|
|
154
|
+
segments.push({ type: 'field', label: match[1] });
|
|
155
|
+
} else {
|
|
156
|
+
segments.push({
|
|
157
|
+
type: 'keyword',
|
|
158
|
+
keyword: match[2].toLowerCase(),
|
|
159
|
+
case: detectCasing(match[2]),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
lastIndex = match.index + match[0].length;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (lastIndex < pattern.length) {
|
|
167
|
+
segments.push({ type: 'literal', value: pattern.slice(lastIndex) });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return segments;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const validateCustomBranchFormatPattern = (pattern) => {
|
|
174
|
+
if (!pattern || !pattern.trim()) {
|
|
175
|
+
return 'Pattern cannot be empty';
|
|
176
|
+
}
|
|
177
|
+
if (!/\btype\b/i.test(pattern)) {
|
|
178
|
+
return 'Pattern must contain the "type" keyword';
|
|
179
|
+
}
|
|
180
|
+
if (!/\bdescription\b/i.test(pattern)) {
|
|
181
|
+
return 'Pattern must contain the "description" keyword';
|
|
182
|
+
}
|
|
183
|
+
// Check balanced braces
|
|
184
|
+
let depth = 0;
|
|
185
|
+
for (const ch of pattern) {
|
|
186
|
+
if (ch === '{') { depth++; }
|
|
187
|
+
if (ch === '}') { depth--; }
|
|
188
|
+
if (depth < 0) { return 'Unbalanced braces in pattern'; }
|
|
189
|
+
}
|
|
190
|
+
if (depth !== 0) { return 'Unbalanced braces in pattern'; }
|
|
191
|
+
// Validate literal parts (separators) are branch-safe
|
|
192
|
+
const segments = parseCustomBranchFormat(pattern);
|
|
193
|
+
const invalidBranchChars = /[~^:?*[\\\s]/;
|
|
194
|
+
for (const seg of segments) {
|
|
195
|
+
if (seg.type !== 'literal') { continue; }
|
|
196
|
+
if (invalidBranchChars.test(seg.value)) {
|
|
197
|
+
return 'Pattern contains characters invalid in branch names (spaces, ~, ^, :, ?, *, [, \\)';
|
|
198
|
+
}
|
|
199
|
+
if (seg.value.includes('..')) {
|
|
200
|
+
return 'Pattern cannot contain ".." (invalid in branch names)';
|
|
201
|
+
}
|
|
202
|
+
if (seg.value.includes('//')) {
|
|
203
|
+
return 'Pattern cannot contain "//" (invalid in branch names)';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const getCustomBranchFields = (pattern) => getCustomFields(pattern);
|
|
210
|
+
|
|
211
|
+
const sanitizeBranchPart = (value) => {
|
|
212
|
+
return value
|
|
213
|
+
.replace(/\s+/g, '-')
|
|
214
|
+
.replace(/[^a-zA-Z0-9-]/g, '')
|
|
215
|
+
.replace(/-+/g, '-')
|
|
216
|
+
.replace(/^-|-$/g, '');
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const formatCustomBranchName = (type, description, segments, scope, customFieldValues = {}) => {
|
|
220
|
+
return segments.map(seg => {
|
|
221
|
+
if (seg.type === 'literal') { return seg.value; }
|
|
222
|
+
if (seg.type === 'field') { return sanitizeBranchPart(customFieldValues[seg.label] || ''); }
|
|
223
|
+
if (seg.type === 'keyword') {
|
|
224
|
+
switch (seg.keyword) {
|
|
225
|
+
case 'type': return applyCasing(type, seg.case);
|
|
226
|
+
case 'scope': return applyCasing(scope || '', seg.case);
|
|
227
|
+
case 'description': return applyCasing(sanitizeBranchPart(description), seg.case);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return '';
|
|
231
|
+
}).join('');
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const formatCustomCommitTitle = (type, description, segments, scope, customFieldValues = {}) => {
|
|
235
|
+
return segments.map(seg => {
|
|
236
|
+
if (seg.type === 'literal') { return seg.value; }
|
|
237
|
+
if (seg.type === 'field') { return customFieldValues[seg.label] || ''; }
|
|
238
|
+
if (seg.type === 'keyword') {
|
|
239
|
+
switch (seg.keyword) {
|
|
240
|
+
case 'type': return applyCasing(type, seg.case);
|
|
241
|
+
case 'scope': return applyCasing(scope || '', seg.case);
|
|
242
|
+
case 'description': return applyCasing(description, seg.case);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return '';
|
|
246
|
+
}).join('');
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const formatCommitTitle = (type, title, format, scope = '*', customFormat, customFieldValues) => {
|
|
57
250
|
// Handle empty title
|
|
58
251
|
if (!title || title.trim().length === 0) {
|
|
59
252
|
return '';
|
|
@@ -61,6 +254,11 @@ const formatCommitTitle = (type, title, format, scope = '*') => {
|
|
|
61
254
|
|
|
62
255
|
const trimmedTitle = title.trim();
|
|
63
256
|
|
|
257
|
+
if (format === 'custom' && customFormat) {
|
|
258
|
+
const segments = parseCustomFormat(customFormat);
|
|
259
|
+
return formatCustomCommitTitle(type, trimmedTitle, segments, scope, customFieldValues);
|
|
260
|
+
}
|
|
261
|
+
|
|
64
262
|
switch (format) {
|
|
65
263
|
case 1:
|
|
66
264
|
default:
|
|
@@ -104,29 +302,43 @@ const log = (message, type) => {
|
|
|
104
302
|
case 'warning':
|
|
105
303
|
msg = yellow(msg);
|
|
106
304
|
break;
|
|
305
|
+
case 'debug':
|
|
306
|
+
msg = magenta(msg);
|
|
307
|
+
break;
|
|
107
308
|
}
|
|
108
309
|
console.log(`${date} ${type === 'error' ? red(msg) : (type === 'success' ? green(msg) : msg)}`);
|
|
109
310
|
};
|
|
110
311
|
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
if (description.length > maxLength) {
|
|
116
|
-
return `Branch description too long (max ${maxLength} characters)`;
|
|
312
|
+
const validBranchCustomField = (value, label) => {
|
|
313
|
+
if (!value || value.trim().length < 1) {
|
|
314
|
+
return `${label} cannot be empty`;
|
|
117
315
|
}
|
|
118
316
|
const invalidChars = /[~^:?*[\\\s]/;
|
|
119
|
-
if (invalidChars.test(
|
|
120
|
-
return
|
|
317
|
+
if (invalidChars.test(value)) {
|
|
318
|
+
return `${label} contains invalid characters (spaces, ~, ^, :, ?, *, [, \\)`;
|
|
121
319
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
return
|
|
320
|
+
if (value.startsWith('.') || value.startsWith('-') ||
|
|
321
|
+
value.endsWith('.') || value.endsWith('-')) {
|
|
322
|
+
return `${label} cannot start or end with . or -`;
|
|
125
323
|
}
|
|
126
324
|
return true;
|
|
127
325
|
};
|
|
128
326
|
|
|
129
|
-
const
|
|
327
|
+
const validBranchDescription = (description, maxLength) => {
|
|
328
|
+
const base = validBranchCustomField(description, 'Branch description');
|
|
329
|
+
if (base !== true) { return base; }
|
|
330
|
+
if (description.length > maxLength) {
|
|
331
|
+
return `Branch description too long (max ${maxLength} characters)`;
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const formatBranchName = (type, description, format, scope = null, customBranchFormat = null, customFieldValues = {}) => {
|
|
337
|
+
if (format === 'custom' && customBranchFormat) {
|
|
338
|
+
const segments = parseCustomBranchFormat(customBranchFormat);
|
|
339
|
+
return formatCustomBranchName(type, description, segments, scope, customFieldValues);
|
|
340
|
+
}
|
|
341
|
+
|
|
130
342
|
const cleanDescription = description
|
|
131
343
|
.toLowerCase()
|
|
132
344
|
.replace(/\s+/g, '-')
|
|
@@ -159,6 +371,167 @@ const checkBranchExists = (branchName) => {
|
|
|
159
371
|
}
|
|
160
372
|
};
|
|
161
373
|
|
|
374
|
+
/** Parse and validate commit title format, auto-correct case */
|
|
375
|
+
const parseAndNormalizeCommitTitle = (title, config, customFieldValues = {}) => {
|
|
376
|
+
// Custom format parsing
|
|
377
|
+
if (config.format === 'custom' && config.customFormat) {
|
|
378
|
+
const segments = parseCustomFormat(config.customFormat);
|
|
379
|
+
|
|
380
|
+
// Build dynamic regex from segments
|
|
381
|
+
const captureNames = [];
|
|
382
|
+
let regexParts = [];
|
|
383
|
+
const captureSegments = segments.filter(s => s.type === 'keyword' || s.type === 'field');
|
|
384
|
+
|
|
385
|
+
for (let i = 0; i < segments.length; i++) {
|
|
386
|
+
const seg = segments[i];
|
|
387
|
+
if (seg.type === 'literal') {
|
|
388
|
+
regexParts.push(seg.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
389
|
+
} else if (seg.type === 'keyword' || seg.type === 'field') {
|
|
390
|
+
const isLast = captureSegments.indexOf(seg) === captureSegments.length - 1;
|
|
391
|
+
captureNames.push(seg);
|
|
392
|
+
regexParts.push(isLast ? '(.+)' : '(.+?)');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const dynamicRegex = new RegExp('^' + regexParts.join('') + '$');
|
|
397
|
+
const match = title.match(dynamicRegex);
|
|
398
|
+
|
|
399
|
+
if (!match) {
|
|
400
|
+
const exampleTitle = formatCommitTitle(
|
|
401
|
+
config.types[0].value, 'description', config.format,
|
|
402
|
+
config.scopes?.[0]?.value, config.customFormat, customFieldValues
|
|
403
|
+
);
|
|
404
|
+
return { error: `Wrong format. Expected: "${exampleTitle}"` };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let type, scope, message;
|
|
408
|
+
const fieldValues = {};
|
|
409
|
+
|
|
410
|
+
for (let i = 0; i < captureNames.length; i++) {
|
|
411
|
+
const seg = captureNames[i];
|
|
412
|
+
const val = match[i + 1].trim();
|
|
413
|
+
if (seg.type === 'keyword') {
|
|
414
|
+
switch (seg.keyword) {
|
|
415
|
+
case 'type': type = val; break;
|
|
416
|
+
case 'scope': scope = val; break;
|
|
417
|
+
case 'description': message = val; break;
|
|
418
|
+
}
|
|
419
|
+
} else if (seg.type === 'field') {
|
|
420
|
+
fieldValues[seg.label] = val;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Validate type
|
|
425
|
+
if (!type) {
|
|
426
|
+
return { error: 'Could not detect type in commit title' };
|
|
427
|
+
}
|
|
428
|
+
const validType = config.types.find(t => t.value.toLowerCase() === type.toLowerCase());
|
|
429
|
+
if (!validType) {
|
|
430
|
+
const validTypes = config.types.map(t => t.value).join(', ');
|
|
431
|
+
return { error: `Invalid type "${type}". Valid types: ${validTypes}` };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Validate scope if present
|
|
435
|
+
let validScope = scope;
|
|
436
|
+
if (scope) {
|
|
437
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
438
|
+
return { error: 'Scope not allowed in current format configuration' };
|
|
439
|
+
}
|
|
440
|
+
const foundScope = config.scopes.find(s => s.value.toLowerCase() === scope.toLowerCase());
|
|
441
|
+
if (!foundScope) {
|
|
442
|
+
const validScopes = config.scopes.map(s => s.value).join(', ');
|
|
443
|
+
return { error: `Invalid scope "${scope}". Valid scopes: ${validScopes}` };
|
|
444
|
+
}
|
|
445
|
+
validScope = foundScope.value;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const normalized = formatCustomCommitTitle(
|
|
449
|
+
validType.value, message, segments, validScope, fieldValues
|
|
450
|
+
);
|
|
451
|
+
return { normalized };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let type, scope, message, detectedFormatGroup;
|
|
455
|
+
|
|
456
|
+
// Try different format patterns
|
|
457
|
+
const format7_8 = /^([^(]+)\(([^)]+)\):\s*(.+)$/; // type(scope): message
|
|
458
|
+
const format5_6 = /^([^(]+)\(([^)]+)\)\s+(.+)$/; // type(scope) message
|
|
459
|
+
const format3_4 = /^([^:]+):\s*(.+)$/; // type: message
|
|
460
|
+
const format1_2 = /^\(([^)]+)\)\s+(.+)$/; // (type) message
|
|
461
|
+
|
|
462
|
+
let match;
|
|
463
|
+
|
|
464
|
+
if ((match = title.match(format7_8))) {
|
|
465
|
+
[, type, scope, message] = match;
|
|
466
|
+
detectedFormatGroup = 'type(scope):';
|
|
467
|
+
} else if ((match = title.match(format5_6))) {
|
|
468
|
+
[, type, scope, message] = match;
|
|
469
|
+
detectedFormatGroup = 'type(scope)';
|
|
470
|
+
} else if ((match = title.match(format3_4))) {
|
|
471
|
+
[, type, message] = match;
|
|
472
|
+
detectedFormatGroup = 'type:';
|
|
473
|
+
} else if ((match = title.match(format1_2))) {
|
|
474
|
+
[, type, message] = match;
|
|
475
|
+
detectedFormatGroup = '(type)';
|
|
476
|
+
} else {
|
|
477
|
+
return { error: 'Invalid commit format. Expected format with type prefix.' };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Verify format matches config
|
|
481
|
+
let expectedFormatGroup;
|
|
482
|
+
if (config.format >= 7) {
|
|
483
|
+
expectedFormatGroup = 'type(scope):';
|
|
484
|
+
} else if (config.format >= 5) {
|
|
485
|
+
expectedFormatGroup = 'type(scope)';
|
|
486
|
+
} else if (config.format >= 3) {
|
|
487
|
+
expectedFormatGroup = 'type:';
|
|
488
|
+
} else {
|
|
489
|
+
expectedFormatGroup = '(type)';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (detectedFormatGroup !== expectedFormatGroup) {
|
|
493
|
+
const exampleTitle = formatCommitTitle(
|
|
494
|
+
config.types[0].value,
|
|
495
|
+
'description',
|
|
496
|
+
config.format,
|
|
497
|
+
config.scopes?.[0]?.value
|
|
498
|
+
);
|
|
499
|
+
return { error: `Wrong format. Expected: "${exampleTitle}"` };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
type = type.trim();
|
|
503
|
+
message = message.trim();
|
|
504
|
+
if (scope) {
|
|
505
|
+
scope = scope.trim();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Validate type exists (case-insensitive)
|
|
509
|
+
const validType = config.types.find(t => t.value.toLowerCase() === type.toLowerCase());
|
|
510
|
+
if (!validType) {
|
|
511
|
+
const validTypes = config.types.map(t => t.value).join(', ');
|
|
512
|
+
return { error: `Invalid type "${type}". Valid types: ${validTypes}` };
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Validate scope if present (case-insensitive)
|
|
516
|
+
let validScope = scope;
|
|
517
|
+
if (scope) {
|
|
518
|
+
if (!config.scopes || config.scopes.length === 0) {
|
|
519
|
+
return { error: 'Scope not allowed in current format configuration' };
|
|
520
|
+
}
|
|
521
|
+
const foundScope = config.scopes.find(s => s.value.toLowerCase() === scope.toLowerCase());
|
|
522
|
+
if (!foundScope) {
|
|
523
|
+
const validScopes = config.scopes.map(s => s.value).join(', ');
|
|
524
|
+
return { error: `Invalid scope "${scope}". Valid scopes: ${validScopes}` };
|
|
525
|
+
}
|
|
526
|
+
validScope = foundScope.value;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Re-format with correct case
|
|
530
|
+
const normalized = formatCommitTitle(validType.value, message, config.format, validScope);
|
|
531
|
+
|
|
532
|
+
return { normalized };
|
|
533
|
+
};
|
|
534
|
+
|
|
162
535
|
|
|
163
536
|
export {
|
|
164
537
|
askForVersion,
|
|
@@ -167,10 +540,21 @@ export {
|
|
|
167
540
|
validCommitTitle,
|
|
168
541
|
validCommitTitleSetupLength,
|
|
169
542
|
validBranchDescription,
|
|
543
|
+
validBranchCustomField,
|
|
170
544
|
validVersion,
|
|
171
545
|
formatCommitTitle,
|
|
172
546
|
formatBranchName,
|
|
173
547
|
checkBranchExists,
|
|
548
|
+
parseAndNormalizeCommitTitle,
|
|
549
|
+
parseCustomFormat,
|
|
550
|
+
customFormatHasScope,
|
|
551
|
+
getCustomFields,
|
|
552
|
+
validateCustomFormatPattern,
|
|
553
|
+
parseCustomBranchFormat,
|
|
554
|
+
validateCustomBranchFormatPattern,
|
|
555
|
+
formatCustomBranchName,
|
|
556
|
+
customBranchFormatHasScope,
|
|
557
|
+
getCustomBranchFields,
|
|
174
558
|
handleCmdExec,
|
|
175
559
|
log,
|
|
176
560
|
};
|