docs: update all markdown documentation
- README.md: Update tech stack versions, simplify content, add links to docs - CONTRIBUTING.md: Add commit guidelines, testing instructions, PR process - SECURITY.md: Add security measures, vulnerability reporting process - PULL_REQUEST_TEMPLATE.md: Add conventional commit types, breaking change section - Remove outdated plan documents (already implemented or superseded) - architecture.md: Already updated with comprehensive system documentation - release-engineering.md: Already updated with current pipeline status
This commit is contained in:
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
31
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,22 +4,37 @@
|
||||
|
||||
## Type of Change
|
||||
|
||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||
- [ ] New feature (non-breaking change that adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update
|
||||
- [ ] `feat` - New feature (minor version bump)
|
||||
- [ ] `fix` - Bug fix (patch version bump)
|
||||
- [ ] `perf` - Performance improvement (patch version bump)
|
||||
- [ ] `docs` - Documentation only
|
||||
- [ ] `refactor` - Code refactoring
|
||||
- [ ] `test` - Adding/updating tests
|
||||
- [ ] `ci` - CI/CD changes
|
||||
- [ ] `chore` - Maintenance tasks
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- [ ] This is a breaking change (major version bump)
|
||||
|
||||
**If breaking, describe the migration path:**
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have followed the [Contributing Guidelines](CONTRIBUTING.md)
|
||||
- [ ] Commit messages follow [Conventional Commits](CONTRIBUTING.md#commit-guidelines)
|
||||
- [ ] Lint passes (`npm run lint`)
|
||||
- [ ] Build succeeds (`npm run build`)
|
||||
- [ ] I have tested my changes locally
|
||||
- [ ] Tests pass (`npm run test:run`)
|
||||
- [ ] API documentation updated (if applicable)
|
||||
|
||||
## Testing
|
||||
|
||||
<!-- Describe how you tested these changes -->
|
||||
|
||||
## Screenshots (if applicable)
|
||||
|
||||
<!-- Add screenshots here -->
|
||||
<!-- Add screenshots for UI changes -->
|
||||
|
||||
## Related Issues
|
||||
|
||||
<!-- Link any related issues: Closes #123 -->
|
||||
<!-- Link issues: Closes #123, Fixes #456 -->
|
||||
|
||||
180
CONTRIBUTING.md
180
CONTRIBUTING.md
@@ -1,60 +1,164 @@
|
||||
# Contributing to CV
|
||||
# Contributing Guidelines
|
||||
|
||||
Thank you for your interest in contributing!
|
||||
Thank you for your interest in contributing to this project! This document provides guidelines and instructions for contributing.
|
||||
|
||||
## Quick Start
|
||||
## Code of Conduct
|
||||
|
||||
Be respectful and constructive. Treat all contributors with courtesy.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20+
|
||||
- npm 10+
|
||||
- Gitleaks (for secret scanning)
|
||||
|
||||
### Setup
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Make your changes
|
||||
4. Run linting (`npm run lint`)
|
||||
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
7. Open a Pull Request
|
||||
2. Clone your fork
|
||||
3. Install dependencies:
|
||||
|
||||
## Development Guidelines
|
||||
```bash
|
||||
npm install
|
||||
cd backend && npm install && cd ..
|
||||
```
|
||||
|
||||
4. Install Gitleaks (for pre-commit hooks):
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install gitleaks
|
||||
|
||||
# Linux
|
||||
curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz | tar -xz
|
||||
sudo mv gitleaks /usr/local/bin/
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Branch Naming
|
||||
|
||||
- `feat/feature-name` - New features
|
||||
- `fix/bug-name` - Bug fixes
|
||||
- `docs/topic` - Documentation changes
|
||||
- `refactor/component` - Code refactoring
|
||||
- `test/test-name` - Test additions/changes
|
||||
|
||||
### Commit Guidelines
|
||||
|
||||
This project uses [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
#### Types
|
||||
|
||||
| Type | Description | Version Impact |
|
||||
|------|-------------|----------------|
|
||||
| `feat` | New feature | Minor |
|
||||
| `fix` | Bug fix | Patch |
|
||||
| `perf` | Performance improvement | Patch |
|
||||
| `revert` | Revert previous commit | Patch |
|
||||
| `docs` | Documentation only | None |
|
||||
| `style` | Code style (formatting) | None |
|
||||
| `refactor` | Code refactoring | None |
|
||||
| `test` | Adding/updating tests | None |
|
||||
| `build` | Build system changes | None |
|
||||
| `ci` | CI/CD changes | None |
|
||||
| `chore` | Maintenance tasks | None |
|
||||
|
||||
#### Scopes
|
||||
|
||||
| Scope | Description |
|
||||
|-------|-------------|
|
||||
| `admin` | Admin panel components |
|
||||
| `api` | Backend API |
|
||||
| `ui` | Frontend UI components |
|
||||
| `docker` | Docker configuration |
|
||||
| `ci` | CI/CD workflows |
|
||||
| `deps` | Dependencies |
|
||||
| `release` | Release configuration |
|
||||
| `auth` | Authentication |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
feat(admin): add export to PDF functionality
|
||||
fix(api): resolve JWT token expiration issue
|
||||
docs(readme): update installation instructions
|
||||
ci(workflow): add staging deployment
|
||||
```
|
||||
|
||||
### Pre-commit Hooks
|
||||
|
||||
The project uses Husky for git hooks:
|
||||
|
||||
- **pre-commit**: Runs ESLint and Gitleaks secret scanning
|
||||
- **commit-msg**: Validates commit message format with commitlint
|
||||
|
||||
### Code Style
|
||||
|
||||
- Follow the existing code style
|
||||
- ESLint configuration is enforced
|
||||
- Run `npm run lint` before committing
|
||||
- Use meaningful commit messages
|
||||
- Fix issues with `npm run lint -- --fix`
|
||||
|
||||
### Component Structure
|
||||
### Testing
|
||||
|
||||
```jsx
|
||||
// Imports at top
|
||||
import { motion } from 'framer-motion';
|
||||
import { Icon } from 'lucide-react';
|
||||
|
||||
// Component definition
|
||||
export default function Component() {
|
||||
return (
|
||||
// JSX
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Your Changes
|
||||
Run tests before submitting:
|
||||
|
||||
```bash
|
||||
# Start dev server
|
||||
npm run dev
|
||||
# Unit tests
|
||||
npm run test:run
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
# Integration tests
|
||||
npm run test:integration
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
# E2E tests (requires running server)
|
||||
npm run test:e2e
|
||||
|
||||
# All tests with coverage
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
## Pull Request Checklist
|
||||
## Pull Request Process
|
||||
|
||||
- [ ] Code follows the existing style
|
||||
- [ ] Linting passes (`npm run lint`)
|
||||
1. Create a feature branch from `master`
|
||||
2. Make your changes following the guidelines above
|
||||
3. Ensure all tests pass
|
||||
4. Ensure lint passes (`npm run lint`)
|
||||
5. Ensure build succeeds (`npm run build`)
|
||||
6. Push to your fork and create a pull request
|
||||
7. Fill out the PR template completely
|
||||
8. Wait for review
|
||||
|
||||
### PR Checklist
|
||||
|
||||
- [ ] Follows contributing guidelines
|
||||
- [ ] Commit messages follow conventional commits
|
||||
- [ ] Lint passes (`npm run lint`)
|
||||
- [ ] Tests pass (`npm run test:run`)
|
||||
- [ ] Build succeeds (`npm run build`)
|
||||
- [ ] Changes are documented in PR description
|
||||
- [ ] Documentation updated if needed
|
||||
|
||||
## Release Process
|
||||
|
||||
Releases are automated via semantic-release:
|
||||
|
||||
1. Merge PR to `master`
|
||||
2. CI runs tests and build
|
||||
3. semantic-release analyzes commits
|
||||
4. Version bumped, changelog updated
|
||||
5. Git tag and release created
|
||||
|
||||
No manual release steps required.
|
||||
|
||||
## Questions?
|
||||
|
||||
Feel free to open an issue for any questions or discussions.
|
||||
Open an issue for questions or discussions about contributions.
|
||||
|
||||
194
README.md
194
README.md
@@ -1,8 +1,8 @@
|
||||
# CV - Tuan-Dat Tran
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
A modern, minimalist CV/Resume single-page application with admin panel and persistent storage.
|
||||
@@ -29,32 +29,33 @@ A modern, minimalist CV/Resume single-page application with admin panel and pers
|
||||
|
||||
- Modern, responsive design
|
||||
- Smooth scroll animations with Framer Motion
|
||||
- Admin panel for easy CV editing (password protected)
|
||||
- Admin panel for CV editing (password protected)
|
||||
- Persistent storage with SQLite
|
||||
- Docker Compose deployment
|
||||
- API-based architecture
|
||||
- RESTful API with OpenAPI documentation
|
||||
- Optional Keycloak integration for SSO
|
||||
- Automated releases with semantic-release
|
||||
|
||||
## Tech Stack
|
||||
|
||||
### Frontend
|
||||
- **React 18** - UI Library
|
||||
- **Vite** - Build Tool
|
||||
- **React 19** - UI Library
|
||||
- **Vite 7** - Build Tool
|
||||
- **Tailwind CSS 4** - Styling
|
||||
- **Framer Motion** - Animations
|
||||
- **Framer Motion 12** - Animations
|
||||
- **Lucide React** - Icons
|
||||
|
||||
### Backend
|
||||
- **Express.js** - API Server
|
||||
- **SQLite** - Database
|
||||
- **better-sqlite3** - SQLite driver
|
||||
- **Express.js 4** - API Server
|
||||
- **Knex.js** - SQL Query Builder
|
||||
- **SQLite** with **better-sqlite3** - Database
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm 9+
|
||||
- Node.js 20+
|
||||
- npm 10+
|
||||
- Docker & Docker Compose (optional)
|
||||
|
||||
### Option 1: Docker Compose (Recommended)
|
||||
@@ -66,6 +67,7 @@ docker-compose up -d
|
||||
# Frontend: http://localhost:5173
|
||||
# Backend API: http://localhost:3001
|
||||
# Admin Panel: http://localhost:5173/admin
|
||||
# API Docs: http://localhost:3001/api/docs
|
||||
```
|
||||
|
||||
### Option 2: Local Development
|
||||
@@ -86,27 +88,28 @@ npm run dev
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Frontend (`.env`):
|
||||
```
|
||||
VITE_API_URL=http://localhost:3001
|
||||
```
|
||||
|
||||
Backend (`backend/.env`):
|
||||
```
|
||||
PORT=3001
|
||||
DB_PATH=./data/cv.db
|
||||
USE_KEYCLOAK=false
|
||||
# Keycloak settings (required if USE_KEYCLOAK=true)
|
||||
AUTH_MODE=simple
|
||||
# Keycloak settings (required if AUTH_MODE=keycloak)
|
||||
KEYCLOAK_URL=https://keycloak.example.com
|
||||
KEYCLOAK_REALM=your-realm
|
||||
KEYCLOAK_CLIENT_ID=cv-admin
|
||||
KEYCLOAK_CLIENT_ID=cv-app
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Pre-commit Setup
|
||||
|
||||
This project uses Gitleaks for secret scanning. Install it before committing:
|
||||
This project uses:
|
||||
- **ESLint** for code linting
|
||||
- **commitlint** for commit message validation
|
||||
- **Husky** for git hooks
|
||||
- **Gitleaks** for secret scanning
|
||||
|
||||
Install Gitleaks before committing:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
@@ -120,152 +123,41 @@ sudo mv gitleaks /usr/local/bin/
|
||||
scoop install gitleaks
|
||||
```
|
||||
|
||||
### API Documentation
|
||||
### Commit Guidelines
|
||||
|
||||
API documentation is available at `/api/docs` when running the server.
|
||||
|
||||
Access the interactive Swagger UI at: `http://localhost:3001/api/docs`
|
||||
|
||||
## Admin Authentication
|
||||
|
||||
### Simple Mode (Default)
|
||||
- A random password is generated and printed to the console on server startup
|
||||
- Look for the "ADMIN PASSWORD" banner in the logs
|
||||
- Enter this password to access the admin panel
|
||||
|
||||
### Keycloak Mode
|
||||
Set `USE_KEYCLOAK=true` and configure Keycloak environment variables:
|
||||
```bash
|
||||
USE_KEYCLOAK=true
|
||||
KEYCLOAK_URL=https://keycloak.example.com
|
||||
KEYCLOAK_REALM=your-realm
|
||||
KEYCLOAK_CLIENT_ID=cv-admin
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/api/cv` | No | Get CV data |
|
||||
| PUT | `/api/cv` | Yes | Update CV data |
|
||||
| GET | `/api/cv/export` | No | Export as JSON |
|
||||
| POST | `/api/cv/import` | Yes | Import JSON data |
|
||||
| GET | `/api/auth/config` | No | Get auth configuration |
|
||||
| POST | `/api/auth/login` | No | Login with password |
|
||||
| GET | `/api/docs` | No | Swagger API documentation |
|
||||
| GET | `/health` | No | Health check |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── src/ # Frontend source
|
||||
│ ├── components/ # CV components
|
||||
│ ├── admin/ # Admin panel
|
||||
│ ├── lib/ # Utilities
|
||||
│ └── App.jsx
|
||||
├── backend/ # Backend source
|
||||
│ ├── routes/ # API routes
|
||||
│ ├── db/ # Database
|
||||
│ └── server.js
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── nginx.conf
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Frontend tests
|
||||
npm run test:run
|
||||
|
||||
# Backend tests
|
||||
cd backend && npm test
|
||||
```
|
||||
|
||||
## 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:
|
||||
This project uses [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
# Examples:
|
||||
feat(ui): add export button
|
||||
fix(api): resolve authentication issue
|
||||
docs(readme): update installation steps
|
||||
```
|
||||
|
||||
#### Commit Types
|
||||
### API Documentation
|
||||
|
||||
| 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 |
|
||||
Interactive Swagger UI available at: `http://localhost:3001/api/docs`
|
||||
|
||||
#### Scopes
|
||||
## Admin Authentication
|
||||
|
||||
Available scopes: `admin`, `api`, `ui`, `docker`, `ci`, `deps`, `release`, `auth`, `skills`, `experience`, `education`, `projects`, `personal`
|
||||
### Simple Mode (Default)
|
||||
- Random password generated on server startup
|
||||
- Password displayed in console logs
|
||||
- JWT token-based authentication
|
||||
|
||||
### Release Workflow
|
||||
|
||||
1. Push to `master` 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 master |
|
||||
| `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 | `master` | semantic-release |
|
||||
| Staging | `staging` | Push to staging |
|
||||
| Nightly | `master` | Daily at 02:00 UTC |
|
||||
|
||||
For detailed documentation, see [Release Engineering Documentation](docs/release-engineering.md).
|
||||
### Keycloak Mode
|
||||
- SSO integration via Keycloak
|
||||
- Configure via `AUTH_MODE=keycloak` and related environment variables
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [Release Engineering](docs/release-engineering.md) | Comprehensive release pipeline documentation |
|
||||
| [Contributing](CONTRIBUTING.md) | Contribution guidelines |
|
||||
| [Security](SECURITY.md) | Security policy |
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
||||
| [Architecture](docs/architecture.md) | System architecture and design patterns |
|
||||
| [Release Engineering](docs/release-engineering.md) | Release and deployment documentation |
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
131
SECURITY.md
131
SECURITY.md
@@ -4,33 +4,124 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| main | :white_check_mark: |
|
||||
| 1.x | :white_check_mark: |
|
||||
| < 1.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take security seriously. If you discover a security vulnerability, please follow these steps:
|
||||
We take security seriously. If you discover a security vulnerability, please follow responsible disclosure.
|
||||
|
||||
1. **Do not** open a public issue
|
||||
2. Email the maintainer directly at `tuan-dat.tran@example.com`
|
||||
3. Include:
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
### How to Report
|
||||
|
||||
### What to Expect
|
||||
**Preferred Method**: Open a security advisory
|
||||
|
||||
- Acknowledgment within 48 hours
|
||||
- Assessment within 7 days
|
||||
- Fix timeline based on severity:
|
||||
- Critical: 24-72 hours
|
||||
- High: 1 week
|
||||
- Medium/Low: Next release
|
||||
1. Go to the repository
|
||||
2. Click "Security" tab
|
||||
3. Click "Report a vulnerability"
|
||||
4. Fill out the advisory form
|
||||
|
||||
### Disclosure Policy
|
||||
**Alternative**: Email the maintainer directly (if available in profile)
|
||||
|
||||
- Please allow time for the fix before public disclosure
|
||||
### What to Include
|
||||
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Affected versions
|
||||
- Potential impact
|
||||
- Suggested fix (if available)
|
||||
|
||||
### Response Timeline
|
||||
|
||||
| Action | Timeline |
|
||||
|--------|----------|
|
||||
| Initial response | Within 48 hours |
|
||||
| Vulnerability assessment | Within 7 days |
|
||||
| Fix timeline based on severity | See below |
|
||||
|
||||
### Fix Timeline by Severity
|
||||
|
||||
| Severity | Timeline |
|
||||
|----------|----------|
|
||||
| Critical | 24-72 hours |
|
||||
| High | 1 week |
|
||||
| Medium | Next release |
|
||||
| Low | Next release |
|
||||
|
||||
## Security Measures
|
||||
|
||||
This project implements several security measures:
|
||||
|
||||
### Code Quality
|
||||
|
||||
- ESLint for code analysis
|
||||
- Automated testing before merge
|
||||
- Code review required for all changes
|
||||
|
||||
### Secret Management
|
||||
|
||||
- Gitleaks pre-commit hook prevents committing secrets
|
||||
- Environment variables for sensitive configuration
|
||||
- No secrets in version control
|
||||
|
||||
### Authentication
|
||||
|
||||
- JWT tokens with configurable expiration
|
||||
- Password hashed in database
|
||||
- Token-based API authentication
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Regular dependency audits via `npm audit`
|
||||
- Dependabot for automated updates (if enabled)
|
||||
|
||||
## Best Practices for Contributors
|
||||
|
||||
### Do Not Commit
|
||||
|
||||
- API keys or tokens
|
||||
- Database credentials
|
||||
- JWT secrets
|
||||
- Password files
|
||||
- Private keys or certificates
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Store sensitive configuration in environment variables:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PORT=3001
|
||||
DB_PATH=./data/cv.db
|
||||
AUTH_MODE=simple
|
||||
JWT_SECRET=your-secret-here
|
||||
|
||||
# Keycloak (if used)
|
||||
KEYCLOAK_URL=https://keycloak.example.com
|
||||
KEYCLOAK_REALM=your-realm
|
||||
KEYCLOAK_CLIENT_ID=cv-app
|
||||
```
|
||||
|
||||
### Running Security Audit
|
||||
|
||||
```bash
|
||||
# Check for vulnerable dependencies
|
||||
npm audit
|
||||
|
||||
# Fix vulnerabilities
|
||||
npm audit fix
|
||||
|
||||
# Check backend dependencies
|
||||
cd backend && npm audit
|
||||
```
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
- Please allow reasonable time for the fix before public disclosure
|
||||
- Coordinated disclosure is appreciated
|
||||
- Credit will be given in the fix commit
|
||||
- Credit will be given in the fix commit (if desired)
|
||||
|
||||
Thank you for helping keep this project secure!
|
||||
## Contact
|
||||
|
||||
For security concerns, use the vulnerability reporting process above.
|
||||
|
||||
Thank you for helping keep this project secure!
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
# DevOps Features Design
|
||||
|
||||
**Date:** 2026-02-20
|
||||
**Status:** Approved
|
||||
**Author:** Claude (AI Assistant)
|
||||
|
||||
## Overview
|
||||
|
||||
Implement four DevOps features for the CV application:
|
||||
1. **Swagger/OpenAPI Documentation** - Interactive API docs at `/api/docs`
|
||||
2. **Database Migrations** - Knex.js-based migration system with auto-migration
|
||||
3. **Quality Gates** - Code coverage, bundle size, npm audit, Lighthouse CI
|
||||
4. **Secret Scanning** - Gitleaks pre-commit hook
|
||||
|
||||
## 1. Swagger/OpenAPI Documentation
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
Route Files (JSDoc comments)
|
||||
│
|
||||
▼
|
||||
swagger-jsdoc
|
||||
(generates spec at runtime)
|
||||
│
|
||||
▼
|
||||
swagger-ui-express
|
||||
│
|
||||
▼
|
||||
GET /api/docs → Interactive Swagger UI
|
||||
GET /api/docs.json → Raw OpenAPI spec
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/api/docs` | Swagger UI interface |
|
||||
| GET | `/api/docs.json` | Raw OpenAPI JSON spec |
|
||||
|
||||
### Implementation
|
||||
|
||||
**Dependencies:**
|
||||
- `swagger-jsdoc` - Generate OpenAPI spec from JSDoc
|
||||
- `swagger-ui-express` - Serve Swagger UI
|
||||
|
||||
**JSDoc format in route files:**
|
||||
```javascript
|
||||
/**
|
||||
* @openapi
|
||||
* /api/cv:
|
||||
* get:
|
||||
* summary: Get CV data
|
||||
* tags: [CV]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV data
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CVData'
|
||||
* 404:
|
||||
* description: CV data not found
|
||||
*/
|
||||
router.get('/', (req, res) => { ... });
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /api/cv:
|
||||
* put:
|
||||
* summary: Update CV data
|
||||
* tags: [CV]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/CVData'
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV data updated
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
*/
|
||||
router.put('/', authMiddleware, (req, res) => { ... });
|
||||
```
|
||||
|
||||
**Swagger setup:**
|
||||
```javascript
|
||||
// backend/routes/docs.js
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'CV API',
|
||||
version: '1.0.0',
|
||||
description: 'API for CV/Resume management',
|
||||
},
|
||||
servers: [
|
||||
{ url: '/api', description: 'API server' }
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
CVData: { /* schema definition */ },
|
||||
},
|
||||
},
|
||||
},
|
||||
apis: ['./routes/*.js'],
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
|
||||
router.use('/docs', swaggerUi.serve, swaggerUi.setup(specs));
|
||||
router.get('/docs.json', (req, res) => res.json(specs));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Database Migrations (Knex.js)
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
backend/
|
||||
├── knexfile.js # Knex configuration
|
||||
├── migrations/
|
||||
│ ├── 20260220000001_initial_schema.js
|
||||
│ └── 20260220000002_add_auth_tables.js
|
||||
├── seeds/
|
||||
│ └── initial_cv_data.js
|
||||
└── db/
|
||||
├── connection.js # Knex instance
|
||||
└── init.js # Auto-migration on startup
|
||||
```
|
||||
|
||||
### Knex Configuration
|
||||
|
||||
```javascript
|
||||
// knexfile.js
|
||||
export default {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: process.env.DB_PATH || './data/cv.db'
|
||||
},
|
||||
migrations: {
|
||||
directory: './migrations',
|
||||
tableName: 'knex_migrations'
|
||||
},
|
||||
seeds: {
|
||||
directory: './seeds'
|
||||
},
|
||||
useNullAsDefault: true
|
||||
};
|
||||
```
|
||||
|
||||
### Migration File Format
|
||||
|
||||
```javascript
|
||||
// migrations/20260220000001_initial_schema.js
|
||||
export async function up(knex) {
|
||||
await knex.schema.createTable('cv_data', (table) => {
|
||||
table.integer('id').primary().checkTo('=', 1);
|
||||
table.text('data').notNullable();
|
||||
table.datetime('updated_at').defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex) {
|
||||
await knex.schema.dropTableIfExists('cv_data');
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Migration on Startup
|
||||
|
||||
```javascript
|
||||
// db/init.js
|
||||
import knex from 'knex';
|
||||
import config from '../knexfile.js';
|
||||
|
||||
let db = null;
|
||||
|
||||
export async function getDB() {
|
||||
if (!db) {
|
||||
db = knex(config);
|
||||
await db.migrate.latest();
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function closeDB() {
|
||||
if (db) {
|
||||
await db.destroy();
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Commands
|
||||
|
||||
```bash
|
||||
# Create new migration
|
||||
npx knex migrate:make migration_name --knexfile knexfile.js
|
||||
|
||||
# Run migrations manually
|
||||
npx knex migrate:latest --knexfile knexfile.js
|
||||
|
||||
# Rollback last migration
|
||||
npx knex migrate:rollback --knexfile knexfile.js
|
||||
|
||||
# Run seeds
|
||||
npx knex seed:run --knexfile knexfile.js
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `knex` - SQL query builder and migrations
|
||||
|
||||
---
|
||||
|
||||
## 3. Quality Gates
|
||||
|
||||
### Overview
|
||||
|
||||
| Gate | Tool | Threshold | Action |
|
||||
|------|------|-----------|--------|
|
||||
| Code Coverage | vitest + Codecov | 80% minimum | Fail CI |
|
||||
| Bundle Size | bundlesize | 500KB max | Fail CI |
|
||||
| npm audit | npm audit | Moderate+ | Fail CI |
|
||||
| Lighthouse | @lhci/cli | Performance 90+ | Fail CI |
|
||||
|
||||
### CI Workflow Integration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml additions
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
# Code Coverage
|
||||
- name: Run tests with coverage
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
|
||||
# Bundle Size
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check bundle size
|
||||
run: npx bundlesize
|
||||
|
||||
# npm audit
|
||||
- name: Security audit
|
||||
run: npm audit --audit-level=moderate
|
||||
|
||||
lighthouse:
|
||||
runs-on: ubuntu-latest
|
||||
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: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
run: npm run test:lighthouse
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
**package.json:**
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./dist/assets/*.js",
|
||||
"maxSize": "500kb"
|
||||
},
|
||||
{
|
||||
"path": "./dist/assets/*.css",
|
||||
"maxSize": "100kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vitest.config.js:**
|
||||
```javascript
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'lcov', 'html'],
|
||||
exclude: ['node_modules/', 'tests/'],
|
||||
lines: 80,
|
||||
functions: 80,
|
||||
branches: 80,
|
||||
statements: 80
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**.lighthouserc.json:**
|
||||
```json
|
||||
{
|
||||
"ci": {
|
||||
"collect": {
|
||||
"url": ["http://localhost:4173/"],
|
||||
"numberOfRuns": 3
|
||||
},
|
||||
"assert": {
|
||||
"assertions": {
|
||||
"categories:performance": ["error", { "minScore": 0.9 }],
|
||||
"categories:accessibility": ["warn", { "minScore": 0.9 }],
|
||||
"categories:best-practices": ["warn", { "minScore": 0.9 }]
|
||||
}
|
||||
},
|
||||
"upload": {
|
||||
"target": "temporary-public-storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `@vitest/coverage-v8` - Coverage reporter
|
||||
- `bundlesize` - Bundle size checking
|
||||
|
||||
### Secrets Required
|
||||
|
||||
| Secret | Description |
|
||||
|--------|-------------|
|
||||
| `CODECOV_TOKEN` | Codecov upload token |
|
||||
|
||||
---
|
||||
|
||||
## 4. Secret Scanning (Gitleaks)
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
Developer stages files
|
||||
│
|
||||
▼
|
||||
git commit
|
||||
│
|
||||
▼
|
||||
.husky/pre-commit
|
||||
│
|
||||
├─► npm run lint
|
||||
│
|
||||
└─► gitleaks protect --staged
|
||||
│
|
||||
▼
|
||||
Secrets found?
|
||||
│ │
|
||||
Yes No
|
||||
│ │
|
||||
▼ ▼
|
||||
Block commit Allow commit
|
||||
```
|
||||
|
||||
### Pre-commit Hook
|
||||
|
||||
```bash
|
||||
# .husky/pre-commit
|
||||
npm run lint
|
||||
|
||||
# Gitleaks secret scanning
|
||||
if command -v gitleaks &> /dev/null; then
|
||||
gitleaks protect --verbose --staged
|
||||
if [ $? -eq 1 ]; then
|
||||
echo ""
|
||||
echo "❌ Secrets detected in staged files!"
|
||||
echo "Please remove sensitive data before committing."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Gitleaks Configuration
|
||||
|
||||
```toml
|
||||
# .gitleaks.toml
|
||||
title = "Gitleaks Configuration"
|
||||
|
||||
[extend]
|
||||
useDefault = true
|
||||
|
||||
[[allowlists]]
|
||||
description = "Allowlisted files"
|
||||
paths = [
|
||||
'package-lock.json',
|
||||
'backend/package-lock.json',
|
||||
'.*\\.md$'
|
||||
]
|
||||
|
||||
[[allowlists]]
|
||||
description = "Allowlisted patterns"
|
||||
regexes = [
|
||||
'VITE_API_URL.*localhost',
|
||||
]
|
||||
```
|
||||
|
||||
### Developer Setup
|
||||
|
||||
Gitleaks must be installed on developer machines:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install gitleaks
|
||||
|
||||
# Linux
|
||||
# Download from https://github.com/gitleaks/gitleaks/releases
|
||||
# Or use go install
|
||||
go install github.com/gitleaks/gitleaks/v8@latest
|
||||
|
||||
# Windows
|
||||
scoop install gitleaks
|
||||
# Or
|
||||
choco install gitleaks
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Add to README or CONTRIBUTING.md:
|
||||
|
||||
```markdown
|
||||
### Pre-commit Setup
|
||||
|
||||
This project uses Gitleaks for secret scanning. Install it before committing:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install gitleaks
|
||||
|
||||
# Linux
|
||||
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.18.1/gitleaks_8.18.1_linux_x64.tar.gz | tar -xz
|
||||
sudo mv gitleaks /usr/local/bin/
|
||||
```
|
||||
|
||||
The pre-commit hook will automatically scan for secrets before each commit.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### New Dependencies
|
||||
|
||||
**Backend:**
|
||||
- `knex` - Database migrations and query builder
|
||||
- `swagger-jsdoc` - OpenAPI spec generation
|
||||
- `swagger-ui-express` - Swagger UI middleware
|
||||
|
||||
**Frontend:**
|
||||
- `@vitest/coverage-v8` - Coverage reporting
|
||||
- `bundlesize` - Bundle size checking
|
||||
|
||||
**Dev:**
|
||||
- `gitleaks` (binary, not npm package)
|
||||
|
||||
### New Files
|
||||
|
||||
```
|
||||
backend/
|
||||
├── knexfile.js
|
||||
├── migrations/
|
||||
│ └── 20260220000001_initial_schema.js
|
||||
├── seeds/
|
||||
│ └── initial_cv_data.js
|
||||
├── routes/
|
||||
│ └── docs.js
|
||||
└── db/
|
||||
└── connection.js
|
||||
|
||||
.gitleaks.toml
|
||||
.lighthouserc.json
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
|
||||
```
|
||||
package.json # Add scripts, bundlesize config
|
||||
vitest.config.js # Add coverage config
|
||||
.github/workflows/ci.yml # Add quality gates
|
||||
.husky/pre-commit # Add gitleaks
|
||||
backend/db/init.js # Use Knex instead of better-sqlite3 directly
|
||||
backend/routes/cv.js # Add JSDoc comments
|
||||
backend/routes/auth.js # Add JSDoc comments
|
||||
README.md # Add gitleaks setup instructions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Implementation Checklist
|
||||
|
||||
- [ ] Install dependencies
|
||||
- [ ] Configure Knex and create initial migration
|
||||
- [ ] Add Swagger UI route
|
||||
- [ ] Add JSDoc comments to all API routes
|
||||
- [ ] Configure Codecov (add CODECOV_TOKEN secret)
|
||||
- [ ] Add bundlesize configuration
|
||||
- [ ] Update CI workflow with quality gates
|
||||
- [ ] Add gitleaks to pre-commit hook
|
||||
- [ ] Update documentation
|
||||
@@ -1,948 +0,0 @@
|
||||
# DevOps Features Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Implement Swagger docs, Knex migrations, quality gates, and secret scanning.
|
||||
|
||||
**Architecture:** Swagger UI served at /api/docs with JSDoc-annotated routes. Knex.js handles database migrations with auto-migration on startup. Quality gates integrated into CI workflow. Gitleaks pre-commit hook for secret scanning.
|
||||
|
||||
**Tech Stack:** swagger-jsdoc, swagger-ui-express, knex, vitest coverage, bundlesize, gitleaks
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Install Backend Dependencies
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/package.json`
|
||||
|
||||
**Step 1: Install Knex for migrations**
|
||||
|
||||
```bash
|
||||
cd backend && npm install knex swagger-jsdoc swagger-ui-express
|
||||
```
|
||||
|
||||
**Step 2: Verify installation**
|
||||
|
||||
Run: `cd backend && npm list knex swagger-jsdoc swagger-ui-express --depth=0`
|
||||
Expected: All packages listed with versions
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/package.json backend/package-lock.json
|
||||
git commit -m "build(deps): add knex, swagger-jsdoc, swagger-ui-express"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Install Frontend Quality Gate Dependencies
|
||||
|
||||
**Files:**
|
||||
- Modify: `package.json`
|
||||
|
||||
**Step 1: Install coverage and bundlesize packages**
|
||||
|
||||
```bash
|
||||
npm install --save-dev @vitest/coverage-v8 bundlesize
|
||||
```
|
||||
|
||||
**Step 2: Verify installation**
|
||||
|
||||
Run: `npm list @vitest/coverage-v8 bundlesize --depth=0`
|
||||
Expected: All packages listed with versions
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json package-lock.json
|
||||
git commit -m "build(deps): add coverage and bundlesize for quality gates"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Create Knex Configuration
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/knexfile.js`
|
||||
|
||||
**Step 1: Create knexfile.js**
|
||||
|
||||
Create `backend/knexfile.js`:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: process.env.DB_PATH || './data/cv.db'
|
||||
},
|
||||
migrations: {
|
||||
directory: './migrations',
|
||||
tableName: 'knex_migrations'
|
||||
},
|
||||
seeds: {
|
||||
directory: './seeds'
|
||||
},
|
||||
useNullAsDefault: true
|
||||
};
|
||||
```
|
||||
|
||||
**Step 2: Verify syntax**
|
||||
|
||||
Run: `cd backend && node -e "import('./knexfile.js').then(m => console.log(m.default))"`
|
||||
Expected: Knex config object printed
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/knexfile.js
|
||||
git commit -m "feat(db): add knex configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Create Initial Migration
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/migrations/20260220000001_initial_schema.js`
|
||||
|
||||
**Step 1: Create migrations directory**
|
||||
|
||||
```bash
|
||||
mkdir -p backend/migrations
|
||||
```
|
||||
|
||||
**Step 2: Create initial migration**
|
||||
|
||||
Create `backend/migrations/20260220000001_initial_schema.js`:
|
||||
|
||||
```javascript
|
||||
export async function up(knex) {
|
||||
await knex.schema.createTable('cv_data', (table) => {
|
||||
table.integer('id').primary();
|
||||
table.text('data').notNullable();
|
||||
table.datetime('updated_at').defaultTo(knex.fn.now());
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex) {
|
||||
await knex.schema.dropTableIfExists('cv_data');
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/migrations/
|
||||
git commit -m "feat(db): add initial schema migration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Create Seed File
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/seeds/initial_cv_data.js`
|
||||
|
||||
**Step 1: Create seeds directory**
|
||||
|
||||
```bash
|
||||
mkdir -p backend/seeds
|
||||
```
|
||||
|
||||
**Step 2: Create seed file**
|
||||
|
||||
Create `backend/seeds/initial_cv_data.js`:
|
||||
|
||||
```javascript
|
||||
export async function seed(knex) {
|
||||
const existing = await knex('cv_data').where({ id: 1 }).first();
|
||||
|
||||
if (!existing) {
|
||||
await knex('cv_data').insert({
|
||||
id: 1,
|
||||
data: JSON.stringify({
|
||||
personal: {
|
||||
name: "Tuan-Dat Tran",
|
||||
title: "Junior DevOps Engineer",
|
||||
intro: "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur, Container-Orchestrierung und automatisierte Deployment-Pipelines.",
|
||||
email: "tuan-dat.tran@example.com",
|
||||
github: "https://github.com/tuan-dat-tran",
|
||||
linkedin: "https://linkedin.com/in/tuan-dat-tran",
|
||||
location: "Deutschland"
|
||||
},
|
||||
experience: [],
|
||||
skills: {},
|
||||
education: [],
|
||||
projects: []
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/seeds/
|
||||
git commit -m "feat(db): add initial cv data seed"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update Database Init to Use Knex
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/db/init.js`
|
||||
|
||||
**Step 1: Replace init.js with Knex-based version**
|
||||
|
||||
Replace `backend/db/init.js` with:
|
||||
|
||||
```javascript
|
||||
import knex from 'knex';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import config from '../knexfile.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
let db = null;
|
||||
|
||||
export async function getDB() {
|
||||
if (!db) {
|
||||
db = knex(config);
|
||||
await db.migrate.latest();
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function initDB() {
|
||||
const db = await getDB();
|
||||
|
||||
const existing = await db('cv_data').where({ id: 1 }).first();
|
||||
if (!existing) {
|
||||
await db('cv_data').insert({
|
||||
id: 1,
|
||||
data: JSON.stringify({
|
||||
personal: {
|
||||
name: "Tuan-Dat Tran",
|
||||
title: "Junior DevOps Engineer",
|
||||
intro: "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur.",
|
||||
email: "tuan-dat.tran@example.com",
|
||||
github: "https://github.com/tuan-dat-tran",
|
||||
linkedin: "https://linkedin.com/in/tuan-dat-tran",
|
||||
location: "Deutschland"
|
||||
},
|
||||
experience: [],
|
||||
skills: {},
|
||||
education: [],
|
||||
projects: []
|
||||
})
|
||||
});
|
||||
console.log('Initialized database with default CV data');
|
||||
}
|
||||
|
||||
console.log(`Database initialized`);
|
||||
}
|
||||
|
||||
export async function closeDB() {
|
||||
if (db) {
|
||||
await db.destroy();
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Verify syntax**
|
||||
|
||||
Run: `cd backend && node -c db/init.js`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/db/init.js
|
||||
git commit -m "refactor(db): use knex for database operations"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Update CV Routes for Knex
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/routes/cv.js`
|
||||
|
||||
**Step 1: Update routes to use Knex**
|
||||
|
||||
Replace `backend/routes/cv.js` with:
|
||||
|
||||
```javascript
|
||||
import { Router } from 'express';
|
||||
import { getDB } from '../db/init.js';
|
||||
import { authMiddleware } from '../middleware/auth.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /cv:
|
||||
* get:
|
||||
* summary: Get CV data
|
||||
* tags: [CV]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV data
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* 404:
|
||||
* description: CV data not found
|
||||
*/
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const db = await getDB();
|
||||
const row = await db('cv_data').where({ id: 1 }).first();
|
||||
|
||||
if (!row) {
|
||||
return res.status(404).json({ error: 'CV data not found' });
|
||||
}
|
||||
|
||||
res.json(JSON.parse(row.data));
|
||||
} catch (error) {
|
||||
console.error('Error fetching CV:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch CV data' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /cv:
|
||||
* put:
|
||||
* summary: Update CV data
|
||||
* tags: [CV]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV data updated
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
*/
|
||||
router.put('/', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
return res.status(400).json({ error: 'Invalid CV data' });
|
||||
}
|
||||
|
||||
if (!data.personal?.name) {
|
||||
return res.status(400).json({ error: 'personal.name is required' });
|
||||
}
|
||||
|
||||
const db = await getDB();
|
||||
await db('cv_data').where({ id: 1 }).update({
|
||||
data: JSON.stringify(data),
|
||||
updated_at: db.fn.now()
|
||||
});
|
||||
|
||||
res.json({ success: true, message: 'CV data updated' });
|
||||
} catch (error) {
|
||||
console.error('Error updating CV:', error);
|
||||
res.status(500).json({ error: 'Failed to update CV data' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /cv/export:
|
||||
* get:
|
||||
* summary: Export CV data as JSON file
|
||||
* tags: [CV]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV JSON file
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
*/
|
||||
router.get('/export', async (req, res) => {
|
||||
try {
|
||||
const db = await getDB();
|
||||
const row = await db('cv_data').where({ id: 1 }).first();
|
||||
|
||||
if (!row) {
|
||||
return res.status(404).json({ error: 'CV data not found' });
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="cv.json"');
|
||||
res.send(row.data);
|
||||
} catch (error) {
|
||||
console.error('Error exporting CV:', error);
|
||||
res.status(500).json({ error: 'Failed to export CV data' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /cv/import:
|
||||
* post:
|
||||
* summary: Import CV data from JSON
|
||||
* tags: [CV]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: CV data imported
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
*/
|
||||
router.post('/import', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
return res.status(400).json({ error: 'Invalid CV data' });
|
||||
}
|
||||
|
||||
if (!data.personal?.name) {
|
||||
return res.status(400).json({ error: 'personal.name is required' });
|
||||
}
|
||||
|
||||
const db = await getDB();
|
||||
await db('cv_data').where({ id: 1 }).update({
|
||||
data: JSON.stringify(data),
|
||||
updated_at: db.fn.now()
|
||||
});
|
||||
|
||||
res.json({ success: true, message: 'CV data imported' });
|
||||
} catch (error) {
|
||||
console.error('Error importing CV:', error);
|
||||
res.status(500).json({ error: 'Failed to import CV data' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/routes/cv.js
|
||||
git commit -m "refactor(api): update cv routes for knex with openapi docs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Create Swagger Docs Route
|
||||
|
||||
**Files:**
|
||||
- Create: `backend/routes/docs.js`
|
||||
|
||||
**Step 1: Create docs route**
|
||||
|
||||
Create `backend/routes/docs.js`:
|
||||
|
||||
```javascript
|
||||
import { Router } from 'express';
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'CV API',
|
||||
version: '1.0.0',
|
||||
description: 'API for CV/Resume management',
|
||||
},
|
||||
servers: [
|
||||
{ url: '/api', description: 'API server' }
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
apis: ['./routes/*.js'],
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
|
||||
router.use('/docs', swaggerUi.serve, swaggerUi.setup(specs, {
|
||||
customCss: '.swagger-ui .topbar { display: none }',
|
||||
customSiteTitle: 'CV API Documentation'
|
||||
}));
|
||||
|
||||
router.get('/docs.json', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(specs);
|
||||
});
|
||||
|
||||
export default router;
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/routes/docs.js
|
||||
git commit -m "feat(api): add swagger docs route"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Add OpenAPI Docs to Auth Routes
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/routes/auth.js`
|
||||
|
||||
**Step 1: Add JSDoc comments to auth routes**
|
||||
|
||||
Read `backend/routes/auth.js` and add OpenAPI JSDoc comments to each endpoint:
|
||||
|
||||
- Add `@openapi` comments for `/auth/config`, `/auth/login`
|
||||
- Document request bodies and responses
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/routes/auth.js
|
||||
git commit -m "docs(api): add openapi docs to auth routes"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Register Swagger Route in Server
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/server.js`
|
||||
|
||||
**Step 1: Import and use docs route**
|
||||
|
||||
Add to `backend/server.js`:
|
||||
|
||||
```javascript
|
||||
import docsRoutes from './routes/docs.js';
|
||||
|
||||
// After other route imports
|
||||
app.use('/api', docsRoutes);
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/server.js
|
||||
git commit -m "feat(api): register swagger docs route"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: Configure Vitest Coverage
|
||||
|
||||
**Files:**
|
||||
- Modify: `vitest.config.js`
|
||||
|
||||
**Step 1: Add coverage configuration**
|
||||
|
||||
Update `vitest.config.js` to include coverage settings:
|
||||
|
||||
```javascript
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'lcov', 'html'],
|
||||
exclude: [
|
||||
'node_modules/',
|
||||
'tests/',
|
||||
'**/*.test.js',
|
||||
'**/*.config.js'
|
||||
],
|
||||
lines: 80,
|
||||
functions: 80,
|
||||
branches: 80,
|
||||
statements: 80
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Add coverage script to package.json**
|
||||
|
||||
Add script: `"test:coverage": "vitest run --coverage"`
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add vitest.config.js package.json
|
||||
git commit -m "feat(ci): add coverage configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 12: Configure Bundle Size Checking
|
||||
|
||||
**Files:**
|
||||
- Modify: `package.json`
|
||||
|
||||
**Step 1: Add bundlesize configuration**
|
||||
|
||||
Add to `package.json`:
|
||||
|
||||
```json
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./dist/assets/*.js",
|
||||
"maxSize": "500kb"
|
||||
},
|
||||
{
|
||||
"path": "./dist/assets/*.css",
|
||||
"maxSize": "100kb"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Step 2: Add bundle check script**
|
||||
|
||||
Add script: `"bundle:check": "npm run build && bundlesize"`
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add package.json
|
||||
git commit -m "feat(ci): add bundlesize configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 13: Create Lighthouse CI Configuration
|
||||
|
||||
**Files:**
|
||||
- Create: `.lighthouserc.json`
|
||||
|
||||
**Step 1: Create lighthouse config**
|
||||
|
||||
Create `.lighthouserc.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ci": {
|
||||
"collect": {
|
||||
"url": ["http://localhost:4173/cv/"],
|
||||
"numberOfRuns": 3
|
||||
},
|
||||
"assert": {
|
||||
"assertions": {
|
||||
"categories:performance": ["error", { "minScore": 0.8 }],
|
||||
"categories:accessibility": ["warn", { "minScore": 0.9 }],
|
||||
"categories:best-practices": ["warn", { "minScore": 0.9 }]
|
||||
}
|
||||
},
|
||||
"upload": {
|
||||
"target": "temporary-public-storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add .lighthouserc.json
|
||||
git commit -m "feat(ci): add lighthouse ci configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 14: Add Quality Gates to CI Workflow
|
||||
|
||||
**Files:**
|
||||
- Modify: `.github/workflows/ci.yml`
|
||||
|
||||
**Step 1: Add quality job**
|
||||
|
||||
Add a new `quality` job to `.github/workflows/ci.yml`:
|
||||
|
||||
```yaml
|
||||
quality:
|
||||
name: Quality Gates
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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: Run tests with coverage
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check bundle size
|
||||
run: npx bundlesize
|
||||
|
||||
- name: Security audit
|
||||
run: npm audit --audit-level=moderate
|
||||
continue-on-error: true
|
||||
```
|
||||
|
||||
**Step 2: Update lighthouse job**
|
||||
|
||||
Update the existing `lighthouse` job to use the new config and enforce thresholds.
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .github/workflows/ci.yml
|
||||
git commit -m "feat(ci): add quality gates to ci workflow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 15: Create Gitleaks Configuration
|
||||
|
||||
**Files:**
|
||||
- Create: `.gitleaks.toml`
|
||||
|
||||
**Step 1: Create gitleaks config**
|
||||
|
||||
Create `.gitleaks.toml`:
|
||||
|
||||
```toml
|
||||
title = "Gitleaks Configuration"
|
||||
|
||||
[extend]
|
||||
useDefault = true
|
||||
|
||||
[[allowlists]]
|
||||
description = "Allowlisted files"
|
||||
paths = [
|
||||
'package-lock.json',
|
||||
'backend/package-lock.json'
|
||||
]
|
||||
|
||||
[[allowlists]]
|
||||
description = "Test secrets"
|
||||
regexes = [
|
||||
'test.*password',
|
||||
'test.*secret',
|
||||
'test.*token'
|
||||
]
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add .gitleaks.toml
|
||||
git commit -m "feat(security): add gitleaks configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 16: Add Gitleaks to Pre-commit Hook
|
||||
|
||||
**Files:**
|
||||
- Modify: `.husky/pre-commit`
|
||||
|
||||
**Step 1: Update pre-commit hook**
|
||||
|
||||
Update `.husky/pre-commit` to:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
|
||||
# Gitleaks secret scanning
|
||||
if command -v gitleaks &> /dev/null; then
|
||||
gitleaks protect --verbose --staged
|
||||
if [ $? -eq 1 ]; then
|
||||
echo ""
|
||||
echo "❌ Secrets detected in staged files!"
|
||||
echo "Please remove sensitive data before committing."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add .husky/pre-commit
|
||||
git commit -m "feat(security): add gitleaks to pre-commit hook"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 17: Update Backend Tests for Knex
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/__tests__/api.test.js`
|
||||
|
||||
**Step 1: Update tests to use async/await with Knex**
|
||||
|
||||
Update the test file to work with the new async Knex-based API.
|
||||
|
||||
**Step 2: Run tests**
|
||||
|
||||
Run: `cd backend && npm test`
|
||||
Expected: All tests pass
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/__tests__/
|
||||
git commit -m "test(api): update tests for knex"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 18: Update Documentation
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
|
||||
**Step 1: Add gitleaks setup instructions**
|
||||
|
||||
Add to README.md under a new "Development Setup" section:
|
||||
|
||||
```markdown
|
||||
### Pre-commit Setup
|
||||
|
||||
This project uses Gitleaks for secret scanning. Install it before committing:
|
||||
|
||||
\`\`\`bash
|
||||
# macOS
|
||||
brew install gitleaks
|
||||
|
||||
# Linux
|
||||
curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_linux_x64.tar.gz | tar -xz
|
||||
sudo mv gitleaks /usr/local/bin/
|
||||
|
||||
# Windows
|
||||
scoop install gitleaks
|
||||
\`\`\`
|
||||
|
||||
### API Documentation
|
||||
|
||||
API documentation is available at `/api/docs` when running the server.
|
||||
```
|
||||
|
||||
**Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add README.md
|
||||
git commit -m "docs(readme): add gitleaks setup and api docs info"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 19: 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: Run backend tests**
|
||||
|
||||
```bash
|
||||
cd backend && npm test
|
||||
```
|
||||
|
||||
Expected: All tests pass
|
||||
|
||||
**Step 4: Test coverage**
|
||||
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
Expected: Coverage report generated
|
||||
|
||||
**Step 5: Build and check bundle**
|
||||
|
||||
```bash
|
||||
npm run build && npx bundlesize
|
||||
```
|
||||
|
||||
Expected: Bundle sizes within limits
|
||||
|
||||
---
|
||||
|
||||
## Task 20: Final Commit and Summary
|
||||
|
||||
**Step 1: Verify all files are committed**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
Expected: Nothing to commit
|
||||
|
||||
**Step 2: View commit history**
|
||||
|
||||
```bash
|
||||
git log --oneline -20
|
||||
```
|
||||
|
||||
**Step 3: Summary**
|
||||
|
||||
DevOps features implemented:
|
||||
- **Swagger UI** at `/api/docs` with OpenAPI 3.0 spec
|
||||
- **Knex migrations** with auto-migration on startup
|
||||
- **Quality gates**: coverage, bundle size, npm audit, Lighthouse CI
|
||||
- **Secret scanning**: Gitleaks pre-commit hook
|
||||
|
||||
---
|
||||
|
||||
## Post-Implementation Checklist
|
||||
|
||||
- [ ] Add `CODECOV_TOKEN` secret to GitHub
|
||||
- [ ] Test Swagger UI at `/api/docs`
|
||||
- [ ] Verify migrations run on startup
|
||||
- [ ] Install gitleaks on developer machine
|
||||
- [ ] Run full CI pipeline to verify quality gates
|
||||
@@ -1,382 +0,0 @@
|
||||
# Release Engineering Design
|
||||
|
||||
**Date:** 2026-02-20
|
||||
**Status:** Approved
|
||||
**Author:** Claude (AI Assistant)
|
||||
|
||||
## Overview
|
||||
|
||||
Implement a comprehensive release engineering pipeline for the CV application using semantic-release with Docker multi-registry publishing, automated changelog generation, and multi-environment deployment (production, staging, nightly).
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Versioning:** Semantic Versioning with Conventional Commits
|
||||
- **Changelog:** Automated from conventional commit messages
|
||||
- **Release Trigger:** Continuous Delivery (automatic from main branch)
|
||||
- **Deployment Targets:** Docker Hub + GitHub Container Registry (GHCR)
|
||||
- **Environments:** Production, Staging, Nightly/Edge
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ GitHub Repository │
|
||||
│ │
|
||||
┌──────────┐ │ main ─────────────────────────────────────────────────► │ Production Release
|
||||
│ Developer│───────►│ │ │ ├─ Git tag v1.0.0
|
||||
└──────────┘ │ └─────► semantic-release │ ├─ GitHub Release
|
||||
│ ├─ version bump │ ├─ Docker Hub: latest, v1.0.0
|
||||
│ ├─ CHANGELOG.md │ └─ GHCR: latest, v1.0.0
|
||||
│ ├─ git tag │
|
||||
│ └─ GitHub release │
|
||||
│ │
|
||||
│ staging ───────► Deploy to staging environment │ Staging Deployment
|
||||
│ (Docker tag: staging) │
|
||||
│ │
|
||||
│ nightly ────────► Build nightly from main │ Nightly/Edge Builds
|
||||
│ (cron job) (Docker tag: nightly, edge) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Versioning Strategy
|
||||
|
||||
### Semantic Versioning (SemVer)
|
||||
|
||||
Version format: `MAJOR.MINOR.PATCH` (e.g., `1.2.3`)
|
||||
|
||||
| Commit Type | Version Bump | Example |
|
||||
|-------------|--------------|---------|
|
||||
| `feat:` | Minor | 1.0.0 → 1.1.0 |
|
||||
| `fix:` | Patch | 1.0.0 → 1.0.1 |
|
||||
| `feat!:` or `BREAKING CHANGE:` | Major | 1.0.0 → 2.0.0 |
|
||||
| `chore:`, `docs:`, `test:`, `style:`, `refactor:`, `perf:` | None | No release |
|
||||
|
||||
### Conventional Commits Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `feat(admin): add password protection for admin panel`
|
||||
- `fix(skills): prevent focus loss when editing category headers`
|
||||
- `docs(readme): update installation instructions`
|
||||
- `feat(api)!: remove deprecated endpoints`
|
||||
|
||||
### Commit Types
|
||||
|
||||
| Type | Description | Release? |
|
||||
|------|-------------|----------|
|
||||
| `feat` | New feature | Yes (minor) |
|
||||
| `fix` | Bug fix | Yes (patch) |
|
||||
| `docs` | Documentation changes | No |
|
||||
| `style` | Code style changes (formatting) | No |
|
||||
| `refactor` | Code refactoring | No |
|
||||
| `perf` | Performance improvements | No |
|
||||
| `test` | Adding/updating tests | No |
|
||||
| `build` | Build system changes | No |
|
||||
| `ci` | CI/CD changes | No |
|
||||
| `chore` | Maintenance tasks | No |
|
||||
| `revert` | Revert previous commit | Yes (if reverted commit was releasing) |
|
||||
|
||||
## Release Pipeline
|
||||
|
||||
### Production Release (main branch)
|
||||
|
||||
1. Developer pushes/merges to `main`
|
||||
2. CI workflow runs (lint, test, build, e2e, integration)
|
||||
3. semantic-release analyzes commits since last release
|
||||
4. If release-worthy commits found:
|
||||
- Calculates new version
|
||||
- Updates `package.json` version
|
||||
- Generates/updates `CHANGELOG.md`
|
||||
- Creates git tag (e.g., `v1.0.0`)
|
||||
- Creates GitHub release with release notes
|
||||
- Builds Docker image
|
||||
- Pushes to Docker Hub and GHCR with version tags
|
||||
|
||||
### Staging Deployment
|
||||
|
||||
1. Push to `staging` branch
|
||||
2. CI workflow runs (lint, test, build)
|
||||
3. Build Docker image
|
||||
4. Push to registries with `staging` tag
|
||||
5. Deploy to staging environment
|
||||
|
||||
### Nightly Builds
|
||||
|
||||
1. Scheduled workflow runs at 02:00 UTC daily
|
||||
2. Checkout `main` branch
|
||||
3. Build Docker image
|
||||
4. Push to registries with tags:
|
||||
- `nightly`
|
||||
- `edge`
|
||||
- `YYYY-MM-DD` (e.g., `2026-02-20`)
|
||||
|
||||
## Docker Registry Publishing
|
||||
|
||||
### Registry Configuration
|
||||
|
||||
**Docker Hub:**
|
||||
- Image: `DOCKERHUB_USERNAME/cv-app`
|
||||
- Requires: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN` secrets
|
||||
|
||||
**GitHub Container Registry (GHCR):**
|
||||
- Image: `ghcr.io/GITHUB_OWNER/cv-app`
|
||||
- Requires: `GITHUB_TOKEN` (automatic)
|
||||
|
||||
### Tag Strategy
|
||||
|
||||
| Event | Docker Hub Tags | GHCR Tags |
|
||||
|-------|-----------------|-----------|
|
||||
| Production release v1.2.3 | `latest`, `v1.2.3`, `1.2`, `1` | `latest`, `v1.2.3`, `1.2`, `1` |
|
||||
| Staging push | `staging` | `staging` |
|
||||
| Nightly build | `nightly`, `edge`, `2026-02-20` | `nightly`, `edge`, `2026-02-20` |
|
||||
|
||||
### Multi-architecture Support
|
||||
|
||||
Build for multiple platforms:
|
||||
- `linux/amd64` (x86_64)
|
||||
- `linux/arm64` (Apple Silicon, AWS Graviton)
|
||||
|
||||
## Changelog Generation
|
||||
|
||||
### Format
|
||||
|
||||
```markdown
|
||||
## [1.1.0] - 2026-02-20
|
||||
|
||||
### Features
|
||||
* **admin:** add password protection for admin panel (#123) ([abc1234](https://github.com/owner/repo/commit/abc1234))
|
||||
* **api:** add authentication endpoints (#120) ([def5678](https://github.com/owner/repo/commit/def5678))
|
||||
|
||||
### Bug Fixes
|
||||
* **skills:** prevent focus loss when editing category headers (#124) ([ghi9012](https://github.com/owner/repo/commit/ghi9012))
|
||||
|
||||
### Documentation
|
||||
* update installation instructions (#125) ([jkl3456](https://github.com/owner/repo/commit/jkl3456))
|
||||
|
||||
### BREAKING CHANGES
|
||||
* **api:** remove deprecated `/api/v1/*` endpoints
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2026-02-15
|
||||
...
|
||||
```
|
||||
|
||||
### Tools
|
||||
|
||||
- `@semantic-release/changelog` - Generates CHANGELOG.md
|
||||
- `@semantic-release/git` - Commits updated files back to repo
|
||||
- `conventional-changelog-conventionalcommits` - Preset for conventional commits
|
||||
|
||||
## GitHub Environments
|
||||
|
||||
### Production
|
||||
|
||||
- **Protection:** Required reviewers (1+)
|
||||
- **Deploy Trigger:** semantic-release on main branch merge
|
||||
- **Secrets:** `DOCKERHUB_TOKEN`, `DOCKERHUB_USERNAME`
|
||||
- **URL:** Production deployment URL
|
||||
|
||||
### Staging
|
||||
|
||||
- **Protection:** Optional reviewers
|
||||
- **Deploy Trigger:** Push to `staging` branch
|
||||
- **URL:** Staging deployment URL
|
||||
|
||||
### Nightly
|
||||
|
||||
- **Protection:** None
|
||||
- **Deploy Trigger:** Scheduled (cron: `0 2 * * *`)
|
||||
- **URL:** Nightly build URL
|
||||
|
||||
## Branch Protection Rules
|
||||
|
||||
### main branch
|
||||
|
||||
| Rule | Setting |
|
||||
|------|---------|
|
||||
| Require PR reviews | 1+ |
|
||||
| Dismiss stale reviews | Yes |
|
||||
| Require linear history | Yes |
|
||||
| Restrict force pushes | Yes |
|
||||
| Allow deletions | No |
|
||||
| Required status checks | `frontend`, `backend`, `integration`, `e2e` |
|
||||
|
||||
### staging branch
|
||||
|
||||
| Rule | Setting |
|
||||
|------|---------|
|
||||
| Require PR reviews | No |
|
||||
| Require linear history | No |
|
||||
| Restrict force pushes | No |
|
||||
| Required status checks | `frontend`, `backend` |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.github/
|
||||
├── workflows/
|
||||
│ ├── ci.yml # Enhanced with commitlint
|
||||
│ ├── release.yml # semantic-release workflow
|
||||
│ ├── staging.yml # Staging deployment
|
||||
│ └── nightly.yml # Nightly builds
|
||||
├── commitlint.config.js # Conventional commits config
|
||||
└── .releaserc.json # semantic-release config
|
||||
|
||||
scripts/
|
||||
├── docker-tags.sh # Docker tag computation
|
||||
└── release-notes.sh # Custom release notes
|
||||
|
||||
docker/
|
||||
├── Dockerfile # Multi-stage build
|
||||
└── docker-compose.prod.yml # Production compose file
|
||||
|
||||
CHANGELOG.md # Generated by semantic-release
|
||||
package.json # Version updated by semantic-release
|
||||
```
|
||||
|
||||
## Implementation Components
|
||||
|
||||
### 1. semantic-release Configuration
|
||||
|
||||
`.releaserc.json`:
|
||||
```json
|
||||
{
|
||||
"branches": ["main"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/git",
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. commitlint Configuration
|
||||
|
||||
`commitlint.config.js`:
|
||||
```javascript
|
||||
module.exports = {
|
||||
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'
|
||||
]]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Husky Git Hooks
|
||||
|
||||
```bash
|
||||
npx husky init
|
||||
echo "npx commitlint --edit \$1" > .husky/commit-msg
|
||||
```
|
||||
|
||||
### 4. GitHub Workflows
|
||||
|
||||
**release.yml:**
|
||||
- Triggers on push to main
|
||||
- Runs semantic-release
|
||||
- Builds and pushes Docker images
|
||||
|
||||
**staging.yml:**
|
||||
- Triggers on push to staging branch
|
||||
- Builds and pushes Docker images with staging tag
|
||||
|
||||
**nightly.yml:**
|
||||
- Triggers on schedule (daily at 02:00 UTC)
|
||||
- Builds from main with nightly/edge tags
|
||||
|
||||
### 5. Docker Configuration
|
||||
|
||||
Multi-stage Dockerfile with:
|
||||
- Build stage for frontend
|
||||
- Build stage for backend
|
||||
- Production stage with nginx
|
||||
|
||||
Multi-platform build using `docker buildx`
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Secrets Management:**
|
||||
- Use GitHub Secrets for sensitive values
|
||||
- Never commit secrets to repository
|
||||
- Use `GITHUB_TOKEN` for GHCR (automatic)
|
||||
|
||||
2. **Branch Protection:**
|
||||
- Require PR reviews for main
|
||||
- Require status checks to pass
|
||||
- Restrict force pushes
|
||||
|
||||
3. **Docker Image Security:**
|
||||
- Use minimal base images (alpine)
|
||||
- Run as non-root user
|
||||
- Scan images for vulnerabilities (optional: Trivy)
|
||||
|
||||
4. **Environment Protection:**
|
||||
- Required reviewers for production
|
||||
- Environment-specific secrets
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. **Phase 1: Setup Tooling**
|
||||
- Install semantic-release and plugins
|
||||
- Configure commitlint and husky
|
||||
- Create .releaserc.json
|
||||
|
||||
2. **Phase 2: Update Workflows**
|
||||
- Enhance ci.yml with commitlint
|
||||
- Create release.yml
|
||||
- Create staging.yml
|
||||
- Create nightly.yml
|
||||
|
||||
3. **Phase 3: Docker Publishing**
|
||||
- Configure Docker Hub credentials
|
||||
- Update Dockerfile for multi-platform
|
||||
- Test image publishing
|
||||
|
||||
4. **Phase 4: Branch Protection**
|
||||
- Create staging branch
|
||||
- Configure branch protection rules
|
||||
- Create GitHub environments
|
||||
|
||||
5. **Phase 5: First Release**
|
||||
- Make a conventional commit
|
||||
- Verify semantic-release runs
|
||||
- Check GitHub release created
|
||||
- Verify Docker images published
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Conventional commits enforced via commitlint
|
||||
- [ ] Version automatically bumped based on commit types
|
||||
- [ ] CHANGELOG.md automatically generated and updated
|
||||
- [ ] Git tags created for each release
|
||||
- [ ] GitHub releases created with release notes
|
||||
- [ ] Docker images published to Docker Hub and GHCR
|
||||
- [ ] Multi-platform Docker images (amd64, arm64)
|
||||
- [ ] Staging deployments on staging branch push
|
||||
- [ ] Nightly builds running daily
|
||||
- [ ] Branch protection rules enforced
|
||||
|
||||
## References
|
||||
|
||||
- [semantic-release Documentation](https://semantic-release.gitbook.io/)
|
||||
- [Conventional Commits Specification](https://www.conventionalcommits.org/)
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [Docker Multi-platform Builds](https://docs.docker.com/build/building/multi-platform/)
|
||||
@@ -1,974 +0,0 @@
|
||||
# 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