@logickernel/agileflow 0.16.0 → 0.17.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 +2 -1
- package/src/utils.js +121 -25
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@ Commands:
|
|
|
15
15
|
push [remote] Push a semantic version tag to the remote repository (default: origin)
|
|
16
16
|
gitlab Create a semantic version tag via GitLab API (for GitLab CI)
|
|
17
17
|
github Create a semantic version tag via GitHub API (for GitHub Actions)
|
|
18
|
+
version Print the agileflow tool version
|
|
18
19
|
|
|
19
20
|
Options:
|
|
20
21
|
--quiet Only output the next version (or empty if no bump)
|
|
@@ -33,7 +34,7 @@ const VALID_OPTIONS = ['--quiet', '--help', '-h', '--version', '-v'];
|
|
|
33
34
|
/**
|
|
34
35
|
* Valid commands.
|
|
35
36
|
*/
|
|
36
|
-
const VALID_COMMANDS = ['push', 'gitlab', 'github'];
|
|
37
|
+
const VALID_COMMANDS = ['push', 'gitlab', 'github', 'version'];
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Parses command line arguments and validates them.
|
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
|
-
|
|
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;
|
|
@@ -106,22 +108,40 @@ function fetchTags() {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
/**
|
|
109
|
-
* Builds a map
|
|
110
|
-
* Uses a single git call instead of one per commit.
|
|
111
|
+
* Builds a tag map from locally stored tags.
|
|
111
112
|
* @returns {Map<string, string[]>}
|
|
112
113
|
*/
|
|
113
|
-
function
|
|
114
|
+
function buildTagMapFromLocal() {
|
|
114
115
|
try {
|
|
115
|
-
|
|
116
|
+
// %(objecttype) distinguishes lightweight (commit) from annotated (tag) refs.
|
|
117
|
+
// %(*objectname) is the peeled commit SHA for annotated tags, but may be empty
|
|
118
|
+
// in shallow clones where git has not computed the peeled ref.
|
|
119
|
+
const output = runWithOutput('git tag --format=%(refname:short)|%(objecttype)|%(*objectname)|%(objectname)').trim();
|
|
116
120
|
if (!output) return new Map();
|
|
117
121
|
const map = new Map();
|
|
118
122
|
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();
|
|
123
|
+
const [name, type, deref, obj] = line.split('|');
|
|
123
124
|
const tagName = (name || '').trim();
|
|
124
|
-
if (!
|
|
125
|
+
if (!tagName) continue;
|
|
126
|
+
|
|
127
|
+
let sha;
|
|
128
|
+
if (type === 'commit') {
|
|
129
|
+
// Lightweight tag: %(objectname) is the commit SHA directly.
|
|
130
|
+
sha = (obj || '').trim();
|
|
131
|
+
} else {
|
|
132
|
+
// Annotated tag: use peeled commit SHA if available.
|
|
133
|
+
sha = (deref || '').trim();
|
|
134
|
+
if (!sha) {
|
|
135
|
+
// Peeling unavailable (shallow clone) — dereference via rev-parse.
|
|
136
|
+
try {
|
|
137
|
+
sha = runWithOutput(`git rev-parse "${tagName}^{}"`).trim();
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!sha) continue;
|
|
125
145
|
if (!map.has(sha)) map.set(sha, []);
|
|
126
146
|
map.get(sha).push(tagName);
|
|
127
147
|
}
|
|
@@ -131,6 +151,60 @@ function buildTagMap() {
|
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Builds a tag map by reading tags directly from the remote via ls-remote.
|
|
156
|
+
* Used as a fallback when git fetch --tags fails (e.g. in shallow CI clones).
|
|
157
|
+
* Does not store anything locally.
|
|
158
|
+
* @returns {Map<string, string[]>}
|
|
159
|
+
*/
|
|
160
|
+
function buildTagMapFromRemote() {
|
|
161
|
+
try {
|
|
162
|
+
const remotes = runWithOutput('git remote').trim();
|
|
163
|
+
if (!remotes) return new Map();
|
|
164
|
+
const remote = remotes.split('\n')[0].trim();
|
|
165
|
+
const output = runWithOutput(`git ls-remote --tags ${remote}`).trim();
|
|
166
|
+
if (!output) return new Map();
|
|
167
|
+
|
|
168
|
+
const map = new Map();
|
|
169
|
+
const direct = new Map(); // tagName -> SHA (tag obj or commit)
|
|
170
|
+
const peeled = new Map(); // tagName -> commit SHA (from ^{} entries)
|
|
171
|
+
|
|
172
|
+
for (const line of output.split('\n')) {
|
|
173
|
+
const tabIdx = line.indexOf('\t');
|
|
174
|
+
if (tabIdx === -1) continue;
|
|
175
|
+
const sha = line.slice(0, tabIdx).trim();
|
|
176
|
+
const ref = line.slice(tabIdx + 1).trim();
|
|
177
|
+
if (ref.endsWith('^{}')) {
|
|
178
|
+
// Annotated tag: peeled entry gives the commit SHA
|
|
179
|
+
peeled.set(ref.slice('refs/tags/'.length, -3), sha);
|
|
180
|
+
} else if (ref.startsWith('refs/tags/')) {
|
|
181
|
+
direct.set(ref.slice('refs/tags/'.length), sha);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const [name, sha] of direct) {
|
|
186
|
+
const commitSha = peeled.get(name) || sha;
|
|
187
|
+
if (!map.has(commitSha)) map.set(commitSha, []);
|
|
188
|
+
map.get(commitSha).push(name);
|
|
189
|
+
}
|
|
190
|
+
return map;
|
|
191
|
+
} catch {
|
|
192
|
+
return new Map();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Builds a map of commit SHA → tag names for all tags in the repository.
|
|
198
|
+
* Tries local tags first; falls back to reading from the remote when local
|
|
199
|
+
* tags are absent (common in shallow CI clones where git fetch --tags fails).
|
|
200
|
+
* @returns {Map<string, string[]>}
|
|
201
|
+
*/
|
|
202
|
+
function buildTagMap() {
|
|
203
|
+
const local = buildTagMapFromLocal();
|
|
204
|
+
if (local.size > 0) return local;
|
|
205
|
+
return buildTagMapFromRemote();
|
|
206
|
+
}
|
|
207
|
+
|
|
134
208
|
/**
|
|
135
209
|
* Parses a conventional commit message.
|
|
136
210
|
* @param {string} message - The commit message to parse
|
|
@@ -158,28 +232,50 @@ function expandCommitInfo(commits) {
|
|
|
158
232
|
if (!commits?.length) {
|
|
159
233
|
return { latestVersion: null, commits: [] };
|
|
160
234
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.sort((a, b) => {
|
|
235
|
+
|
|
236
|
+
// Find the commit tagged with the highest semver version in the history.
|
|
237
|
+
// Using highest-version (not first-found) handles the case where a lower version
|
|
238
|
+
// was accidentally tagged on a recent commit (e.g. a failed CI run).
|
|
239
|
+
let bestIndex = -1;
|
|
240
|
+
let bestVersion = null;
|
|
241
|
+
|
|
242
|
+
for (let i = 0; i < commits.length; i++) {
|
|
243
|
+
const semverTags = commits[i].tags?.filter(tag => SEMVER_PATTERN.test(tag));
|
|
244
|
+
if (!semverTags?.length) continue;
|
|
245
|
+
|
|
246
|
+
const highest = semverTags.sort((a, b) => {
|
|
173
247
|
const pa = parseVersion(a);
|
|
174
248
|
const pb = parseVersion(b);
|
|
175
249
|
if (pb.major !== pa.major) return pb.major - pa.major;
|
|
176
250
|
if (pb.minor !== pa.minor) return pb.minor - pa.minor;
|
|
177
251
|
return pb.patch - pa.patch;
|
|
178
252
|
})[0];
|
|
179
|
-
|
|
253
|
+
|
|
254
|
+
if (!bestVersion) {
|
|
255
|
+
bestVersion = highest;
|
|
256
|
+
bestIndex = i;
|
|
257
|
+
} else {
|
|
258
|
+
const pBest = parseVersion(bestVersion);
|
|
259
|
+
const pCand = parseVersion(highest);
|
|
260
|
+
const isHigher =
|
|
261
|
+
pCand.major > pBest.major ||
|
|
262
|
+
(pCand.major === pBest.major && pCand.minor > pBest.minor) ||
|
|
263
|
+
(pCand.major === pBest.major && pCand.minor === pBest.minor && pCand.patch > pBest.patch);
|
|
264
|
+
if (isHigher) {
|
|
265
|
+
bestVersion = highest;
|
|
266
|
+
bestIndex = i;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (bestIndex === -1) {
|
|
272
|
+
return { latestVersion: null, commits };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Return only commits newer than the highest-version tag
|
|
180
276
|
return {
|
|
181
|
-
latestVersion,
|
|
182
|
-
commits: commits.slice(0,
|
|
277
|
+
latestVersion: bestVersion,
|
|
278
|
+
commits: commits.slice(0, bestIndex),
|
|
183
279
|
};
|
|
184
280
|
}
|
|
185
281
|
|