docs(release): add devops features implementation plan
This commit is contained in:
948
docs/plans/2026-02-20-devops-features.md
Normal file
948
docs/plans/2026-02-20-devops-features.md
Normal file
@@ -0,0 +1,948 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user