@logickernel/agileflow 0.16.0 → 0.16.1

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 +65 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logickernel/agileflow",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
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
@@ -98,7 +98,9 @@ function fetchTags() {
98
98
  try {
99
99
  const remotes = runWithOutput('git remote').trim();
100
100
  if (!remotes) return false;
101
- runWithOutput('git fetch --tags --prune --prune-tags');
101
+ // --force allows updating local tags that conflict with remote tags.
102
+ // Avoid --prune-tags which can fail in shallow CI clones.
103
+ runWithOutput('git fetch --tags --force');
102
104
  return true;
103
105
  } catch {
104
106
  return false;
@@ -112,16 +114,35 @@ function fetchTags() {
112
114
  */
113
115
  function buildTagMap() {
114
116
  try {
115
- const output = runWithOutput('git tag --format=%(refname:short)|%(*objectname)|%(objectname)').trim();
117
+ // %(objecttype) distinguishes lightweight (commit) from annotated (tag) refs.
118
+ // %(*objectname) is the peeled commit SHA for annotated tags, but may be empty
119
+ // in shallow clones where git has not computed the peeled ref.
120
+ const output = runWithOutput('git tag --format=%(refname:short)|%(objecttype)|%(*objectname)|%(objectname)').trim();
116
121
  if (!output) return new Map();
117
122
  const map = new Map();
118
123
  for (const line of output.split('\n')) {
119
- const [name, deref, obj] = line.split('|');
120
- // Annotated tags dereference to the commit via %(*objectname);
121
- // lightweight tags point directly via %(objectname).
122
- const sha = (deref || obj || '').trim();
124
+ const [name, type, deref, obj] = line.split('|');
123
125
  const tagName = (name || '').trim();
124
- if (!sha || !tagName) continue;
126
+ if (!tagName) continue;
127
+
128
+ let sha;
129
+ if (type === 'commit') {
130
+ // Lightweight tag: %(objectname) is the commit SHA directly.
131
+ sha = (obj || '').trim();
132
+ } else {
133
+ // Annotated tag: use peeled commit SHA if available.
134
+ sha = (deref || '').trim();
135
+ if (!sha) {
136
+ // Peeling unavailable (shallow clone) — dereference via rev-parse.
137
+ try {
138
+ sha = runWithOutput(`git rev-parse "${tagName}^{}"`).trim();
139
+ } catch {
140
+ continue;
141
+ }
142
+ }
143
+ }
144
+
145
+ if (!sha) continue;
125
146
  if (!map.has(sha)) map.set(sha, []);
126
147
  map.get(sha).push(tagName);
127
148
  }
@@ -158,28 +179,50 @@ function expandCommitInfo(commits) {
158
179
  if (!commits?.length) {
159
180
  return { latestVersion: null, commits: [] };
160
181
  }
161
-
162
- const taggedIndex = commits.findIndex(commit =>
163
- commit.tags?.some(tag => SEMVER_PATTERN.test(tag))
164
- );
165
-
166
- if (taggedIndex === -1) {
167
- return { latestVersion: null, commits };
168
- }
169
-
170
- const latestVersion = commits[taggedIndex].tags
171
- .filter(tag => SEMVER_PATTERN.test(tag))
172
- .sort((a, b) => {
182
+
183
+ // Find the commit tagged with the highest semver version in the history.
184
+ // Using highest-version (not first-found) handles the case where a lower version
185
+ // was accidentally tagged on a recent commit (e.g. a failed CI run).
186
+ let bestIndex = -1;
187
+ let bestVersion = null;
188
+
189
+ for (let i = 0; i < commits.length; i++) {
190
+ const semverTags = commits[i].tags?.filter(tag => SEMVER_PATTERN.test(tag));
191
+ if (!semverTags?.length) continue;
192
+
193
+ const highest = semverTags.sort((a, b) => {
173
194
  const pa = parseVersion(a);
174
195
  const pb = parseVersion(b);
175
196
  if (pb.major !== pa.major) return pb.major - pa.major;
176
197
  if (pb.minor !== pa.minor) return pb.minor - pa.minor;
177
198
  return pb.patch - pa.patch;
178
199
  })[0];
179
- // Exclude the tagged commit itself - only return commits since the tag
200
+
201
+ if (!bestVersion) {
202
+ bestVersion = highest;
203
+ bestIndex = i;
204
+ } else {
205
+ const pBest = parseVersion(bestVersion);
206
+ const pCand = parseVersion(highest);
207
+ const isHigher =
208
+ pCand.major > pBest.major ||
209
+ (pCand.major === pBest.major && pCand.minor > pBest.minor) ||
210
+ (pCand.major === pBest.major && pCand.minor === pBest.minor && pCand.patch > pBest.patch);
211
+ if (isHigher) {
212
+ bestVersion = highest;
213
+ bestIndex = i;
214
+ }
215
+ }
216
+ }
217
+
218
+ if (bestIndex === -1) {
219
+ return { latestVersion: null, commits };
220
+ }
221
+
222
+ // Return only commits newer than the highest-version tag
180
223
  return {
181
- latestVersion,
182
- commits: commits.slice(0, taggedIndex),
224
+ latestVersion: bestVersion,
225
+ commits: commits.slice(0, bestIndex),
183
226
  };
184
227
  }
185
228