@codebakers/cli 1.6.0 ā 2.1.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/dist/commands/audit.d.ts +19 -0
- package/dist/commands/audit.js +730 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +176 -0
- package/dist/commands/doctor.js +59 -4
- package/dist/commands/heal.d.ts +41 -0
- package/dist/commands/heal.js +734 -0
- package/dist/commands/login.js +12 -16
- package/dist/commands/provision.d.ts +55 -3
- package/dist/commands/provision.js +243 -74
- package/dist/commands/scaffold.js +158 -80
- package/dist/commands/setup.js +60 -19
- package/dist/commands/upgrade.d.ts +4 -0
- package/dist/commands/upgrade.js +90 -0
- package/dist/config.d.ts +61 -5
- package/dist/config.js +268 -5
- package/dist/index.js +44 -3
- package/dist/lib/api.d.ts +45 -0
- package/dist/lib/api.js +159 -0
- package/dist/mcp/server.js +146 -0
- package/package.json +1 -1
- package/src/commands/audit.ts +827 -0
- package/src/commands/config.ts +216 -0
- package/src/commands/doctor.ts +69 -4
- package/src/commands/heal.ts +889 -0
- package/src/commands/login.ts +14 -18
- package/src/commands/provision.ts +323 -101
- package/src/commands/scaffold.ts +188 -81
- package/src/commands/setup.ts +65 -20
- package/src/commands/upgrade.ts +110 -0
- package/src/config.ts +320 -11
- package/src/index.ts +48 -3
- package/src/lib/api.ts +183 -0
- package/src/mcp/server.ts +160 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.heal = heal;
|
|
7
|
+
exports.healWatch = healWatch;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const ERROR_PATTERNS = [
|
|
13
|
+
// TypeScript Errors
|
|
14
|
+
{
|
|
15
|
+
category: 'typescript',
|
|
16
|
+
severity: 'high',
|
|
17
|
+
pattern: /TS2307.*Cannot find module '([^']+)'/,
|
|
18
|
+
autoFixable: true,
|
|
19
|
+
confidence: 90,
|
|
20
|
+
fixId: 'install_module',
|
|
21
|
+
fixDescription: 'Install missing module',
|
|
22
|
+
risk: 'safe'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
category: 'typescript',
|
|
26
|
+
severity: 'medium',
|
|
27
|
+
pattern: /TS2322.*Type '([^']+)' is not assignable to type '([^']+)'/,
|
|
28
|
+
autoFixable: false,
|
|
29
|
+
confidence: 60,
|
|
30
|
+
fixId: 'fix_type',
|
|
31
|
+
fixDescription: 'Fix type mismatch',
|
|
32
|
+
risk: 'moderate'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
category: 'typescript',
|
|
36
|
+
severity: 'medium',
|
|
37
|
+
pattern: /TS2339.*Property '([^']+)' does not exist on type '([^']+)'/,
|
|
38
|
+
autoFixable: false,
|
|
39
|
+
confidence: 70,
|
|
40
|
+
fixId: 'add_property',
|
|
41
|
+
fixDescription: 'Add missing property to type',
|
|
42
|
+
risk: 'moderate'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
category: 'typescript',
|
|
46
|
+
severity: 'low',
|
|
47
|
+
pattern: /TS7006.*Parameter '([^']+)' implicitly has an 'any' type/,
|
|
48
|
+
autoFixable: false,
|
|
49
|
+
confidence: 80,
|
|
50
|
+
fixId: 'add_type',
|
|
51
|
+
fixDescription: 'Add explicit type annotation',
|
|
52
|
+
risk: 'safe'
|
|
53
|
+
},
|
|
54
|
+
// Dependency Errors
|
|
55
|
+
{
|
|
56
|
+
category: 'dependency',
|
|
57
|
+
severity: 'high',
|
|
58
|
+
pattern: /Module not found: Can't resolve '([^']+)'/,
|
|
59
|
+
autoFixable: true,
|
|
60
|
+
confidence: 90,
|
|
61
|
+
fixId: 'install_package',
|
|
62
|
+
fixDescription: 'Install missing package',
|
|
63
|
+
risk: 'safe'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
category: 'dependency',
|
|
67
|
+
severity: 'high',
|
|
68
|
+
pattern: /Cannot find module '([^']+)'/,
|
|
69
|
+
autoFixable: true,
|
|
70
|
+
confidence: 85,
|
|
71
|
+
fixId: 'install_package',
|
|
72
|
+
fixDescription: 'Install missing package',
|
|
73
|
+
risk: 'safe'
|
|
74
|
+
},
|
|
75
|
+
// Database Errors
|
|
76
|
+
{
|
|
77
|
+
category: 'database',
|
|
78
|
+
severity: 'critical',
|
|
79
|
+
pattern: /connect ECONNREFUSED/,
|
|
80
|
+
autoFixable: false,
|
|
81
|
+
confidence: 95,
|
|
82
|
+
fixId: 'check_db',
|
|
83
|
+
fixDescription: 'Check database connection - ensure database is running',
|
|
84
|
+
risk: 'safe'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
category: 'database',
|
|
88
|
+
severity: 'high',
|
|
89
|
+
pattern: /relation "([^"]+)" does not exist/,
|
|
90
|
+
autoFixable: true,
|
|
91
|
+
confidence: 90,
|
|
92
|
+
fixId: 'run_migrations',
|
|
93
|
+
fixDescription: 'Run database migrations',
|
|
94
|
+
fixCommand: 'npx drizzle-kit push',
|
|
95
|
+
risk: 'moderate'
|
|
96
|
+
},
|
|
97
|
+
// Auth Errors
|
|
98
|
+
{
|
|
99
|
+
category: 'auth',
|
|
100
|
+
severity: 'critical',
|
|
101
|
+
pattern: /AUTH_SECRET.*missing|undefined|AUTH_SECRET is not set/i,
|
|
102
|
+
autoFixable: true,
|
|
103
|
+
confidence: 95,
|
|
104
|
+
fixId: 'generate_auth_secret',
|
|
105
|
+
fixDescription: 'Generate AUTH_SECRET',
|
|
106
|
+
risk: 'safe'
|
|
107
|
+
},
|
|
108
|
+
// Configuration Errors
|
|
109
|
+
{
|
|
110
|
+
category: 'configuration',
|
|
111
|
+
severity: 'high',
|
|
112
|
+
pattern: /Missing required environment variable:?\s*([A-Z_]+)/i,
|
|
113
|
+
autoFixable: false,
|
|
114
|
+
confidence: 95,
|
|
115
|
+
fixId: 'add_env_var',
|
|
116
|
+
fixDescription: 'Add missing environment variable',
|
|
117
|
+
risk: 'safe'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
category: 'configuration',
|
|
121
|
+
severity: 'high',
|
|
122
|
+
pattern: /NEXT_PUBLIC_([A-Z_]+).*undefined/,
|
|
123
|
+
autoFixable: false,
|
|
124
|
+
confidence: 90,
|
|
125
|
+
fixId: 'add_env_var',
|
|
126
|
+
fixDescription: 'Add missing public environment variable',
|
|
127
|
+
risk: 'safe'
|
|
128
|
+
},
|
|
129
|
+
// Security Errors
|
|
130
|
+
{
|
|
131
|
+
category: 'security',
|
|
132
|
+
severity: 'high',
|
|
133
|
+
pattern: /found (\d+) vulnerabilit(y|ies)/i,
|
|
134
|
+
autoFixable: true,
|
|
135
|
+
confidence: 80,
|
|
136
|
+
fixId: 'npm_audit_fix',
|
|
137
|
+
fixDescription: 'Run npm audit fix',
|
|
138
|
+
fixCommand: 'npm audit fix',
|
|
139
|
+
risk: 'moderate'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
category: 'security',
|
|
143
|
+
severity: 'critical',
|
|
144
|
+
pattern: /(\d+) critical/i,
|
|
145
|
+
autoFixable: false,
|
|
146
|
+
confidence: 90,
|
|
147
|
+
fixId: 'npm_audit_fix_critical',
|
|
148
|
+
fixDescription: 'Review and fix critical vulnerabilities manually',
|
|
149
|
+
risk: 'risky'
|
|
150
|
+
},
|
|
151
|
+
// Build Errors
|
|
152
|
+
{
|
|
153
|
+
category: 'build',
|
|
154
|
+
severity: 'high',
|
|
155
|
+
pattern: /Build failed|Failed to compile/i,
|
|
156
|
+
autoFixable: false,
|
|
157
|
+
confidence: 50,
|
|
158
|
+
fixId: 'fix_build',
|
|
159
|
+
fixDescription: 'Review build errors',
|
|
160
|
+
risk: 'moderate'
|
|
161
|
+
},
|
|
162
|
+
// Runtime Errors
|
|
163
|
+
{
|
|
164
|
+
category: 'runtime',
|
|
165
|
+
severity: 'high',
|
|
166
|
+
pattern: /Cannot read propert(y|ies) of (undefined|null)/,
|
|
167
|
+
autoFixable: false,
|
|
168
|
+
confidence: 70,
|
|
169
|
+
fixId: 'add_null_check',
|
|
170
|
+
fixDescription: 'Add null/undefined check',
|
|
171
|
+
risk: 'safe'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
category: 'runtime',
|
|
175
|
+
severity: 'high',
|
|
176
|
+
pattern: /([A-Za-z]+) is not defined/,
|
|
177
|
+
autoFixable: false,
|
|
178
|
+
confidence: 75,
|
|
179
|
+
fixId: 'add_import',
|
|
180
|
+
fixDescription: 'Add missing import or declaration',
|
|
181
|
+
risk: 'safe'
|
|
182
|
+
}
|
|
183
|
+
];
|
|
184
|
+
function generateErrorId() {
|
|
185
|
+
return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
186
|
+
}
|
|
187
|
+
function classifyError(errorText, file, line) {
|
|
188
|
+
for (const rule of ERROR_PATTERNS) {
|
|
189
|
+
const match = errorText.match(rule.pattern);
|
|
190
|
+
if (match) {
|
|
191
|
+
return {
|
|
192
|
+
id: generateErrorId(),
|
|
193
|
+
timestamp: new Date(),
|
|
194
|
+
category: rule.category,
|
|
195
|
+
severity: rule.severity,
|
|
196
|
+
message: errorText.trim(),
|
|
197
|
+
file,
|
|
198
|
+
line,
|
|
199
|
+
autoFixable: rule.autoFixable,
|
|
200
|
+
confidence: rule.confidence,
|
|
201
|
+
suggestedFixes: [{
|
|
202
|
+
id: rule.fixId,
|
|
203
|
+
description: rule.fixDescription,
|
|
204
|
+
confidence: rule.confidence,
|
|
205
|
+
risk: rule.risk,
|
|
206
|
+
requiresReview: rule.risk !== 'safe',
|
|
207
|
+
command: rule.fixCommand
|
|
208
|
+
}]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Unknown error
|
|
213
|
+
return {
|
|
214
|
+
id: generateErrorId(),
|
|
215
|
+
timestamp: new Date(),
|
|
216
|
+
category: 'unknown',
|
|
217
|
+
severity: 'medium',
|
|
218
|
+
message: errorText.trim(),
|
|
219
|
+
file,
|
|
220
|
+
line,
|
|
221
|
+
autoFixable: false,
|
|
222
|
+
confidence: 0,
|
|
223
|
+
suggestedFixes: []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// ERROR SCANNING
|
|
228
|
+
// ============================================================================
|
|
229
|
+
async function scanTypeScriptErrors() {
|
|
230
|
+
const errors = [];
|
|
231
|
+
try {
|
|
232
|
+
(0, child_process_1.execSync)('npx tsc --noEmit 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const output = error.stdout || error.stderr || error.message || '';
|
|
236
|
+
const lines = output.split('\n');
|
|
237
|
+
let currentFile = '';
|
|
238
|
+
let currentLine = 0;
|
|
239
|
+
for (const line of lines) {
|
|
240
|
+
// Match file:line:col pattern
|
|
241
|
+
const fileMatch = line.match(/^([^:]+):(\d+):(\d+)/);
|
|
242
|
+
if (fileMatch) {
|
|
243
|
+
currentFile = fileMatch[1];
|
|
244
|
+
currentLine = parseInt(fileMatch[2], 10);
|
|
245
|
+
}
|
|
246
|
+
// Match TS error codes
|
|
247
|
+
const tsMatch = line.match(/error (TS\d+):/);
|
|
248
|
+
if (tsMatch) {
|
|
249
|
+
const classified = classifyError(line, currentFile, currentLine);
|
|
250
|
+
errors.push(classified);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return errors;
|
|
255
|
+
}
|
|
256
|
+
async function scanBuildErrors() {
|
|
257
|
+
const errors = [];
|
|
258
|
+
try {
|
|
259
|
+
// Check if build script exists
|
|
260
|
+
const packageJson = JSON.parse(await promises_1.default.readFile('package.json', 'utf-8'));
|
|
261
|
+
if (!packageJson.scripts?.build) {
|
|
262
|
+
return errors;
|
|
263
|
+
}
|
|
264
|
+
(0, child_process_1.execSync)('npm run build 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
const output = error.stdout || error.stderr || error.message || '';
|
|
268
|
+
const lines = output.split('\n');
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
if (line.includes('error') || line.includes('Error') || line.includes('failed')) {
|
|
271
|
+
const classified = classifyError(line);
|
|
272
|
+
// Avoid duplicates from TS errors
|
|
273
|
+
if (classified.category !== 'typescript') {
|
|
274
|
+
errors.push(classified);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return errors;
|
|
280
|
+
}
|
|
281
|
+
async function scanEnvironmentIssues() {
|
|
282
|
+
const errors = [];
|
|
283
|
+
// Check for .env files
|
|
284
|
+
const envFiles = ['.env', '.env.local', '.env.development'];
|
|
285
|
+
let hasEnvFile = false;
|
|
286
|
+
for (const file of envFiles) {
|
|
287
|
+
try {
|
|
288
|
+
await promises_1.default.access(file);
|
|
289
|
+
hasEnvFile = true;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// File doesn't exist
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!hasEnvFile) {
|
|
297
|
+
errors.push({
|
|
298
|
+
id: generateErrorId(),
|
|
299
|
+
timestamp: new Date(),
|
|
300
|
+
category: 'configuration',
|
|
301
|
+
severity: 'high',
|
|
302
|
+
message: 'No .env file found - environment variables may not be configured',
|
|
303
|
+
autoFixable: false,
|
|
304
|
+
confidence: 90,
|
|
305
|
+
suggestedFixes: [{
|
|
306
|
+
id: 'create_env',
|
|
307
|
+
description: 'Create .env.local file with required variables',
|
|
308
|
+
confidence: 90,
|
|
309
|
+
risk: 'safe',
|
|
310
|
+
requiresReview: true
|
|
311
|
+
}]
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
// Check for AUTH_SECRET if using auth
|
|
315
|
+
try {
|
|
316
|
+
const envContent = await promises_1.default.readFile('.env.local', 'utf-8').catch(() => '');
|
|
317
|
+
const packageJson = JSON.parse(await promises_1.default.readFile('package.json', 'utf-8'));
|
|
318
|
+
const hasAuth = packageJson.dependencies?.['next-auth'] ||
|
|
319
|
+
packageJson.dependencies?.['@auth/core'] ||
|
|
320
|
+
packageJson.dependencies?.['@supabase/auth-helpers-nextjs'];
|
|
321
|
+
if (hasAuth && !envContent.includes('AUTH_SECRET')) {
|
|
322
|
+
errors.push({
|
|
323
|
+
id: generateErrorId(),
|
|
324
|
+
timestamp: new Date(),
|
|
325
|
+
category: 'auth',
|
|
326
|
+
severity: 'critical',
|
|
327
|
+
message: 'AUTH_SECRET not found in .env.local - authentication will not work',
|
|
328
|
+
autoFixable: true,
|
|
329
|
+
confidence: 95,
|
|
330
|
+
suggestedFixes: [{
|
|
331
|
+
id: 'generate_auth_secret',
|
|
332
|
+
description: 'Generate and add AUTH_SECRET to .env.local',
|
|
333
|
+
confidence: 95,
|
|
334
|
+
risk: 'safe',
|
|
335
|
+
requiresReview: false
|
|
336
|
+
}]
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
// Skip if can't read files
|
|
342
|
+
}
|
|
343
|
+
return errors;
|
|
344
|
+
}
|
|
345
|
+
async function scanSecurityIssues() {
|
|
346
|
+
const errors = [];
|
|
347
|
+
try {
|
|
348
|
+
const output = (0, child_process_1.execSync)('npm audit --json 2>&1', { encoding: 'utf-8', stdio: 'pipe' });
|
|
349
|
+
const audit = JSON.parse(output);
|
|
350
|
+
if (audit.metadata?.vulnerabilities) {
|
|
351
|
+
const { critical, high, moderate, low } = audit.metadata.vulnerabilities;
|
|
352
|
+
if (critical > 0) {
|
|
353
|
+
errors.push({
|
|
354
|
+
id: generateErrorId(),
|
|
355
|
+
timestamp: new Date(),
|
|
356
|
+
category: 'security',
|
|
357
|
+
severity: 'critical',
|
|
358
|
+
message: `${critical} critical vulnerabilities found`,
|
|
359
|
+
autoFixable: false,
|
|
360
|
+
confidence: 95,
|
|
361
|
+
suggestedFixes: [{
|
|
362
|
+
id: 'npm_audit_fix',
|
|
363
|
+
description: 'Run npm audit fix --force (may have breaking changes)',
|
|
364
|
+
confidence: 70,
|
|
365
|
+
risk: 'risky',
|
|
366
|
+
requiresReview: true,
|
|
367
|
+
command: 'npm audit fix --force'
|
|
368
|
+
}]
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (high > 0) {
|
|
372
|
+
errors.push({
|
|
373
|
+
id: generateErrorId(),
|
|
374
|
+
timestamp: new Date(),
|
|
375
|
+
category: 'security',
|
|
376
|
+
severity: 'high',
|
|
377
|
+
message: `${high} high-severity vulnerabilities found`,
|
|
378
|
+
autoFixable: true,
|
|
379
|
+
confidence: 85,
|
|
380
|
+
suggestedFixes: [{
|
|
381
|
+
id: 'npm_audit_fix',
|
|
382
|
+
description: 'Run npm audit fix',
|
|
383
|
+
confidence: 85,
|
|
384
|
+
risk: 'moderate',
|
|
385
|
+
requiresReview: false,
|
|
386
|
+
command: 'npm audit fix'
|
|
387
|
+
}]
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (moderate > 0) {
|
|
391
|
+
errors.push({
|
|
392
|
+
id: generateErrorId(),
|
|
393
|
+
timestamp: new Date(),
|
|
394
|
+
category: 'security',
|
|
395
|
+
severity: 'medium',
|
|
396
|
+
message: `${moderate} moderate vulnerabilities found`,
|
|
397
|
+
autoFixable: true,
|
|
398
|
+
confidence: 90,
|
|
399
|
+
suggestedFixes: [{
|
|
400
|
+
id: 'npm_audit_fix',
|
|
401
|
+
description: 'Run npm audit fix',
|
|
402
|
+
confidence: 90,
|
|
403
|
+
risk: 'safe',
|
|
404
|
+
requiresReview: false,
|
|
405
|
+
command: 'npm audit fix'
|
|
406
|
+
}]
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// npm audit failed or no issues
|
|
413
|
+
}
|
|
414
|
+
return errors;
|
|
415
|
+
}
|
|
416
|
+
async function scanDatabaseIssues() {
|
|
417
|
+
const errors = [];
|
|
418
|
+
try {
|
|
419
|
+
// Check if using Drizzle
|
|
420
|
+
const packageJson = JSON.parse(await promises_1.default.readFile('package.json', 'utf-8'));
|
|
421
|
+
if (!packageJson.dependencies?.['drizzle-orm']) {
|
|
422
|
+
return errors;
|
|
423
|
+
}
|
|
424
|
+
// Check for migrations folder
|
|
425
|
+
const migrationsPaths = ['drizzle', 'src/db/migrations', 'migrations'];
|
|
426
|
+
let hasMigrations = false;
|
|
427
|
+
for (const migPath of migrationsPaths) {
|
|
428
|
+
try {
|
|
429
|
+
const stat = await promises_1.default.stat(migPath);
|
|
430
|
+
if (stat.isDirectory()) {
|
|
431
|
+
const files = await promises_1.default.readdir(migPath);
|
|
432
|
+
if (files.some(f => f.endsWith('.sql'))) {
|
|
433
|
+
hasMigrations = true;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// Directory doesn't exist
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (!hasMigrations) {
|
|
443
|
+
errors.push({
|
|
444
|
+
id: generateErrorId(),
|
|
445
|
+
timestamp: new Date(),
|
|
446
|
+
category: 'database',
|
|
447
|
+
severity: 'medium',
|
|
448
|
+
message: 'No database migrations found - schema may not be pushed',
|
|
449
|
+
autoFixable: true,
|
|
450
|
+
confidence: 85,
|
|
451
|
+
suggestedFixes: [{
|
|
452
|
+
id: 'generate_migrations',
|
|
453
|
+
description: 'Generate and push migrations',
|
|
454
|
+
confidence: 85,
|
|
455
|
+
risk: 'moderate',
|
|
456
|
+
requiresReview: true,
|
|
457
|
+
command: 'npx drizzle-kit generate && npx drizzle-kit push'
|
|
458
|
+
}]
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// Skip if can't read package.json
|
|
464
|
+
}
|
|
465
|
+
return errors;
|
|
466
|
+
}
|
|
467
|
+
// ============================================================================
|
|
468
|
+
// FIX APPLICATION
|
|
469
|
+
// ============================================================================
|
|
470
|
+
async function applyFix(error, fix) {
|
|
471
|
+
switch (fix.id) {
|
|
472
|
+
case 'install_package':
|
|
473
|
+
case 'install_module':
|
|
474
|
+
return await installMissingPackage(error);
|
|
475
|
+
case 'generate_auth_secret':
|
|
476
|
+
return await generateAuthSecret();
|
|
477
|
+
case 'npm_audit_fix':
|
|
478
|
+
return await runCommand('npm audit fix');
|
|
479
|
+
case 'run_migrations':
|
|
480
|
+
return await runCommand('npx drizzle-kit push');
|
|
481
|
+
case 'generate_migrations':
|
|
482
|
+
return await runCommand('npx drizzle-kit generate && npx drizzle-kit push');
|
|
483
|
+
default:
|
|
484
|
+
if (fix.command) {
|
|
485
|
+
return await runCommand(fix.command);
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async function installMissingPackage(error) {
|
|
491
|
+
// Extract package name from error message
|
|
492
|
+
const match = error.message.match(/(?:Cannot find module|Can't resolve) '([^']+)'/);
|
|
493
|
+
if (!match)
|
|
494
|
+
return false;
|
|
495
|
+
let packageName = match[1];
|
|
496
|
+
// Skip relative imports
|
|
497
|
+
if (packageName.startsWith('.') || packageName.startsWith('@/')) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
// Extract base package name (handle scoped packages and subpaths)
|
|
501
|
+
if (packageName.startsWith('@')) {
|
|
502
|
+
packageName = packageName.split('/').slice(0, 2).join('/');
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
packageName = packageName.split('/')[0];
|
|
506
|
+
}
|
|
507
|
+
return await runCommand(`npm install ${packageName}`);
|
|
508
|
+
}
|
|
509
|
+
async function generateAuthSecret() {
|
|
510
|
+
try {
|
|
511
|
+
const crypto = await import('crypto');
|
|
512
|
+
const secret = crypto.randomBytes(32).toString('base64');
|
|
513
|
+
let envContent = '';
|
|
514
|
+
try {
|
|
515
|
+
envContent = await promises_1.default.readFile('.env.local', 'utf-8');
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
// File doesn't exist, create it
|
|
519
|
+
}
|
|
520
|
+
if (envContent.includes('AUTH_SECRET=')) {
|
|
521
|
+
envContent = envContent.replace(/AUTH_SECRET=.*/, `AUTH_SECRET=${secret}`);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
envContent += `\nAUTH_SECRET=${secret}\n`;
|
|
525
|
+
}
|
|
526
|
+
await promises_1.default.writeFile('.env.local', envContent.trim() + '\n');
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function runCommand(command) {
|
|
534
|
+
try {
|
|
535
|
+
(0, child_process_1.execSync)(command, { stdio: 'pipe' });
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// DISPLAY
|
|
544
|
+
// ============================================================================
|
|
545
|
+
function displayError(error) {
|
|
546
|
+
const severityColors = {
|
|
547
|
+
critical: chalk_1.default.red,
|
|
548
|
+
high: chalk_1.default.red,
|
|
549
|
+
medium: chalk_1.default.yellow,
|
|
550
|
+
low: chalk_1.default.blue,
|
|
551
|
+
info: chalk_1.default.gray
|
|
552
|
+
};
|
|
553
|
+
const severityIcons = {
|
|
554
|
+
critical: 'š“',
|
|
555
|
+
high: 'š ',
|
|
556
|
+
medium: 'š”',
|
|
557
|
+
low: 'šµ',
|
|
558
|
+
info: 'ā¹ļø'
|
|
559
|
+
};
|
|
560
|
+
const color = severityColors[error.severity];
|
|
561
|
+
const icon = severityIcons[error.severity];
|
|
562
|
+
console.log(`\n${icon} ${color(chalk_1.default.bold(error.category.toUpperCase()))} (${error.severity})`);
|
|
563
|
+
console.log(chalk_1.default.white(` ${error.message}`));
|
|
564
|
+
if (error.file) {
|
|
565
|
+
console.log(chalk_1.default.gray(` š ${error.file}${error.line ? `:${error.line}` : ''}`));
|
|
566
|
+
}
|
|
567
|
+
if (error.autoFixable) {
|
|
568
|
+
console.log(chalk_1.default.green(` ā Auto-fixable (${error.confidence}% confidence)`));
|
|
569
|
+
}
|
|
570
|
+
for (const fix of error.suggestedFixes) {
|
|
571
|
+
const riskColor = fix.risk === 'safe' ? chalk_1.default.green :
|
|
572
|
+
fix.risk === 'moderate' ? chalk_1.default.yellow : chalk_1.default.red;
|
|
573
|
+
console.log(chalk_1.default.cyan(` ā ${fix.description}`) + riskColor(` [${fix.risk}]`));
|
|
574
|
+
if (fix.command) {
|
|
575
|
+
console.log(chalk_1.default.gray(` $ ${fix.command}`));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
// ============================================================================
|
|
580
|
+
// MAIN HEAL FUNCTION
|
|
581
|
+
// ============================================================================
|
|
582
|
+
async function heal(options = {}) {
|
|
583
|
+
console.log(chalk_1.default.blue('\nš„ CodeBakers Self-Healing System\n'));
|
|
584
|
+
console.log(chalk_1.default.gray('Scanning for issues...\n'));
|
|
585
|
+
const allErrors = [];
|
|
586
|
+
// Scan for all types of errors
|
|
587
|
+
const scanners = [
|
|
588
|
+
{ name: 'TypeScript', fn: scanTypeScriptErrors },
|
|
589
|
+
{ name: 'Build', fn: scanBuildErrors },
|
|
590
|
+
{ name: 'Environment', fn: scanEnvironmentIssues },
|
|
591
|
+
{ name: 'Security', fn: scanSecurityIssues },
|
|
592
|
+
{ name: 'Database', fn: scanDatabaseIssues }
|
|
593
|
+
];
|
|
594
|
+
for (const scanner of scanners) {
|
|
595
|
+
const spinner = (0, ora_1.default)(`Checking ${scanner.name}...`).start();
|
|
596
|
+
try {
|
|
597
|
+
const errors = await scanner.fn();
|
|
598
|
+
allErrors.push(...errors);
|
|
599
|
+
if (errors.length > 0) {
|
|
600
|
+
spinner.warn(`${scanner.name}: ${errors.length} issue(s)`);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
spinner.succeed(`${scanner.name}: OK`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
spinner.fail(`${scanner.name}: Error scanning`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Filter by severity if specified
|
|
611
|
+
let errors = allErrors;
|
|
612
|
+
if (options.severity) {
|
|
613
|
+
errors = errors.filter(e => e.severity === options.severity);
|
|
614
|
+
}
|
|
615
|
+
// Remove duplicates by message
|
|
616
|
+
const seen = new Set();
|
|
617
|
+
errors = errors.filter(e => {
|
|
618
|
+
if (seen.has(e.message))
|
|
619
|
+
return false;
|
|
620
|
+
seen.add(e.message);
|
|
621
|
+
return true;
|
|
622
|
+
});
|
|
623
|
+
console.log('');
|
|
624
|
+
if (errors.length === 0) {
|
|
625
|
+
console.log(chalk_1.default.green('⨠No issues found! Your project is healthy.\n'));
|
|
626
|
+
return { errors: [], fixed: 0, remaining: 0 };
|
|
627
|
+
}
|
|
628
|
+
console.log(chalk_1.default.yellow(`Found ${errors.length} issue(s):`));
|
|
629
|
+
// Sort by severity
|
|
630
|
+
const severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
|
|
631
|
+
errors.sort((a, b) => severityOrder.indexOf(a.severity) - severityOrder.indexOf(b.severity));
|
|
632
|
+
// Display all errors
|
|
633
|
+
for (const error of errors) {
|
|
634
|
+
displayError(error);
|
|
635
|
+
}
|
|
636
|
+
console.log('');
|
|
637
|
+
// Apply fixes
|
|
638
|
+
let fixed = 0;
|
|
639
|
+
if (!options.dryRun) {
|
|
640
|
+
const fixableErrors = errors.filter(e => e.autoFixable &&
|
|
641
|
+
e.confidence >= 80 &&
|
|
642
|
+
e.suggestedFixes.some(f => f.risk !== 'risky'));
|
|
643
|
+
if (fixableErrors.length > 0) {
|
|
644
|
+
console.log(chalk_1.default.blue(`\nš§ Applying ${fixableErrors.length} auto-fix(es)...\n`));
|
|
645
|
+
for (const error of fixableErrors) {
|
|
646
|
+
const safeFix = error.suggestedFixes.find(f => f.risk !== 'risky');
|
|
647
|
+
if (!safeFix)
|
|
648
|
+
continue;
|
|
649
|
+
if (!options.auto) {
|
|
650
|
+
// In non-auto mode, we just show what would be fixed
|
|
651
|
+
console.log(chalk_1.default.gray(`Would fix: ${error.category} - ${safeFix.description}`));
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
const spinner = (0, ora_1.default)(`Fixing: ${safeFix.description}`).start();
|
|
655
|
+
try {
|
|
656
|
+
const success = await applyFix(error, safeFix);
|
|
657
|
+
if (success) {
|
|
658
|
+
spinner.succeed(`Fixed: ${safeFix.description}`);
|
|
659
|
+
error.fixed = true;
|
|
660
|
+
fixed++;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
spinner.fail(`Failed: ${safeFix.description}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
spinner.fail(`Error: ${safeFix.description}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Summary
|
|
673
|
+
const remaining = errors.length - fixed;
|
|
674
|
+
console.log(chalk_1.default.blue('\nš Summary'));
|
|
675
|
+
console.log(` Total issues: ${chalk_1.default.white(errors.length)}`);
|
|
676
|
+
console.log(` Fixed: ${chalk_1.default.green(fixed)}`);
|
|
677
|
+
console.log(` Remaining: ${remaining > 0 ? chalk_1.default.yellow(remaining) : chalk_1.default.green(0)}`);
|
|
678
|
+
if (options.dryRun) {
|
|
679
|
+
console.log(chalk_1.default.gray('\n [Dry run - no changes made]'));
|
|
680
|
+
}
|
|
681
|
+
else if (!options.auto && errors.some(e => e.autoFixable)) {
|
|
682
|
+
console.log(chalk_1.default.gray('\n Run with --auto to apply fixes automatically'));
|
|
683
|
+
}
|
|
684
|
+
console.log('');
|
|
685
|
+
return {
|
|
686
|
+
errors,
|
|
687
|
+
fixed,
|
|
688
|
+
remaining
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
// ============================================================================
|
|
692
|
+
// WATCH MODE
|
|
693
|
+
// ============================================================================
|
|
694
|
+
async function healWatch() {
|
|
695
|
+
console.log(chalk_1.default.blue('\nšļø Self-Healing Watch Mode\n'));
|
|
696
|
+
console.log(chalk_1.default.gray('Monitoring for errors... (Ctrl+C to stop)\n'));
|
|
697
|
+
// Initial scan
|
|
698
|
+
await heal({ auto: true });
|
|
699
|
+
// Watch for file changes using dynamic import
|
|
700
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
701
|
+
let chokidarModule = null;
|
|
702
|
+
try {
|
|
703
|
+
// @ts-ignore - chokidar is an optional dependency for watch mode
|
|
704
|
+
chokidarModule = await import('chokidar');
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
console.log(chalk_1.default.yellow('\nWatch mode requires chokidar:'));
|
|
708
|
+
console.log(chalk_1.default.cyan(' npm install -D chokidar\n'));
|
|
709
|
+
console.log(chalk_1.default.gray('For now, run `codebakers heal` manually after making changes.'));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const watcher = chokidarModule.watch(['src/**/*.{ts,tsx}', 'app/**/*.{ts,tsx}'], {
|
|
713
|
+
ignored: /node_modules/,
|
|
714
|
+
persistent: true,
|
|
715
|
+
ignoreInitial: true
|
|
716
|
+
});
|
|
717
|
+
let debounceTimer = null;
|
|
718
|
+
watcher.on('change', (filePath) => {
|
|
719
|
+
console.log(chalk_1.default.gray(`\nFile changed: ${filePath}`));
|
|
720
|
+
// Debounce to avoid running multiple times
|
|
721
|
+
if (debounceTimer) {
|
|
722
|
+
clearTimeout(debounceTimer);
|
|
723
|
+
}
|
|
724
|
+
debounceTimer = setTimeout(async () => {
|
|
725
|
+
await heal({ auto: true });
|
|
726
|
+
}, 1000);
|
|
727
|
+
});
|
|
728
|
+
// Keep process alive
|
|
729
|
+
process.on('SIGINT', () => {
|
|
730
|
+
console.log(chalk_1.default.blue('\n\nš Watch mode stopped\n'));
|
|
731
|
+
watcher.close();
|
|
732
|
+
process.exit(0);
|
|
733
|
+
});
|
|
734
|
+
}
|