Fix all req.params and req.query type errors
- Add getParamString helper function for req.params - Replace all req.params destructuring with getParamString - Fix remaining req.query.* direct usage errors - All TypeScript compilation errors now resolved
This commit is contained in:
@@ -6,7 +6,7 @@ import { logger } from '../services/logger.js';
|
|||||||
import { calculateRequiredEffortApplicationManagementWithBreakdown } from '../services/effortCalculation.js';
|
import { calculateRequiredEffortApplicationManagementWithBreakdown } from '../services/effortCalculation.js';
|
||||||
import { findBIAMatch, loadBIAData, clearBIACache, calculateSimilarity } from '../services/biaMatchingService.js';
|
import { findBIAMatch, loadBIAData, clearBIACache, calculateSimilarity } from '../services/biaMatchingService.js';
|
||||||
import { calculateApplicationCompleteness } from '../services/dataCompletenessConfig.js';
|
import { calculateApplicationCompleteness } from '../services/dataCompletenessConfig.js';
|
||||||
import { getQueryString } from '../utils/queryHelpers.js';
|
import { getQueryString, getParamString } from '../utils/queryHelpers.js';
|
||||||
import type { SearchFilters, ReferenceValue, ClassificationResult, ApplicationDetails, ApplicationStatus } from '../types/index.js';
|
import type { SearchFilters, ReferenceValue, ClassificationResult, ApplicationDetails, ApplicationStatus } from '../types/index.js';
|
||||||
import type { Server, Flows, Certificate, Domain, AzureSubscription, CMDBObjectTypeName } from '../generated/jira-types.js';
|
import type { Server, Flows, Certificate, Domain, AzureSubscription, CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ router.get('/bia-comparison', async (req: Request, res: Response) => {
|
|||||||
// - mode=edit: Force refresh from Jira for editing (includes _jiraUpdatedAt for conflict detection)
|
// - mode=edit: Force refresh from Jira for editing (includes _jiraUpdatedAt for conflict detection)
|
||||||
router.get('/:id', async (req: Request, res: Response) => {
|
router.get('/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
const mode = getQueryString(req, 'mode');
|
const mode = getQueryString(req, 'mode');
|
||||||
|
|
||||||
// Don't treat special routes as application IDs
|
// Don't treat special routes as application IDs
|
||||||
@@ -359,7 +359,7 @@ router.get('/:id', async (req: Request, res: Response) => {
|
|||||||
// Update application with conflict detection
|
// Update application with conflict detection
|
||||||
router.put('/:id', async (req: Request, res: Response) => {
|
router.put('/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
const { updates, _jiraUpdatedAt } = req.body as {
|
const { updates, _jiraUpdatedAt } = req.body as {
|
||||||
updates?: {
|
updates?: {
|
||||||
applicationFunctions?: ReferenceValue[];
|
applicationFunctions?: ReferenceValue[];
|
||||||
@@ -471,7 +471,7 @@ router.put('/:id', async (req: Request, res: Response) => {
|
|||||||
// Force update (ignore conflicts)
|
// Force update (ignore conflicts)
|
||||||
router.put('/:id/force', async (req: Request, res: Response) => {
|
router.put('/:id/force', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
|
|
||||||
const application = await dataService.getApplicationById(id);
|
const application = await dataService.getApplicationById(id);
|
||||||
@@ -552,7 +552,7 @@ router.post('/calculate-effort', async (req: Request, res: Response) => {
|
|||||||
// Get application classification history
|
// Get application classification history
|
||||||
router.get('/:id/history', async (req: Request, res: Response) => {
|
router.get('/:id/history', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
const history = await databaseService.getClassificationsByApplicationId(id);
|
const history = await databaseService.getClassificationsByApplicationId(id);
|
||||||
res.json(history);
|
res.json(history);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -564,7 +564,8 @@ router.get('/:id/history', async (req: Request, res: Response) => {
|
|||||||
// Get related objects for an application (from cache)
|
// Get related objects for an application (from cache)
|
||||||
router.get('/:id/related/:objectType', async (req: Request, res: Response) => {
|
router.get('/:id/related/:objectType', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id, objectType } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
|
const objectType = getParamString(req, 'objectType');
|
||||||
|
|
||||||
// Map object type string to CMDBObjectTypeName
|
// Map object type string to CMDBObjectTypeName
|
||||||
const typeMap: Record<string, CMDBObjectTypeName> = {
|
const typeMap: Record<string, CMDBObjectTypeName> = {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Router, Request, Response } from 'express';
|
|||||||
import { cacheStore } from '../services/cacheStore.js';
|
import { cacheStore } from '../services/cacheStore.js';
|
||||||
import { syncEngine } from '../services/syncEngine.js';
|
import { syncEngine } from '../services/syncEngine.js';
|
||||||
import { logger } from '../services/logger.js';
|
import { logger } from '../services/logger.js';
|
||||||
import { getQueryString } from '../utils/queryHelpers.js';
|
import { getQueryString, getParamString } from '../utils/queryHelpers.js';
|
||||||
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
||||||
import type { CMDBObjectTypeName } from '../generated/jira-types.js';
|
import type { CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||||
|
|
||||||
@@ -77,13 +77,12 @@ router.post('/sync', async (req: Request, res: Response) => {
|
|||||||
// Trigger sync for a specific object type
|
// Trigger sync for a specific object type
|
||||||
router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { objectType } = req.params;
|
const objectType = getParamString(req, 'objectType');
|
||||||
|
|
||||||
// Validate object type - convert to string if array
|
// Validate object type
|
||||||
const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType;
|
if (!OBJECT_TYPES[objectType]) {
|
||||||
if (!OBJECT_TYPES[objectTypeStr]) {
|
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: `Unknown object type: ${objectTypeStr}`,
|
error: `Unknown object type: ${objectType}`,
|
||||||
supportedTypes: Object.keys(OBJECT_TYPES),
|
supportedTypes: Object.keys(OBJECT_TYPES),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -91,22 +90,23 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
logger.info(`Manual sync triggered for ${objectType}`);
|
logger.info(`Manual sync triggered for ${objectType}`);
|
||||||
|
|
||||||
const result = await syncEngine.syncType(objectTypeStr as CMDBObjectTypeName);
|
const result = await syncEngine.syncType(objectType as CMDBObjectTypeName);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
objectType: objectTypeStr,
|
objectType: objectType,
|
||||||
stats: result,
|
stats: result,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const objectType = getParamString(req, 'objectType');
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Failed to sync object type';
|
const errorMessage = error instanceof Error ? error.message : 'Failed to sync object type';
|
||||||
logger.error(`Failed to sync object type ${req.params.objectType}`, error);
|
logger.error(`Failed to sync object type ${objectType}`, error);
|
||||||
|
|
||||||
// Return 409 (Conflict) if sync is already in progress, otherwise 500
|
// Return 409 (Conflict) if sync is already in progress, otherwise 500
|
||||||
const statusCode = errorMessage.includes('already in progress') ? 409 : 500;
|
const statusCode = errorMessage.includes('already in progress') ? 409 : 500;
|
||||||
res.status(statusCode).json({
|
res.status(statusCode).json({
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
objectType: req.params.objectType,
|
objectType: objectType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -114,12 +114,11 @@ router.post('/sync/:objectType', async (req: Request, res: Response) => {
|
|||||||
// Clear cache for a specific type
|
// Clear cache for a specific type
|
||||||
router.delete('/clear/:objectType', async (req: Request, res: Response) => {
|
router.delete('/clear/:objectType', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { objectType } = req.params;
|
const objectType = getParamString(req, 'objectType');
|
||||||
const objectTypeStr = Array.isArray(objectType) ? objectType[0] : objectType;
|
|
||||||
|
|
||||||
if (!OBJECT_TYPES[objectTypeStr]) {
|
if (!OBJECT_TYPES[objectType]) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: `Unknown object type: ${objectTypeStr}`,
|
error: `Unknown object type: ${objectType}`,
|
||||||
supportedTypes: Object.keys(OBJECT_TYPES),
|
supportedTypes: Object.keys(OBJECT_TYPES),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -135,7 +134,8 @@ router.delete('/clear/:objectType', async (req: Request, res: Response) => {
|
|||||||
deletedCount: deleted,
|
deletedCount: deleted,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to clear cache for ${req.params.objectType}`, error);
|
const objectType = getParamString(req, 'objectType');
|
||||||
|
logger.error(`Failed to clear cache for ${objectType}`, error);
|
||||||
res.status(500).json({ error: 'Failed to clear cache' });
|
res.status(500).json({ error: 'Failed to clear cache' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import { dataService } from '../services/dataService.js';
|
|||||||
import { databaseService } from '../services/database.js';
|
import { databaseService } from '../services/database.js';
|
||||||
import { logger } from '../services/logger.js';
|
import { logger } from '../services/logger.js';
|
||||||
import { config } from '../config/env.js';
|
import { config } from '../config/env.js';
|
||||||
import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js';
|
import { getQueryString, getQueryNumber, getParamString } from '../utils/queryHelpers.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Get AI classification for an application
|
// Get AI classification for an application
|
||||||
router.post('/suggest/:id', async (req: Request, res: Response) => {
|
router.post('/suggest/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
// Get provider from query parameter or request body, default to config
|
// Get provider from query parameter or request body, default to config
|
||||||
const provider = (getQueryString(req, 'provider') as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider;
|
const provider = (getQueryString(req, 'provider') as AIProvider) || (req.body.provider as AIProvider) || config.defaultAIProvider;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ router.get('/taxonomy', (req: Request, res: Response) => {
|
|||||||
// Get function by code
|
// Get function by code
|
||||||
router.get('/function/:code', (req: Request, res: Response) => {
|
router.get('/function/:code', (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { code } = req.params;
|
const code = getParamString(req, 'code');
|
||||||
const func = aiService.getFunctionByCode(code);
|
const func = aiService.getFunctionByCode(code);
|
||||||
|
|
||||||
if (!func) {
|
if (!func) {
|
||||||
@@ -112,7 +112,7 @@ router.get('/ai-status', (req: Request, res: Response) => {
|
|||||||
// Get the AI prompt for an application (for debugging/copy-paste)
|
// Get the AI prompt for an application (for debugging/copy-paste)
|
||||||
router.get('/prompt/:id', async (req: Request, res: Response) => {
|
router.get('/prompt/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
|
|
||||||
const application = await dataService.getApplicationById(id);
|
const application = await dataService.getApplicationById(id);
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@@ -131,7 +131,7 @@ router.get('/prompt/:id', async (req: Request, res: Response) => {
|
|||||||
// Chat with AI about an application
|
// Chat with AI about an application
|
||||||
router.post('/chat/:id', async (req: Request, res: Response) => {
|
router.post('/chat/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const id = getParamString(req, 'id');
|
||||||
const { message, conversationId, provider: requestProvider } = req.body;
|
const { message, conversationId, provider: requestProvider } = req.body;
|
||||||
|
|
||||||
if (!message || typeof message !== 'string' || message.trim().length === 0) {
|
if (!message || typeof message !== 'string' || message.trim().length === 0) {
|
||||||
@@ -168,7 +168,7 @@ router.post('/chat/:id', async (req: Request, res: Response) => {
|
|||||||
// Get conversation history
|
// Get conversation history
|
||||||
router.get('/chat/conversation/:conversationId', (req: Request, res: Response) => {
|
router.get('/chat/conversation/:conversationId', (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { conversationId } = req.params;
|
const conversationId = getParamString(req, 'conversationId');
|
||||||
const messages = aiService.getConversationHistory(conversationId);
|
const messages = aiService.getConversationHistory(conversationId);
|
||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
@@ -186,7 +186,7 @@ router.get('/chat/conversation/:conversationId', (req: Request, res: Response) =
|
|||||||
// Clear a conversation
|
// Clear a conversation
|
||||||
router.delete('/chat/conversation/:conversationId', (req: Request, res: Response) => {
|
router.delete('/chat/conversation/:conversationId', (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { conversationId } = req.params;
|
const conversationId = getParamString(req, 'conversationId');
|
||||||
const deleted = aiService.clearConversation(conversationId);
|
const deleted = aiService.clearConversation(conversationId);
|
||||||
|
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response } from 'express';
|
||||||
import { cmdbService } from '../services/cmdbService.js';
|
import { cmdbService } from '../services/cmdbService.js';
|
||||||
import { logger } from '../services/logger.js';
|
import { logger } from '../services/logger.js';
|
||||||
import { getQueryString, getQueryNumber } from '../utils/queryHelpers.js';
|
import { getQueryString, getQueryNumber, getParamString } from '../utils/queryHelpers.js';
|
||||||
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
import { OBJECT_TYPES } from '../generated/jira-schema.js';
|
||||||
import type { CMDBObject, CMDBObjectTypeName } from '../generated/jira-types.js';
|
import type { CMDBObject, CMDBObjectTypeName } from '../generated/jira-types.js';
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ router.get('/', (req: Request, res: Response) => {
|
|||||||
// Get all objects of a type
|
// Get all objects of a type
|
||||||
router.get('/:type', async (req: Request, res: Response) => {
|
router.get('/:type', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { type } = req.params;
|
const type = getParamString(req, 'type');
|
||||||
const limit = getQueryNumber(req, 'limit', 1000);
|
const limit = getQueryNumber(req, 'limit', 1000);
|
||||||
const offset = getQueryNumber(req, 'offset', 0);
|
const offset = getQueryNumber(req, 'offset', 0);
|
||||||
const search = getQueryString(req, 'search');
|
const search = getQueryString(req, 'search');
|
||||||
@@ -71,7 +71,8 @@ router.get('/:type', async (req: Request, res: Response) => {
|
|||||||
// Get a specific object by ID
|
// Get a specific object by ID
|
||||||
router.get('/:type/:id', async (req: Request, res: Response) => {
|
router.get('/:type/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { type, id } = req.params;
|
const type = getParamString(req, 'type');
|
||||||
|
const id = getParamString(req, 'id');
|
||||||
const forceRefresh = getQueryString(req, 'refresh') === 'true';
|
const forceRefresh = getQueryString(req, 'refresh') === 'true';
|
||||||
|
|
||||||
// Validate type
|
// Validate type
|
||||||
@@ -102,7 +103,9 @@ router.get('/:type/:id', async (req: Request, res: Response) => {
|
|||||||
// Get related objects
|
// Get related objects
|
||||||
router.get('/:type/:id/related/:relationType', async (req: Request, res: Response) => {
|
router.get('/:type/:id/related/:relationType', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { type, id, relationType } = req.params;
|
const type = getParamString(req, 'type');
|
||||||
|
const id = getParamString(req, 'id');
|
||||||
|
const relationType = getParamString(req, 'relationType');
|
||||||
const attribute = getQueryString(req, 'attribute');
|
const attribute = getQueryString(req, 'attribute');
|
||||||
|
|
||||||
// Validate types
|
// Validate types
|
||||||
@@ -139,7 +142,9 @@ router.get('/:type/:id/related/:relationType', async (req: Request, res: Respons
|
|||||||
// Get objects referencing this object (inbound references)
|
// Get objects referencing this object (inbound references)
|
||||||
router.get('/:type/:id/referenced-by/:sourceType', async (req: Request, res: Response) => {
|
router.get('/:type/:id/referenced-by/:sourceType', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { type, id, sourceType } = req.params;
|
const type = getParamString(req, 'type');
|
||||||
|
const id = getParamString(req, 'id');
|
||||||
|
const sourceType = getParamString(req, 'sourceType');
|
||||||
const attribute = getQueryString(req, 'attribute');
|
const attribute = getQueryString(req, 'attribute');
|
||||||
|
|
||||||
// Validate types
|
// Validate types
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Helper functions for Express request query parameters
|
* Helper functions for Express request query and params
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
@@ -32,3 +32,12 @@ export function getQueryBoolean(req: Request, key: string, defaultValue = false)
|
|||||||
if (value === undefined) return defaultValue;
|
if (value === undefined) return defaultValue;
|
||||||
return value === 'true' || value === '1';
|
return value === 'true' || value === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a route parameter as a string, handling both string and string[] types
|
||||||
|
*/
|
||||||
|
export function getParamString(req: Request, key: string): string {
|
||||||
|
const value = req.params[key];
|
||||||
|
if (Array.isArray(value)) return value[0] as string;
|
||||||
|
return value as string;
|
||||||
|
}
|
||||||
|
|||||||
192
docs/TYPESCRIPT-LOCAL-VS-CI.md
Normal file
192
docs/TYPESCRIPT-LOCAL-VS-CI.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# Waarom TypeScript Errors Lokaal Niet Optreden Maar Wel in CI/CD
|
||||||
|
|
||||||
|
## Het Probleem
|
||||||
|
|
||||||
|
TypeScript compilation errors die lokaal niet optreden, maar wel in Azure DevOps pipelines of Docker builds. Dit is een veelvoorkomend probleem met verschillende oorzaken.
|
||||||
|
|
||||||
|
## Belangrijkste Oorzaken
|
||||||
|
|
||||||
|
### 1. **tsx vs tsc - Development vs Production Build**
|
||||||
|
|
||||||
|
**Lokaal (Development):**
|
||||||
|
```bash
|
||||||
|
npm run dev # Gebruikt: tsx watch src/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**In Docker/CI (Production Build):**
|
||||||
|
```bash
|
||||||
|
npm run build # Gebruikt: tsc (TypeScript Compiler)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verschil:**
|
||||||
|
- **`tsx`** (TypeScript Execute): Een runtime TypeScript executor die code direct uitvoert zonder volledige type checking. Het is **minder strict** en laat veel type errors door.
|
||||||
|
- **`tsc`** (TypeScript Compiler): De officiële TypeScript compiler die **volledige type checking** doet en alle errors rapporteert.
|
||||||
|
|
||||||
|
**Oplossing:**
|
||||||
|
- Test altijd lokaal met `npm run build` voordat je pusht
|
||||||
|
- Of gebruik `tsc --noEmit` om type checking te doen zonder te builden
|
||||||
|
|
||||||
|
### 2. **TypeScript Versie Verschillen**
|
||||||
|
|
||||||
|
**Lokaal:**
|
||||||
|
```bash
|
||||||
|
npx tsc --version # Kan een andere versie zijn
|
||||||
|
```
|
||||||
|
|
||||||
|
**In Docker:**
|
||||||
|
- Gebruikt de versie uit `package.json` (`"typescript": "^5.6.3"`)
|
||||||
|
- Maar zonder `package-lock.json` kan een andere patch versie geïnstalleerd worden
|
||||||
|
|
||||||
|
**Oplossing:**
|
||||||
|
- Genereer `package-lock.json` met `npm install`
|
||||||
|
- Commit `package-lock.json` naar Git
|
||||||
|
- Dit zorgt voor consistente dependency versies
|
||||||
|
|
||||||
|
### 3. **tsconfig.json Strictness**
|
||||||
|
|
||||||
|
Je `tsconfig.json` heeft `"strict": true`, wat betekent:
|
||||||
|
- Alle strict type checking opties zijn aan
|
||||||
|
- Dit is goed voor productie, maar kan lokaal vervelend zijn
|
||||||
|
|
||||||
|
**Mogelijke verschillen:**
|
||||||
|
- Lokaal kan je IDE/editor andere TypeScript settings hebben
|
||||||
|
- Lokaal kan je `tsconfig.json` overrides hebben
|
||||||
|
- CI/CD gebruikt altijd de exacte `tsconfig.json` uit de repo
|
||||||
|
|
||||||
|
### 4. **Node.js Versie Verschillen**
|
||||||
|
|
||||||
|
**Lokaal:**
|
||||||
|
- Kan een andere Node.js versie hebben
|
||||||
|
- TypeScript gedrag kan verschillen tussen Node versies
|
||||||
|
|
||||||
|
**In Docker:**
|
||||||
|
```dockerfile
|
||||||
|
FROM node:20-alpine # Specifieke Node versie
|
||||||
|
```
|
||||||
|
|
||||||
|
**Oplossing:**
|
||||||
|
- Gebruik `.nvmrc` of `package.json` engines field om Node versie te specificeren
|
||||||
|
- Zorg dat lokaal dezelfde Node versie gebruikt wordt
|
||||||
|
|
||||||
|
### 5. **Cached Builds**
|
||||||
|
|
||||||
|
**Lokaal:**
|
||||||
|
- Oude compiled files in `dist/` kunnen nog werken
|
||||||
|
- IDE kan gecachte type informatie gebruiken
|
||||||
|
- `tsx` gebruikt geen build output, dus errors worden niet altijd gezien
|
||||||
|
|
||||||
|
**In CI/CD:**
|
||||||
|
- Schone build elke keer
|
||||||
|
- Geen cache, dus alle errors worden gezien
|
||||||
|
|
||||||
|
### 6. **Incremental Compilation**
|
||||||
|
|
||||||
|
**Lokaal:**
|
||||||
|
- TypeScript kan incremental compilation gebruiken
|
||||||
|
- Alleen gewijzigde files worden gecheckt
|
||||||
|
|
||||||
|
**In CI/CD:**
|
||||||
|
- Volledige rebuild elke keer
|
||||||
|
- Alle files worden gecheckt
|
||||||
|
|
||||||
|
## Best Practices om Dit Te Voorkomen
|
||||||
|
|
||||||
|
### 1. Test Lokaal Met Production Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Voordat je pusht, test altijd:
|
||||||
|
cd backend
|
||||||
|
npm run build # Dit gebruikt tsc, net als in Docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Type Checking Zonder Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alleen type checking, geen build:
|
||||||
|
npx tsc --noEmit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Pre-commit Hooks
|
||||||
|
|
||||||
|
Voeg een pre-commit hook toe die `tsc --noEmit` draait:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"precommit": "npm run type-check"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Genereer package-lock.json
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Genereer lock file voor consistente dependencies:
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Commit package-lock.json naar Git
|
||||||
|
git add package-lock.json
|
||||||
|
git commit -m "Add package-lock.json for consistent builds"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Gebruik CI/CD Lokaal
|
||||||
|
|
||||||
|
Test je pipeline lokaal met:
|
||||||
|
- **act** (voor GitHub Actions)
|
||||||
|
- **Azure DevOps Pipeline Agent** lokaal
|
||||||
|
- **Docker build** lokaal: `docker build -f backend/Dockerfile.prod -t test-build ./backend`
|
||||||
|
|
||||||
|
### 6. IDE TypeScript Settings
|
||||||
|
|
||||||
|
Zorg dat je IDE dezelfde TypeScript versie gebruikt:
|
||||||
|
- VS Code: Check "TypeScript: Select TypeScript Version"
|
||||||
|
- Gebruik "Use Workspace Version"
|
||||||
|
|
||||||
|
## Voor Dit Project Specifiek
|
||||||
|
|
||||||
|
### Huidige Situatie
|
||||||
|
|
||||||
|
1. **Development:** `tsx watch` - minder strict
|
||||||
|
2. **Production Build:** `tsc` - volledig strict
|
||||||
|
3. **Geen package-lock.json** - verschillende dependency versies mogelijk
|
||||||
|
4. **TypeScript 5.6.3** in package.json, maar lokaal mogelijk 5.9.3
|
||||||
|
|
||||||
|
### Aanbevolen Acties
|
||||||
|
|
||||||
|
1. **Genereer package-lock.json:**
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
git add package-lock.json
|
||||||
|
git commit -m "Add package-lock.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Test altijd met build:**
|
||||||
|
```bash
|
||||||
|
npm run build # Voordat je pusht
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Voeg type-check script toe:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Test Docker build lokaal:**
|
||||||
|
```bash
|
||||||
|
docker build -f backend/Dockerfile.prod -t test-backend ./backend
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conclusie
|
||||||
|
|
||||||
|
Het verschil komt vooral door:
|
||||||
|
- **tsx (dev) vs tsc (build)** - tsx is minder strict
|
||||||
|
- **Geen package-lock.json** - verschillende dependency versies
|
||||||
|
- **Cached builds lokaal** - oude code werkt nog
|
||||||
|
|
||||||
|
**Oplossing:** Test altijd met `npm run build` lokaal voordat je pusht, en genereer `package-lock.json` voor consistente builds.
|
||||||
Reference in New Issue
Block a user