docs: add release engineering implementation plan
This commit is contained in:
974
docs/plans/2026-02-20-release-engineering.md
Normal file
974
docs/plans/2026-02-20-release-engineering.md
Normal 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
|
||||
Reference in New Issue
Block a user