Massive Dependency Modernization Project
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 3m58s
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 3m58s
This commit is contained in:
308
docs/adr/0059-dependency-modernization.md
Normal file
308
docs/adr/0059-dependency-modernization.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# ADR-059: Dependency Modernization Plan
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2026-02-12
|
||||
**Implemented**: 2026-02-12
|
||||
|
||||
## Context
|
||||
|
||||
NPM audit and security scanning identified deprecated dependencies requiring modernization:
|
||||
|
||||
| Dependency | Current | Issue | Replacement |
|
||||
| --------------- | ------- | ----------------------- | --------------------------------------- |
|
||||
| `swagger-jsdoc` | 6.2.8 | Unmaintained since 2022 | `tsoa` (decorator-based OpenAPI) |
|
||||
| `rimraf` | 6.1.2 | Legacy cleanup utility | Node.js `fs.rm()` (native since v14.14) |
|
||||
|
||||
**Constraints**:
|
||||
|
||||
- Existing `@openapi` JSDoc annotations in 20 route files
|
||||
- ADR-018 compliance (API documentation strategy)
|
||||
- Zero-downtime migration (phased approach)
|
||||
- Must maintain Express 5.x compatibility
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. swagger-jsdoc → tsoa Migration
|
||||
|
||||
**Architecture**: tsoa controller classes + Express integration (no replacement of Express routing layer).
|
||||
|
||||
```text
|
||||
Current: Route Files → JSDoc Annotations → swagger-jsdoc → OpenAPI Spec
|
||||
Future: Controller Classes → @Route/@Get decorators → tsoa → OpenAPI Spec + Route Registration
|
||||
```
|
||||
|
||||
**Controller Pattern**: Base controller providing common utilities:
|
||||
|
||||
```typescript
|
||||
// src/controllers/base.controller.ts
|
||||
export abstract class BaseController {
|
||||
protected sendSuccess<T>(res: Response, data: T, status = 200) {
|
||||
return sendSuccess(res, data, status);
|
||||
}
|
||||
protected sendError(
|
||||
res: Response,
|
||||
code: ErrorCode,
|
||||
msg: string,
|
||||
status: number,
|
||||
details?: unknown,
|
||||
) {
|
||||
return sendError(res, code, msg, status, details);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Express Integration Strategy**: tsoa generates routes.ts; wrap with Express middleware pipeline:
|
||||
|
||||
```typescript
|
||||
// server.ts integration
|
||||
import { RegisterRoutes } from './src/generated/routes';
|
||||
RegisterRoutes(app); // tsoa registers routes with existing Express app
|
||||
```
|
||||
|
||||
### 2. rimraf → fs.rm() Migration
|
||||
|
||||
**Change**: Replace `rimraf coverage .coverage` script with Node.js native API.
|
||||
|
||||
```json
|
||||
// package.json (before)
|
||||
"clean": "rimraf coverage .coverage"
|
||||
|
||||
// package.json (after)
|
||||
"clean": "node -e \"import('fs/promises').then(fs => Promise.all([fs.rm('coverage', {recursive:true,force:true}), fs.rm('.coverage', {recursive:true,force:true})]))\""
|
||||
```
|
||||
|
||||
**Alternative**: Create `scripts/clean.mjs` for maintainability:
|
||||
|
||||
```javascript
|
||||
// scripts/clean.mjs
|
||||
import { rm } from 'fs/promises';
|
||||
await Promise.all([
|
||||
rm('coverage', { recursive: true, force: true }),
|
||||
rm('.coverage', { recursive: true, force: true }),
|
||||
]);
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Infrastructure (Tasks 1-4)
|
||||
|
||||
| Task | Description | Dependencies |
|
||||
| ---- | ---------------------------------------------- | ------------ |
|
||||
| 1 | Install tsoa, configure tsoa.json | None |
|
||||
| 2 | Create BaseController with utility methods | Task 1 |
|
||||
| 3 | Configure Express integration (RegisterRoutes) | Task 2 |
|
||||
| 4 | Set up tsoa spec generation in build pipeline | Task 3 |
|
||||
|
||||
### Phase 2: Controller Migration (Tasks 5-14)
|
||||
|
||||
Priority order matches ADR-018:
|
||||
|
||||
| Task | Route File | Controller Class | Dependencies |
|
||||
| ---- | ----------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------ |
|
||||
| 5 | health.routes.ts | HealthController | Task 4 |
|
||||
| 6 | auth.routes.ts | AuthController | Task 4 |
|
||||
| 7 | gamification.routes.ts | AchievementsController | Task 4 |
|
||||
| 8 | flyer.routes.ts | FlyersController | Task 4 |
|
||||
| 9 | user.routes.ts | UsersController | Task 4 |
|
||||
| 10 | budget.routes.ts | BudgetController | Task 4 |
|
||||
| 11 | recipe.routes.ts | RecipeController | Task 4 |
|
||||
| 12 | store.routes.ts | StoreController | Task 4 |
|
||||
| 13 | admin.routes.ts | AdminController | Task 4 |
|
||||
| 14 | Remaining routes (deals, price, upc, inventory, ai, receipt, category, stats, personalization, reactions, system) | Various | Task 4 |
|
||||
|
||||
### Phase 3: Cleanup and rimraf (Tasks 15-18)
|
||||
|
||||
| Task | Description | Dependencies |
|
||||
| ---- | -------------------------------- | ------------------- |
|
||||
| 15 | Create scripts/clean.mjs | None |
|
||||
| 16 | Update package.json clean script | Task 15 |
|
||||
| 17 | Remove rimraf dependency | Task 16 |
|
||||
| 18 | Remove swagger-jsdoc + types | Tasks 5-14 complete |
|
||||
|
||||
### Phase 4: Verification (Tasks 19-24)
|
||||
|
||||
| Task | Description | Dependencies |
|
||||
| ---- | --------------------------------- | ------------ |
|
||||
| 19 | Run type-check | Tasks 15-18 |
|
||||
| 20 | Run unit tests | Task 19 |
|
||||
| 21 | Run integration tests | Task 20 |
|
||||
| 22 | Verify OpenAPI spec completeness | Task 21 |
|
||||
| 23 | Update ADR-018 (reference tsoa) | Task 22 |
|
||||
| 24 | Update CLAUDE.md (swagger → tsoa) | Task 23 |
|
||||
|
||||
### Task Dependency Graph
|
||||
|
||||
```text
|
||||
[1: Install tsoa]
|
||||
|
|
||||
[2: BaseController]
|
||||
|
|
||||
[3: Express Integration]
|
||||
|
|
||||
[4: Build Pipeline]
|
||||
|
|
||||
+------------------+------------------+
|
||||
| | | | |
|
||||
[5] [6] [7] [8] [9-14]
|
||||
Health Auth Gamif Flyer Others
|
||||
| | | | |
|
||||
+------------------+------------------+
|
||||
|
|
||||
[18: Remove swagger-jsdoc]
|
||||
|
|
||||
[15: clean.mjs] -----> [16: Update pkg.json]
|
||||
|
|
||||
[17: Remove rimraf]
|
||||
|
|
||||
[19: type-check]
|
||||
|
|
||||
[20: unit tests]
|
||||
|
|
||||
[21: integration tests]
|
||||
|
|
||||
[22: Verify OpenAPI]
|
||||
|
|
||||
[23: Update ADR-018]
|
||||
|
|
||||
[24: Update CLAUDE.md]
|
||||
```
|
||||
|
||||
### Critical Path
|
||||
|
||||
**Minimum time to completion**: Tasks 1 → 2 → 3 → 4 → 5 (or any controller) → 18 → 19 → 20 → 21 → 22 → 23 → 24
|
||||
|
||||
**Parallelization opportunities**:
|
||||
|
||||
- Tasks 5-14 (all controller migrations) can run in parallel after Task 4
|
||||
- Tasks 15-17 (rimraf removal) can run in parallel with controller migrations
|
||||
|
||||
## Technical Decisions
|
||||
|
||||
### tsoa Configuration
|
||||
|
||||
```json
|
||||
// tsoa.json
|
||||
{
|
||||
"entryFile": "server.ts",
|
||||
"noImplicitAdditionalProperties": "throw-on-extras",
|
||||
"controllerPathGlobs": ["src/controllers/**/*.controller.ts"],
|
||||
"spec": {
|
||||
"outputDirectory": "src/generated",
|
||||
"specVersion": 3,
|
||||
"basePath": "/api/v1"
|
||||
},
|
||||
"routes": {
|
||||
"routesDir": "src/generated",
|
||||
"middleware": "express"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Decorator Migration Example
|
||||
|
||||
**Before** (swagger-jsdoc):
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @openapi
|
||||
* /health/ping:
|
||||
* get:
|
||||
* summary: Simple ping endpoint
|
||||
* tags: [Health]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Server is responsive
|
||||
*/
|
||||
router.get('/ping', validateRequest(emptySchema), handler);
|
||||
```
|
||||
|
||||
**After** (tsoa):
|
||||
|
||||
```typescript
|
||||
@Route('health')
|
||||
@Tags('Health')
|
||||
export class HealthController extends BaseController {
|
||||
@Get('ping')
|
||||
@SuccessResponse(200, 'Server is responsive')
|
||||
public async ping(): Promise<{ message: string }> {
|
||||
return { message: 'pong' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Zod Integration
|
||||
|
||||
tsoa uses its own validation. Options:
|
||||
|
||||
1. **Replace Zod with tsoa validation** - Use `@Body`, `@Query`, `@Path` decorators with TypeScript types
|
||||
2. **Hybrid approach** - Keep Zod schemas, call `validateRequest()` within controller methods
|
||||
3. **Custom template** - Generate tsoa routes that call Zod validation middleware
|
||||
|
||||
**Recommended**: Option 1 for new controllers; gradually migrate existing Zod schemas.
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
| --------------------------------------- | ---------- | ------ | ------------------------------------------- |
|
||||
| tsoa/Express 5.x incompatibility | Medium | High | Test in dev container before migration |
|
||||
| Missing OpenAPI coverage post-migration | Low | Medium | Compare generated specs before/after |
|
||||
| Authentication middleware integration | Medium | Medium | Test @Security decorator with passport-jwt |
|
||||
| Test regression from route changes | Low | High | Run full test suite after each controller |
|
||||
| Build time increase (tsoa generation) | Low | Low | Add to npm run build; cache generated files |
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- **Type-safe API contracts**: tsoa decorators derive types from TypeScript
|
||||
- **Reduced duplication**: No more parallel JSDoc + TypeScript type definitions
|
||||
- **Modern tooling**: Active tsoa community (vs. unmaintained swagger-jsdoc)
|
||||
- **Native Node.js**: fs.rm() is built-in, no external dependency
|
||||
- **Smaller dependency tree**: Remove rimraf (5 transitive deps) + swagger-jsdoc (8 transitive deps)
|
||||
|
||||
### Negative
|
||||
|
||||
- **Learning curve**: Decorator-based controller pattern differs from Express handlers
|
||||
- **Migration effort**: 20 route files require conversion
|
||||
- **Generated code**: `src/generated/routes.ts` must be version-controlled or regenerated on build
|
||||
|
||||
### Neutral
|
||||
|
||||
- **Build step change**: Add `tsoa spec && tsoa routes` to build pipeline
|
||||
- **Testing approach**: May need to adjust test structure for controller classes
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### 1. Update swagger-jsdoc to fork/successor
|
||||
|
||||
**Rejected**: No active fork; community has moved to tsoa, fastify-swagger, or NestJS.
|
||||
|
||||
### 2. NestJS migration
|
||||
|
||||
**Rejected**: Full framework migration (Express → NestJS) is disproportionate to the problem scope.
|
||||
|
||||
### 3. fastify-swagger
|
||||
|
||||
**Rejected**: Requires Express → Fastify migration; out of scope.
|
||||
|
||||
### 4. Keep rimraf, accept deprecation warning
|
||||
|
||||
**Rejected**: Native fs.rm() is trivial replacement; no reason to maintain deprecated dependency.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------ | ------------------------------------- |
|
||||
| `tsoa.json` | tsoa configuration |
|
||||
| `src/controllers/base.controller.ts` | Base controller with utilities |
|
||||
| `src/controllers/*.controller.ts` | Individual domain controllers |
|
||||
| `src/generated/routes.ts` | tsoa-generated Express routes |
|
||||
| `src/generated/swagger.json` | Generated OpenAPI 3.0 spec |
|
||||
| `scripts/clean.mjs` | Native fs.rm() replacement for rimraf |
|
||||
|
||||
## Related ADRs
|
||||
|
||||
- [ADR-018](./0018-api-documentation-strategy.md) - API Documentation Strategy (will be updated)
|
||||
- [ADR-003](./0003-standardized-input-validation-using-middleware.md) - Input Validation (Zod integration)
|
||||
- [ADR-028](./0028-api-response-standardization.md) - Response Standardization (BaseController pattern)
|
||||
- [ADR-001](./0001-standardized-error-handling.md) - Error Handling (error utilities in BaseController)
|
||||
Reference in New Issue
Block a user