docs: add release engineering implementation plan

This commit is contained in:
Tuan-Dat Tran
2026-02-20 09:57:00 +01:00
parent 1741fe1ca1
commit b3f2919791

View File

@@ -0,0 +1,974 @@
# Release Engineering Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Implement comprehensive release engineering with semantic-release, Docker multi-registry publishing, and multi-environment deployment (production, staging, nightly).
**Architecture:** semantic-release handles versioning, changelog, and GitHub releases from conventional commits. GitHub Actions workflows build and publish Docker images to Docker Hub and GHCR. Separate workflows for staging and nightly deployments.
**Tech Stack:** semantic-release, commitlint, husky, Docker buildx, GitHub Actions
---
## Task 1: Install Release Engineering Dependencies
**Files:**
- Modify: `package.json`
- Create: `.releaserc.json`
- Create: `commitlint.config.js`
**Step 1: Install semantic-release and plugins**
```bash
npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git @semantic-release/github @commitlint/cli @commitlint/config-conventional husky
```
**Step 2: Verify installation**
Run: `npm list semantic-release @commitlint/cli husky --depth=0`
Expected: All packages listed with versions
**Step 3: Commit**
```bash
git add package.json package-lock.json
git commit -m "build: add semantic-release, commitlint, and husky dependencies"
```
---
## Task 2: Configure semantic-release
**Files:**
- Create: `.releaserc.json`
**Step 1: Create semantic-release configuration file**
Create `.releaserc.json`:
```json
{
"branches": ["main", { "name": "staging", "prerelease": true }],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "revert", "release": "patch" },
{ "type": "docs", "release": false },
{ "type": "style", "release": false },
{ "type": "refactor", "release": false },
{ "type": "test", "release": false },
{ "type": "build", "release": false },
{ "type": "ci", "release": false },
{ "type": "chore", "release": false }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "feat", "section": "Features", "hidden": false },
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
{ "type": "perf", "section": "Performance Improvements", "hidden": false },
{ "type": "revert", "section": "Reverts", "hidden": false },
{ "type": "docs", "section": "Documentation", "hidden": false },
{ "type": "style", "section": "Styles", "hidden": true },
{ "type": "refactor", "section": "Code Refactoring", "hidden": true },
{ "type": "test", "section": "Tests", "hidden": true },
{ "type": "build", "section": "Build System", "hidden": true },
{ "type": "ci", "section": "Continuous Integration", "hidden": true },
{ "type": "chore", "section": "Chores", "hidden": true }
]
}
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file."
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/github",
{
"assets": [
{ "path": "dist/**/*", "label": "Distribution" }
]
}
]
]
}
```
**Step 2: Verify JSON is valid**
Run: `node -e "console.log(JSON.parse(require('fs').readFileSync('.releaserc.json', 'utf8')))"`
Expected: JSON object printed without errors
**Step 3: Commit**
```bash
git add .releaserc.json
git commit -m "chore(ci): configure semantic-release"
```
---
## Task 3: Configure commitlint
**Files:**
- Create: `commitlint.config.js`
**Step 1: Create commitlint configuration file**
Create `commitlint.config.js`:
```javascript
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'ci',
'chore',
'revert',
],
],
'scope-enum': [
2,
'always',
[
'admin',
'api',
'ui',
'docker',
'ci',
'deps',
'release',
'auth',
'skills',
'experience',
'education',
'projects',
'personal',
],
],
'scope-empty': [1, 'never'],
'subject-case': [2, 'always', 'lower-case'],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100],
},
};
```
**Step 2: Verify configuration is valid**
Run: `npx commitlint --from HEAD~1 --to HEAD --verbose` (skip if only one commit)
Expected: Either passes or reports linting issues
**Step 3: Commit**
```bash
git add commitlint.config.js
git commit -m "chore(ci): configure commitlint for conventional commits"
```
---
## Task 4: Configure Husky Git Hooks
**Files:**
- Create: `.husky/commit-msg`
- Create: `.husky/pre-commit`
**Step 1: Initialize husky**
```bash
npx husky init
```
Expected: `.husky/` directory created
**Step 2: Create commit-msg hook**
Replace `.husky/commit-msg` content with:
```bash
npx --no -- commitlint --edit "$1"
```
**Step 3: Create pre-commit hook**
Replace `.husky/pre-commit` content with:
```bash
npm run lint
```
**Step 4: Make hooks executable**
```bash
chmod +x .husky/pre-commit .husky/commit-msg
```
**Step 5: Verify husky is configured in package.json**
Run: `cat package.json | grep -A 2 '"scripts"'`
Expected: `prepare` script present with `husky`
If not present, add to package.json scripts:
```json
"prepare": "husky"
```
**Step 6: Commit**
```bash
git add .husky package.json
git commit -m "chore(ci): configure husky git hooks for commitlint"
```
---
## Task 5: Create Docker Multi-Platform Configuration
**Files:**
- Modify: `Dockerfile`
- Create: `docker bake.hcl`
**Step 1: Update Dockerfile for multi-platform support**
Read current `Dockerfile` and ensure it supports multi-platform. The existing Dockerfile should already work, but verify:
- No hardcoded architecture-specific paths
- Uses multi-stage build
- Backend builds better-sqlite3 from source
**Step 2: Create docker-bake.hcl for multi-registry publishing**
Create `docker-bake.hcl`:
```hcl
variable "REGISTRY_USER" {
default = ""
}
variable "IMAGE_NAME" {
default = "cv-app"
}
variable "TAG" {
default = "latest"
}
variable "VERSION" {
default = ""
}
group "default" {
targets = ["cv-app"]
}
target "cv-app" {
context = "."
platforms = ["linux/amd64", "linux/arm64"]
tags = [
notequal("",VERSION) ? "${REGISTRY_USER}/${IMAGE_NAME}:${VERSION}" : "",
notequal("",VERSION) ? "${REGISTRY_USER}/${IMAGE_NAME}:latest" : "",
notequal("",VERSION) ? "ghcr.io/${REGISTRY_USER}/${IMAGE_NAME}:${VERSION}" : "",
notequal("",VERSION) ? "ghcr.io/${REGISTRY_USER}/${IMAGE_NAME}:latest" : "",
]
args = {
NODE_VERSION = "20"
}
}
```
**Step 3: Commit**
```bash
git add Dockerfile docker-bake.hcl
git commit -m "feat(docker): add multi-platform build configuration"
```
---
## Task 6: Create Release Workflow
**Files:**
- Modify: `.github/workflows/release.yml`
**Step 1: Replace existing release.yml**
Replace `.github/workflows/release.yml` content with:
```yaml
name: Release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
packages: write
id-token: write
jobs:
release:
name: Release
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install backend dependencies
working-directory: ./backend
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm run test:run
- name: Run backend tests
working-directory: ./backend
run: npm test
- name: Build
run: npm run build
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: release
if: needs.release.result == 'success'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version
id: version
run: |
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "latest")
echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:latest
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/cv-app:latest
ghcr.io/${{ github.repository_owner }}/cv-app:${{ steps.version.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
```
**Step 2: Verify YAML syntax**
Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"`
Expected: No errors
**Step 3: Commit**
```bash
git add .github/workflows/release.yml
git commit -m "feat(ci): add semantic-release workflow with Docker publishing"
```
---
## Task 7: Create Staging Workflow
**Files:**
- Create: `.github/workflows/staging.yml`
**Step 1: Create staging workflow**
Create `.github/workflows/staging.yml`:
```yaml
name: Staging Deploy
on:
push:
branches: [staging]
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build-and-deploy:
name: Build & Deploy to Staging
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install backend dependencies
working-directory: ./backend
run: npm ci
- name: Lint
run: npm run lint
- name: Run tests
run: npm run test:run
- name: Build
run: npm run build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:staging
ghcr.io/${{ github.repository_owner }}/cv-app:staging
cache-from: type=gha
cache-to: type=gha,mode=max
```
**Step 2: Verify YAML syntax**
Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/staging.yml'))"`
Expected: No errors
**Step 3: Commit**
```bash
git add .github/workflows/staging.yml
git commit -m "feat(ci): add staging deployment workflow"
```
---
## Task 8: Create Nightly Build Workflow
**Files:**
- Create: `.github/workflows/nightly.yml`
**Step 1: Create nightly workflow**
Create `.github/workflows/nightly.yml`:
```yaml
name: Nightly Build
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
nightly:
name: Build Nightly Image
runs-on: ubuntu-latest
environment: nightly
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Get date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install backend dependencies
working-directory: ./backend
run: npm ci
- name: Build
run: npm run build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:nightly
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:edge
${{ secrets.DOCKERHUB_USERNAME }}/cv-app:${{ steps.date.outputs.date }}
ghcr.io/${{ github.repository_owner }}/cv-app:nightly
ghcr.io/${{ github.repository_owner }}/cv-app:edge
ghcr.io/${{ github.repository_owner }}/cv-app:${{ steps.date.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max
```
**Step 2: Verify YAML syntax**
Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/nightly.yml'))"`
Expected: No errors
**Step 3: Commit**
```bash
git add .github/workflows/nightly.yml
git commit -m "feat(ci): add nightly build workflow"
```
---
## Task 9: Enhance CI Workflow with Commitlint
**Files:**
- Modify: `.github/workflows/ci.yml`
**Step 1: Add commitlint job to CI**
Add this job to `.github/workflows/ci.yml` after the `jobs:` line:
```yaml
commitlint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Validate current commit
if: github.event_name == 'push'
run: npx commitlint --from HEAD~1 --to HEAD --verbose
- name: Validate PR commits
if: github.event_name == 'pull_request'
run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
```
**Step 2: Update job dependencies**
Add `commitlint` to the `needs` array of downstream jobs. For example, change:
```yaml
integration:
runs-on: ubuntu-latest
needs: [frontend, backend]
```
to:
```yaml
integration:
runs-on: ubuntu-latest
needs: [frontend, backend, commitlint]
```
Do the same for `e2e` and `regression` jobs.
**Step 3: Verify YAML syntax**
Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"`
Expected: No errors
**Step 4: Commit**
```bash
git add .github/workflows/ci.yml
git commit -m "feat(ci): add commitlint validation to CI workflow"
```
---
## Task 10: Create CHANGELOG.md
**Files:**
- Create: `CHANGELOG.md`
**Step 1: Create initial CHANGELOG**
Create `CHANGELOG.md`:
```markdown
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Release engineering with semantic-release
- Multi-platform Docker builds (amd64, arm64)
- Docker Hub and GHCR publishing
- Staging and nightly deployment workflows
- Commitlint for conventional commits
## [0.0.0] - 2026-02-20
### Added
- Initial CV application
- React frontend with Tailwind CSS v4
- Express.js backend with SQLite
- Admin panel for CV editing
- Password protection for admin
- Docker Compose deployment
```
**Step 2: Commit**
```bash
git add CHANGELOG.md
git commit -m "docs: add initial CHANGELOG.md"
```
---
## Task 11: Update README with Release Information
**Files:**
- Modify: `README.md`
**Step 1: Add release engineering section to README**
Add this section after the "Testing" section:
```markdown
## Release Process
This project uses [semantic-release](https://semantic-release.gitbook.io/) for automated versioning and releases.
### Conventional Commits
All commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
#### Commit Types
| Type | Description | Version Bump |
|------|-------------|--------------|
| `feat` | New feature | Minor |
| `fix` | Bug fix | Patch |
| `feat!` or `BREAKING CHANGE` | Breaking change | Major |
| `docs`, `style`, `refactor`, `test`, `build`, `ci`, `chore` | Non-release changes | None |
#### Scopes
Available scopes: `admin`, `api`, `ui`, `docker`, `ci`, `deps`, `release`, `auth`, `skills`, `experience`, `education`, `projects`, `personal`
### Release Workflow
1. Push to `main` branch
2. CI runs tests and linting
3. semantic-release analyzes commits
4. If release needed:
- Version is bumped in package.json
- CHANGELOG.md is updated
- Git tag is created
- GitHub release is published
- Docker images are built and pushed
### Docker Images
Images are published to both Docker Hub and GitHub Container Registry:
| Tag | Description |
|-----|-------------|
| `latest` | Latest stable release |
| `v1.2.3` | Specific version |
| `staging` | Staging environment |
| `nightly`, `edge` | Daily builds from main |
| `YYYY-MM-DD` | Dated nightly build |
```bash
# Pull from Docker Hub
docker pull username/cv-app:latest
# Pull from GHCR
docker pull ghcr.io/owner/cv-app:latest
```
### Environments
| Environment | Branch | Trigger |
|-------------|--------|---------|
| Production | `main` | semantic-release |
| Staging | `staging` | Push to staging |
| Nightly | `main` | Daily at 02:00 UTC |
```
**Step 2: Commit**
```bash
git add README.md
git commit -m "docs: add release process documentation"
```
---
## Task 12: Update package.json Version
**Files:**
- Modify: `package.json`
**Step 1: Update version field**
Change `"version": "0.0.0"` to `"version": "0.0.0-dev.1"` in package.json.
This indicates the project is in development before the first release. semantic-release will set the proper version on first release.
**Step 2: Commit**
```bash
git add package.json
git commit -m "chore: set initial development version"
```
---
## Task 13: Verify All Configurations
**Step 1: Run lint**
```bash
npm run lint
```
Expected: No errors
**Step 2: Run tests**
```bash
npm run test:run
```
Expected: All tests pass
**Step 3: Test commitlint locally**
```bash
echo "feat(test): test commit message" | npx commitlint
```
Expected: No errors
```bash
echo "invalid commit message" | npx commitlint
```
Expected: Error message about invalid format
**Step 4: Verify husky hooks**
```bash
ls -la .husky/
```
Expected: `pre-commit` and `commit-msg` files exist and are executable
---
## Task 14: Final Commit and Summary
**Step 1: Verify all files are committed**
```bash
git status
```
Expected: "nothing to commit, working tree clean" or only untracked files that shouldn't be committed
**Step 2: View commit history**
```bash
git log --oneline -15
```
Expected: All 12+ commits from this plan visible
**Step 3: Summary**
The release engineering system is now configured:
- **semantic-release** handles automated versioning and releases
- **commitlint** enforces conventional commits
- **husky** provides git hooks for validation
- **GitHub Actions** workflows for release, staging, and nightly builds
- **Docker** multi-platform images published to Docker Hub and GHCR
---
## Required Secrets
Before the first release, configure these secrets in GitHub repository settings:
| Secret | Description |
|--------|-------------|
| `DOCKERHUB_USERNAME` | Docker Hub username |
| `DOCKERHUB_TOKEN` | Docker Hub access token |
| `GITHUB_TOKEN` | Automatic (no configuration needed) |
---
## Post-Implementation Checklist
- [ ] Configure Docker Hub secrets in GitHub
- [ ] Create `staging` branch
- [ ] Configure GitHub environments (production, staging, nightly)
- [ ] Set up branch protection rules for `main`
- [ ] Test release by merging a `feat:` commit to main