@logickernel/agileflow 0.15.1 → 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.
- package/package.json +1 -1
- package/src/git-push.js +4 -3
- package/src/github-push.js +1 -1
- package/src/gitlab-push.js +1 -1
- package/src/index.js +5 -4
- package/src/utils.js +71 -23
package/package.json
CHANGED
package/src/git-push.js
CHANGED
|
@@ -14,8 +14,9 @@ const os = require('os');
|
|
|
14
14
|
* @param {boolean} quiet - If true, suppress success message
|
|
15
15
|
* @returns {Promise<void>}
|
|
16
16
|
*/
|
|
17
|
-
async function pushTag(tagName, message, quiet = false) {
|
|
17
|
+
async function pushTag(tagName, message, quiet = false, remote = 'origin') {
|
|
18
18
|
const safeTag = String(tagName).replace(/"/g, '\\"');
|
|
19
|
+
const safeRemote = String(remote).replace(/"/g, '\\"');
|
|
19
20
|
|
|
20
21
|
// Write message to a temp file to avoid shell escaping issues with special characters
|
|
21
22
|
const tempFile = path.join(os.tmpdir(), `agileflow-tag-${crypto.randomBytes(8).toString('hex')}.txt`);
|
|
@@ -25,8 +26,8 @@ async function pushTag(tagName, message, quiet = false) {
|
|
|
25
26
|
// Create annotated tag using -F to read message from file
|
|
26
27
|
execSync(`git tag -a "${safeTag}" -F "${tempFile}"`, { stdio: 'pipe' });
|
|
27
28
|
|
|
28
|
-
// Push to
|
|
29
|
-
execSync(`git push
|
|
29
|
+
// Push to remote
|
|
30
|
+
execSync(`git push "${safeRemote}" "${safeTag}"`, { stdio: 'pipe' });
|
|
30
31
|
|
|
31
32
|
if (!quiet) {
|
|
32
33
|
console.log(`Tag ${tagName} created and pushed successfully.`);
|
package/src/github-push.js
CHANGED
|
@@ -130,7 +130,7 @@ function makeRequest({ method, path, accessToken, body }) {
|
|
|
130
130
|
* @param {boolean} quiet - If true, suppress success message
|
|
131
131
|
* @returns {Promise<void>}
|
|
132
132
|
*/
|
|
133
|
-
async function pushTag(tagName, message, quiet = false) {
|
|
133
|
+
async function pushTag(tagName, message, quiet = false, remote = 'origin') {
|
|
134
134
|
const accessToken = process.env.AGILEFLOW_TOKEN;
|
|
135
135
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
136
136
|
const commitSha = process.env.GITHUB_SHA;
|
package/src/gitlab-push.js
CHANGED
|
@@ -96,7 +96,7 @@ function createTagViaAPI(tagName, message, projectPath, serverHost, accessToken,
|
|
|
96
96
|
* @param {string} message - The tag message
|
|
97
97
|
* @returns {Promise<void>}
|
|
98
98
|
*/
|
|
99
|
-
async function pushTag(tagName, message, quiet = false) {
|
|
99
|
+
async function pushTag(tagName, message, quiet = false, remote = 'origin') {
|
|
100
100
|
const accessToken = process.env.AGILEFLOW_TOKEN;
|
|
101
101
|
const serverHost = process.env.CI_SERVER_HOST;
|
|
102
102
|
const projectPath = process.env.CI_PROJECT_PATH;
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@ Usage:
|
|
|
12
12
|
|
|
13
13
|
Commands:
|
|
14
14
|
<none> Prints the current version, next version, commits, and changelog
|
|
15
|
-
push
|
|
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
18
|
|
|
@@ -93,7 +93,7 @@ function displayVersionInfo(info, quiet) {
|
|
|
93
93
|
* @param {string} pushType - 'push', 'gitlab', or 'github'
|
|
94
94
|
* @param {{quiet: boolean}} options
|
|
95
95
|
*/
|
|
96
|
-
async function handlePushCommand(pushType, options) {
|
|
96
|
+
async function handlePushCommand(pushType, options, remote = 'origin') {
|
|
97
97
|
const info = await processVersionInfo();
|
|
98
98
|
|
|
99
99
|
// Display version info
|
|
@@ -125,7 +125,7 @@ async function handlePushCommand(pushType, options) {
|
|
|
125
125
|
console.log(`\nCreating tag ${info.newVersion}...`);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
await pushModule.pushTag(info.newVersion, tagMessage, options.quiet);
|
|
128
|
+
await pushModule.pushTag(info.newVersion, tagMessage, options.quiet, remote);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
async function main() {
|
|
@@ -156,7 +156,8 @@ async function main() {
|
|
|
156
156
|
|
|
157
157
|
// Handle push commands
|
|
158
158
|
if (cmd === 'push' || cmd === 'gitlab' || cmd === 'github') {
|
|
159
|
-
|
|
159
|
+
const remote = rest.find(arg => !arg.startsWith('-')) || 'origin';
|
|
160
|
+
await handlePushCommand(cmd, options, remote);
|
|
160
161
|
return;
|
|
161
162
|
}
|
|
162
163
|
|
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;
|
|
@@ -112,16 +114,35 @@ function fetchTags() {
|
|
|
112
114
|
*/
|
|
113
115
|
function buildTagMap() {
|
|
114
116
|
try {
|
|
115
|
-
|
|
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 (!
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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,
|
|
224
|
+
latestVersion: bestVersion,
|
|
225
|
+
commits: commits.slice(0, bestIndex),
|
|
183
226
|
};
|
|
184
227
|
}
|
|
185
228
|
|
|
@@ -413,7 +456,12 @@ function getAllBranchCommits(branch) {
|
|
|
413
456
|
try {
|
|
414
457
|
resolvedSha = runWithOutput(`git rev-parse --verify -- origin/${branch}`).trim();
|
|
415
458
|
} catch {
|
|
416
|
-
|
|
459
|
+
// Last resort: use HEAD (detached HEAD in CI where remote tracking isn't set up)
|
|
460
|
+
try {
|
|
461
|
+
resolvedSha = runWithOutput('git rev-parse HEAD').trim();
|
|
462
|
+
} catch {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
417
465
|
}
|
|
418
466
|
}
|
|
419
467
|
|