@logickernel/agileflow 0.2.0 → 0.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logickernel/agileflow",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Automatic semantic versioning and changelog generation based on conventional commits",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -34,6 +34,6 @@
34
34
  "type": "git",
35
35
  "url": "git@code.logickernel.com:kernel/agileflow.git"
36
36
  },
37
- "author": "",
37
+ "author": "Víctor H. Valle <victor.valle@logickernel.com>",
38
38
  "license": "ISC"
39
39
  }
package/src/git-push.js CHANGED
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { run } = require('./utils');
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
4
7
 
5
8
  /**
6
9
  * Creates an annotated tag and pushes it to the remote repository.
@@ -11,16 +14,27 @@ const { run } = require('./utils');
11
14
  */
12
15
  async function pushTag(tagName, message) {
13
16
  const safeTag = String(tagName).replace(/"/g, '\\"');
14
- const safeMsg = String(message).replace(/"/g, '\\"');
15
17
 
16
- // Create annotated tag
17
- run(`git tag -a "${safeTag}" -m "${safeMsg}"`);
18
-
19
- // Push to origin
20
- run(`git push origin "${safeTag}"`);
18
+ // Write message to a temp file to avoid shell escaping issues with special characters
19
+ const tempFile = path.join(os.tmpdir(), `agileflow-tag-${Date.now()}.txt`);
20
+ try {
21
+ fs.writeFileSync(tempFile, message, 'utf8');
22
+
23
+ // Create annotated tag using -F to read message from file
24
+ execSync(`git tag -a "${safeTag}" -F "${tempFile}"`, { stdio: 'pipe' });
25
+
26
+ // Push to origin
27
+ execSync(`git push origin "${safeTag}"`, { stdio: 'pipe' });
28
+ } finally {
29
+ // Clean up temp file
30
+ try {
31
+ fs.unlinkSync(tempFile);
32
+ } catch {
33
+ // Ignore cleanup errors
34
+ }
35
+ }
21
36
  }
22
37
 
23
38
  module.exports = {
24
39
  pushTag,
25
40
  };
26
-
package/src/index.js CHANGED
@@ -38,24 +38,32 @@ function parseArgs(args) {
38
38
 
39
39
  /**
40
40
  * Displays version info to the console.
41
- * @param {{currentVersion: string|null, nextVersion: string|null, commits: Array, changelog: string}} info
42
- * @param {boolean} quiet - Only output the next version
41
+ * @param {{currentVersion: string|null, newVersion: string|null, commits: Array, changelog: string}} info
42
+ * @param {boolean} quiet - Only output the new version
43
43
  */
44
44
  function displayVersionInfo(info, quiet) {
45
- const { currentVersion, nextVersion, commits, changelog } = info;
45
+ const { currentVersion, newVersion, commits, changelog } = info;
46
46
 
47
47
  if (quiet) {
48
- if (nextVersion) {
49
- console.log(nextVersion);
48
+ if (newVersion) {
49
+ console.log(newVersion);
50
50
  }
51
51
  return;
52
52
  }
53
53
 
54
- console.log(`Current version: ${currentVersion || 'none'}`);
55
- console.log(`Next version: ${nextVersion || 'no bump needed'}`);
56
- console.log(`Commits since current version: ${commits.length}`);
54
+
55
+ // List commits
56
+ console.log(`Commits since current version (${commits.length}):`);
57
+ for (const commit of commits) {
58
+ const subject = commit.message.split('\n')[0].trim();
59
+ const shortHash = commit.hash.substring(0, 7);
60
+ console.log(` ${shortHash} ${subject}`);
61
+ }
62
+
63
+ console.log(`\nCurrent version: ${currentVersion || 'none'}`);
64
+ console.log(`New version: ${newVersion || 'no bump needed'}`);
57
65
  if (changelog) {
58
- console.log(`\nChangelog:\n${changelog}`);
66
+ console.log(`\nChangelog:\n\n${changelog}`);
59
67
  }
60
68
  }
61
69
 
@@ -71,10 +79,7 @@ async function handlePushCommand(pushType, options) {
71
79
  displayVersionInfo(info, options.quiet);
72
80
 
73
81
  // Skip push if no version bump needed
74
- if (!info.nextVersion) {
75
- if (!options.quiet) {
76
- console.log('\nNo version bump needed. Skipping tag creation.');
77
- }
82
+ if (!info.newVersion) {
78
83
  return;
79
84
  }
80
85
 
@@ -93,16 +98,16 @@ async function handlePushCommand(pushType, options) {
93
98
  }
94
99
 
95
100
  // Create tag message from changelog
96
- const tagMessage = info.changelog || info.nextVersion;
101
+ const tagMessage = info.changelog || info.newVersion;
97
102
 
98
103
  if (!options.quiet) {
99
- console.log(`\nCreating tag ${info.nextVersion}...`);
104
+ console.log(`\nCreating tag ${info.newVersion}...`);
100
105
  }
101
106
 
102
- await pushModule.pushTag(info.nextVersion, tagMessage);
107
+ await pushModule.pushTag(info.newVersion, tagMessage);
103
108
 
104
109
  if (!options.quiet) {
105
- console.log(`Tag ${info.nextVersion} created and pushed successfully.`);
110
+ console.log(`Tag ${info.newVersion} created and pushed successfully.`);
106
111
  }
107
112
  }
108
113
 
package/src/utils.js CHANGED
@@ -67,6 +67,21 @@ const TYPE_ORDER = ['feat', 'fix', 'perf', 'refactor', 'style', 'test', 'docs',
67
67
  const PATCH_TYPES = ['fix', 'perf', 'refactor', 'test', 'build', 'ci', 'revert'];
68
68
  const SEMVER_PATTERN = /^v(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.-]+)?$/;
69
69
 
70
+ // Friendly header names for changelog
71
+ const TYPE_HEADERS = {
72
+ feat: 'Features:',
73
+ fix: 'Fixes:',
74
+ perf: 'Performance:',
75
+ refactor: 'Refactors:',
76
+ style: 'Style:',
77
+ test: 'Tests:',
78
+ docs: 'Documentation:',
79
+ build: 'Build:',
80
+ ci: 'CI:',
81
+ chore: 'Chores:',
82
+ revert: 'Reverts:',
83
+ };
84
+
70
85
  /**
71
86
  * Fetches tags from remote (non-destructive) if a remote is configured.
72
87
  * @returns {boolean} True if tags were fetched, false if using local tags only
@@ -242,6 +257,16 @@ function analyzeCommitsForVersioning(commits) {
242
257
  return { hasBreaking, hasFeat, hasPatchTypes, commitsByType };
243
258
  }
244
259
 
260
+ /**
261
+ * Capitalizes the first letter of a string.
262
+ * @param {string} str - The string to capitalize
263
+ * @returns {string} Capitalized string
264
+ */
265
+ function capitalize(str) {
266
+ if (!str) return str;
267
+ return str.charAt(0).toUpperCase() + str.slice(1);
268
+ }
269
+
245
270
  /**
246
271
  * Generates changelog entries for a commit type section.
247
272
  * @param {Array} commits - Commits of this type
@@ -271,20 +296,22 @@ function generateTypeChangelog(commits) {
271
296
 
272
297
  const lines = [];
273
298
  for (const entry of noScope) {
274
- lines.push(`- ${entry.description}${entry.issueRef}`);
299
+ const ref = entry.issueRef ? ` ${entry.issueRef}` : '';
300
+ lines.push(`- ${capitalize(entry.description)}${ref}`);
275
301
  }
276
302
  for (const scope of Object.keys(byScope).sort()) {
277
303
  for (const entry of byScope[scope]) {
278
- lines.push(`- **${scope}**: ${entry.description}${entry.issueRef}`);
304
+ const ref = entry.issueRef ? ` ${entry.issueRef}` : '';
305
+ lines.push(`- ${scope}: ${capitalize(entry.description)}${ref}`);
279
306
  }
280
307
  }
281
308
  return lines;
282
309
  }
283
310
 
284
311
  /**
285
- * Calculates the next version and generates a changelog.
312
+ * Calculates the new version and generates a changelog.
286
313
  * @param {{latestVersion: string|null, commits: Array}} expandedInfo
287
- * @returns {{nextVersion: string|null, changelog: string}}
314
+ * @returns {{newVersion: string|null, changelog: string}}
288
315
  */
289
316
  function calculateNextVersionAndChangelog(expandedInfo) {
290
317
  const { latestVersion, commits } = expandedInfo;
@@ -292,7 +319,7 @@ function calculateNextVersionAndChangelog(expandedInfo) {
292
319
  const analysis = analyzeCommitsForVersioning(commits);
293
320
 
294
321
  const bump = determineVersionBumpType(analysis, current.major === 0);
295
- const nextVersion = applyVersionBump(current, bump);
322
+ const newVersion = applyVersionBump(current, bump);
296
323
 
297
324
  // Generate changelog
298
325
  const changelogLines = [];
@@ -300,7 +327,7 @@ function calculateNextVersionAndChangelog(expandedInfo) {
300
327
  const typeCommits = analysis.commitsByType[type];
301
328
  if (!typeCommits?.length) continue;
302
329
 
303
- changelogLines.push(`### ${type}`);
330
+ changelogLines.push(TYPE_HEADERS[type] || `${capitalize(type)}:`);
304
331
  changelogLines.push(...generateTypeChangelog(typeCommits));
305
332
  changelogLines.push('');
306
333
  }
@@ -309,7 +336,7 @@ function calculateNextVersionAndChangelog(expandedInfo) {
309
336
  changelogLines.pop();
310
337
  }
311
338
 
312
- return { nextVersion, changelog: changelogLines.join('\n') };
339
+ return { newVersion, changelog: changelogLines.join('\n') };
313
340
  }
314
341
 
315
342
  /**
@@ -355,7 +382,7 @@ function getAllBranchCommits(branch) {
355
382
 
356
383
  /**
357
384
  * Processes version information for the current branch.
358
- * @returns {Promise<{currentVersion: string|null, nextVersion: string|null, commits: Array, changelog: string}>}
385
+ * @returns {Promise<{currentVersion: string|null, newVersion: string|null, commits: Array, changelog: string}>}
359
386
  */
360
387
  async function processVersionInfo() {
361
388
  ensureGitRepo();
@@ -365,11 +392,11 @@ async function processVersionInfo() {
365
392
  const allCommits = getAllBranchCommits(branch);
366
393
  const expandedInfo = expandCommitInfo(allCommits);
367
394
  const { latestVersion, commits } = expandedInfo;
368
- const { nextVersion, changelog } = calculateNextVersionAndChangelog(expandedInfo);
395
+ const { newVersion, changelog } = calculateNextVersionAndChangelog(expandedInfo);
369
396
 
370
397
  return {
371
398
  currentVersion: latestVersion,
372
- nextVersion,
399
+ newVersion,
373
400
  commits,
374
401
  changelog,
375
402
  };