cerber-core 1.0.0
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.
- package/.cerber-example/BIBLE.md +132 -0
- package/.cerber-example/CERBER_LAW.md +200 -0
- package/.cerber-example/connections/contracts/booking-to-pricing.json +44 -0
- package/.cerber-example/connections/contracts/pricing-to-booking.json +37 -0
- package/.cerber-example/modules/booking-calendar/MODULE.md +225 -0
- package/.cerber-example/modules/booking-calendar/contract.json +106 -0
- package/.cerber-example/modules/booking-calendar/dependencies.json +8 -0
- package/.cerber-example/modules/pricing-engine/MODULE.md +160 -0
- package/.cerber-example/modules/pricing-engine/contract.json +64 -0
- package/.cerber-example/modules/pricing-engine/dependencies.json +8 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +1379 -0
- package/bin/cerber +105 -0
- package/bin/cerber-focus +31 -0
- package/bin/cerber-guardian +90 -0
- package/bin/cerber-health +113 -0
- package/bin/cerber-morning +19 -0
- package/bin/cerber-repair +21 -0
- package/dist/cerber/index.d.ts +47 -0
- package/dist/cerber/index.d.ts.map +1 -0
- package/dist/cerber/index.js +154 -0
- package/dist/cerber/index.js.map +1 -0
- package/dist/guardian/index.d.ts +70 -0
- package/dist/guardian/index.d.ts.map +1 -0
- package/dist/guardian/index.js +271 -0
- package/dist/guardian/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/examples/backend-schema.ts +72 -0
- package/examples/frontend-schema.ts +67 -0
- package/examples/health-checks.ts +196 -0
- package/examples/solo-integration/README.md +457 -0
- package/examples/solo-integration/package.json +47 -0
- package/examples/team-integration/README.md +347 -0
- package/examples/team-integration/package.json +23 -0
- package/package.json +104 -0
- package/solo/README.md +258 -0
- package/solo/config/performance-budget.json +53 -0
- package/solo/config/solo-contract.json +71 -0
- package/solo/lib/feature-flags.ts +177 -0
- package/solo/scripts/cerber-auto-repair.js +260 -0
- package/solo/scripts/cerber-daily-check.js +282 -0
- package/solo/scripts/cerber-dashboard.js +191 -0
- package/solo/scripts/cerber-deps-health.js +247 -0
- package/solo/scripts/cerber-docs-sync.js +304 -0
- package/solo/scripts/cerber-flags-check.js +229 -0
- package/solo/scripts/cerber-performance-budget.js +271 -0
- package/solo/scripts/cerber-rollback.js +229 -0
- package/solo/scripts/cerber-snapshot.js +319 -0
- package/team/README.md +327 -0
- package/team/config/team-contract.json +27 -0
- package/team/lib/module-system.ts +157 -0
- package/team/scripts/cerber-add-module.sh +195 -0
- package/team/scripts/cerber-connections-check.sh +186 -0
- package/team/scripts/cerber-focus.sh +170 -0
- package/team/scripts/cerber-module-check.sh +165 -0
- package/team/scripts/cerber-team-morning.sh +210 -0
- package/team/templates/BIBLE_TEMPLATE.md +52 -0
- package/team/templates/CONNECTION_TEMPLATE.json +20 -0
- package/team/templates/MODULE_TEMPLATE.md +60 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cerber SOLO - Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Extends Cerber Core with automation for solo developers
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Colored terminal output
|
|
10
|
+
* - System health summary
|
|
11
|
+
* - Quick actions menu
|
|
12
|
+
*
|
|
13
|
+
* @author Stefan Pitek
|
|
14
|
+
* @copyright 2026 Stefan Pitek
|
|
15
|
+
* @license MIT
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
// Terminal colors
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
bright: '\x1b[1m',
|
|
24
|
+
dim: '\x1b[2m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
green: '\x1b[32m',
|
|
27
|
+
yellow: '\x1b[33m',
|
|
28
|
+
blue: '\x1b[34m',
|
|
29
|
+
magenta: '\x1b[35m',
|
|
30
|
+
cyan: '\x1b[36m',
|
|
31
|
+
white: '\x1b[37m',
|
|
32
|
+
bgRed: '\x1b[41m',
|
|
33
|
+
bgGreen: '\x1b[42m',
|
|
34
|
+
bgYellow: '\x1b[43m',
|
|
35
|
+
bgBlue: '\x1b[44m'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function colorize(text, color) {
|
|
39
|
+
return `${colors[color] || ''}${text}${colors.reset}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function header(text) {
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(colorize('='.repeat(60), 'cyan'));
|
|
45
|
+
console.log(colorize(text, 'bright'));
|
|
46
|
+
console.log(colorize('='.repeat(60), 'cyan'));
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function section(title) {
|
|
51
|
+
console.log(colorize(`\n${title}`, 'yellow'));
|
|
52
|
+
console.log(colorize('-'.repeat(40), 'dim'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function success(text) {
|
|
56
|
+
console.log(colorize(`✅ ${text}`, 'green'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function warning(text) {
|
|
60
|
+
console.log(colorize(`⚠️ ${text}`, 'yellow'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function error(text) {
|
|
64
|
+
console.log(colorize(`❌ ${text}`, 'red'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function info(text) {
|
|
68
|
+
console.log(colorize(`ℹ️ ${text}`, 'blue'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function metric(label, value, status = 'normal') {
|
|
72
|
+
const statusColor = status === 'good' ? 'green' :
|
|
73
|
+
status === 'warning' ? 'yellow' :
|
|
74
|
+
status === 'error' ? 'red' : 'white';
|
|
75
|
+
console.log(` ${colorize(label + ':', 'dim')} ${colorize(value, statusColor)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Main dashboard
|
|
79
|
+
header('🛡️ Cerber SOLO Dashboard');
|
|
80
|
+
|
|
81
|
+
// System status
|
|
82
|
+
section('📊 System Status');
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const packagePath = require('path').join(process.cwd(), 'package.json');
|
|
86
|
+
const fs = require('fs');
|
|
87
|
+
|
|
88
|
+
if (fs.existsSync(packagePath)) {
|
|
89
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
90
|
+
metric('Project', pkg.name || 'unknown');
|
|
91
|
+
metric('Version', pkg.version || '0.0.0');
|
|
92
|
+
success('Package configuration found');
|
|
93
|
+
} else {
|
|
94
|
+
warning('No package.json found');
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
error('Could not read package.json');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Git status
|
|
101
|
+
section('📂 Git Status');
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
stdio: 'pipe'
|
|
107
|
+
}).trim();
|
|
108
|
+
|
|
109
|
+
metric('Branch', branch);
|
|
110
|
+
|
|
111
|
+
const status = execSync('git status --short', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
112
|
+
|
|
113
|
+
if (status) {
|
|
114
|
+
const lines = status.split('\n').length;
|
|
115
|
+
warning(`${lines} file(s) with changes`);
|
|
116
|
+
} else {
|
|
117
|
+
success('Working directory clean');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check for unpushed commits
|
|
121
|
+
try {
|
|
122
|
+
const unpushed = execSync('git log @{u}.. --oneline 2>/dev/null || true', {
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
stdio: 'pipe'
|
|
125
|
+
}).trim();
|
|
126
|
+
|
|
127
|
+
if (unpushed) {
|
|
128
|
+
const count = unpushed.split('\n').filter(l => l.trim()).length;
|
|
129
|
+
info(`${count} unpushed commit(s)`);
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// No upstream
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
info('Not a git repository');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Guardian status
|
|
139
|
+
section('🛡️ Guardian Status');
|
|
140
|
+
|
|
141
|
+
const guardianPaths = [
|
|
142
|
+
'scripts/validate-schema.mjs',
|
|
143
|
+
'scripts/validate-schema.js',
|
|
144
|
+
'.husky/pre-commit'
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const guardianExists = guardianPaths.some(p => {
|
|
148
|
+
const fs = require('fs');
|
|
149
|
+
const path = require('path');
|
|
150
|
+
return fs.existsSync(path.join(process.cwd(), p));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (guardianExists) {
|
|
154
|
+
success('Guardian installed and active');
|
|
155
|
+
} else {
|
|
156
|
+
warning('Guardian not detected');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// SOLO tools
|
|
160
|
+
section('⚡ SOLO Tools');
|
|
161
|
+
|
|
162
|
+
const tools = [
|
|
163
|
+
{ name: 'Auto-repair', cmd: 'cerber:repair' },
|
|
164
|
+
{ name: 'Deps health', cmd: 'cerber:deps' },
|
|
165
|
+
{ name: 'Performance', cmd: 'cerber:perf' },
|
|
166
|
+
{ name: 'Docs sync', cmd: 'cerber:docs' },
|
|
167
|
+
{ name: 'Feature flags', cmd: 'cerber:flags' },
|
|
168
|
+
{ name: 'Snapshot', cmd: 'cerber:snapshot' }
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
console.log();
|
|
172
|
+
tools.forEach(tool => {
|
|
173
|
+
console.log(` ${colorize('•', 'cyan')} ${tool.name}: ${colorize(`npm run ${tool.cmd}`, 'dim')}`);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Quick actions
|
|
177
|
+
section('🚀 Quick Actions');
|
|
178
|
+
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(colorize(' 1.', 'bright') + ' Run morning check: ' + colorize('npm run cerber:morning', 'cyan'));
|
|
181
|
+
console.log(colorize(' 2.', 'bright') + ' Auto-fix issues: ' + colorize('npm run cerber:repair', 'cyan'));
|
|
182
|
+
console.log(colorize(' 3.', 'bright') + ' Check dependencies: ' + colorize('npm run cerber:deps', 'cyan'));
|
|
183
|
+
console.log(colorize(' 4.', 'bright') + ' Before pushing: ' + colorize('npm run cerber:pre-push', 'cyan'));
|
|
184
|
+
console.log(colorize(' 5.', 'bright') + ' End of day snapshot: ' + colorize('npm run cerber:snapshot', 'cyan'));
|
|
185
|
+
|
|
186
|
+
// Footer
|
|
187
|
+
console.log();
|
|
188
|
+
console.log(colorize('='.repeat(60), 'cyan'));
|
|
189
|
+
console.log(colorize('\n✨ Cerber SOLO - Built by Stefan Pitek\n', 'magenta'));
|
|
190
|
+
|
|
191
|
+
process.exit(0);
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cerber SOLO - Dependency Health Checker
|
|
5
|
+
*
|
|
6
|
+
* Extends Cerber Core with automation for solo developers
|
|
7
|
+
*
|
|
8
|
+
* Checks:
|
|
9
|
+
* - npm audit (vulnerabilities)
|
|
10
|
+
* - Outdated packages
|
|
11
|
+
* - Deprecated packages
|
|
12
|
+
* - Unmaintained packages (no updates 2+ years)
|
|
13
|
+
*
|
|
14
|
+
* @author Stefan Pitek
|
|
15
|
+
* @copyright 2026 Stefan Pitek
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
console.log('🏥 Cerber SOLO - Dependency Health Check\n');
|
|
24
|
+
|
|
25
|
+
let healthScore = 100;
|
|
26
|
+
const issues = [];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check for security vulnerabilities
|
|
30
|
+
*/
|
|
31
|
+
function checkVulnerabilities() {
|
|
32
|
+
console.log('🔒 Checking security vulnerabilities...');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
execSync('npm audit --json > /tmp/audit-result.json 2>/dev/null', {
|
|
36
|
+
stdio: 'pipe',
|
|
37
|
+
encoding: 'utf8'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const auditData = JSON.parse(fs.readFileSync('/tmp/audit-result.json', 'utf8'));
|
|
41
|
+
|
|
42
|
+
const critical = auditData.metadata?.vulnerabilities?.critical || 0;
|
|
43
|
+
const high = auditData.metadata?.vulnerabilities?.high || 0;
|
|
44
|
+
const moderate = auditData.metadata?.vulnerabilities?.moderate || 0;
|
|
45
|
+
const low = auditData.metadata?.vulnerabilities?.low || 0;
|
|
46
|
+
|
|
47
|
+
if (critical > 0 || high > 0 || moderate > 0 || low > 0) {
|
|
48
|
+
console.log(` ⚠️ Found vulnerabilities:`);
|
|
49
|
+
if (critical > 0) console.log(` 🔴 Critical: ${critical}`);
|
|
50
|
+
if (high > 0) console.log(` 🟠 High: ${high}`);
|
|
51
|
+
if (moderate > 0) console.log(` 🟡 Moderate: ${moderate}`);
|
|
52
|
+
if (low > 0) console.log(` 🟢 Low: ${low}`);
|
|
53
|
+
|
|
54
|
+
healthScore -= (critical * 20) + (high * 10) + (moderate * 5) + (low * 2);
|
|
55
|
+
issues.push({
|
|
56
|
+
type: 'security',
|
|
57
|
+
severity: critical > 0 ? 'critical' : high > 0 ? 'high' : 'moderate',
|
|
58
|
+
message: `${critical + high + moderate + low} vulnerabilities found`,
|
|
59
|
+
action: 'Run: npm audit fix'
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
console.log(' ✅ No vulnerabilities found');
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.log(' ℹ️ npm audit check skipped (may not be available)');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check for outdated packages
|
|
71
|
+
*/
|
|
72
|
+
function checkOutdated() {
|
|
73
|
+
console.log('\n📦 Checking outdated packages...');
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const outdated = execSync('npm outdated --json 2>/dev/null', {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
stdio: 'pipe'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!outdated) {
|
|
82
|
+
console.log(' ✅ All packages are up to date');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const packages = JSON.parse(outdated);
|
|
87
|
+
const count = Object.keys(packages).length;
|
|
88
|
+
|
|
89
|
+
if (count > 0) {
|
|
90
|
+
console.log(` ⚠️ ${count} outdated packages found`);
|
|
91
|
+
|
|
92
|
+
// Show top 5 outdated packages
|
|
93
|
+
const entries = Object.entries(packages).slice(0, 5);
|
|
94
|
+
entries.forEach(([name, info]) => {
|
|
95
|
+
console.log(` ${name}: ${info.current} → ${info.latest}`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (count > 5) {
|
|
99
|
+
console.log(` ... and ${count - 5} more`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
healthScore -= Math.min(count * 2, 30);
|
|
103
|
+
issues.push({
|
|
104
|
+
type: 'outdated',
|
|
105
|
+
severity: 'low',
|
|
106
|
+
message: `${count} packages are outdated`,
|
|
107
|
+
action: 'Run: npm update'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// npm outdated exits with code 1 when packages are outdated
|
|
112
|
+
console.log(' ℹ️ Could not check outdated packages');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check for deprecated packages
|
|
118
|
+
*/
|
|
119
|
+
function checkDeprecated() {
|
|
120
|
+
console.log('\n⚠️ Checking for deprecated packages...');
|
|
121
|
+
|
|
122
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(packagePath)) {
|
|
125
|
+
console.log(' ⚠️ No package.json found');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
131
|
+
const allDeps = {
|
|
132
|
+
...packageData.dependencies || {},
|
|
133
|
+
...packageData.devDependencies || {}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Known deprecated packages (example list)
|
|
137
|
+
const knownDeprecated = [
|
|
138
|
+
'request',
|
|
139
|
+
'node-uuid',
|
|
140
|
+
'babel-preset-es2015',
|
|
141
|
+
'gulp-util',
|
|
142
|
+
'coffee-script'
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const deprecated = Object.keys(allDeps).filter(dep =>
|
|
146
|
+
knownDeprecated.some(d => dep.includes(d))
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (deprecated.length > 0) {
|
|
150
|
+
console.log(` ⚠️ ${deprecated.length} deprecated packages found:`);
|
|
151
|
+
deprecated.forEach(pkg => {
|
|
152
|
+
console.log(` - ${pkg}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
healthScore -= deprecated.length * 10;
|
|
156
|
+
issues.push({
|
|
157
|
+
type: 'deprecated',
|
|
158
|
+
severity: 'moderate',
|
|
159
|
+
message: `${deprecated.length} deprecated packages in use`,
|
|
160
|
+
action: 'Replace with maintained alternatives'
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
console.log(' ✅ No known deprecated packages');
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.log(` ❌ Error checking deprecated packages: ${error.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check package-lock.json health
|
|
172
|
+
*/
|
|
173
|
+
function checkLockFile() {
|
|
174
|
+
console.log('\n🔒 Checking package-lock.json...');
|
|
175
|
+
|
|
176
|
+
const lockPath = path.join(process.cwd(), 'package-lock.json');
|
|
177
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(lockPath)) {
|
|
180
|
+
console.log(' ⚠️ No package-lock.json found');
|
|
181
|
+
issues.push({
|
|
182
|
+
type: 'lock-file',
|
|
183
|
+
severity: 'moderate',
|
|
184
|
+
message: 'Missing package-lock.json',
|
|
185
|
+
action: 'Run: npm install'
|
|
186
|
+
});
|
|
187
|
+
healthScore -= 10;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const lockStat = fs.statSync(lockPath);
|
|
193
|
+
const packageStat = fs.statSync(packagePath);
|
|
194
|
+
|
|
195
|
+
if (packageStat.mtime > lockStat.mtime) {
|
|
196
|
+
console.log(' ⚠️ package.json is newer than package-lock.json');
|
|
197
|
+
issues.push({
|
|
198
|
+
type: 'lock-file',
|
|
199
|
+
severity: 'low',
|
|
200
|
+
message: 'package-lock.json may be out of sync',
|
|
201
|
+
action: 'Run: npm install'
|
|
202
|
+
});
|
|
203
|
+
healthScore -= 5;
|
|
204
|
+
} else {
|
|
205
|
+
console.log(' ✅ package-lock.json is up to date');
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.log(` ❌ Error checking lock file: ${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Run all checks
|
|
213
|
+
checkVulnerabilities();
|
|
214
|
+
checkOutdated();
|
|
215
|
+
checkDeprecated();
|
|
216
|
+
checkLockFile();
|
|
217
|
+
|
|
218
|
+
// Generate report
|
|
219
|
+
console.log('\n' + '='.repeat(60));
|
|
220
|
+
console.log('\n📊 Dependency Health Report\n');
|
|
221
|
+
|
|
222
|
+
healthScore = Math.max(0, healthScore);
|
|
223
|
+
const grade = healthScore >= 90 ? 'A' :
|
|
224
|
+
healthScore >= 75 ? 'B' :
|
|
225
|
+
healthScore >= 60 ? 'C' :
|
|
226
|
+
healthScore >= 40 ? 'D' : 'F';
|
|
227
|
+
|
|
228
|
+
const gradeEmoji = grade === 'A' ? '✅' :
|
|
229
|
+
grade === 'B' ? '👍' :
|
|
230
|
+
grade === 'C' ? '⚠️' :
|
|
231
|
+
grade === 'D' ? '🔴' : '💀';
|
|
232
|
+
|
|
233
|
+
console.log(`${gradeEmoji} Health Score: ${healthScore}/100 (Grade: ${grade})`);
|
|
234
|
+
console.log(` Issues Found: ${issues.length}`);
|
|
235
|
+
|
|
236
|
+
if (issues.length > 0) {
|
|
237
|
+
console.log('\n🔧 Recommended Actions:\n');
|
|
238
|
+
issues.forEach((issue, idx) => {
|
|
239
|
+
console.log(`${idx + 1}. [${issue.severity.toUpperCase()}] ${issue.message}`);
|
|
240
|
+
console.log(` → ${issue.action}\n`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('='.repeat(60));
|
|
245
|
+
|
|
246
|
+
// Exit with appropriate code
|
|
247
|
+
process.exit(healthScore < 60 ? 1 : 0);
|