diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3df0dfce8ee2abecabf596a6c3243119190212e6..efbaa2252ab033d9979b43b1914119c7d1fffd35 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,8 @@ include: - local: templates/Rules.yaml - local: templates/Runners/eole-docker.yaml - local: templates/Lint/Commitlint.yaml + - local: templates/Release/Semantic-release.yaml stages: - lint + - release diff --git a/release-rules.js b/release-rules.js new file mode 100644 index 0000000000000000000000000000000000000000..c63c850df486dffd88619df1618385ed3c5eef1c --- /dev/null +++ b/release-rules.js @@ -0,0 +1,18 @@ +// No release is triggered for the types commented out below. +// Commits using these types will be incorporated into the next release. +// +// NOTE: Any changes here must be reflected in `CONTRIBUTING.md`. +module.exports = [ + {breaking: true, release: 'major'}, + // {type: 'build', release: 'patch'}, + // {type: 'chore', release: 'patch'}, + // {type: 'ci', release: 'patch'}, + {type: 'docs', release: 'patch'}, + {type: 'feat', release: 'minor'}, + {type: 'fix', release: 'patch'}, + {type: 'perf', release: 'patch'}, + {type: 'refactor', release: 'patch'}, + {type: 'revert', release: 'patch'}, + {type: 'style', release: 'patch'}, + {type: 'test', release: 'patch'}, +]; diff --git a/release.config.js b/release.config.js new file mode 100644 index 0000000000000000000000000000000000000000..d749cd2fd5fbc384cee8fdf0aeb10e02093b08d4 --- /dev/null +++ b/release.config.js @@ -0,0 +1,105 @@ +module.exports = { + branches: 'stable', + tagFormat: 'release/${version}', + plugins: [ + ['@semantic-release/commit-analyzer', { + preset: 'angular', + releaseRules: './release-rules.js', + }], + '@semantic-release/release-notes-generator', + ['@semantic-release/changelog', { + changelogFile: 'docs/CHANGELOG.md', + changelogTitle: '# Changelog', + }], + ['@semantic-release/git', { + assets: ['docs'], + message: 'chore(release): ${nextRelease.version}\n\n${nextRelease.notes}' + }], + '@semantic-release/gitlab', + ], + generateNotes: { + preset: 'angular', + writerOpts: { + // Required due to upstream bug preventing all types being displayed. + // Bug: https://github.com/conventional-changelog/conventional-changelog/issues/317 + // Fix: https://github.com/conventional-changelog/conventional-changelog/pull/410 + transform: (commit, context) => { + const issues = [] + + commit.notes.forEach(note => { + note.title = `BREAKING CHANGES` + }) + + // NOTE: Any changes here must be reflected in `CONTRIBUTING.md`. + if (commit.type === `feat`) { + commit.type = `Features` + } else if (commit.type === `fix`) { + commit.type = `Bug Fixes` + } else if (commit.type === `perf`) { + commit.type = `Performance Improvements` + } else if (commit.type === `revert`) { + commit.type = `Reverts` + } else if (commit.type === `docs`) { + commit.type = `Documentation` + } else if (commit.type === `style`) { + commit.type = `Styles` + } else if (commit.type === `refactor`) { + commit.type = `Code Refactoring` + } else if (commit.type === `test`) { + commit.type = `Tests` + } else if (commit.type === `build`) { + commit.type = `Build System` + // } else if (commit.type === `chore`) { + // commit.type = `Maintenance` + } else if (commit.type === `ci`) { + commit.type = `Continuous Integration` + } else { + return + } + + if (commit.scope === `*`) { + commit.scope = `` + } + + if (typeof commit.hash === `string`) { + commit.shortHash = commit.hash.substring(0, 7) + } + + if (typeof commit.subject === `string`) { + let url = context.repository + ? `${context.host}/${context.owner}/${context.repository}` + : context.repoUrl + if (url) { + url = `${url}/issues/` + // Issue URLs. + commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => { + issues.push(issue) + return `[#${issue}](${url}${issue})` + }) + } + if (context.host) { + // User URLs. + commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => { + if (username.includes('/')) { + return `@${username}` + } + + return `[@${username}](${context.host}/${username})` + }) + } + } + + // remove references that already appear in the subject + commit.references = commit.references.filter(reference => { + if (issues.indexOf(reference.issue) === -1) { + return true + } + + return false + }) + + return commit + }, + }, + }, +}; diff --git a/templates/Release/Semantic-release.yaml b/templates/Release/Semantic-release.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eb9ccf9709035cd65110f1225033a728d42e6834 --- /dev/null +++ b/templates/Release/Semantic-release.yaml @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +# Produce a new release using semantic versionning scheme when new +# commits arrive on the production branch. +# +# USAGE +# ===== +# +# include: +# - project: EOLE/infra/ci-tools +# ref: stable +# file: /templates/Release/Semantic-release.yaml +# +# REQUIREMENTS +# ============ +# +# - A `release` stage must be present in your pipeline +# - One of the `semantic-release` configuration file must be present +# - `release.config.js` +# - `.releaserc` +# - `.releaserc.js` +# - `.releaserc.json` +# - `.releaserc.yaml` +# - `.releaserc.yml` +# - an access token named `GITLAB_TOKEN` with +# - `api` +# - `read_repository` +# - `write_repository` +# - The variable `$STABLE_BRANCH` defined +# +# OPTIONAL VARIABLES +# ================== +# +# - `SEMANTIC_RELEASE_IMAGE`: name of the `semantic-release` docker +# image to use +# +# SEE ALSO +# ======== +# +# - Semantic release software: https://github.com/semantic-release/semantic-release/ +# - Commitlint: https://github.com/conventional-changelog/commitlint/ +# +# IMPORTANT NOTE +# ============== +# +# We can't merge rules with `!reference` until we switch to Gitlab >= 14.3 +# https://gitlab.com/gitlab-org/gitlab/-/issues/322992 + +.on-stable-with-semantic-release-config: + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + when: never + # Exclude semantic-release commits on $STABLE_BRANCH + - if: $CI_COMMIT_BRANCH == $STABLE_BRANCH && $CI_COMMIT_MESSAGE =~ /^chore\(release\):/ + when: never + - if: $CI_COMMIT_BRANCH == $STABLE_BRANCH + exists: + - release.config.js + - .releaserc + - .releaserc.yaml + - .releaserc.yml + - .releaserc.json + - .releaserc.js + when: on_success + +semantic-release: + stage: release + extends: .on-stable-with-semantic-release-config + image: "$SEMANTIC_RELEASE_IMAGE" + variables: + SEMANTIC_RELEASE_IMAGE: 'hub.eole.education/eole/semantic-release:latest' + script: + - 'semantic-release' +...