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
|
## Type of Change
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
- [ ] `feat` - New feature (minor version bump)
|
||||||
- [ ] New feature (non-breaking change that adds functionality)
|
- [ ] `fix` - Bug fix (patch version bump)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- [ ] `perf` - Performance improvement (patch version bump)
|
||||||
- [ ] Documentation update
|
- [ ] `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
|
## Checklist
|
||||||
|
|
||||||
- [ ] I have followed the [Contributing Guidelines](CONTRIBUTING.md)
|
- [ ] Commit messages follow [Conventional Commits](CONTRIBUTING.md#commit-guidelines)
|
||||||
- [ ] Lint passes (`npm run lint`)
|
- [ ] Lint passes (`npm run lint`)
|
||||||
- [ ] Build succeeds (`npm run build`)
|
- [ ] 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)
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
<!-- Add screenshots here -->
|
<!-- Add screenshots for UI changes -->
|
||||||
|
|
||||||
## Related Issues
|
## 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
|
1. Fork the repository
|
||||||
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
2. Clone your fork
|
||||||
3. Make your changes
|
3. Install dependencies:
|
||||||
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
|
|
||||||
|
|
||||||
## 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
|
### Code Style
|
||||||
|
|
||||||
- Follow the existing code style
|
- ESLint configuration is enforced
|
||||||
- Run `npm run lint` before committing
|
- Run `npm run lint` before committing
|
||||||
- Use meaningful commit messages
|
- Fix issues with `npm run lint -- --fix`
|
||||||
|
|
||||||
### Component Structure
|
### Testing
|
||||||
|
|
||||||
```jsx
|
Run tests before submitting:
|
||||||
// Imports at top
|
|
||||||
import { motion } from 'framer-motion';
|
|
||||||
import { Icon } from 'lucide-react';
|
|
||||||
|
|
||||||
// Component definition
|
|
||||||
export default function Component() {
|
|
||||||
return (
|
|
||||||
// JSX
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Your Changes
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start dev server
|
# Unit tests
|
||||||
npm run dev
|
npm run test:run
|
||||||
|
|
||||||
# Build for production
|
# Integration tests
|
||||||
npm run build
|
npm run test:integration
|
||||||
|
|
||||||
# Preview production build
|
# E2E tests (requires running server)
|
||||||
npm run preview
|
npm run test:e2e
|
||||||
|
|
||||||
|
# All tests with coverage
|
||||||
|
npm run test:coverage
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pull Request Checklist
|
## Pull Request Process
|
||||||
|
|
||||||
- [ ] Code follows the existing style
|
1. Create a feature branch from `master`
|
||||||
- [ ] Linting passes (`npm run lint`)
|
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`)
|
- [ ] 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?
|
## 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
|
# CV - Tuan-Dat Tran
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
A modern, minimalist CV/Resume single-page application with admin panel and persistent storage.
|
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
|
- Modern, responsive design
|
||||||
- Smooth scroll animations with Framer Motion
|
- 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
|
- Persistent storage with SQLite
|
||||||
- Docker Compose deployment
|
- Docker Compose deployment
|
||||||
- API-based architecture
|
- RESTful API with OpenAPI documentation
|
||||||
- Optional Keycloak integration for SSO
|
- Optional Keycloak integration for SSO
|
||||||
|
- Automated releases with semantic-release
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
- **React 18** - UI Library
|
- **React 19** - UI Library
|
||||||
- **Vite** - Build Tool
|
- **Vite 7** - Build Tool
|
||||||
- **Tailwind CSS 4** - Styling
|
- **Tailwind CSS 4** - Styling
|
||||||
- **Framer Motion** - Animations
|
- **Framer Motion 12** - Animations
|
||||||
- **Lucide React** - Icons
|
- **Lucide React** - Icons
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
- **Express.js** - API Server
|
- **Express.js 4** - API Server
|
||||||
- **SQLite** - Database
|
- **Knex.js** - SQL Query Builder
|
||||||
- **better-sqlite3** - SQLite driver
|
- **SQLite** with **better-sqlite3** - Database
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 20+
|
||||||
- npm 9+
|
- npm 10+
|
||||||
- Docker & Docker Compose (optional)
|
- Docker & Docker Compose (optional)
|
||||||
|
|
||||||
### Option 1: Docker Compose (Recommended)
|
### Option 1: Docker Compose (Recommended)
|
||||||
@@ -66,6 +67,7 @@ docker-compose up -d
|
|||||||
# Frontend: http://localhost:5173
|
# Frontend: http://localhost:5173
|
||||||
# Backend API: http://localhost:3001
|
# Backend API: http://localhost:3001
|
||||||
# Admin Panel: http://localhost:5173/admin
|
# Admin Panel: http://localhost:5173/admin
|
||||||
|
# API Docs: http://localhost:3001/api/docs
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 2: Local Development
|
### Option 2: Local Development
|
||||||
@@ -86,27 +88,28 @@ npm run dev
|
|||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
Frontend (`.env`):
|
|
||||||
```
|
|
||||||
VITE_API_URL=http://localhost:3001
|
|
||||||
```
|
|
||||||
|
|
||||||
Backend (`backend/.env`):
|
Backend (`backend/.env`):
|
||||||
```
|
```
|
||||||
PORT=3001
|
PORT=3001
|
||||||
DB_PATH=./data/cv.db
|
DB_PATH=./data/cv.db
|
||||||
USE_KEYCLOAK=false
|
AUTH_MODE=simple
|
||||||
# Keycloak settings (required if USE_KEYCLOAK=true)
|
# Keycloak settings (required if AUTH_MODE=keycloak)
|
||||||
KEYCLOAK_URL=https://keycloak.example.com
|
KEYCLOAK_URL=https://keycloak.example.com
|
||||||
KEYCLOAK_REALM=your-realm
|
KEYCLOAK_REALM=your-realm
|
||||||
KEYCLOAK_CLIENT_ID=cv-admin
|
KEYCLOAK_CLIENT_ID=cv-app
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
### Pre-commit 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
|
```bash
|
||||||
# macOS
|
# macOS
|
||||||
@@ -120,152 +123,41 @@ sudo mv gitleaks /usr/local/bin/
|
|||||||
scoop install gitleaks
|
scoop install gitleaks
|
||||||
```
|
```
|
||||||
|
|
||||||
### API Documentation
|
### Commit Guidelines
|
||||||
|
|
||||||
API documentation is available at `/api/docs` when running the server.
|
This project uses [Conventional Commits](https://www.conventionalcommits.org/):
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
<type>(<scope>): <description>
|
<type>(<scope>): <description>
|
||||||
|
|
||||||
[optional body]
|
# Examples:
|
||||||
|
feat(ui): add export button
|
||||||
[optional footer]
|
fix(api): resolve authentication issue
|
||||||
|
docs(readme): update installation steps
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Commit Types
|
### API Documentation
|
||||||
|
|
||||||
| Type | Description | Version Bump |
|
Interactive Swagger UI available at: `http://localhost:3001/api/docs`
|
||||||
|------|-------------|--------------|
|
|
||||||
| `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
|
## 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
|
### Keycloak Mode
|
||||||
|
- SSO integration via Keycloak
|
||||||
1. Push to `master` branch
|
- Configure via `AUTH_MODE=keycloak` and related environment variables
|
||||||
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).
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
| Document | Description |
|
| Document | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| [Release Engineering](docs/release-engineering.md) | Comprehensive release pipeline documentation |
|
| [Architecture](docs/architecture.md) | System architecture and design patterns |
|
||||||
| [Contributing](CONTRIBUTING.md) | Contribution guidelines |
|
| [Release Engineering](docs/release-engineering.md) | Release and deployment documentation |
|
||||||
| [Security](SECURITY.md) | Security policy |
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
MIT License - see [LICENSE](LICENSE) for details.
|
||||||
|
|||||||
123
SECURITY.md
123
SECURITY.md
@@ -4,33 +4,124 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| main | :white_check_mark: |
|
| 1.x | :white_check_mark: |
|
||||||
|
| < 1.0 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## 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.
|
||||||
|
|
||||||
|
### How to Report
|
||||||
|
|
||||||
|
**Preferred Method**: Open a security advisory
|
||||||
|
|
||||||
|
1. Go to the repository
|
||||||
|
2. Click "Security" tab
|
||||||
|
3. Click "Report a vulnerability"
|
||||||
|
4. Fill out the advisory form
|
||||||
|
|
||||||
|
**Alternative**: Email the maintainer directly (if available in profile)
|
||||||
|
|
||||||
|
### What to Include
|
||||||
|
|
||||||
1. **Do not** open a public issue
|
|
||||||
2. Email the maintainer directly at `tuan-dat.tran@example.com`
|
|
||||||
3. Include:
|
|
||||||
- Description of the vulnerability
|
- Description of the vulnerability
|
||||||
- Steps to reproduce
|
- Steps to reproduce
|
||||||
|
- Affected versions
|
||||||
- Potential impact
|
- Potential impact
|
||||||
- Suggested fix (if any)
|
- Suggested fix (if available)
|
||||||
|
|
||||||
### What to Expect
|
### Response Timeline
|
||||||
|
|
||||||
- Acknowledgment within 48 hours
|
| Action | Timeline |
|
||||||
- Assessment within 7 days
|
|--------|----------|
|
||||||
- Fix timeline based on severity:
|
| Initial response | Within 48 hours |
|
||||||
- Critical: 24-72 hours
|
| Vulnerability assessment | Within 7 days |
|
||||||
- High: 1 week
|
| Fix timeline based on severity | See below |
|
||||||
- Medium/Low: Next release
|
|
||||||
|
|
||||||
### Disclosure Policy
|
### Fix Timeline by Severity
|
||||||
|
|
||||||
- Please allow time for the fix before public disclosure
|
| 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
|
- Coordinated disclosure is appreciated
|
||||||
- Credit will be given in the fix commit
|
- Credit will be given in the fix commit (if desired)
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
For security concerns, use the vulnerability reporting process above.
|
||||||
|
|
||||||
Thank you for helping keep this project secure!
|
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