ci: semantic-release automation for tag/changelog (Option D)

Закрывает эту тему окончательно. После merge в main CI анализирует
conventional commits с последнего tag'а, бампит package.json + CHANGELOG.md
и пушит git tag vX.Y.Z. Tag pipeline далее собирает images и ждёт
manual gate на trigger-deploy-prod.

Что добавил:
- ordinis-admin-ui/.releaserc.json — config (commit-analyzer, release-notes,
  changelog, npm[no-publish], git, gitlab). branches: ['main']. Маппинг
  conventional-commit types к bump levels.
- ordinis-admin-ui/package.json — devDeps: semantic-release@24.2 + 6 plugins.
- .gitlab-ci.yml — new 'release' stage + release job:
  - needs: trigger-deploy-staging (release ТОЛЬКО после зелёного staging)
  - image: node:22-alpine + pnpm@9.15.4
  - GL_TOKEN из CI variable GITLAB_TOKEN (semantic-release/gitlab требует
    именно GL_TOKEN env name)
  - allow_failure: true (нет релизных commits = OK, не блокирует pipeline)
- RELEASE.md — operator docs:
  - One-time setup: создать Project Access Token + GITLAB_TOKEN CI var
  - Commit conventions table (feat→minor, fix→patch, breaking→major)
  - Skip release (use chore: type или [skip ci])
  - Dry-run command для local validation

Prod safety: trigger-deploy-prod остаётся manual (when: manual). Автоматизация
ТОЛЬКО tag-создания, не deploy. Tag → CI runs → human кликает Deploy.

ACTION REQUIRED от user (one-time):
  1. GitLab → Settings → Access Tokens → создать Project Access Token
     scopes: write_repository + api, role: Maintainer
  2. GitLab → Settings → CI/CD → Variables → GITLAB_TOKEN (Masked, Protected)

Без этого release job будет fail'ить (но pipeline не покраснеет — allow_failure).
This commit is contained in:
Zimin A.N.
2026-05-11 14:51:03 +03:00
parent 3223a5ced7
commit 6805c70a6b
5 changed files with 2697 additions and 0 deletions
+49
View File
@@ -11,6 +11,7 @@ stages:
- build
- publish
- trigger-deploy
- release
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
@@ -366,3 +367,51 @@ trigger-deploy-prod:
rules:
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
when: manual
# =============================================================
# Release automation — semantic-release.
# =============================================================
# Анализирует conventional commits с последнего tag'а, решает bump (patch/
# minor/major), генерирует CHANGELOG.md, bump'ит package.json, создаёт git tag
# vX.Y.Z и GitLab Release. Tag pipeline далее автоматически собирает images
# и ждёт manual gate на trigger-deploy-prod.
#
# Запускается ТОЛЬКО на main, ТОЛЬКО после успешного staging deploy.
# Если нет релизных commits (только chore/test/ci/docs/style) — exit 0 без
# tag'а. Loop prevention: коммит `chore(release): vX.Y.Z [skip ci]` —
# `[skip ci]` стандартный GitLab marker, новый pipeline не триггерится.
#
# Setup ONE-TIME (см. RELEASE.md):
# 1. Settings → Access Tokens → create Project Access Token
# - role: Maintainer
# - scopes: write_repository, api
# - expiry: 1 год (renew раз в год)
# 2. Settings → CI/CD → Variables → add:
# GITLAB_TOKEN = <token> (Masked, Protected)
#
# Tuning: чтобы НЕ релизить feat: автоматом — добавь в commit message
# `[skip release]` и semantic-release пропустит этот commit (см. parserOpts).
release:
stage: release
image: node:22-alpine
needs:
- job: trigger-deploy-staging
artifacts: false
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
variables:
# semantic-release требует full git history для commit analysis.
GIT_DEPTH: "0"
# GL_TOKEN — @semantic-release/gitlab plugin requires this name specifically.
GL_TOKEN: $GITLAB_TOKEN
before_script:
- apk add --no-cache git ca-certificates
- npm install -g --silent pnpm@9.15.4
- cd ordinis-admin-ui
- pnpm install --frozen-lockfile --silent
script:
- npx semantic-release
# Не блокирует следующие pipelines если release fail'нул (e.g. нет
# релизных commits). Manual investigation если нужно.
allow_failure: true
+99
View File
@@ -0,0 +1,99 @@
# Release Automation
Ordinis uses **semantic-release** для автоматического создания tag'ов на основе
conventional commits. После merge в `main` CI анализирует commits, решает
bump (patch/minor/major), генерирует `CHANGELOG.md` и пушит tag `vX.Y.Z`.
Tag pipeline далее собирает images и ждёт manual gate на `trigger-deploy-prod`.
Prod deploy остаётся под human control — semantic-release создаёт tag,
human кликает Deploy.
## One-time setup
1. **Create Project Access Token** (GitLab UI):
- `Settings → Access Tokens → Add new token`
- Name: `semantic-release`
- Role: `Maintainer`
- Scopes: `write_repository`, `api`
- Expiry: 1 год (renew annually)
2. **Add CI variable**:
- `Settings → CI/CD → Variables → Add variable`
- Key: `GITLAB_TOKEN`
- Value: (token из шага 1)
- Type: `Variable`
- Flags: `Masked` ✅, `Protected` ✅ (только main + tag pipelines)
После этого release job в `.gitlab-ci.yml` будет работать.
## Commit conventions
Conventional Commits (https://www.conventionalcommits.org/):
| Type | Bump | Section в CHANGELOG |
|------|------|---------------------|
| `feat:` | **minor** | ✨ Features |
| `fix:` | patch | 🐛 Fixes |
| `perf:` | patch | ⚡ Performance |
| `refactor:` | patch | ♻️ Refactoring |
| `revert:` | patch | ⏪ Reverts |
| `docs:` | none | 📝 Docs |
| `chore:` | none | hidden |
| `test:` | none | hidden |
| `style:` | none | hidden |
| `ci:` | none | hidden |
| `build:` | none | hidden |
**Breaking changes** — добавь `BREAKING CHANGE:` в footer commit message → major bump.
```
feat(api): drop legacy v1 endpoint
BREAKING CHANGE: /api/v1/* paths больше не поддерживаются.
Все клиенты должны мигрировать на /api/v2.
```
## Skip release for specific commit
Если хочешь смержить `feat:` или `fix:` но **не** делать release сейчас —
добавь `[skip ci]` в commit message ИЛИ используй non-release type
(`chore:`, `docs:`, etc).
```
chore(deps): bump axios 1.7 -> 1.8
```
## Loop prevention
Release job создаёт commit `chore(release): vX.Y.Z [skip ci]`. `[skip ci]`
стандартный GitLab marker, новый pipeline не триггерится. Версия package.json
+ CHANGELOG.md обновляются в этом коммите.
## Branch policy
Релизы создаются ТОЛЬКО с `main`. Feature branches (`feat/*`, `fix/*`) собираются
для staging но НЕ создают tag'и. Релиз = merge в main + успешный staging deploy +
наличие релизных commits.
## Local dry-run
Проверить что semantic-release решит сделать без push:
```bash
cd ordinis-admin-ui
pnpm install
npx semantic-release --dry-run --no-ci
```
Покажет следующую версию + preview CHANGELOG entries.
## Rollback
Если случайно зарелизили — удали tag:
```bash
git tag -d v1.3.1
git push origin :refs/tags/v1.3.1
```
GitLab Releases UI — удали release object отдельно.
+77
View File
@@ -0,0 +1,77 @@
{
"branches": ["main"],
"tagFormat": "v${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "refactor", "release": "patch" },
{ "type": "revert", "release": "patch" },
{ "type": "docs", "release": false },
{ "type": "style", "release": false },
{ "type": "chore", "release": false },
{ "type": "test", "release": false },
{ "type": "build", "release": false },
{ "type": "ci", "release": false },
{ "breaking": true, "release": "major" }
],
"parserOpts": {
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
}
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "feat", "section": "✨ Features" },
{ "type": "fix", "section": "🐛 Fixes" },
{ "type": "perf", "section": "⚡ Performance" },
{ "type": "refactor", "section": "♻️ Refactoring" },
{ "type": "revert", "section": "⏪ Reverts" },
{ "type": "docs", "section": "📝 Docs", "hidden": false },
{ "type": "chore", "hidden": true },
{ "type": "test", "hidden": true },
{ "type": "ci", "hidden": true },
{ "type": "build", "hidden": true },
{ "type": "style", "hidden": true }
]
}
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Ordinis Release Notes\n\nAuto-generated by semantic-release based on conventional commits.\nSee `.releaserc.json` for release rules."
}
],
[
"@semantic-release/npm",
{
"npmPublish": false,
"tarballDir": false
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/gitlab",
{
"gitlabUrl": "https://git.nstart.cloud"
}
]
]
}
+7
View File
@@ -48,6 +48,12 @@
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/gitlab": "^13.2.4",
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.1",
"@tailwindcss/vite": "^4.0.0-beta.7",
"@tanstack/router-vite-plugin": "^1.86.0",
"@testing-library/jest-dom": "^6.9.1",
@@ -59,6 +65,7 @@
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"jsdom": "^29.1.1",
"semantic-release": "^24.2.1",
"tailwindcss": "^4.0.0-beta.7",
"typescript": "^5.7.2",
"vite": "^6.0.5",
+2465
View File
File diff suppressed because it is too large Load Diff