@logickernel/agileflow 0.17.1 → 0.20.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/utils.js +109 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logickernel/agileflow",
3
- "version": "0.17.1",
3
+ "version": "0.20.0",
4
4
  "description": "Automatic semantic versioning and changelog generation based on conventional commits",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/utils.js CHANGED
@@ -224,9 +224,57 @@ function parseConventionalCommit(message) {
224
224
  }
225
225
 
226
226
  /**
227
- * Expands commit information by finding the latest version and filtering commits.
228
- * @param {Array} commits - Array of commit objects (newest to oldest)
229
- * @returns {{latestVersion: string|null, commits: Array}} Filtered commits since last version
227
+ * Finds the highest semver version tag in a list of commits.
228
+ * @param {Array} commits - The commits to scan
229
+ * @returns {{latestVersion: string|null, taggedCommitHash: string|null}}
230
+ */
231
+ function findLatestVersionTag(commits) {
232
+ if (!commits?.length) {
233
+ return { latestVersion: null, taggedCommitHash: null };
234
+ }
235
+
236
+ let bestHash = null;
237
+ let bestVersion = null;
238
+
239
+ for (let i = 0; i < commits.length; i++) {
240
+ const semverTags = commits[i].tags?.filter(tag => SEMVER_PATTERN.test(tag));
241
+ if (!semverTags?.length) continue;
242
+
243
+ const highest = semverTags.sort((a, b) => {
244
+ const pa = parseVersion(a);
245
+ const pb = parseVersion(b);
246
+ if (pb.major !== pa.major) return pb.major - pa.major;
247
+ if (pb.minor !== pa.minor) return pb.minor - pa.minor;
248
+ return pb.patch - pa.patch;
249
+ })[0];
250
+
251
+ if (!bestVersion) {
252
+ bestVersion = highest;
253
+ bestHash = commits[i].hash;
254
+ } else {
255
+ const pBest = parseVersion(bestVersion);
256
+ const pCand = parseVersion(highest);
257
+ const isHigher =
258
+ pCand.major > pBest.major ||
259
+ (pCand.major === pBest.major && pCand.minor > pBest.minor) ||
260
+ (pCand.major === pBest.major && pCand.minor === pBest.minor && pCand.patch > pBest.patch);
261
+ if (isHigher) {
262
+ bestVersion = highest;
263
+ bestHash = commits[i].hash;
264
+ }
265
+ }
266
+ }
267
+
268
+ return { latestVersion: bestVersion, taggedCommitHash: bestHash };
269
+ }
270
+
271
+ /**
272
+ * Expands commit info by finding the latest version tag and returning
273
+ * only commits since that tag. Uses positional slicing from a date-sorted
274
+ * commit list — for accurate results with merge commits, prefer using
275
+ * findLatestVersionTag + getCommitsSinceTag (as processVersionInfo does).
276
+ * @param {Array} commits - All commits (date-sorted, newest first)
277
+ * @returns {{latestVersion: string|null, commits: Array}}
230
278
  */
231
279
  function expandCommitInfo(commits) {
232
280
  if (!commits?.length) {
@@ -548,6 +596,45 @@ function getAllBranchCommits(branch) {
548
596
  }
549
597
  }
550
598
 
599
+ /**
600
+ * Retrieves commits reachable from the branch but not from the given tag,
601
+ * using git's native range selection so that date ordering cannot cause
602
+ * commits from merged branches to be missed.
603
+ * @param {string} tagHash - The commit hash of the version tag
604
+ * @param {string} branchSha - The resolved SHA of the branch tip
605
+ * @returns {Array<{hash: string, datetime: string, author: string, message: string, tags: Array<string>}>}
606
+ */
607
+ function getCommitsSinceTag(tagHash, branchSha) {
608
+ const tagMap = buildTagMap();
609
+ const RS = '\x1E';
610
+ const COMMIT_SEP = `${RS}${RS}`;
611
+
612
+ try {
613
+ const logCmd = `git log --format=%H${RS}%ai${RS}%an${RS}%B${COMMIT_SEP} ${tagHash}..${branchSha}`;
614
+ const output = runWithOutput(logCmd).trim();
615
+ if (!output) return [];
616
+
617
+ return output
618
+ .split(COMMIT_SEP)
619
+ .filter(block => block.trim())
620
+ .map(block => {
621
+ const parts = block.split(RS);
622
+ if (parts.length < 4) return null;
623
+ const hash = parts[0].trim();
624
+ return {
625
+ hash,
626
+ datetime: parts[1].trim(),
627
+ author: parts[2].trim(),
628
+ message: parts.slice(3).join(RS).trim(),
629
+ tags: tagMap.get(hash) || [],
630
+ };
631
+ })
632
+ .filter(Boolean);
633
+ } catch {
634
+ return [];
635
+ }
636
+ }
637
+
551
638
  /**
552
639
  * Processes version information for the current branch.
553
640
  * @returns {Promise<{currentVersion: string|null, newVersion: string|null, commits: Array, changelog: string}>}
@@ -556,12 +643,25 @@ async function processVersionInfo() {
556
643
  ensureGitRepo();
557
644
  const branch = getCurrentBranch();
558
645
  fetchTags();
559
-
646
+
560
647
  const allCommits = getAllBranchCommits(branch);
561
- const expandedInfo = expandCommitInfo(allCommits);
562
- const { latestVersion, commits } = expandedInfo;
648
+ const { latestVersion, taggedCommitHash } = findLatestVersionTag(allCommits);
649
+
650
+ let commits;
651
+ if (taggedCommitHash) {
652
+ // Use git's range selection (tag..HEAD) to correctly identify all commits
653
+ // since the tag, regardless of commit date ordering. This fixes an issue
654
+ // where commits from merged feature branches could be missed because their
655
+ // dates are older than the tagged commit.
656
+ const branchSha = allCommits[0]?.hash;
657
+ commits = branchSha ? getCommitsSinceTag(taggedCommitHash, branchSha) : [];
658
+ } else {
659
+ commits = allCommits;
660
+ }
661
+
662
+ const expandedInfo = { latestVersion, commits };
563
663
  const { newVersion, changelog } = calculateNextVersionAndChangelog(expandedInfo);
564
-
664
+
565
665
  return {
566
666
  currentVersion: latestVersion,
567
667
  newVersion,
@@ -577,6 +677,8 @@ module.exports = {
577
677
  getCurrentBranch,
578
678
  fetchTags,
579
679
  getAllBranchCommits,
680
+ findLatestVersionTag,
681
+ getCommitsSinceTag,
580
682
  expandCommitInfo,
581
683
  calculateNextVersionAndChangelog,
582
684
  processVersionInfo,