diff --git a/docs/plans/2026-02-20-release-engineering.md b/docs/plans/2026-02-20-release-engineering.md new file mode 100644 index 0000000..5c36fd8 --- /dev/null +++ b/docs/plans/2026-02-20-release-engineering.md @@ -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: + +``` +(): + +[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