@logickernel/agileflow 0.2.2 → 0.4.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/package.json +1 -1
- package/src/index.js +47 -7
- package/src/utils.js +43 -13
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -12,9 +12,9 @@ Usage:
|
|
|
12
12
|
|
|
13
13
|
Commands:
|
|
14
14
|
<none> Prints the current version, next version, commits, and changelog
|
|
15
|
-
push Push a semantic version tag to the remote repository
|
|
16
|
-
gitlab
|
|
17
|
-
github
|
|
15
|
+
push Push a semantic version tag to the remote repository
|
|
16
|
+
gitlab Create a semantic version tag via GitLab API (for GitLab CI)
|
|
17
|
+
github Create a semantic version tag via GitHub API (for GitHub Actions)
|
|
18
18
|
|
|
19
19
|
Options:
|
|
20
20
|
--quiet Only output the next version (or empty if no bump)
|
|
@@ -26,11 +26,32 @@ For more information, visit: https://code.logickernel.com/tools/agileflow
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Valid options that can be passed to commands.
|
|
30
|
+
*/
|
|
31
|
+
const VALID_OPTIONS = ['--quiet', '--help', '-h', '--version', '-v'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Valid commands.
|
|
35
|
+
*/
|
|
36
|
+
const VALID_COMMANDS = ['push', 'gitlab', 'github'];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parses command line arguments and validates them.
|
|
30
40
|
* @param {Array<string>} args - Command line arguments
|
|
31
41
|
* @returns {{quiet: boolean}}
|
|
42
|
+
* @throws {Error} If invalid options are found
|
|
32
43
|
*/
|
|
33
44
|
function parseArgs(args) {
|
|
45
|
+
// Check for invalid options
|
|
46
|
+
for (const arg of args) {
|
|
47
|
+
if (arg.startsWith('--') && !VALID_OPTIONS.includes(arg)) {
|
|
48
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
49
|
+
}
|
|
50
|
+
if (arg.startsWith('-') && !arg.startsWith('--') && !VALID_OPTIONS.includes(arg)) {
|
|
51
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
return {
|
|
35
56
|
quiet: args.includes('--quiet'),
|
|
36
57
|
};
|
|
@@ -61,7 +82,7 @@ function displayVersionInfo(info, quiet) {
|
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
console.log(`\nCurrent version: ${currentVersion || 'none'}`);
|
|
64
|
-
console.log(`New version:
|
|
85
|
+
console.log(`New version: ${newVersion || 'no bump needed'}`);
|
|
65
86
|
if (changelog) {
|
|
66
87
|
console.log(`\nChangelog:\n\n${changelog}`);
|
|
67
88
|
}
|
|
@@ -113,7 +134,17 @@ async function handlePushCommand(pushType, options) {
|
|
|
113
134
|
|
|
114
135
|
async function main() {
|
|
115
136
|
const [, , cmd, ...rest] = process.argv;
|
|
116
|
-
|
|
137
|
+
|
|
138
|
+
let options;
|
|
139
|
+
try {
|
|
140
|
+
options = parseArgs(cmd ? [cmd, ...rest] : rest);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error(`Error: ${err.message}`);
|
|
143
|
+
console.error();
|
|
144
|
+
printHelp();
|
|
145
|
+
process.exit(1);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
117
148
|
|
|
118
149
|
// Handle help
|
|
119
150
|
if (cmd === '-h' || cmd === '--help' || cmd === 'help') {
|
|
@@ -134,13 +165,22 @@ async function main() {
|
|
|
134
165
|
}
|
|
135
166
|
|
|
136
167
|
// Unknown command (not an option)
|
|
137
|
-
if (cmd && !cmd.startsWith('--')) {
|
|
168
|
+
if (cmd && !cmd.startsWith('--') && !cmd.startsWith('-')) {
|
|
138
169
|
console.error(`Error: Unknown command "${cmd}"`);
|
|
139
170
|
console.error();
|
|
140
171
|
printHelp();
|
|
141
172
|
process.exit(1);
|
|
142
173
|
}
|
|
143
174
|
|
|
175
|
+
// Invalid option (starts with -- but not valid)
|
|
176
|
+
if (cmd && cmd.startsWith('--') && !VALID_OPTIONS.includes(cmd)) {
|
|
177
|
+
console.error(`Error: Unknown option "${cmd}"`);
|
|
178
|
+
console.error();
|
|
179
|
+
printHelp();
|
|
180
|
+
process.exit(1);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
144
184
|
// Default: show version info
|
|
145
185
|
const info = await processVersionInfo();
|
|
146
186
|
displayVersionInfo(info, options.quiet);
|
package/src/utils.js
CHANGED
|
@@ -170,13 +170,16 @@ function extractIssueReference(message) {
|
|
|
170
170
|
* @param {string} subject - First line of commit message
|
|
171
171
|
* @param {Object} parsed - Parsed conventional commit info
|
|
172
172
|
* @param {string} fullMessage - Full commit message
|
|
173
|
+
* @param {boolean} isBreakingSection - Whether this is for the breaking changes section
|
|
173
174
|
* @returns {string} Formatted description
|
|
174
175
|
*/
|
|
175
|
-
function formatChangelogDescription(subject, parsed, fullMessage) {
|
|
176
|
+
function formatChangelogDescription(subject, parsed, fullMessage, isBreakingSection = false) {
|
|
176
177
|
if (!parsed) return subject;
|
|
177
178
|
let description = parsed.description;
|
|
178
179
|
const isBreaking = parsed.breaking || /BREAKING CHANGE:/i.test(fullMessage);
|
|
179
|
-
|
|
180
|
+
|
|
181
|
+
// Only add BREAKING prefix if not in breaking changes section
|
|
182
|
+
if (isBreaking && !isBreakingSection) {
|
|
180
183
|
description = `BREAKING: ${description}`;
|
|
181
184
|
}
|
|
182
185
|
return description;
|
|
@@ -229,13 +232,25 @@ function applyVersionBump(current, bump) {
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Checks if a commit is a breaking change.
|
|
237
|
+
* @param {Object} commit - Commit object
|
|
238
|
+
* @param {Object} parsed - Parsed conventional commit info
|
|
239
|
+
* @returns {boolean}
|
|
240
|
+
*/
|
|
241
|
+
function isBreakingChange(commit, parsed) {
|
|
242
|
+
if (!parsed) return false;
|
|
243
|
+
return parsed.breaking || /BREAKING CHANGE:/i.test(commit.message);
|
|
244
|
+
}
|
|
245
|
+
|
|
232
246
|
/**
|
|
233
247
|
* Analyzes commits to determine version bump requirements.
|
|
234
248
|
* @param {Array} commits - Array of commit objects
|
|
235
|
-
* @returns {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean, commitsByType: Object}}
|
|
249
|
+
* @returns {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean, commitsByType: Object, breakingCommits: Array}}
|
|
236
250
|
*/
|
|
237
251
|
function analyzeCommitsForVersioning(commits) {
|
|
238
252
|
const commitsByType = Object.fromEntries(TYPE_ORDER.map(t => [t, []]));
|
|
253
|
+
const breakingCommits = [];
|
|
239
254
|
let hasBreaking = false, hasFeat = false, hasPatchTypes = false;
|
|
240
255
|
|
|
241
256
|
for (const commit of commits) {
|
|
@@ -243,18 +258,23 @@ function analyzeCommitsForVersioning(commits) {
|
|
|
243
258
|
if (!parsed) continue;
|
|
244
259
|
|
|
245
260
|
const { type, breaking } = parsed;
|
|
246
|
-
const isBreaking =
|
|
261
|
+
const isBreaking = isBreakingChange(commit, parsed);
|
|
247
262
|
|
|
248
|
-
if (isBreaking)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
263
|
+
if (isBreaking) {
|
|
264
|
+
hasBreaking = true;
|
|
265
|
+
breakingCommits.push(commit);
|
|
266
|
+
} else {
|
|
267
|
+
// Only add to type sections if not breaking
|
|
268
|
+
if (type === 'feat') hasFeat = true;
|
|
269
|
+
else if (PATCH_TYPES.includes(type)) hasPatchTypes = true;
|
|
270
|
+
|
|
271
|
+
if (commitsByType[type]) {
|
|
272
|
+
commitsByType[type].push(commit);
|
|
273
|
+
}
|
|
254
274
|
}
|
|
255
275
|
}
|
|
256
276
|
|
|
257
|
-
return { hasBreaking, hasFeat, hasPatchTypes, commitsByType };
|
|
277
|
+
return { hasBreaking, hasFeat, hasPatchTypes, commitsByType, breakingCommits };
|
|
258
278
|
}
|
|
259
279
|
|
|
260
280
|
/**
|
|
@@ -270,9 +290,10 @@ function capitalize(str) {
|
|
|
270
290
|
/**
|
|
271
291
|
* Generates changelog entries for a commit type section.
|
|
272
292
|
* @param {Array} commits - Commits of this type
|
|
293
|
+
* @param {boolean} isBreakingSection - Whether this is for the breaking changes section
|
|
273
294
|
* @returns {Array<string>} Changelog lines
|
|
274
295
|
*/
|
|
275
|
-
function generateTypeChangelog(commits) {
|
|
296
|
+
function generateTypeChangelog(commits, isBreakingSection = false) {
|
|
276
297
|
const byScope = {};
|
|
277
298
|
const noScope = [];
|
|
278
299
|
|
|
@@ -283,7 +304,7 @@ function generateTypeChangelog(commits) {
|
|
|
283
304
|
const subject = commit.message.split('\n')[0].trim();
|
|
284
305
|
const entry = {
|
|
285
306
|
scope: parsed.scope,
|
|
286
|
-
description: formatChangelogDescription(subject, parsed, commit.message),
|
|
307
|
+
description: formatChangelogDescription(subject, parsed, commit.message, isBreakingSection),
|
|
287
308
|
issueRef: extractIssueReference(commit.message) || '',
|
|
288
309
|
};
|
|
289
310
|
|
|
@@ -323,6 +344,15 @@ function calculateNextVersionAndChangelog(expandedInfo) {
|
|
|
323
344
|
|
|
324
345
|
// Generate changelog
|
|
325
346
|
const changelogLines = [];
|
|
347
|
+
|
|
348
|
+
// Add breaking changes section first if any
|
|
349
|
+
if (analysis.breakingCommits.length > 0) {
|
|
350
|
+
changelogLines.push('BREAKING CHANGES:');
|
|
351
|
+
changelogLines.push(...generateTypeChangelog(analysis.breakingCommits, true));
|
|
352
|
+
changelogLines.push('');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Add regular type sections
|
|
326
356
|
for (const type of TYPE_ORDER) {
|
|
327
357
|
const typeCommits = analysis.commitsByType[type];
|
|
328
358
|
if (!typeCommits?.length) continue;
|