@haposoft/cafekit 0.3.1 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Risk Calculation Script
5
+ * Đánh giá mức độ risk của code changes
6
+ *
7
+ * Usage:
8
+ * node calculate-risk.js
9
+ * node calculate-risk.js --files "file1.ts,file2.ts"
10
+ * node calculate-risk.js --json
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ // Risk scoring configuration
18
+ const RISK_CONFIG = {
19
+ // File type weights
20
+ fileTypes: {
21
+ database: 5, // Schema, migrations
22
+ api: 3, // API endpoints
23
+ auth: 5, // Authentication
24
+ payment: 5, // Payment processing
25
+ security: 5, // Security-related
26
+ config: 2, // Configuration
27
+ frontend: 1, // UI components
28
+ test: 0 // Test files
29
+ },
30
+
31
+ // Change size weights
32
+ changeSize: {
33
+ huge: { threshold: 500, weight: 3 },
34
+ large: { threshold: 200, weight: 2 },
35
+ medium: { threshold: 50, weight: 1 },
36
+ small: { threshold: 0, weight: 0 }
37
+ },
38
+
39
+ // Dependency weights
40
+ dependencies: {
41
+ many: { threshold: 20, weight: 3 },
42
+ some: { threshold: 10, weight: 2 },
43
+ few: { threshold: 5, weight: 1 },
44
+ none: { threshold: 0, weight: 0 }
45
+ },
46
+
47
+ // Risk levels
48
+ levels: {
49
+ critical: { threshold: 15, label: 'CRITICAL', emoji: '⚠️⚠️⚠️' },
50
+ high: { threshold: 10, label: 'HIGH', emoji: '⚠️⚠️' },
51
+ medium: { threshold: 5, label: 'MEDIUM', emoji: '⚠️' },
52
+ low: { threshold: 0, label: 'LOW', emoji: '✅' }
53
+ }
54
+ };
55
+
56
+ function getChangedFiles(fileList) {
57
+ if (fileList) {
58
+ return fileList.split(',').map(f => f.trim());
59
+ }
60
+
61
+ try {
62
+ const output = execSync('git diff HEAD --name-only', { encoding: 'utf-8' });
63
+ return output.trim().split('\n').filter(f => f);
64
+ } catch (e) {
65
+ console.error('❌ Error getting changed files:', e.message);
66
+ return [];
67
+ }
68
+ }
69
+
70
+ function getChangeStats(files) {
71
+ const stats = {
72
+ filesChanged: files.length,
73
+ linesAdded: 0,
74
+ linesRemoved: 0,
75
+ totalLines: 0
76
+ };
77
+
78
+ try {
79
+ const output = execSync('git diff HEAD --numstat', { encoding: 'utf-8' });
80
+ const lines = output.trim().split('\n');
81
+
82
+ lines.forEach(line => {
83
+ const [added, removed] = line.split('\t').map(n => parseInt(n) || 0);
84
+ stats.linesAdded += added;
85
+ stats.linesRemoved += removed;
86
+ });
87
+
88
+ stats.totalLines = stats.linesAdded + stats.linesRemoved;
89
+ } catch (e) {
90
+ // Ignore errors
91
+ }
92
+
93
+ return stats;
94
+ }
95
+
96
+ function classifyFiles(files) {
97
+ const classified = {
98
+ database: [],
99
+ api: [],
100
+ auth: [],
101
+ payment: [],
102
+ security: [],
103
+ config: [],
104
+ frontend: [],
105
+ test: [],
106
+ other: []
107
+ };
108
+
109
+ files.forEach(file => {
110
+ const lower = file.toLowerCase();
111
+
112
+ if (lower.includes('test.') || lower.includes('spec.')) {
113
+ classified.test.push(file);
114
+ } else if (lower.includes('schema') || lower.includes('migration') || lower.includes('.sql')) {
115
+ classified.database.push(file);
116
+ } else if (lower.includes('/api/') || lower.includes('route') || lower.includes('endpoint')) {
117
+ classified.api.push(file);
118
+ } else if (lower.includes('auth') || lower.includes('login') || lower.includes('session')) {
119
+ classified.auth.push(file);
120
+ } else if (lower.includes('payment') || lower.includes('checkout') || lower.includes('stripe')) {
121
+ classified.payment.push(file);
122
+ } else if (lower.includes('security') || lower.includes('crypto') || lower.includes('encrypt')) {
123
+ classified.security.push(file);
124
+ } else if (lower.includes('config') || lower.includes('.env') || lower.includes('package.json')) {
125
+ classified.config.push(file);
126
+ } else if (lower.includes('component') || lower.includes('screen') || lower.includes('page')) {
127
+ classified.frontend.push(file);
128
+ } else {
129
+ classified.other.push(file);
130
+ }
131
+ });
132
+
133
+ return classified;
134
+ }
135
+
136
+ function countDependencies(files) {
137
+ let totalDeps = 0;
138
+
139
+ files.forEach(file => {
140
+ if (!fs.existsSync(file)) return;
141
+
142
+ try {
143
+ const basename = path.basename(file, path.extname(file));
144
+ const srcDir = 'src';
145
+
146
+ if (!fs.existsSync(srcDir)) return;
147
+
148
+ // Count imports of this file
149
+ const grepCmd = `grep -r "from.*${basename}" ${srcDir} --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | wc -l`;
150
+ const count = parseInt(execSync(grepCmd, { encoding: 'utf-8' }).trim()) || 0;
151
+ totalDeps += count;
152
+ } catch (e) {
153
+ // Ignore errors
154
+ }
155
+ });
156
+
157
+ return totalDeps;
158
+ }
159
+
160
+ function detectBreakingChanges(files) {
161
+ const breaking = [];
162
+
163
+ files.forEach(file => {
164
+ if (!fs.existsSync(file)) return;
165
+
166
+ try {
167
+ // Check git diff for breaking patterns
168
+ const diffCmd = `git diff HEAD -- "${file}"`;
169
+ const diff = execSync(diffCmd, { encoding: 'utf-8' });
170
+
171
+ // Detect function signature changes
172
+ if (diff.match(/^-.*function.*\(/m) && diff.match(/^\+.*function.*\(/m)) {
173
+ breaking.push({
174
+ file,
175
+ type: 'function_signature',
176
+ description: 'Function signature may have changed'
177
+ });
178
+ }
179
+
180
+ // Detect interface changes
181
+ if (diff.match(/^-.*interface/m) || diff.match(/^\+.*interface/m)) {
182
+ breaking.push({
183
+ file,
184
+ type: 'interface_change',
185
+ description: 'Interface definition changed'
186
+ });
187
+ }
188
+
189
+ // Detect API endpoint changes
190
+ if (diff.match(/^-.*\.(get|post|put|delete|patch)\(/m)) {
191
+ breaking.push({
192
+ file,
193
+ type: 'api_endpoint',
194
+ description: 'API endpoint may have changed'
195
+ });
196
+ }
197
+ } catch (e) {
198
+ // Ignore errors
199
+ }
200
+ });
201
+
202
+ return breaking;
203
+ }
204
+
205
+ function calculateRiskScore(data) {
206
+ let score = 0;
207
+ const breakdown = [];
208
+
209
+ // File type risk
210
+ Object.entries(data.classified).forEach(([type, files]) => {
211
+ if (files.length > 0 && RISK_CONFIG.fileTypes[type]) {
212
+ const weight = RISK_CONFIG.fileTypes[type];
213
+ const points = weight * Math.min(files.length, 3); // Cap at 3x
214
+ score += points;
215
+ breakdown.push({
216
+ factor: `${type} files`,
217
+ weight,
218
+ count: files.length,
219
+ points,
220
+ description: `${files.length} ${type} file(s) changed`
221
+ });
222
+ }
223
+ });
224
+
225
+ // Change size risk
226
+ const totalLines = data.stats.totalLines;
227
+ for (const [size, config] of Object.entries(RISK_CONFIG.changeSize)) {
228
+ if (totalLines >= config.threshold) {
229
+ score += config.weight;
230
+ breakdown.push({
231
+ factor: 'change size',
232
+ weight: config.weight,
233
+ count: totalLines,
234
+ points: config.weight,
235
+ description: `${totalLines} lines changed (${size})`
236
+ });
237
+ break;
238
+ }
239
+ }
240
+
241
+ // Dependency risk
242
+ const depCount = data.dependencies;
243
+ for (const [level, config] of Object.entries(RISK_CONFIG.dependencies)) {
244
+ if (depCount >= config.threshold) {
245
+ score += config.weight;
246
+ breakdown.push({
247
+ factor: 'dependencies',
248
+ weight: config.weight,
249
+ count: depCount,
250
+ points: config.weight,
251
+ description: `${depCount} files depend on changes (${level})`
252
+ });
253
+ break;
254
+ }
255
+ }
256
+
257
+ // Breaking changes risk
258
+ if (data.breakingChanges.length > 0) {
259
+ const points = Math.min(data.breakingChanges.length * 2, 5);
260
+ score += points;
261
+ breakdown.push({
262
+ factor: 'breaking changes',
263
+ weight: 2,
264
+ count: data.breakingChanges.length,
265
+ points,
266
+ description: `${data.breakingChanges.length} potential breaking change(s)`
267
+ });
268
+ }
269
+
270
+ // Determine risk level
271
+ let level = RISK_CONFIG.levels.low;
272
+ for (const [name, config] of Object.entries(RISK_CONFIG.levels)) {
273
+ if (score >= config.threshold) {
274
+ level = config;
275
+ }
276
+ }
277
+
278
+ return {
279
+ score,
280
+ maxScore: 25,
281
+ level: level.label,
282
+ emoji: level.emoji,
283
+ breakdown
284
+ };
285
+ }
286
+
287
+ function generateRecommendations(data, risk) {
288
+ const recommendations = [];
289
+
290
+ // High risk recommendations
291
+ if (risk.level === 'CRITICAL' || risk.level === 'HIGH') {
292
+ recommendations.push({
293
+ priority: 'CRITICAL',
294
+ action: 'Extensive testing required',
295
+ description: 'Run full test suite including integration and E2E tests'
296
+ });
297
+
298
+ recommendations.push({
299
+ priority: 'CRITICAL',
300
+ action: 'Code review required',
301
+ description: 'Get at least 2 senior developers to review changes'
302
+ });
303
+ }
304
+
305
+ // Database changes
306
+ if (data.classified.database.length > 0) {
307
+ recommendations.push({
308
+ priority: 'HIGH',
309
+ action: 'Database migration plan',
310
+ description: 'Create rollback script and test migration on staging'
311
+ });
312
+ }
313
+
314
+ // API changes
315
+ if (data.classified.api.length > 0) {
316
+ recommendations.push({
317
+ priority: 'HIGH',
318
+ action: 'API documentation update',
319
+ description: 'Update API docs and notify frontend team'
320
+ });
321
+ }
322
+
323
+ // Auth changes
324
+ if (data.classified.auth.length > 0) {
325
+ recommendations.push({
326
+ priority: 'CRITICAL',
327
+ action: 'Security review',
328
+ description: 'Review authentication flow and test all auth scenarios'
329
+ });
330
+ }
331
+
332
+ // Payment changes
333
+ if (data.classified.payment.length > 0) {
334
+ recommendations.push({
335
+ priority: 'CRITICAL',
336
+ action: 'Payment testing',
337
+ description: 'Test payment flow in sandbox environment'
338
+ });
339
+ }
340
+
341
+ // Breaking changes
342
+ if (data.breakingChanges.length > 0) {
343
+ recommendations.push({
344
+ priority: 'HIGH',
345
+ action: 'Update all consumers',
346
+ description: `Update ${data.dependencies} files that depend on changed code`
347
+ });
348
+ }
349
+
350
+ // Many dependencies
351
+ if (data.dependencies > 10) {
352
+ recommendations.push({
353
+ priority: 'MEDIUM',
354
+ action: 'Regression testing',
355
+ description: 'Test all affected features to prevent regression'
356
+ });
357
+ }
358
+
359
+ return recommendations;
360
+ }
361
+
362
+ function printReport(data, risk, recommendations) {
363
+ console.log('\n🎯 Risk Assessment Report\n');
364
+ console.log('═'.repeat(60));
365
+
366
+ // Risk score
367
+ console.log(`\n📊 Risk Score: ${risk.score}/${risk.maxScore}`);
368
+ console.log(`🎚️ Risk Level: ${risk.emoji} ${risk.level}`);
369
+
370
+ // Summary
371
+ console.log(`\n📋 Summary:`);
372
+ console.log(` Files changed: ${data.stats.filesChanged}`);
373
+ console.log(` Lines changed: ${data.stats.totalLines} (+${data.stats.linesAdded}, -${data.stats.linesRemoved})`);
374
+ console.log(` Dependencies: ${data.dependencies} files affected`);
375
+ console.log(` Breaking changes: ${data.breakingChanges.length}`);
376
+
377
+ // File classification
378
+ console.log(`\n📁 File Classification:`);
379
+ Object.entries(data.classified).forEach(([type, files]) => {
380
+ if (files.length > 0) {
381
+ const weight = RISK_CONFIG.fileTypes[type] || 0;
382
+ const emoji = weight >= 5 ? '🔴' : weight >= 3 ? '🟡' : '🟢';
383
+ console.log(` ${emoji} ${type}: ${files.length} file(s)`);
384
+ files.slice(0, 3).forEach(file => {
385
+ console.log(` - ${file}`);
386
+ });
387
+ if (files.length > 3) {
388
+ console.log(` ... and ${files.length - 3} more`);
389
+ }
390
+ }
391
+ });
392
+
393
+ // Risk breakdown
394
+ console.log(`\n⚖️ Risk Breakdown:`);
395
+ risk.breakdown.forEach(item => {
396
+ console.log(` • ${item.description}`);
397
+ console.log(` Points: ${item.points} (weight: ${item.weight})`);
398
+ });
399
+
400
+ // Breaking changes
401
+ if (data.breakingChanges.length > 0) {
402
+ console.log(`\n⚠️ Potential Breaking Changes:`);
403
+ data.breakingChanges.forEach((change, i) => {
404
+ console.log(` ${i + 1}. ${change.file}`);
405
+ console.log(` Type: ${change.type}`);
406
+ console.log(` ${change.description}`);
407
+ });
408
+ }
409
+
410
+ // Recommendations
411
+ console.log(`\n💡 Recommendations:`);
412
+ recommendations.forEach((rec, i) => {
413
+ const emoji = rec.priority === 'CRITICAL' ? '🔴' : rec.priority === 'HIGH' ? '🟡' : '🟢';
414
+ console.log(` ${emoji} [${rec.priority}] ${rec.action}`);
415
+ console.log(` ${rec.description}`);
416
+ });
417
+
418
+ console.log('\n' + '═'.repeat(60));
419
+ console.log('');
420
+ }
421
+
422
+ function printJSON(data, risk, recommendations) {
423
+ const output = {
424
+ risk,
425
+ data,
426
+ recommendations,
427
+ timestamp: new Date().toISOString()
428
+ };
429
+ console.log(JSON.stringify(output, null, 2));
430
+ }
431
+
432
+ // Main
433
+ const args = process.argv.slice(2);
434
+ const fileList = args.includes('--files') ? args[args.indexOf('--files') + 1] : null;
435
+ const jsonOutput = args.includes('--json');
436
+
437
+ const files = getChangedFiles(fileList);
438
+
439
+ if (files.length === 0) {
440
+ console.error('❌ No changed files found');
441
+ console.error('Usage: node calculate-risk.js [--files "file1.ts,file2.ts"] [--json]');
442
+ process.exit(1);
443
+ }
444
+
445
+ const stats = getChangeStats(files);
446
+ const classified = classifyFiles(files);
447
+ const dependencies = countDependencies(files);
448
+ const breakingChanges = detectBreakingChanges(files);
449
+
450
+ const data = {
451
+ files,
452
+ stats,
453
+ classified,
454
+ dependencies,
455
+ breakingChanges
456
+ };
457
+
458
+ const risk = calculateRiskScore(data);
459
+ const recommendations = generateRecommendations(data, risk);
460
+
461
+ if (jsonOutput) {
462
+ printJSON(data, risk, recommendations);
463
+ } else {
464
+ printReport(data, risk, recommendations);
465
+ }
466
+
467
+ // Export for programmatic use
468
+ if (typeof module !== 'undefined' && module.exports) {
469
+ module.exports = {
470
+ calculateRiskScore,
471
+ generateRecommendations,
472
+ classifyFiles,
473
+ getChangeStats
474
+ };
475
+ }
@@ -0,0 +1,202 @@
1
+ #!/bin/bash
2
+
3
+ # Find Dependencies Script
4
+ # Tìm tất cả files bị ảnh hưởng bởi code changes
5
+ #
6
+ # Usage:
7
+ # ./find-dependencies.sh <file-path>
8
+ # ./find-dependencies.sh src/services/authService.ts
9
+
10
+ set -e
11
+
12
+ if [ $# -eq 0 ]; then
13
+ echo "Usage: $0 <file-path>"
14
+ echo "Example: $0 src/services/authService.ts"
15
+ exit 1
16
+ fi
17
+
18
+ FILE_PATH="$1"
19
+ FILE_NAME=$(basename "$FILE_PATH")
20
+ FILE_BASE="${FILE_NAME%.*}"
21
+ SRC_DIR="${SRC_DIR:-src}"
22
+
23
+ echo "🔍 Finding dependencies for: $FILE_PATH"
24
+ echo ""
25
+
26
+ # 1. Find direct imports
27
+ echo "📦 Direct Imports:"
28
+ echo "Files that import this module:"
29
+ echo ""
30
+
31
+ # TypeScript/JavaScript imports
32
+ grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
33
+ grep -v "node_modules" | \
34
+ grep -v ".test." | \
35
+ grep -v ".spec." | \
36
+ cut -d: -f1 | \
37
+ sort -u | \
38
+ while read -r file; do
39
+ echo " - $file"
40
+ done
41
+
42
+ # Alternative import syntax
43
+ grep -r "import.*from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
44
+ grep -v "node_modules" | \
45
+ grep -v ".test." | \
46
+ grep -v ".spec." | \
47
+ cut -d: -f1 | \
48
+ sort -u | \
49
+ while read -r file; do
50
+ echo " - $file"
51
+ done
52
+
53
+ echo ""
54
+
55
+ # 2. Find function/class usage
56
+ echo "🔧 Function/Class Usage:"
57
+ echo "Searching for common exports..."
58
+ echo ""
59
+
60
+ # Extract exported names from the file
61
+ if [ -f "$FILE_PATH" ]; then
62
+ # Find exported functions
63
+ EXPORTS=$(grep -E "export (function|const|class|interface|type)" "$FILE_PATH" 2>/dev/null | \
64
+ sed -E 's/export (function|const|class|interface|type) ([a-zA-Z0-9_]+).*/\2/' | \
65
+ head -10)
66
+
67
+ if [ -n "$EXPORTS" ]; then
68
+ for export_name in $EXPORTS; do
69
+ echo " Searching for: $export_name"
70
+
71
+ # Find usage (excluding the source file itself)
72
+ grep -r "\b$export_name\b" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
73
+ grep -v "node_modules" | \
74
+ grep -v "$FILE_PATH" | \
75
+ grep -v ".test." | \
76
+ grep -v ".spec." | \
77
+ cut -d: -f1 | \
78
+ sort -u | \
79
+ head -5 | \
80
+ while read -r file; do
81
+ echo " - $file"
82
+ done
83
+ done
84
+ fi
85
+ fi
86
+
87
+ echo ""
88
+
89
+ # 3. Find API endpoint usage (if this is an API file)
90
+ if [[ "$FILE_PATH" == *"/api/"* ]] || [[ "$FILE_PATH" == *"routes"* ]]; then
91
+ echo "🌐 API Endpoint Usage:"
92
+ echo "Searching for API calls..."
93
+ echo ""
94
+
95
+ # Extract API endpoints from the file
96
+ ENDPOINTS=$(grep -E "(get|post|put|delete|patch)\(['\"]" "$FILE_PATH" 2>/dev/null | \
97
+ sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/" | \
98
+ head -5)
99
+
100
+ if [ -n "$ENDPOINTS" ]; then
101
+ for endpoint in $ENDPOINTS; do
102
+ echo " Endpoint: $endpoint"
103
+
104
+ # Find frontend calls to this endpoint
105
+ grep -r "$endpoint" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
106
+ grep -v "node_modules" | \
107
+ grep -v "$FILE_PATH" | \
108
+ cut -d: -f1 | \
109
+ sort -u | \
110
+ head -5 | \
111
+ while read -r file; do
112
+ echo " - $file"
113
+ done
114
+ done
115
+ fi
116
+
117
+ echo ""
118
+ fi
119
+
120
+ # 4. Find component usage (if this is a React component)
121
+ if [[ "$FILE_PATH" == *"components"* ]] || [[ "$FILE_PATH" == *".tsx" ]]; then
122
+ echo "⚛️ Component Usage:"
123
+ echo "Searching for component references..."
124
+ echo ""
125
+
126
+ COMPONENT_NAME=$(basename "$FILE_PATH" .tsx)
127
+ COMPONENT_NAME=$(basename "$COMPONENT_NAME" .jsx)
128
+
129
+ # Find JSX usage
130
+ grep -r "<$COMPONENT_NAME" "$SRC_DIR" --include="*.tsx" --include="*.jsx" 2>/dev/null | \
131
+ grep -v "node_modules" | \
132
+ grep -v "$FILE_PATH" | \
133
+ cut -d: -f1 | \
134
+ sort -u | \
135
+ while read -r file; do
136
+ echo " - $file"
137
+ done
138
+
139
+ echo ""
140
+ fi
141
+
142
+ # 5. Find test files
143
+ echo "🧪 Related Tests:"
144
+ echo "Test files for this module:"
145
+ echo ""
146
+
147
+ # Find test files with similar names
148
+ find "$SRC_DIR" -type f \( -name "*$FILE_BASE*.test.*" -o -name "*$FILE_BASE*.spec.*" \) 2>/dev/null | \
149
+ while read -r file; do
150
+ echo " - $file"
151
+ done
152
+
153
+ # Find test files that import this module
154
+ grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.test.*" --include="*.spec.*" 2>/dev/null | \
155
+ cut -d: -f1 | \
156
+ sort -u | \
157
+ while read -r file; do
158
+ echo " - $file"
159
+ done
160
+
161
+ echo ""
162
+
163
+ # 6. Summary
164
+ echo "📊 Summary:"
165
+ echo ""
166
+
167
+ IMPORT_COUNT=$(grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
168
+ grep -v "node_modules" | \
169
+ grep -v ".test." | \
170
+ grep -v ".spec." | \
171
+ wc -l | tr -d ' ')
172
+
173
+ TEST_COUNT=$(find "$SRC_DIR" -type f \( -name "*$FILE_BASE*.test.*" -o -name "*$FILE_BASE*.spec.*" \) 2>/dev/null | wc -l | tr -d ' ')
174
+
175
+ echo " Direct imports: $IMPORT_COUNT"
176
+ echo " Related tests: $TEST_COUNT"
177
+ echo ""
178
+
179
+ # 7. Recommendations
180
+ echo "💡 Recommendations:"
181
+ echo ""
182
+
183
+ if [ "$IMPORT_COUNT" -gt 10 ]; then
184
+ echo " ⚠️ HIGH IMPACT: This file is imported by many files ($IMPORT_COUNT)"
185
+ echo " → Test thoroughly before deploying"
186
+ echo " → Consider backward compatibility"
187
+ fi
188
+
189
+ if [ "$TEST_COUNT" -eq 0 ]; then
190
+ echo " ⚠️ NO TESTS: No test files found for this module"
191
+ echo " → Add unit tests before deploying"
192
+ fi
193
+
194
+ if [[ "$FILE_PATH" == *"/api/"* ]]; then
195
+ echo " ⚠️ API CHANGE: This is an API file"
196
+ echo " → Check for breaking changes"
197
+ echo " → Update API documentation"
198
+ echo " → Test all API consumers"
199
+ fi
200
+
201
+ echo ""
202
+ echo "✅ Dependency analysis complete"