@logickernel/agileflow 0.1.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/README.md +289 -0
- package/bin/agileflow +4 -0
- package/docs/README.md +46 -0
- package/docs/best-practices.md +339 -0
- package/docs/branching-strategy.md +448 -0
- package/docs/cli-reference.md +110 -0
- package/docs/configuration.md +232 -0
- package/docs/conventional-commits/type-build.md +23 -0
- package/docs/conventional-commits/type-chore.md +24 -0
- package/docs/conventional-commits/type-ci.md +23 -0
- package/docs/conventional-commits/type-docs.md +23 -0
- package/docs/conventional-commits/type-feat.md +28 -0
- package/docs/conventional-commits/type-fix.md +27 -0
- package/docs/conventional-commits/type-perf.md +24 -0
- package/docs/conventional-commits/type-refactor.md +24 -0
- package/docs/conventional-commits/type-revert.md +16 -0
- package/docs/conventional-commits/type-style.md +24 -0
- package/docs/conventional-commits/type-test.md +24 -0
- package/docs/conventional-commits.md +234 -0
- package/docs/getting-started.md +209 -0
- package/docs/gitlab-ci-template.md +324 -0
- package/docs/installation.md +163 -0
- package/docs/migration-guide.md +390 -0
- package/docs/release-management.md +498 -0
- package/docs/troubleshooting.md +341 -0
- package/docs/version-centric-cicd.md +166 -0
- package/package.json +39 -0
- package/src/git-utils.js +57 -0
- package/src/index.js +88 -0
- package/src/local.js +17 -0
- package/src/utils.js +376 -0
- package/templates/agileflow-tagged.gitlab-ci.yml +66 -0
- package/templates/agileflow.gitlab-ci.yml +64 -0
package/src/utils.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { runWithOutput, ensureGitRepo } = require('./git-utils');
|
|
4
|
+
|
|
5
|
+
// Conventional commit type configuration
|
|
6
|
+
const TYPE_ORDER = ['feat', 'fix', 'perf', 'refactor', 'style', 'test', 'docs', 'build', 'ci', 'chore', 'revert'];
|
|
7
|
+
const PATCH_TYPES = ['fix', 'perf', 'refactor', 'test', 'build', 'ci', 'revert'];
|
|
8
|
+
const SEMVER_PATTERN = /^v(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9.-]+)?$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fetches tags from remote (non-destructive) if a remote is configured.
|
|
12
|
+
* This is a safe operation that doesn't modify the working directory.
|
|
13
|
+
* @returns {boolean} True if tags were fetched, false if using local tags only
|
|
14
|
+
*/
|
|
15
|
+
function fetchTagsLocally() {
|
|
16
|
+
try {
|
|
17
|
+
const remotes = runWithOutput('git remote').trim();
|
|
18
|
+
if (!remotes) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
runWithOutput('git fetch --tags --prune --prune-tags');
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validates that the current branch is the expected name.
|
|
30
|
+
* @param {string} expectedName - The expected name of the branch
|
|
31
|
+
* @throws {Error} If the current branch is not the expected name or in detached HEAD state
|
|
32
|
+
*/
|
|
33
|
+
function validateBranchName(expectedName) {
|
|
34
|
+
const branch = runWithOutput('git branch --show-current').trim();
|
|
35
|
+
if (!branch) {
|
|
36
|
+
throw new Error('Repository is in a detached HEAD state. Please check out a branch and try again.');
|
|
37
|
+
}
|
|
38
|
+
if (branch !== expectedName) {
|
|
39
|
+
throw new Error(`Current branch is "${branch}", not "${expectedName}". Switch to ${expectedName} or use --branch ${branch}.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets all tags pointing to a specific commit.
|
|
45
|
+
* @param {string} commitSha - The commit SHA to check for tags
|
|
46
|
+
* @returns {Array<string>} Array of tag names, empty array if none
|
|
47
|
+
*/
|
|
48
|
+
function getTagsForCommit(commitSha) {
|
|
49
|
+
try {
|
|
50
|
+
const output = runWithOutput(`git tag --points-at ${commitSha}`).trim();
|
|
51
|
+
return output ? output.split('\n').map(t => t.trim()).filter(Boolean) : [];
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parses a conventional commit message.
|
|
59
|
+
* @param {string} message - The commit message to parse
|
|
60
|
+
* @returns {{type: string, breaking: boolean, scope: string, description: string}|null} Parsed commit or null if not conventional
|
|
61
|
+
*/
|
|
62
|
+
function parseConventionalCommit(message) {
|
|
63
|
+
if (!message) return null;
|
|
64
|
+
|
|
65
|
+
// Get the first line (subject) of the commit message
|
|
66
|
+
const subject = message.split('\n')[0].trim();
|
|
67
|
+
|
|
68
|
+
// Conventional commit pattern: type[!]?(scope)?: description
|
|
69
|
+
const match = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s+(.+)$/);
|
|
70
|
+
if (!match) return null;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
type: match[1].toLowerCase(),
|
|
74
|
+
breaking: Boolean(match[2]),
|
|
75
|
+
scope: match[3] ? String(match[3]).trim() : '',
|
|
76
|
+
description: String(match[4]).trim(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Expands commit information by finding the latest version and filtering commits.
|
|
82
|
+
* Filters out commits that are older than the latest commit with a semver tag.
|
|
83
|
+
* Commits are expected to be ordered from newest to oldest.
|
|
84
|
+
* @param {Array<{hash: string, datetime: string, author: string, message: string, tags: Array<string>}>} commits - Array of commit objects
|
|
85
|
+
* @returns {{latestVersion: string|null, commits: Array}} Object with latestVersion and filtered commits array
|
|
86
|
+
*/
|
|
87
|
+
function expandCommitInfo(commits) {
|
|
88
|
+
if (!commits?.length) {
|
|
89
|
+
return { latestVersion: null, commits: [] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Find the first commit (newest) that has a semver tag
|
|
93
|
+
const taggedIndex = commits.findIndex(commit =>
|
|
94
|
+
commit.tags?.some(tag => SEMVER_PATTERN.test(tag))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (taggedIndex === -1) {
|
|
98
|
+
return { latestVersion: null, commits };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const latestVersion = commits[taggedIndex].tags.find(tag => SEMVER_PATTERN.test(tag));
|
|
102
|
+
return {
|
|
103
|
+
latestVersion,
|
|
104
|
+
commits: commits.slice(0, taggedIndex + 1),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extracts issue reference from commit message (e.g., #123 or (#123)).
|
|
110
|
+
* @param {string} message - The commit message
|
|
111
|
+
* @returns {string|null} Issue reference like "(#123)" or null if not found
|
|
112
|
+
*/
|
|
113
|
+
function extractIssueReference(message) {
|
|
114
|
+
const match = message?.match(/\(?#(\d+)\)?/);
|
|
115
|
+
return match ? `(#${match[1]})` : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Formats the first line of commit message for changelog, removing the type prefix.
|
|
120
|
+
* @param {string} subject - The first line of the commit message
|
|
121
|
+
* @param {Object} parsed - Parsed conventional commit info
|
|
122
|
+
* @param {string} fullMessage - Full commit message to check for BREAKING CHANGE:
|
|
123
|
+
* @returns {string} Formatted description
|
|
124
|
+
*/
|
|
125
|
+
function formatChangelogDescription(subject, parsed, fullMessage) {
|
|
126
|
+
if (!parsed) {
|
|
127
|
+
// Not a conventional commit, use subject as-is
|
|
128
|
+
return subject;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let description = parsed.description;
|
|
132
|
+
|
|
133
|
+
// Add BREAKING prefix if it's a breaking change (check both ! and BREAKING CHANGE:)
|
|
134
|
+
const isBreaking = parsed.breaking || /BREAKING CHANGE:/i.test(fullMessage);
|
|
135
|
+
if (isBreaking) {
|
|
136
|
+
description = `BREAKING: ${description}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return description;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parses a semver version string into its components.
|
|
144
|
+
* @param {string|null} version - Version string like "v1.2.3" or "v1.2.3-beta"
|
|
145
|
+
* @returns {{major: number, minor: number, patch: number}} Parsed version or {0,0,0} if invalid
|
|
146
|
+
*/
|
|
147
|
+
function parseVersion(version) {
|
|
148
|
+
if (!version) return { major: 0, minor: 0, patch: 0 };
|
|
149
|
+
const match = version.match(SEMVER_PATTERN);
|
|
150
|
+
if (!match) return { major: 0, minor: 0, patch: 0 };
|
|
151
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Determines the version bump type based on commit analysis.
|
|
156
|
+
* @param {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean}} analysis - Commit analysis
|
|
157
|
+
* @param {boolean} isPreOneZero - Whether current version is 0.x.x
|
|
158
|
+
* @returns {'major'|'minor'|'patch'|'none'} The version bump type
|
|
159
|
+
*/
|
|
160
|
+
function determineVersionBumpType(analysis, isPreOneZero) {
|
|
161
|
+
const { hasBreaking, hasFeat, hasPatchTypes } = analysis;
|
|
162
|
+
|
|
163
|
+
if (isPreOneZero) {
|
|
164
|
+
if (hasBreaking || hasFeat) return 'minor';
|
|
165
|
+
if (hasPatchTypes) return 'patch';
|
|
166
|
+
} else {
|
|
167
|
+
if (hasBreaking) return 'major';
|
|
168
|
+
if (hasFeat) return 'minor';
|
|
169
|
+
if (hasPatchTypes) return 'patch';
|
|
170
|
+
}
|
|
171
|
+
return 'none';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Applies a version bump to the current version.
|
|
176
|
+
* @param {{major: number, minor: number, patch: number}} current - Current version
|
|
177
|
+
* @param {'major'|'minor'|'patch'|'none'} bump - Bump type
|
|
178
|
+
* @returns {string} Next version string like "v1.2.3"
|
|
179
|
+
*/
|
|
180
|
+
function applyVersionBump(current, bump) {
|
|
181
|
+
let { major, minor, patch } = current;
|
|
182
|
+
switch (bump) {
|
|
183
|
+
case 'major': return `v${major + 1}.0.0`;
|
|
184
|
+
case 'minor': return `v${major}.${minor + 1}.0`;
|
|
185
|
+
case 'patch': return `v${major}.${minor}.${patch + 1}`;
|
|
186
|
+
default: return `v${major}.${minor}.${patch}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Analyzes commits to determine version bump requirements.
|
|
192
|
+
* @param {Array} commits - Array of commit objects
|
|
193
|
+
* @returns {{hasBreaking: boolean, hasFeat: boolean, hasPatchTypes: boolean, commitsByType: Object}}
|
|
194
|
+
*/
|
|
195
|
+
function analyzeCommitsForVersioning(commits) {
|
|
196
|
+
const commitsByType = Object.fromEntries(TYPE_ORDER.map(t => [t, []]));
|
|
197
|
+
let hasBreaking = false, hasFeat = false, hasPatchTypes = false;
|
|
198
|
+
|
|
199
|
+
for (const commit of commits) {
|
|
200
|
+
const parsed = parseConventionalCommit(commit.message);
|
|
201
|
+
if (!parsed) continue;
|
|
202
|
+
|
|
203
|
+
const { type, breaking } = parsed;
|
|
204
|
+
const isBreaking = breaking || /BREAKING CHANGE:/i.test(commit.message);
|
|
205
|
+
|
|
206
|
+
if (isBreaking) hasBreaking = true;
|
|
207
|
+
if (type === 'feat') hasFeat = true;
|
|
208
|
+
else if (PATCH_TYPES.includes(type)) hasPatchTypes = true;
|
|
209
|
+
|
|
210
|
+
if (commitsByType[type]) {
|
|
211
|
+
commitsByType[type].push(commit);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { hasBreaking, hasFeat, hasPatchTypes, commitsByType };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Generates changelog entries for a commit type section.
|
|
220
|
+
* @param {Array} commits - Commits of this type
|
|
221
|
+
* @returns {Array<string>} Changelog lines
|
|
222
|
+
*/
|
|
223
|
+
function generateTypeChangelog(commits) {
|
|
224
|
+
const byScope = {};
|
|
225
|
+
const noScope = [];
|
|
226
|
+
|
|
227
|
+
for (const commit of commits) {
|
|
228
|
+
const parsed = parseConventionalCommit(commit.message);
|
|
229
|
+
if (!parsed) continue;
|
|
230
|
+
|
|
231
|
+
const subject = commit.message.split('\n')[0].trim();
|
|
232
|
+
const entry = {
|
|
233
|
+
scope: parsed.scope,
|
|
234
|
+
description: formatChangelogDescription(subject, parsed, commit.message),
|
|
235
|
+
issueRef: extractIssueReference(commit.message) || '',
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
if (parsed.scope) {
|
|
239
|
+
(byScope[parsed.scope] ??= []).push(entry);
|
|
240
|
+
} else {
|
|
241
|
+
noScope.push(entry);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const lines = [];
|
|
246
|
+
for (const entry of noScope) {
|
|
247
|
+
lines.push(`- ${entry.description}${entry.issueRef}`);
|
|
248
|
+
}
|
|
249
|
+
for (const scope of Object.keys(byScope).sort()) {
|
|
250
|
+
for (const entry of byScope[scope]) {
|
|
251
|
+
lines.push(`- **${scope}**: ${entry.description}${entry.issueRef}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Calculates the next version and generates a changelog from expanded commit info.
|
|
259
|
+
* @param {{latestVersion: string|null, commits: Array}} expandedInfo - Output from expandCommitInfo
|
|
260
|
+
* @returns {{nextVersion: string, changelog: string}} Object with nextVersion and changelog
|
|
261
|
+
*/
|
|
262
|
+
function calculateNextVersionAndChangelog(expandedInfo) {
|
|
263
|
+
const { latestVersion, commits } = expandedInfo;
|
|
264
|
+
const current = parseVersion(latestVersion);
|
|
265
|
+
const analysis = analyzeCommitsForVersioning(commits);
|
|
266
|
+
|
|
267
|
+
const bump = determineVersionBumpType(analysis, current.major === 0);
|
|
268
|
+
const nextVersion = applyVersionBump(current, bump);
|
|
269
|
+
|
|
270
|
+
// Generate changelog
|
|
271
|
+
const changelogLines = [];
|
|
272
|
+
for (const type of TYPE_ORDER) {
|
|
273
|
+
const typeCommits = analysis.commitsByType[type];
|
|
274
|
+
if (!typeCommits?.length) continue;
|
|
275
|
+
|
|
276
|
+
changelogLines.push(`### ${type}`);
|
|
277
|
+
changelogLines.push(...generateTypeChangelog(typeCommits));
|
|
278
|
+
changelogLines.push('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Remove trailing empty line
|
|
282
|
+
if (changelogLines.at(-1) === '') {
|
|
283
|
+
changelogLines.pop();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { nextVersion, changelog: changelogLines.join('\n') };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Processes version information for a branch, returning version details and changelog.
|
|
291
|
+
* @param {string} branch - The branch to process
|
|
292
|
+
* @returns {Promise<{previousVersion: string|null, nextVersion: string, commits: Array, conventionalCommits: Object, changelog: string}>} Promise resolving to version info
|
|
293
|
+
*/
|
|
294
|
+
async function processVersionInfo(branch) {
|
|
295
|
+
ensureGitRepo();
|
|
296
|
+
validateBranchName(branch);
|
|
297
|
+
fetchTagsLocally();
|
|
298
|
+
|
|
299
|
+
const allCommits = getAllBranchCommits(branch);
|
|
300
|
+
const expandedInfo = expandCommitInfo(allCommits);
|
|
301
|
+
const { latestVersion, commits } = expandedInfo;
|
|
302
|
+
|
|
303
|
+
// Group commits by conventional type
|
|
304
|
+
const conventionalCommits = Object.fromEntries(TYPE_ORDER.map(t => [t, []]));
|
|
305
|
+
for (const commit of commits) {
|
|
306
|
+
const parsed = parseConventionalCommit(commit.message);
|
|
307
|
+
if (parsed && conventionalCommits[parsed.type]) {
|
|
308
|
+
conventionalCommits[parsed.type].push({ ...commit, conventional: parsed });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const { nextVersion, changelog } = calculateNextVersionAndChangelog(expandedInfo);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
previousVersion: latestVersion,
|
|
316
|
+
nextVersion,
|
|
317
|
+
commits,
|
|
318
|
+
conventionalCommits,
|
|
319
|
+
changelog,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Retrieves all commits in the history of the specified branch, including merged commits.
|
|
325
|
+
* Returns a simplified array with commit hash, datetime, author, commit message, and tags.
|
|
326
|
+
* @param {string} branch - The branch to get commits from
|
|
327
|
+
* @returns {Array<{hash: string, datetime: string, author: string, message: string, tags: Array<string>}>} Array of commit objects
|
|
328
|
+
*/
|
|
329
|
+
function getAllBranchCommits(branch) {
|
|
330
|
+
try {
|
|
331
|
+
// Verify the branch exists
|
|
332
|
+
runWithOutput(`git rev-parse --verify ${branch}`);
|
|
333
|
+
} catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const RS = '\x1E'; // Record Separator
|
|
338
|
+
const COMMIT_SEP = `${RS}${RS}`;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const logCmd = `git log --format=%H${RS}%ai${RS}%an${RS}%B${COMMIT_SEP} ${branch}`;
|
|
342
|
+
const output = runWithOutput(logCmd).trim();
|
|
343
|
+
if (!output) return [];
|
|
344
|
+
|
|
345
|
+
return output
|
|
346
|
+
.split(COMMIT_SEP)
|
|
347
|
+
.filter(block => block.trim())
|
|
348
|
+
.map(block => {
|
|
349
|
+
const parts = block.split(RS);
|
|
350
|
+
if (parts.length < 4) return null;
|
|
351
|
+
const hash = parts[0].trim();
|
|
352
|
+
return {
|
|
353
|
+
hash,
|
|
354
|
+
datetime: parts[1].trim(),
|
|
355
|
+
author: parts[2].trim(),
|
|
356
|
+
message: parts.slice(3).join(RS).trim(),
|
|
357
|
+
tags: getTagsForCommit(hash),
|
|
358
|
+
};
|
|
359
|
+
})
|
|
360
|
+
.filter(Boolean);
|
|
361
|
+
} catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
ensureGitRepo,
|
|
368
|
+
fetchTagsLocally,
|
|
369
|
+
validateBranchName,
|
|
370
|
+
getAllBranchCommits,
|
|
371
|
+
expandCommitInfo,
|
|
372
|
+
calculateNextVersionAndChangelog,
|
|
373
|
+
processVersionInfo,
|
|
374
|
+
parseConventionalCommit,
|
|
375
|
+
};
|
|
376
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AgileFlow GitLab CI Template
|
|
2
|
+
#
|
|
3
|
+
# This template provides a simplified, version-centric CI/CD pipeline that focuses on
|
|
4
|
+
# version management rather than environment-based deployments. The pipeline consists
|
|
5
|
+
# of 6 stages that work together to create a streamlined release process.
|
|
6
|
+
#
|
|
7
|
+
# Key Benefits:
|
|
8
|
+
# - Version-centric approach: All deployments, tests, and operations are performed
|
|
9
|
+
# on well-identified versions rather than branch-based environments
|
|
10
|
+
# - Simplified stages: Clear separation of concerns with minimal complexity
|
|
11
|
+
# - Automated versioning: Integrates with AgileFlow to automatically generate
|
|
12
|
+
# semantic versions based on commit history
|
|
13
|
+
# - Consistent deployments: All environments use the same version artifacts
|
|
14
|
+
#
|
|
15
|
+
# Stages Overview:
|
|
16
|
+
# 1. version - Generate semantic version and release notes
|
|
17
|
+
# 2. test - Run tests against source code before building
|
|
18
|
+
# 3. build - Build application artifacts and Docker images
|
|
19
|
+
# 4. deploy - Deploy to various environments using the generated version
|
|
20
|
+
# 5. e2e - Run end-to-end tests against deployed versions
|
|
21
|
+
# 6. clean - Cleanup temporary resources and artifacts
|
|
22
|
+
#
|
|
23
|
+
# Usage:
|
|
24
|
+
# Include this template in your .gitlab-ci.yml:
|
|
25
|
+
# include:
|
|
26
|
+
# - local: templates/AgileFlow.gitlab-ci.yml
|
|
27
|
+
#
|
|
28
|
+
# The agileflow job will automatically:
|
|
29
|
+
# - Calculate the next semantic version based on commit history
|
|
30
|
+
# - Generate comprehensive release notes from conventional commits
|
|
31
|
+
# - Create and push version tags to the repository
|
|
32
|
+
# - Make the VERSION variable available to subsequent stages
|
|
33
|
+
|
|
34
|
+
stages:
|
|
35
|
+
- version # Calculate the next semantic version based on commit history,
|
|
36
|
+
# if there's a tag, it will use that as the version.
|
|
37
|
+
|
|
38
|
+
# Although the version is available in the $VERSION variable in all scenarios,
|
|
39
|
+
# the rest of the stages are recommended to run when there's a version tag pushed.
|
|
40
|
+
# This way the deployment process is version-centric.
|
|
41
|
+
- test # Run tests against source code before building.
|
|
42
|
+
- build # Build the image, and push to the registry.
|
|
43
|
+
- deploy # Automatically deploy to DEV, manual deploy to STG and to PROD.
|
|
44
|
+
- e2e # Run end-to-end tests against the deployed version.
|
|
45
|
+
- clean # Cleanup temporary resources and artifacts.
|
|
46
|
+
|
|
47
|
+
# Example:
|
|
48
|
+
# Run the job only when there's a version tag pushed. The version tags starts with v and semver.
|
|
49
|
+
#
|
|
50
|
+
# stage: build
|
|
51
|
+
# rules:
|
|
52
|
+
# - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/
|
|
53
|
+
|
|
54
|
+
# Version Generation Stage
|
|
55
|
+
# This stage uses the AgileFlow tool to automatically generate semantic versions
|
|
56
|
+
# and release notes based on the main branch's commit history. The VERSION
|
|
57
|
+
# variable is made available as an artifact for use in subsequent stages.
|
|
58
|
+
agileflow:
|
|
59
|
+
stage: version
|
|
60
|
+
image: registry.logickernel.com/kernel/agileflow:0.9.0
|
|
61
|
+
only:
|
|
62
|
+
- main
|
|
63
|
+
script:
|
|
64
|
+
- agileflow gitlab-ci
|
|
65
|
+
tags:
|
|
66
|
+
- agileflow
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# AgileFlow GitLab CI Template
|
|
2
|
+
#
|
|
3
|
+
# This template provides a simplified, version-centric CI/CD pipeline that focuses on
|
|
4
|
+
# version management rather than environment-based deployments. The pipeline consists
|
|
5
|
+
# of 6 stages that work together to create a streamlined release process.
|
|
6
|
+
#
|
|
7
|
+
# Key Benefits:
|
|
8
|
+
# - Version-centric approach: All deployments, tests, and operations are performed
|
|
9
|
+
# on well-identified versions rather than branch-based environments
|
|
10
|
+
# - Simplified stages: Clear separation of concerns with minimal complexity
|
|
11
|
+
# - Automated versioning: Integrates with AgileFlow to automatically generate
|
|
12
|
+
# semantic versions based on commit history
|
|
13
|
+
# - Consistent deployments: All environments use the same version artifacts
|
|
14
|
+
#
|
|
15
|
+
# Stages Overview:
|
|
16
|
+
# 1. version - Generate semantic version and release notes
|
|
17
|
+
# 2. test - Run tests against source code before building
|
|
18
|
+
# 3. build - Build application artifacts and Docker images
|
|
19
|
+
# 4. deploy - Deploy to various environments using the generated version
|
|
20
|
+
# 5. e2e - Run end-to-end tests against deployed versions
|
|
21
|
+
# 6. clean - Cleanup temporary resources and artifacts
|
|
22
|
+
#
|
|
23
|
+
# Usage:
|
|
24
|
+
# Include this template in your .gitlab-ci.yml:
|
|
25
|
+
# include:
|
|
26
|
+
# - local: templates/AgileFlow.gitlab-ci.yml
|
|
27
|
+
#
|
|
28
|
+
# The agileflow job will automatically:
|
|
29
|
+
# - Calculate the next semantic version based on commit history
|
|
30
|
+
# - Generate comprehensive release notes from conventional commits
|
|
31
|
+
# - Create and push version tags to the repository
|
|
32
|
+
# - Make the VERSION variable available to subsequent stages
|
|
33
|
+
|
|
34
|
+
stages:
|
|
35
|
+
- version # Calculate the next semantic version based on commit history,
|
|
36
|
+
# if there's a tag, it will use that as the version.
|
|
37
|
+
|
|
38
|
+
# Although the version is available in the $VERSION variable in all scenarios,
|
|
39
|
+
# the rest of the stages are recommended to run when there's a version tag pushed.
|
|
40
|
+
# This way the deployment process is version-centric.
|
|
41
|
+
- test # Run tests against source code before building.
|
|
42
|
+
- build # Build the image, and push to the registry.
|
|
43
|
+
- deploy # Automatically deploy to DEV, manual deploy to STG and to PROD.
|
|
44
|
+
- e2e # Run end-to-end tests against the deployed version.
|
|
45
|
+
- clean # Cleanup temporary resources and artifacts.
|
|
46
|
+
|
|
47
|
+
# Example:
|
|
48
|
+
# Run the job only when there's a version tag pushed. The version tags starts with v and semver.
|
|
49
|
+
#
|
|
50
|
+
# stage: build
|
|
51
|
+
# rules:
|
|
52
|
+
# - if: $CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/
|
|
53
|
+
|
|
54
|
+
# Version Generation Stage
|
|
55
|
+
# This stage uses the AgileFlow tool to automatically generate semantic versions
|
|
56
|
+
# and release notes based on the main branch's commit history. The VERSION
|
|
57
|
+
# variable is made available as an artifact for use in subsequent stages.
|
|
58
|
+
agileflow:
|
|
59
|
+
stage: version
|
|
60
|
+
image: registry.logickernel.com/kernel/agileflow:0.9.0
|
|
61
|
+
only:
|
|
62
|
+
- main
|
|
63
|
+
script:
|
|
64
|
+
- agileflow gitlab-ci
|