@monodog/backend 1.2.4 → 1.2.6
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/package.json +3 -6
- package/src/cli.ts +1 -0
- package/dist/cli.js +0 -229
- package/dist/config-loader.js +0 -97
- package/dist/gitService.js +0 -240
- package/dist/index.js +0 -1320
- package/dist/utils/helpers.js +0 -198
package/dist/index.js
DELETED
|
@@ -1,1320 +0,0 @@
|
|
|
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.startServer = startServer;
|
|
7
|
-
const express_1 = __importDefault(require("express"));
|
|
8
|
-
const cors_1 = __importDefault(require("cors"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const fs_1 = __importDefault(require("fs"));
|
|
11
|
-
const body_parser_1 = require("body-parser");
|
|
12
|
-
const monorepo_scanner_1 = require("@monodog/monorepo-scanner");
|
|
13
|
-
const ci_status_1 = require("@monodog/ci-status");
|
|
14
|
-
const helpers_1 = require("@monodog/utils/helpers");
|
|
15
|
-
const helpers_2 = require("./utils/helpers");
|
|
16
|
-
const client_1 = require("@prisma/client");
|
|
17
|
-
// Import the validateConfig function from your utils
|
|
18
|
-
// import { validateConfig } from '../../apps/dashboard/src/components/modules/config-inspector/utils/config.utils';
|
|
19
|
-
const gitService_1 = require("./gitService");
|
|
20
|
-
const prisma = new client_1.PrismaClient();
|
|
21
|
-
// The main function exported and called by the CLI
|
|
22
|
-
function startServer(rootPath, port, host) {
|
|
23
|
-
const app = (0, express_1.default)();
|
|
24
|
-
// --- Middleware ---
|
|
25
|
-
// 1. Logging Middleware
|
|
26
|
-
app.use((_req, _res, next) => {
|
|
27
|
-
console.log(`[SERVER] ${_req.method} ${_req.url} (Root: ${rootPath})`);
|
|
28
|
-
next();
|
|
29
|
-
});
|
|
30
|
-
app.use((0, cors_1.default)());
|
|
31
|
-
app.use((0, body_parser_1.json)());
|
|
32
|
-
// Health check
|
|
33
|
-
app.get('/api/health', (_, res) => {
|
|
34
|
-
res.json({
|
|
35
|
-
status: 'ok',
|
|
36
|
-
timestamp: Date.now(),
|
|
37
|
-
version: '1.0.0',
|
|
38
|
-
services: {
|
|
39
|
-
scanner: 'active',
|
|
40
|
-
ci: 'active',
|
|
41
|
-
database: 'active',
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
// Get all packages from database (DONE)
|
|
46
|
-
app.get('/api/packages', async (_req, res) => {
|
|
47
|
-
try {
|
|
48
|
-
// Try to get packages from database first
|
|
49
|
-
let dbPackages = await prisma.package.findMany();
|
|
50
|
-
if (!dbPackages.length) {
|
|
51
|
-
try {
|
|
52
|
-
const rootDir = path_1.default.resolve(rootPath);
|
|
53
|
-
const packages = (0, helpers_1.scanMonorepo)(rootDir);
|
|
54
|
-
console.log('packages --> scan', packages.length);
|
|
55
|
-
for (const pkg of packages) {
|
|
56
|
-
await (0, helpers_2.storePackage)(pkg);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
throw new Error('Error ' + error);
|
|
61
|
-
}
|
|
62
|
-
dbPackages = await prisma.package.findMany();
|
|
63
|
-
}
|
|
64
|
-
const transformedPackages = dbPackages.map(pkg => {
|
|
65
|
-
// We create a new object 'transformedPkg' based on the database record 'pkg'
|
|
66
|
-
const transformedPkg = { ...pkg };
|
|
67
|
-
// 1. Maintainers (Your Logic)
|
|
68
|
-
transformedPkg.maintainers = pkg.maintainers
|
|
69
|
-
? JSON.parse(pkg.maintainers)
|
|
70
|
-
: [];
|
|
71
|
-
// 2. Tags
|
|
72
|
-
// transformedPkg.tags = pkg.tags
|
|
73
|
-
// ? JSON.parse(pkg.tags)
|
|
74
|
-
// : [];
|
|
75
|
-
// 3. Scripts/repository (should default to an object, not an array)
|
|
76
|
-
transformedPkg.scripts = pkg.scripts ? JSON.parse(pkg.scripts) : {};
|
|
77
|
-
transformedPkg.repository = pkg.repository
|
|
78
|
-
? JSON.parse(pkg.repository)
|
|
79
|
-
: {};
|
|
80
|
-
// 4. Dependencies List
|
|
81
|
-
transformedPkg.dependencies = pkg.dependencies
|
|
82
|
-
? JSON.parse(pkg.dependencies)
|
|
83
|
-
: [];
|
|
84
|
-
transformedPkg.devDependencies = pkg.devDependencies
|
|
85
|
-
? JSON.parse(pkg.devDependencies)
|
|
86
|
-
: [];
|
|
87
|
-
transformedPkg.peerDependencies = pkg.peerDependencies
|
|
88
|
-
? JSON.parse(pkg.peerDependencies)
|
|
89
|
-
: [];
|
|
90
|
-
return transformedPkg; // Return the fully transformed object
|
|
91
|
-
});
|
|
92
|
-
res.json(transformedPackages);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
res.status(500).json({ error: 'Failed to fetch packages, ' + error });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
app.get('/api/packages/refresh', async (_req, res) => {
|
|
99
|
-
try {
|
|
100
|
-
const rootDir = path_1.default.resolve(rootPath);
|
|
101
|
-
const packages = (0, helpers_1.scanMonorepo)(rootDir);
|
|
102
|
-
console.log('packages -->', packages.length);
|
|
103
|
-
for (const pkg of packages) {
|
|
104
|
-
await (0, helpers_2.storePackage)(pkg);
|
|
105
|
-
}
|
|
106
|
-
res.json(packages);
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
res.status(500).json({ error: 'Failed to refresh packages' });
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
// Get package details
|
|
113
|
-
app.get('/api/packages/:name', async (_req, res) => {
|
|
114
|
-
try {
|
|
115
|
-
const { name } = _req.params;
|
|
116
|
-
const pkg = await prisma.package.findUnique({
|
|
117
|
-
where: {
|
|
118
|
-
name: name,
|
|
119
|
-
},
|
|
120
|
-
include: {
|
|
121
|
-
dependenciesInfo: true,
|
|
122
|
-
commits: true,
|
|
123
|
-
packageHealth: true,
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
if (!pkg) {
|
|
127
|
-
return res.status(404).json({ error: 'Package not found' });
|
|
128
|
-
}
|
|
129
|
-
const transformedPkg = { ...pkg };
|
|
130
|
-
// --- APPLY PARSING TO EACH FIELD ---
|
|
131
|
-
// 1. Maintainers (Your Logic)
|
|
132
|
-
// transformedPkg.maintainers = pkg.maintainers
|
|
133
|
-
// ? JSON.parse(pkg.maintainers)
|
|
134
|
-
// : [];
|
|
135
|
-
// 2. Tags
|
|
136
|
-
// transformedPkg.tags = pkg.tags
|
|
137
|
-
// ? JSON.parse(pkg.tags)
|
|
138
|
-
// : [];
|
|
139
|
-
// 3. Scripts/repository (should default to an object, not an array)
|
|
140
|
-
transformedPkg.scripts = pkg.scripts ? JSON.parse(pkg.scripts) : {};
|
|
141
|
-
transformedPkg.repository = pkg.repository
|
|
142
|
-
? JSON.parse(pkg.repository)
|
|
143
|
-
: {};
|
|
144
|
-
// 4. Dependencies List
|
|
145
|
-
transformedPkg.dependencies = pkg.dependencies
|
|
146
|
-
? JSON.parse(pkg.dependencies)
|
|
147
|
-
: [];
|
|
148
|
-
transformedPkg.devDependencies = pkg.devDependencies
|
|
149
|
-
? JSON.parse(pkg.devDependencies)
|
|
150
|
-
: [];
|
|
151
|
-
transformedPkg.peerDependencies = pkg.peerDependencies
|
|
152
|
-
? JSON.parse(pkg.peerDependencies)
|
|
153
|
-
: [];
|
|
154
|
-
// Get additional package information
|
|
155
|
-
const reports = await (0, monorepo_scanner_1.generateReports)();
|
|
156
|
-
const packageReport = reports.find((r) => r.package.name === name);
|
|
157
|
-
const result = {
|
|
158
|
-
...transformedPkg,
|
|
159
|
-
report: packageReport,
|
|
160
|
-
ciStatus: await ci_status_1.ciStatusManager.getPackageStatus(name),
|
|
161
|
-
};
|
|
162
|
-
res.json(result);
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
res.status(500).json({ error: 'Failed to fetch package details' });
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
// Get commit details
|
|
169
|
-
app.get('/api/commits/:packagePath', async (_req, res) => {
|
|
170
|
-
try {
|
|
171
|
-
const { packagePath } = _req.params;
|
|
172
|
-
// Decode the package path
|
|
173
|
-
const decodedPath = decodeURIComponent(packagePath);
|
|
174
|
-
console.log('🔍 Fetching commits for path:', decodedPath);
|
|
175
|
-
console.log('📁 Current working directory:', process.cwd());
|
|
176
|
-
const gitService = new gitService_1.GitService();
|
|
177
|
-
// Check if this is an absolute path and convert to relative if needed
|
|
178
|
-
let relativePath = decodedPath;
|
|
179
|
-
const projectRoot = process.cwd();
|
|
180
|
-
// If it's an absolute path, make it relative to project root
|
|
181
|
-
if (path_1.default.isAbsolute(decodedPath)) {
|
|
182
|
-
relativePath = path_1.default.relative(projectRoot, decodedPath);
|
|
183
|
-
console.log('🔄 Converted absolute path to relative:', relativePath);
|
|
184
|
-
}
|
|
185
|
-
// Check if the path exists
|
|
186
|
-
try {
|
|
187
|
-
await fs_1.default.promises.access(relativePath);
|
|
188
|
-
console.log('✅ Path exists:', relativePath);
|
|
189
|
-
}
|
|
190
|
-
catch (fsError) {
|
|
191
|
-
console.log('❌ Path does not exist:', relativePath);
|
|
192
|
-
// Try the original path as well
|
|
193
|
-
try {
|
|
194
|
-
await fs_1.default.promises.access(decodedPath);
|
|
195
|
-
console.log('✅ Original path exists:', decodedPath);
|
|
196
|
-
relativePath = decodedPath; // Use original path if it exists
|
|
197
|
-
}
|
|
198
|
-
catch (secondError) {
|
|
199
|
-
return res.status(404).json({
|
|
200
|
-
error: `Package path not found: ${relativePath} (also tried: ${decodedPath})`,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const commits = await gitService.getAllCommits(relativePath);
|
|
205
|
-
console.log(`✅ Successfully fetched ${commits.length} commits for ${relativePath}`);
|
|
206
|
-
res.json(commits);
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
console.error('💥 Error fetching commit details:', error);
|
|
210
|
-
res.status(500).json({
|
|
211
|
-
error: 'Failed to fetch commit details',
|
|
212
|
-
message: error.message,
|
|
213
|
-
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
// Get dependency graph
|
|
218
|
-
// app.get('/api/graph', async (_req, res) => {
|
|
219
|
-
// try {
|
|
220
|
-
// const packages = scanMonorepo(process.cwd());
|
|
221
|
-
// const graph = generateDependencyGraph(packages);
|
|
222
|
-
// const circularDeps = findCircularDependencies(packages);
|
|
223
|
-
// res.json({
|
|
224
|
-
// ...graph,
|
|
225
|
-
// circularDependencies: circularDeps,
|
|
226
|
-
// metadata: {
|
|
227
|
-
// totalNodes: graph.nodes.length,
|
|
228
|
-
// totalEdges: graph.edges.length,
|
|
229
|
-
// circularDependencies: circularDeps.length,
|
|
230
|
-
// },
|
|
231
|
-
// });
|
|
232
|
-
// } catch (error) {
|
|
233
|
-
// res.status(500).json({ error: 'Failed to generate dependency graph' });
|
|
234
|
-
// }
|
|
235
|
-
// });
|
|
236
|
-
// Get monorepo statistics
|
|
237
|
-
app.get('/api/stats', async (_req, res) => {
|
|
238
|
-
try {
|
|
239
|
-
const packages = (0, helpers_1.scanMonorepo)(process.cwd());
|
|
240
|
-
const stats = (0, helpers_1.generateMonorepoStats)(packages);
|
|
241
|
-
res.json({
|
|
242
|
-
...stats,
|
|
243
|
-
timestamp: Date.now(),
|
|
244
|
-
scanDuration: 0, // Would be calculated from actual scan
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
res.status(500).json({ error: 'Failed to fetch statistics' });
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
// Get CI status for all packages
|
|
252
|
-
app.get('/api/ci/status', async (_req, res) => {
|
|
253
|
-
try {
|
|
254
|
-
const packages = (0, helpers_1.scanMonorepo)(process.cwd());
|
|
255
|
-
const ciStatus = await (0, ci_status_1.getMonorepoCIStatus)(packages);
|
|
256
|
-
res.json(ciStatus);
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
res.status(500).json({ error: 'Failed to fetch CI status' });
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
// Get CI status for specific package
|
|
263
|
-
app.get('/api/ci/packages/:name', async (_req, res) => {
|
|
264
|
-
try {
|
|
265
|
-
const { name } = _req.params;
|
|
266
|
-
const status = await ci_status_1.ciStatusManager.getPackageStatus(name);
|
|
267
|
-
if (!status) {
|
|
268
|
-
return res.status(404).json({ error: 'Package CI status not found' });
|
|
269
|
-
}
|
|
270
|
-
res.json(status);
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
res.status(500).json({ error: 'Failed to fetch package CI status' });
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
// Trigger CI build for package
|
|
277
|
-
app.post('/api/ci/trigger', async (_req, res) => {
|
|
278
|
-
try {
|
|
279
|
-
const { packageName, providerName, branch } = _req.body;
|
|
280
|
-
if (!packageName) {
|
|
281
|
-
return res.status(400).json({ error: 'Package name is required' });
|
|
282
|
-
}
|
|
283
|
-
const result = await ci_status_1.ciStatusManager.triggerBuild(packageName, providerName || 'github', branch || 'main');
|
|
284
|
-
if (result.success) {
|
|
285
|
-
res.json({
|
|
286
|
-
success: true,
|
|
287
|
-
buildId: result.buildId,
|
|
288
|
-
message: `Build triggered for ${packageName}`,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
res.status(400).json({
|
|
293
|
-
success: false,
|
|
294
|
-
error: result.error,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
catch (error) {
|
|
299
|
-
res.status(500).json({ error: 'Failed to trigger build' });
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
// Get build logs
|
|
303
|
-
app.get('/api/ci/builds/:buildId/logs', async (_req, res) => {
|
|
304
|
-
try {
|
|
305
|
-
const { buildId } = _req.params;
|
|
306
|
-
const { provider } = _req.query;
|
|
307
|
-
if (!provider) {
|
|
308
|
-
return res.status(400).json({ error: 'Provider is required' });
|
|
309
|
-
}
|
|
310
|
-
const logs = await ci_status_1.ciStatusManager.getBuildLogs(buildId, provider);
|
|
311
|
-
res.json({ buildId, logs });
|
|
312
|
-
}
|
|
313
|
-
catch (error) {
|
|
314
|
-
res.status(500).json({ error: 'Failed to fetch build logs' });
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
// Get build artifacts
|
|
318
|
-
app.get('/api/ci/builds/:buildId/artifacts', async (_req, res) => {
|
|
319
|
-
try {
|
|
320
|
-
const { buildId } = _req.params;
|
|
321
|
-
const { provider } = _req.query;
|
|
322
|
-
if (!provider) {
|
|
323
|
-
return res.status(400).json({ error: 'Provider is required' });
|
|
324
|
-
}
|
|
325
|
-
const artifacts = await ci_status_1.ciStatusManager.getBuildArtifacts(buildId, provider);
|
|
326
|
-
res.json({ buildId, artifacts });
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
res.status(500).json({ error: 'Failed to fetch build artifacts' });
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
// Perform full monorepo scan
|
|
333
|
-
app.post('/api/scan', async (_req, res) => {
|
|
334
|
-
try {
|
|
335
|
-
const { force } = _req.body;
|
|
336
|
-
if (force) {
|
|
337
|
-
monorepo_scanner_1.scanner.clearCache();
|
|
338
|
-
}
|
|
339
|
-
const result = await (0, monorepo_scanner_1.quickScan)();
|
|
340
|
-
res.json({
|
|
341
|
-
success: true,
|
|
342
|
-
message: 'Scan completed successfully',
|
|
343
|
-
result,
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
res.status(500).json({
|
|
348
|
-
success: false,
|
|
349
|
-
error: 'Failed to perform scan',
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
// Get scan results
|
|
354
|
-
app.get('/api/scan/results', async (_req, res) => {
|
|
355
|
-
try {
|
|
356
|
-
const result = await (0, monorepo_scanner_1.quickScan)();
|
|
357
|
-
res.json(result);
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
res.status(500).json({ error: 'Failed to fetch scan results' });
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
// Export scan results
|
|
364
|
-
app.get('/api/scan/export/:format', async (_req, res) => {
|
|
365
|
-
try {
|
|
366
|
-
const { format } = _req.params;
|
|
367
|
-
const { filename } = _req.query;
|
|
368
|
-
if (!['json', 'csv', 'html'].includes(format)) {
|
|
369
|
-
return res.status(400).json({ error: 'Invalid export format' });
|
|
370
|
-
}
|
|
371
|
-
const result = await (0, monorepo_scanner_1.quickScan)();
|
|
372
|
-
const exportData = monorepo_scanner_1.scanner.exportResults(result, format);
|
|
373
|
-
if (format === 'json') {
|
|
374
|
-
res.json(result);
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
const contentType = format === 'csv' ? 'text/csv' : 'text/html';
|
|
378
|
-
const contentDisposition = filename
|
|
379
|
-
? `attachment; filename="${filename}"`
|
|
380
|
-
: `attachment; filename="monorepo-scan.${format}"`;
|
|
381
|
-
res.setHeader('Content-Type', contentType);
|
|
382
|
-
res.setHeader('Content-Disposition', contentDisposition);
|
|
383
|
-
res.send(exportData);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
catch (error) {
|
|
387
|
-
res.status(500).json({ error: 'Failed to export scan results' });
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
// ---------- HEALTH --------------------
|
|
391
|
-
// Get package health metrics
|
|
392
|
-
app.get('/api/health/packages/:name', async (_req, res) => {
|
|
393
|
-
try {
|
|
394
|
-
console.log('_req.params -->', _req.params);
|
|
395
|
-
const { name } = _req.params;
|
|
396
|
-
const packages = (0, helpers_1.scanMonorepo)(process.cwd());
|
|
397
|
-
const packageInfo = packages.find(p => p.name === name);
|
|
398
|
-
if (!packageInfo) {
|
|
399
|
-
return res.status(404).json({ error: 'Package not found' });
|
|
400
|
-
}
|
|
401
|
-
// Get health metrics (mock data for now)
|
|
402
|
-
const health = {
|
|
403
|
-
buildStatus: 'success',
|
|
404
|
-
testCoverage: Math.floor(Math.random() * 100),
|
|
405
|
-
lintStatus: 'pass',
|
|
406
|
-
securityAudit: 'pass',
|
|
407
|
-
overallScore: Math.floor(Math.random() * 40) + 60,
|
|
408
|
-
lastUpdated: new Date(),
|
|
409
|
-
};
|
|
410
|
-
res.json({
|
|
411
|
-
packageName: name,
|
|
412
|
-
health,
|
|
413
|
-
size: {
|
|
414
|
-
size: Math.floor(Math.random() * 1024 * 1024), // Random size
|
|
415
|
-
files: Math.floor(Math.random() * 1000),
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
res.status(500).json({ error: 'Failed to fetch health metrics' });
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
// Get all package health metrics
|
|
424
|
-
// app.get('/api/health/packages', async (_req, res) => {
|
|
425
|
-
// try {
|
|
426
|
-
// // Try to get health data from database
|
|
427
|
-
// const healthData = await prisma.healthStatus.findMany();
|
|
428
|
-
// console.log('healthData -->', healthData);
|
|
429
|
-
// // Transform the data to match the expected frontend format
|
|
430
|
-
// const transformedHealthData = healthData.map(health => {
|
|
431
|
-
// const transformedHealth = { ...health };
|
|
432
|
-
// // Parse any JSON fields that were stored as strings
|
|
433
|
-
// if (health.metrics) {
|
|
434
|
-
// transformedHealth.metrics = JSON.parse(health.metrics);
|
|
435
|
-
// } else {
|
|
436
|
-
// transformedHealth.metrics = [];
|
|
437
|
-
// }
|
|
438
|
-
// if (health.packageHealth) {
|
|
439
|
-
// transformedHealth.packageHealth = JSON.parse(health.packageHealth);
|
|
440
|
-
// } else {
|
|
441
|
-
// transformedHealth.packageHealth = [];
|
|
442
|
-
// }
|
|
443
|
-
// // Ensure we have all required fields with defaults
|
|
444
|
-
// return {
|
|
445
|
-
// id: transformedHealth.id,
|
|
446
|
-
// overallScore: transformedHealth.overallScore || 0,
|
|
447
|
-
// metrics: transformedHealth.metrics || [],
|
|
448
|
-
// packageHealth: transformedHealth.packageHealth || [],
|
|
449
|
-
// createdAt: transformedHealth.createdAt,
|
|
450
|
-
// updatedAt: transformedHealth.updatedAt,
|
|
451
|
-
// };
|
|
452
|
-
// });
|
|
453
|
-
// // Return the latest health data (you might want to sort by createdAt desc)
|
|
454
|
-
// const latestHealthData = transformedHealthData.sort(
|
|
455
|
-
// (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
|
|
456
|
-
// )[0] || {
|
|
457
|
-
// overallScore: 0,
|
|
458
|
-
// metrics: [],
|
|
459
|
-
// packageHealth: [],
|
|
460
|
-
// };
|
|
461
|
-
// res.json(latestHealthData);
|
|
462
|
-
// } catch (error) {
|
|
463
|
-
// console.error('Error fetching health data:', error);
|
|
464
|
-
// res.status(500).json({ error: 'Failed to fetch health data' });
|
|
465
|
-
// }
|
|
466
|
-
// });
|
|
467
|
-
app.get('/api/health/packages', async (_req, res) => {
|
|
468
|
-
try {
|
|
469
|
-
// Fetch all package health data from database
|
|
470
|
-
const packageHealthData = await prisma.packageHealth.findMany();
|
|
471
|
-
console.log('packageHealthData -->', packageHealthData.length);
|
|
472
|
-
// Transform the data to match the expected frontend format
|
|
473
|
-
const packages = packageHealthData.map(pkg => {
|
|
474
|
-
const health = {
|
|
475
|
-
buildStatus: pkg.packageBuildStatus,
|
|
476
|
-
testCoverage: pkg.packageTestCoverage,
|
|
477
|
-
lintStatus: pkg.packageLintStatus,
|
|
478
|
-
securityAudit: pkg.packageSecurity,
|
|
479
|
-
overallScore: pkg.packageOverallScore,
|
|
480
|
-
};
|
|
481
|
-
return {
|
|
482
|
-
packageName: pkg.packageName,
|
|
483
|
-
health: health,
|
|
484
|
-
isHealthy: pkg.packageOverallScore >= 80,
|
|
485
|
-
};
|
|
486
|
-
});
|
|
487
|
-
// Calculate summary statistics
|
|
488
|
-
const total = packages.length;
|
|
489
|
-
const healthy = packages.filter(pkg => pkg.isHealthy).length;
|
|
490
|
-
const unhealthy = packages.filter(pkg => !pkg.isHealthy).length;
|
|
491
|
-
const averageScore = packages.length > 0
|
|
492
|
-
? packages.reduce((sum, pkg) => sum + pkg.health.overallScore, 0) /
|
|
493
|
-
packages.length
|
|
494
|
-
: 0;
|
|
495
|
-
const response = {
|
|
496
|
-
packages: packages,
|
|
497
|
-
summary: {
|
|
498
|
-
total: total,
|
|
499
|
-
healthy: healthy,
|
|
500
|
-
unhealthy: unhealthy,
|
|
501
|
-
averageScore: averageScore,
|
|
502
|
-
},
|
|
503
|
-
};
|
|
504
|
-
console.log('Transformed health data -->', response.summary);
|
|
505
|
-
res.json(response);
|
|
506
|
-
}
|
|
507
|
-
catch (error) {
|
|
508
|
-
console.error('Error fetching health data from database:', error);
|
|
509
|
-
res
|
|
510
|
-
.status(500)
|
|
511
|
-
.json({ error: 'Failed to fetch health data from database' });
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
app.get('/api/health/refresh', async (_req, res) => {
|
|
515
|
-
try {
|
|
516
|
-
const rootDir = path_1.default.resolve(rootPath);
|
|
517
|
-
const packages = (0, helpers_1.scanMonorepo)(rootDir);
|
|
518
|
-
console.log('packages -->', packages.length);
|
|
519
|
-
const healthMetrics = await Promise.all(packages.map(async (pkg) => {
|
|
520
|
-
try {
|
|
521
|
-
// Await each health check function since they return promises
|
|
522
|
-
const buildStatus = await (0, monorepo_scanner_1.funCheckBuildStatus)(pkg);
|
|
523
|
-
const testCoverage = await (0, monorepo_scanner_1.funCheckTestCoverage)(pkg);
|
|
524
|
-
const lintStatus = await (0, monorepo_scanner_1.funCheckLintStatus)(pkg);
|
|
525
|
-
const securityAudit = await (0, monorepo_scanner_1.funCheckSecurityAudit)(pkg);
|
|
526
|
-
// Calculate overall health score
|
|
527
|
-
const overallScore = (0, helpers_1.calculatePackageHealth)(buildStatus, testCoverage, lintStatus, securityAudit);
|
|
528
|
-
const health = {
|
|
529
|
-
buildStatus: buildStatus,
|
|
530
|
-
testCoverage: testCoverage,
|
|
531
|
-
lintStatus: lintStatus,
|
|
532
|
-
securityAudit: securityAudit,
|
|
533
|
-
overallScore: overallScore.overallScore,
|
|
534
|
-
};
|
|
535
|
-
const packageStatus = health.overallScore >= 80 ? 'healthy' : (health.overallScore >= 60 && health.overallScore < 80 ? 'warning' : 'error');
|
|
536
|
-
console.log(pkg.name, '-->', health, packageStatus);
|
|
537
|
-
// FIX: Use upsert to handle existing packages and proper Prisma syntax
|
|
538
|
-
await prisma.packageHealth.upsert({
|
|
539
|
-
where: {
|
|
540
|
-
packageName: pkg.name,
|
|
541
|
-
},
|
|
542
|
-
update: {
|
|
543
|
-
packageOverallScore: overallScore.overallScore,
|
|
544
|
-
packageBuildStatus: buildStatus,
|
|
545
|
-
packageTestCoverage: testCoverage,
|
|
546
|
-
packageLintStatus: lintStatus,
|
|
547
|
-
packageSecurity: securityAudit,
|
|
548
|
-
packageDependencies: '',
|
|
549
|
-
updatedAt: new Date(),
|
|
550
|
-
package: {
|
|
551
|
-
update: {
|
|
552
|
-
where: { name: pkg.name },
|
|
553
|
-
data: { status: packageStatus },
|
|
554
|
-
},
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
create: {
|
|
558
|
-
packageName: pkg.name,
|
|
559
|
-
packageOverallScore: overallScore.overallScore,
|
|
560
|
-
packageBuildStatus: buildStatus,
|
|
561
|
-
packageTestCoverage: testCoverage,
|
|
562
|
-
packageLintStatus: lintStatus,
|
|
563
|
-
packageSecurity: securityAudit,
|
|
564
|
-
packageDependencies: '',
|
|
565
|
-
},
|
|
566
|
-
});
|
|
567
|
-
return {
|
|
568
|
-
packageName: pkg.name,
|
|
569
|
-
health,
|
|
570
|
-
isHealthy: health.overallScore >= 80,
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
return {
|
|
575
|
-
packageName: pkg.name,
|
|
576
|
-
health: null,
|
|
577
|
-
isHealthy: false,
|
|
578
|
-
error: 'Failed to fetch health metrics',
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
}));
|
|
582
|
-
res.json({
|
|
583
|
-
packages: healthMetrics,
|
|
584
|
-
summary: {
|
|
585
|
-
total: packages.length,
|
|
586
|
-
healthy: healthMetrics.filter(h => h.isHealthy).length,
|
|
587
|
-
unhealthy: healthMetrics.filter(h => !h.isHealthy).length,
|
|
588
|
-
averageScore: healthMetrics
|
|
589
|
-
.filter(h => h.health)
|
|
590
|
-
.reduce((sum, h) => sum + h.health.overallScore, 0) /
|
|
591
|
-
healthMetrics.filter(h => h.health).length,
|
|
592
|
-
},
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
catch (error) {
|
|
596
|
-
res.status(500).json({ error: 'Failed to fetch health metrics' });
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
// function calculateHealthStatus(
|
|
600
|
-
// healthy: number,
|
|
601
|
-
// total: number
|
|
602
|
-
// ): 'healthy' | 'warning' | 'error' {
|
|
603
|
-
// if (total === 0) return 'healthy';
|
|
604
|
-
// const ratio = healthy / total;
|
|
605
|
-
// if (ratio >= 0.8) return 'healthy';
|
|
606
|
-
// if (ratio >= 0.6) return 'warning';
|
|
607
|
-
// return 'error';
|
|
608
|
-
// }
|
|
609
|
-
// Search packages
|
|
610
|
-
app.get('/api/search', async (_req, res) => {
|
|
611
|
-
try {
|
|
612
|
-
const { q: query, type, status } = _req.query;
|
|
613
|
-
const packages = (0, helpers_1.scanMonorepo)(process.cwd());
|
|
614
|
-
let filtered = packages;
|
|
615
|
-
// Filter by search query
|
|
616
|
-
if (query) {
|
|
617
|
-
const searchTerm = query.toLowerCase();
|
|
618
|
-
filtered = filtered.filter(pkg => pkg.name.toLowerCase().includes(searchTerm) ||
|
|
619
|
-
pkg.description?.toLowerCase().includes(searchTerm));
|
|
620
|
-
}
|
|
621
|
-
// Filter by type
|
|
622
|
-
if (type && type !== 'all') {
|
|
623
|
-
filtered = filtered.filter(pkg => pkg.type === type);
|
|
624
|
-
}
|
|
625
|
-
// Filter by status (would need health data)
|
|
626
|
-
if (status && status !== 'all') {
|
|
627
|
-
// This would filter by actual health status
|
|
628
|
-
// For now, just return all packages
|
|
629
|
-
}
|
|
630
|
-
res.json({
|
|
631
|
-
query,
|
|
632
|
-
results: filtered,
|
|
633
|
-
total: filtered.length,
|
|
634
|
-
filters: { type, status },
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
catch (error) {
|
|
638
|
-
res.status(500).json({ error: 'Failed to search packages' });
|
|
639
|
-
}
|
|
640
|
-
});
|
|
641
|
-
// Get recent activity
|
|
642
|
-
app.get('/api/activity', async (_req, res) => {
|
|
643
|
-
try {
|
|
644
|
-
const { limit = 20 } = _req.query;
|
|
645
|
-
const packages = (0, helpers_1.scanMonorepo)(process.cwd());
|
|
646
|
-
// Mock recent activity data
|
|
647
|
-
const activities = packages
|
|
648
|
-
.slice(0, Math.min(Number(limit), packages.length))
|
|
649
|
-
.map((pkg, index) => ({
|
|
650
|
-
id: `activity-${Date.now()}-${index}`,
|
|
651
|
-
type: [
|
|
652
|
-
'package_updated',
|
|
653
|
-
'build_success',
|
|
654
|
-
'test_passed',
|
|
655
|
-
'dependency_updated',
|
|
656
|
-
][Math.floor(Math.random() * 4)],
|
|
657
|
-
packageName: pkg.name,
|
|
658
|
-
message: `Activity for ${pkg.name}`,
|
|
659
|
-
timestamp: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000), // Random time in last week
|
|
660
|
-
metadata: {
|
|
661
|
-
version: pkg.version,
|
|
662
|
-
type: pkg.type,
|
|
663
|
-
},
|
|
664
|
-
}));
|
|
665
|
-
// Sort by timestamp (newest first)
|
|
666
|
-
activities.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
667
|
-
res.json({
|
|
668
|
-
activities: activities.slice(0, Number(limit)),
|
|
669
|
-
total: activities.length,
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
catch (error) {
|
|
673
|
-
res.status(500).json({ error: 'Failed to fetch recent activity' });
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
// Get system information
|
|
677
|
-
app.get('/api/system', (_, res) => {
|
|
678
|
-
res.json({
|
|
679
|
-
nodeVersion: process.version,
|
|
680
|
-
platform: process.platform,
|
|
681
|
-
arch: process.arch,
|
|
682
|
-
memory: process.memoryUsage(),
|
|
683
|
-
uptime: process.uptime(),
|
|
684
|
-
pid: process.pid,
|
|
685
|
-
cwd: process.cwd(),
|
|
686
|
-
env: {
|
|
687
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
688
|
-
PORT: process.env.PORT,
|
|
689
|
-
},
|
|
690
|
-
});
|
|
691
|
-
});
|
|
692
|
-
// ------------------------- CONFIGURATION TAB ------------------------- //
|
|
693
|
-
// Get all configuration files from the file system
|
|
694
|
-
app.get('/api/config/files', async (_req, res) => {
|
|
695
|
-
try {
|
|
696
|
-
// Find the monorepo root instead of using process.cwd()
|
|
697
|
-
const rootDir = findMonorepoRoot();
|
|
698
|
-
console.log('Monorepo root directory:', rootDir);
|
|
699
|
-
console.log('Backend directory:', __dirname);
|
|
700
|
-
const configFiles = await scanConfigFiles(rootDir);
|
|
701
|
-
// Transform to match frontend ConfigFile interface
|
|
702
|
-
const transformedFiles = configFiles.map(file => ({
|
|
703
|
-
id: file.id,
|
|
704
|
-
name: file.name,
|
|
705
|
-
path: file.path,
|
|
706
|
-
type: file.type,
|
|
707
|
-
content: file.content,
|
|
708
|
-
size: file.size,
|
|
709
|
-
lastModified: file.lastModified,
|
|
710
|
-
hasSecrets: file.hasSecrets,
|
|
711
|
-
isEditable: true,
|
|
712
|
-
// validation: validateConfig(file.content, file.name),
|
|
713
|
-
}));
|
|
714
|
-
res.json({
|
|
715
|
-
success: true,
|
|
716
|
-
files: transformedFiles,
|
|
717
|
-
total: transformedFiles.length,
|
|
718
|
-
timestamp: Date.now(),
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
catch (error) {
|
|
722
|
-
console.error('Error fetching configuration files:', error);
|
|
723
|
-
res.status(500).json({
|
|
724
|
-
success: false,
|
|
725
|
-
error: 'Failed to fetch configuration files',
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
/**
|
|
730
|
-
* Find the monorepo root by looking for package.json with workspaces or pnpm-workspace.yaml
|
|
731
|
-
*/
|
|
732
|
-
function findMonorepoRoot() {
|
|
733
|
-
let currentDir = __dirname;
|
|
734
|
-
while (currentDir !== path_1.default.parse(currentDir).root) {
|
|
735
|
-
const packageJsonPath = path_1.default.join(currentDir, 'package.json');
|
|
736
|
-
const pnpmWorkspacePath = path_1.default.join(currentDir, 'pnpm-workspace.yaml');
|
|
737
|
-
// Check if this directory has package.json with workspaces or pnpm-workspace.yaml
|
|
738
|
-
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
739
|
-
try {
|
|
740
|
-
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
741
|
-
// If it has workspaces or is the root monorepo package
|
|
742
|
-
if (packageJson.workspaces || fs_1.default.existsSync(pnpmWorkspacePath)) {
|
|
743
|
-
console.log('✅ Found monorepo root:', currentDir);
|
|
744
|
-
return currentDir;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
catch (error) {
|
|
748
|
-
// Continue searching if package.json is invalid
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
// Check if we're at the git root
|
|
752
|
-
const gitPath = path_1.default.join(currentDir, '.git');
|
|
753
|
-
if (fs_1.default.existsSync(gitPath)) {
|
|
754
|
-
console.log('✅ Found git root (likely monorepo root):', currentDir);
|
|
755
|
-
return currentDir;
|
|
756
|
-
}
|
|
757
|
-
// Go up one directory
|
|
758
|
-
const parentDir = path_1.default.dirname(currentDir);
|
|
759
|
-
if (parentDir === currentDir)
|
|
760
|
-
break; // Prevent infinite loop
|
|
761
|
-
currentDir = parentDir;
|
|
762
|
-
}
|
|
763
|
-
// Fallback to process.cwd() if we can't find the root
|
|
764
|
-
console.log('⚠️ Could not find monorepo root, using process.cwd():', process.cwd());
|
|
765
|
-
return process.cwd();
|
|
766
|
-
}
|
|
767
|
-
// Helper function to scan for configuration files
|
|
768
|
-
async function scanConfigFiles(rootDir) {
|
|
769
|
-
const configPatterns = [
|
|
770
|
-
// Root level config files
|
|
771
|
-
'package.json',
|
|
772
|
-
'pnpm-workspace.yaml',
|
|
773
|
-
'pnpm-lock.yaml',
|
|
774
|
-
'turbo.json',
|
|
775
|
-
'tsconfig.json',
|
|
776
|
-
'.eslintrc.*',
|
|
777
|
-
'.prettierrc',
|
|
778
|
-
'.prettierignore',
|
|
779
|
-
'.editorconfig',
|
|
780
|
-
'.nvmrc',
|
|
781
|
-
'.gitignore',
|
|
782
|
-
'commitlint.config.js',
|
|
783
|
-
'.releaserc.json',
|
|
784
|
-
'env.example',
|
|
785
|
-
// App-specific config files
|
|
786
|
-
'vite.config.*',
|
|
787
|
-
'tailwind.config.*',
|
|
788
|
-
'postcss.config.*',
|
|
789
|
-
'components.json',
|
|
790
|
-
// Package-specific config files
|
|
791
|
-
'jest.config.*',
|
|
792
|
-
'.env*',
|
|
793
|
-
'dockerfile*',
|
|
794
|
-
];
|
|
795
|
-
const configFiles = [];
|
|
796
|
-
const scannedPaths = new Set();
|
|
797
|
-
function scanDirectory(dir, depth = 0) {
|
|
798
|
-
if (scannedPaths.has(dir) || depth > 8)
|
|
799
|
-
return; // Limit depth for safety
|
|
800
|
-
scannedPaths.add(dir);
|
|
801
|
-
try {
|
|
802
|
-
const items = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
803
|
-
for (const item of items) {
|
|
804
|
-
const fullPath = path_1.default.join(dir, item.name);
|
|
805
|
-
if (item.isDirectory()) {
|
|
806
|
-
// Skip node_modules and other non-source directories
|
|
807
|
-
if (!shouldSkipDirectory(item.name, depth)) {
|
|
808
|
-
scanDirectory(fullPath, depth + 1);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
// Check if file matches config patterns
|
|
813
|
-
if (isConfigFile(item.name)) {
|
|
814
|
-
try {
|
|
815
|
-
const stats = fs_1.default.statSync(fullPath);
|
|
816
|
-
const content = fs_1.default.readFileSync(fullPath, 'utf8');
|
|
817
|
-
const relativePath = fullPath.replace(rootDir, '').replace(/\\/g, '/') ||
|
|
818
|
-
`/${item.name}`;
|
|
819
|
-
configFiles.push({
|
|
820
|
-
id: relativePath,
|
|
821
|
-
name: item.name,
|
|
822
|
-
path: relativePath,
|
|
823
|
-
type: getFileType(item.name),
|
|
824
|
-
content: content,
|
|
825
|
-
size: stats.size,
|
|
826
|
-
lastModified: stats.mtime.toISOString(),
|
|
827
|
-
hasSecrets: containsSecrets(content, item.name),
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
catch (error) {
|
|
831
|
-
console.warn(`Could not read file: ${fullPath}`);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
catch (error) {
|
|
838
|
-
console.warn(`Could not scan directory: ${dir}`);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
function shouldSkipDirectory(dirName, depth) {
|
|
842
|
-
const skipDirs = [
|
|
843
|
-
'node_modules',
|
|
844
|
-
'dist',
|
|
845
|
-
'build',
|
|
846
|
-
'.git',
|
|
847
|
-
'.next',
|
|
848
|
-
'.vscode',
|
|
849
|
-
'.turbo',
|
|
850
|
-
'.husky',
|
|
851
|
-
'.github',
|
|
852
|
-
'.vite',
|
|
853
|
-
'migrations',
|
|
854
|
-
'coverage',
|
|
855
|
-
'.cache',
|
|
856
|
-
'tmp',
|
|
857
|
-
'temp',
|
|
858
|
-
];
|
|
859
|
-
// At root level, be more permissive
|
|
860
|
-
if (depth === 0) {
|
|
861
|
-
return skipDirs.includes(dirName);
|
|
862
|
-
}
|
|
863
|
-
// Deeper levels, skip more directories
|
|
864
|
-
return skipDirs.includes(dirName) || dirName.startsWith('.');
|
|
865
|
-
}
|
|
866
|
-
function isConfigFile(filename) {
|
|
867
|
-
return configPatterns.some(pattern => {
|
|
868
|
-
if (pattern.includes('*')) {
|
|
869
|
-
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
870
|
-
return regex.test(filename.toLowerCase());
|
|
871
|
-
}
|
|
872
|
-
return filename.toLowerCase() === pattern.toLowerCase();
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
console.log(`🔍 Scanning for config files in: ${rootDir}`);
|
|
876
|
-
// Start scanning from root
|
|
877
|
-
scanDirectory(rootDir);
|
|
878
|
-
// Sort files by path for consistent ordering
|
|
879
|
-
configFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
880
|
-
console.log(`📁 Found ${configFiles.length} configuration files`);
|
|
881
|
-
// Log some sample files for debugging
|
|
882
|
-
if (configFiles.length > 0) {
|
|
883
|
-
console.log('Sample config files found:');
|
|
884
|
-
configFiles.slice(0, 5).forEach(file => {
|
|
885
|
-
console.log(` - ${file.path} (${file.type})`);
|
|
886
|
-
});
|
|
887
|
-
if (configFiles.length > 5) {
|
|
888
|
-
console.log(` ... and ${configFiles.length - 5} more`);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
return configFiles;
|
|
892
|
-
}
|
|
893
|
-
function getFileType(filename) {
|
|
894
|
-
const extension = filename.split('.').pop()?.toLowerCase();
|
|
895
|
-
switch (extension) {
|
|
896
|
-
case 'json':
|
|
897
|
-
return 'json';
|
|
898
|
-
case 'yaml':
|
|
899
|
-
case 'yml':
|
|
900
|
-
return 'yaml';
|
|
901
|
-
case 'js':
|
|
902
|
-
return 'javascript';
|
|
903
|
-
case 'ts':
|
|
904
|
-
return 'typescript';
|
|
905
|
-
case 'env':
|
|
906
|
-
return 'env';
|
|
907
|
-
case 'md':
|
|
908
|
-
return 'markdown';
|
|
909
|
-
case 'cjs':
|
|
910
|
-
return 'javascript';
|
|
911
|
-
case 'config':
|
|
912
|
-
return 'text';
|
|
913
|
-
case 'example':
|
|
914
|
-
return filename.includes('.env') ? 'env' : 'text';
|
|
915
|
-
default:
|
|
916
|
-
return 'text';
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
function containsSecrets(content, filename) {
|
|
920
|
-
// Only check .env files and files that might contain secrets
|
|
921
|
-
if (!filename.includes('.env') && !filename.includes('config')) {
|
|
922
|
-
return false;
|
|
923
|
-
}
|
|
924
|
-
const secretPatterns = [
|
|
925
|
-
/password\s*=\s*[^\s]/i,
|
|
926
|
-
/secret\s*=\s*[^\s]/i,
|
|
927
|
-
/key\s*=\s*[^\s]/i,
|
|
928
|
-
/token\s*=\s*[^\s]/i,
|
|
929
|
-
/auth\s*=\s*[^\s]/i,
|
|
930
|
-
/credential\s*=\s*[^\s]/i,
|
|
931
|
-
/api_key\s*=\s*[^\s]/i,
|
|
932
|
-
/private_key\s*=\s*[^\s]/i,
|
|
933
|
-
/DATABASE_URL/i,
|
|
934
|
-
/JWT_SECRET/i,
|
|
935
|
-
/GITHUB_TOKEN/i,
|
|
936
|
-
];
|
|
937
|
-
return secretPatterns.some(pattern => pattern.test(content));
|
|
938
|
-
}
|
|
939
|
-
// Save configuration file
|
|
940
|
-
// app.put('/api/config/files/:id', async (_req, res) => {
|
|
941
|
-
// try {
|
|
942
|
-
// const { id } = _req.params;
|
|
943
|
-
// const { content } = _req.body;
|
|
944
|
-
// if (!content) {
|
|
945
|
-
// return res.status(400).json({
|
|
946
|
-
// success: false,
|
|
947
|
-
// error: 'Content is required',
|
|
948
|
-
// });
|
|
949
|
-
// }
|
|
950
|
-
// const rootDir = process.cwd();
|
|
951
|
-
// const filePath = path.join(rootDir, id.startsWith('/') ? id.slice(1) : id);
|
|
952
|
-
// // Security check: ensure the file is within the project directory
|
|
953
|
-
// if (!filePath.startsWith(rootDir)) {
|
|
954
|
-
// return res.status(403).json({
|
|
955
|
-
// success: false,
|
|
956
|
-
// error: 'Access denied',
|
|
957
|
-
// });
|
|
958
|
-
// }
|
|
959
|
-
// // Check if file exists and is writable
|
|
960
|
-
// try {
|
|
961
|
-
// await fs.promises.access(filePath, fs.constants.W_OK);
|
|
962
|
-
// } catch (error) {
|
|
963
|
-
// return res.status(403).json({
|
|
964
|
-
// success: false,
|
|
965
|
-
// error: 'File is not writable or does not exist',
|
|
966
|
-
// });
|
|
967
|
-
// }
|
|
968
|
-
// // // Backup the original file (optional but recommended)
|
|
969
|
-
// // const backupPath = `${filePath}.backup-${Date.now()}`;
|
|
970
|
-
// // try {
|
|
971
|
-
// // await fs.promises.copyFile(filePath, backupPath);
|
|
972
|
-
// // } catch (error) {
|
|
973
|
-
// // console.warn('Could not create backup file:', error);
|
|
974
|
-
// // }
|
|
975
|
-
// // Write the new content
|
|
976
|
-
// await fs.promises.writeFile(filePath, content, 'utf8');
|
|
977
|
-
// // Get file stats for updated information
|
|
978
|
-
// const stats = await fs.promises.stat(filePath);
|
|
979
|
-
// const filename = path.basename(filePath);
|
|
980
|
-
// const updatedFile = {
|
|
981
|
-
// id: id,
|
|
982
|
-
// name: filename,
|
|
983
|
-
// path: id,
|
|
984
|
-
// type: getFileType(filename),
|
|
985
|
-
// content: content,
|
|
986
|
-
// size: stats.size,
|
|
987
|
-
// lastModified: stats.mtime.toISOString(),
|
|
988
|
-
// hasSecrets: containsSecrets(content, filename),
|
|
989
|
-
// isEditable: true,
|
|
990
|
-
// validation: validateConfig(content, filename),
|
|
991
|
-
// };
|
|
992
|
-
// res.json({
|
|
993
|
-
// success: true,
|
|
994
|
-
// file: updatedFile,
|
|
995
|
-
// message: 'File saved successfully',
|
|
996
|
-
// // backupCreated: fs.existsSync(backupPath),
|
|
997
|
-
// });
|
|
998
|
-
// } catch (error) {
|
|
999
|
-
// console.error('Error saving configuration file:', error);
|
|
1000
|
-
// res.status(500).json({
|
|
1001
|
-
// success: false,
|
|
1002
|
-
// error: 'Failed to save configuration file',
|
|
1003
|
-
// });
|
|
1004
|
-
// }
|
|
1005
|
-
// });
|
|
1006
|
-
app.put('/api/config/files/:id', async (_req, res) => {
|
|
1007
|
-
try {
|
|
1008
|
-
const { id } = _req.params;
|
|
1009
|
-
const { content } = _req.body;
|
|
1010
|
-
if (!content) {
|
|
1011
|
-
return res.status(400).json({
|
|
1012
|
-
success: false,
|
|
1013
|
-
error: 'Content is required',
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
// Use the monorepo root
|
|
1017
|
-
const rootDir = findMonorepoRoot();
|
|
1018
|
-
const filePath = path_1.default.join(rootDir, id.startsWith('/') ? id.slice(1) : id);
|
|
1019
|
-
console.log('💾 Saving file:', filePath);
|
|
1020
|
-
console.log('📁 Root directory:', rootDir);
|
|
1021
|
-
// Security check: ensure the file is within the project directory
|
|
1022
|
-
if (!filePath.startsWith(rootDir)) {
|
|
1023
|
-
return res.status(403).json({
|
|
1024
|
-
success: false,
|
|
1025
|
-
error: 'Access denied',
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
// Check if file exists and is writable
|
|
1029
|
-
try {
|
|
1030
|
-
await fs_1.default.promises.access(filePath, fs_1.default.constants.W_OK);
|
|
1031
|
-
}
|
|
1032
|
-
catch (error) {
|
|
1033
|
-
return res.status(403).json({
|
|
1034
|
-
success: false,
|
|
1035
|
-
error: 'File is not writable or does not exist',
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
// Write the new content
|
|
1039
|
-
await fs_1.default.promises.writeFile(filePath, content, 'utf8');
|
|
1040
|
-
// Get file stats for updated information
|
|
1041
|
-
const stats = await fs_1.default.promises.stat(filePath);
|
|
1042
|
-
const filename = path_1.default.basename(filePath);
|
|
1043
|
-
const updatedFile = {
|
|
1044
|
-
id: id,
|
|
1045
|
-
name: filename,
|
|
1046
|
-
path: id,
|
|
1047
|
-
type: getFileType(filename),
|
|
1048
|
-
content: content,
|
|
1049
|
-
size: stats.size,
|
|
1050
|
-
lastModified: stats.mtime.toISOString(),
|
|
1051
|
-
hasSecrets: containsSecrets(content, filename),
|
|
1052
|
-
isEditable: true,
|
|
1053
|
-
// validation: validateConfig(content, filename),
|
|
1054
|
-
};
|
|
1055
|
-
res.json({
|
|
1056
|
-
success: true,
|
|
1057
|
-
file: updatedFile,
|
|
1058
|
-
message: 'File saved successfully',
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
catch (error) {
|
|
1062
|
-
console.error('Error saving configuration file:', error);
|
|
1063
|
-
res.status(500).json({
|
|
1064
|
-
success: false,
|
|
1065
|
-
error: 'Failed to save configuration file',
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
// Update package configuration with path from frontend - preserving all fields, no backups
|
|
1070
|
-
app.put('/api/packages/update', async (req, res) => {
|
|
1071
|
-
try {
|
|
1072
|
-
const { packageName, config, packagePath } = req.body;
|
|
1073
|
-
if (!packageName || !config || !packagePath) {
|
|
1074
|
-
return res.status(400).json({
|
|
1075
|
-
success: false,
|
|
1076
|
-
error: 'Package name, configuration, and package path are required',
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
|
-
console.log('💾 Updating package configuration for:', packageName);
|
|
1080
|
-
console.log('📁 Package path:', packagePath);
|
|
1081
|
-
// Validate JSON syntax with better error handling
|
|
1082
|
-
let newConfig;
|
|
1083
|
-
try {
|
|
1084
|
-
newConfig = JSON.parse(config);
|
|
1085
|
-
}
|
|
1086
|
-
catch (error) {
|
|
1087
|
-
console.error('JSON parsing error:', error);
|
|
1088
|
-
return res.status(400).json({
|
|
1089
|
-
success: false,
|
|
1090
|
-
error: 'Invalid JSON configuration',
|
|
1091
|
-
message: `JSON parsing error: ${error instanceof Error ? error.message : 'Invalid format'}`,
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
const packageJsonPath = path_1.default.join(packagePath, 'package.json');
|
|
1095
|
-
// Security check: ensure the path is valid
|
|
1096
|
-
if (!fs_1.default.existsSync(packagePath)) {
|
|
1097
|
-
return res.status(404).json({
|
|
1098
|
-
success: false,
|
|
1099
|
-
error: 'Package directory not found',
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
// Check if package.json exists
|
|
1103
|
-
if (!fs_1.default.existsSync(packageJsonPath)) {
|
|
1104
|
-
return res.status(404).json({
|
|
1105
|
-
success: false,
|
|
1106
|
-
error: 'package.json not found in the specified directory',
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
// Read the existing package.json to preserve all fields
|
|
1110
|
-
const existingContent = await fs_1.default.promises.readFile(packageJsonPath, 'utf8');
|
|
1111
|
-
let existingConfig;
|
|
1112
|
-
try {
|
|
1113
|
-
existingConfig = JSON.parse(existingContent);
|
|
1114
|
-
}
|
|
1115
|
-
catch (error) {
|
|
1116
|
-
return res.status(500).json({
|
|
1117
|
-
success: false,
|
|
1118
|
-
error: 'Existing package.json contains invalid JSON',
|
|
1119
|
-
message: `Error parsing existing package.json: ${error instanceof Error ? error.message : 'Invalid JSON'}`,
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
// Merge the new configuration with existing configuration
|
|
1123
|
-
const mergedConfig = {
|
|
1124
|
-
...existingConfig,
|
|
1125
|
-
name: newConfig.name || existingConfig.name,
|
|
1126
|
-
version: newConfig.version || existingConfig.version,
|
|
1127
|
-
description: newConfig.description !== undefined
|
|
1128
|
-
? newConfig.description
|
|
1129
|
-
: existingConfig.description,
|
|
1130
|
-
license: newConfig.license !== undefined
|
|
1131
|
-
? newConfig.license
|
|
1132
|
-
: existingConfig.license,
|
|
1133
|
-
repository: newConfig.repository || existingConfig.repository,
|
|
1134
|
-
scripts: newConfig.scripts || existingConfig.scripts,
|
|
1135
|
-
dependencies: newConfig.dependencies || existingConfig.dependencies,
|
|
1136
|
-
devDependencies: newConfig.devDependencies || existingConfig.devDependencies,
|
|
1137
|
-
peerDependencies: newConfig.peerDependencies || existingConfig.peerDependencies,
|
|
1138
|
-
};
|
|
1139
|
-
// Write the merged configuration back
|
|
1140
|
-
const formattedConfig = JSON.stringify(mergedConfig, null, 2);
|
|
1141
|
-
await fs_1.default.promises.writeFile(packageJsonPath, formattedConfig, 'utf8');
|
|
1142
|
-
// Update the package in the database - use correct field names based on your Prisma schema
|
|
1143
|
-
const updateData = {
|
|
1144
|
-
// Use 'lastUpdated' instead of 'updatedAt' based on the error message
|
|
1145
|
-
lastUpdated: new Date(),
|
|
1146
|
-
};
|
|
1147
|
-
// Only update fields that exist in your Prisma schema
|
|
1148
|
-
if (newConfig.version)
|
|
1149
|
-
updateData.version = newConfig.version;
|
|
1150
|
-
if (newConfig.description !== undefined)
|
|
1151
|
-
updateData.description = newConfig.description || '';
|
|
1152
|
-
if (newConfig.license !== undefined)
|
|
1153
|
-
updateData.license = newConfig.license || '';
|
|
1154
|
-
if (newConfig.scripts)
|
|
1155
|
-
updateData.scripts = JSON.stringify(newConfig.scripts);
|
|
1156
|
-
if (newConfig.repository)
|
|
1157
|
-
updateData.repository = JSON.stringify(newConfig.repository);
|
|
1158
|
-
if (newConfig.dependencies)
|
|
1159
|
-
updateData.dependencies = JSON.stringify(newConfig.dependencies);
|
|
1160
|
-
if (newConfig.devDependencies)
|
|
1161
|
-
updateData.devDependencies = JSON.stringify(newConfig.devDependencies);
|
|
1162
|
-
if (newConfig.peerDependencies)
|
|
1163
|
-
updateData.peerDependencies = JSON.stringify(newConfig.peerDependencies);
|
|
1164
|
-
console.log('📝 Updating database with:', updateData);
|
|
1165
|
-
const updatedPackage = await prisma.package.update({
|
|
1166
|
-
where: { name: packageName },
|
|
1167
|
-
data: updateData,
|
|
1168
|
-
});
|
|
1169
|
-
// Transform the response
|
|
1170
|
-
const transformedPackage = {
|
|
1171
|
-
...updatedPackage,
|
|
1172
|
-
maintainers: updatedPackage.maintainers
|
|
1173
|
-
? JSON.parse(updatedPackage.maintainers)
|
|
1174
|
-
: [],
|
|
1175
|
-
scripts: updatedPackage.scripts ? JSON.parse(updatedPackage.scripts) : {},
|
|
1176
|
-
repository: updatedPackage.repository
|
|
1177
|
-
? JSON.parse(updatedPackage.repository)
|
|
1178
|
-
: {},
|
|
1179
|
-
dependencies: updatedPackage.dependencies
|
|
1180
|
-
? JSON.parse(updatedPackage.dependencies)
|
|
1181
|
-
: {},
|
|
1182
|
-
devDependencies: updatedPackage.devDependencies
|
|
1183
|
-
? JSON.parse(updatedPackage.devDependencies)
|
|
1184
|
-
: {},
|
|
1185
|
-
peerDependencies: updatedPackage.peerDependencies
|
|
1186
|
-
? JSON.parse(updatedPackage.peerDependencies)
|
|
1187
|
-
: {},
|
|
1188
|
-
};
|
|
1189
|
-
// Return success response
|
|
1190
|
-
return res.json({
|
|
1191
|
-
success: true,
|
|
1192
|
-
message: 'Package configuration updated successfully',
|
|
1193
|
-
package: transformedPackage,
|
|
1194
|
-
preservedFields: true,
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
catch (error) {
|
|
1198
|
-
console.error('Error updating package configuration:', error);
|
|
1199
|
-
// Ensure we always return JSON, even for errors
|
|
1200
|
-
return res.status(500).json({
|
|
1201
|
-
success: false,
|
|
1202
|
-
error: 'Failed to update package configuration',
|
|
1203
|
-
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
// Error handling middleware
|
|
1208
|
-
app.use((error, _req, res, _next) => {
|
|
1209
|
-
console.error('Error:', error);
|
|
1210
|
-
res.status(500).json({
|
|
1211
|
-
error: 'Internal server error',
|
|
1212
|
-
message: error.message,
|
|
1213
|
-
timestamp: Date.now(),
|
|
1214
|
-
});
|
|
1215
|
-
});
|
|
1216
|
-
// 404 handler
|
|
1217
|
-
app.use('*', (_, res) => {
|
|
1218
|
-
res.status(404).json({
|
|
1219
|
-
error: 'Endpoint not found',
|
|
1220
|
-
timestamp: Date.now(),
|
|
1221
|
-
});
|
|
1222
|
-
});
|
|
1223
|
-
const PORT = parseInt(port ? port.toString() : '4000');
|
|
1224
|
-
app.listen(PORT, host, async () => {
|
|
1225
|
-
const pcount = await prisma.package.count();
|
|
1226
|
-
console.log(`[Database] Total packages found: ${pcount}`);
|
|
1227
|
-
console.log(`🚀 Backend server running on http://${host}:${PORT}`);
|
|
1228
|
-
console.log(`📊 API endpoints available:`);
|
|
1229
|
-
console.log(` - GET /api/health`);
|
|
1230
|
-
console.log(` - GET /api/packages/refresh`);
|
|
1231
|
-
console.log(` - GET /api/packages`);
|
|
1232
|
-
console.log(` - GET /api/packages/:name`);
|
|
1233
|
-
console.log(` - GET /api/commits/:packagePath`);
|
|
1234
|
-
// console.log(` - GET /api/graph`);
|
|
1235
|
-
console.log(` - GET /api/stats`);
|
|
1236
|
-
console.log(` - GET /api/ci/status`);
|
|
1237
|
-
console.log(` - POST /api/ci/trigger`);
|
|
1238
|
-
console.log(` - POST /api/scan`);
|
|
1239
|
-
console.log(` - GET /api/health/packages`);
|
|
1240
|
-
console.log(` - GET /api/search`);
|
|
1241
|
-
console.log(` - GET /api/activity`);
|
|
1242
|
-
console.log(` - GET /api/system`);
|
|
1243
|
-
}).on('error', (err) => {
|
|
1244
|
-
// Handle common errors like EADDRINUSE (port already in use)
|
|
1245
|
-
if (err.message.includes('EADDRINUSE')) {
|
|
1246
|
-
console.error(`Error: Port ${port} is already in use. Please specify a different port via configuration file.`);
|
|
1247
|
-
process.exit(1);
|
|
1248
|
-
}
|
|
1249
|
-
else {
|
|
1250
|
-
console.error('Server failed to start:', err);
|
|
1251
|
-
process.exit(1);
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
// const appD = express();
|
|
1255
|
-
// // Serve static files from the 'dist' directory
|
|
1256
|
-
// appD.use(express.static(rootPath + '/apps/dashboard/dist'));
|
|
1257
|
-
// // For any other routes, serve the index.html
|
|
1258
|
-
// appD.get('*', (req, res) => {
|
|
1259
|
-
// res.sendFile(path.join(rootPath, '/apps/dashboard/dist', 'index.html'));
|
|
1260
|
-
// });
|
|
1261
|
-
// appD.listen(3000, () => {
|
|
1262
|
-
// console.log(`dashboard Server listening at http://localhost:${3000}`);
|
|
1263
|
-
// });
|
|
1264
|
-
// export default app;
|
|
1265
|
-
// const overallScore =
|
|
1266
|
-
// healthMetrics.reduce((sum, h) => sum + h.health!.overallScore, 0) /
|
|
1267
|
-
// healthMetrics.length;
|
|
1268
|
-
// const metrics: HealthMetric[] = [
|
|
1269
|
-
// {
|
|
1270
|
-
// name: 'Package Health',
|
|
1271
|
-
// value: healthMetrics.filter(h => h.isHealthy).length || 0,
|
|
1272
|
-
// status: calculateHealthStatus(
|
|
1273
|
-
// healthMetrics.filter(h => h.isHealthy).length,
|
|
1274
|
-
// packages.length
|
|
1275
|
-
// ),
|
|
1276
|
-
// description: `${healthMetrics.filter(h => h.isHealthy).length || 0} healthy packages out of ${packages.length || 0}`,
|
|
1277
|
-
// },
|
|
1278
|
-
// {
|
|
1279
|
-
// name: 'Overall Score',
|
|
1280
|
-
// value: Math.round(overallScore),
|
|
1281
|
-
// status:
|
|
1282
|
-
// Math.round(overallScore) >= 80
|
|
1283
|
-
// ? 'healthy'
|
|
1284
|
-
// : Math.round(overallScore) >= 60
|
|
1285
|
-
// ? 'warning'
|
|
1286
|
-
// : 'error',
|
|
1287
|
-
// description: `Average health score: ${Math.round(overallScore)}/100`,
|
|
1288
|
-
// },
|
|
1289
|
-
// {
|
|
1290
|
-
// name: 'Unhealthy Packages',
|
|
1291
|
-
// value: healthMetrics.filter(h => !h.isHealthy).length || 0,
|
|
1292
|
-
// status:
|
|
1293
|
-
// (healthMetrics.filter(h => !h.isHealthy).length || 0) === 0
|
|
1294
|
-
// ? 'healthy'
|
|
1295
|
-
// : (healthMetrics.filter(h => !h.isHealthy).length || 0) <= 2
|
|
1296
|
-
// ? 'warning'
|
|
1297
|
-
// : 'error',
|
|
1298
|
-
// description: `${healthMetrics.filter(h => !h.isHealthy).length || 0} packages need attention`,
|
|
1299
|
-
// },
|
|
1300
|
-
// ];
|
|
1301
|
-
// const packageHealth = packages.map((pkg: any) => ({
|
|
1302
|
-
// package: pkg.packageName,
|
|
1303
|
-
// score: pkg.health?.overallScore || 0,
|
|
1304
|
-
// issues: pkg.error
|
|
1305
|
-
// ? [pkg.error]
|
|
1306
|
-
// : (pkg.health?.overallScore || 0) < 80
|
|
1307
|
-
// ? ['Needs improvement']
|
|
1308
|
-
// : [],
|
|
1309
|
-
// }));
|
|
1310
|
-
// res.status(200).json({
|
|
1311
|
-
// overallScore,
|
|
1312
|
-
// metrics,
|
|
1313
|
-
// packageHealth,
|
|
1314
|
-
// });
|
|
1315
|
-
}
|
|
1316
|
-
// This is a test file to ensure the TypeScript compiler can find an input.
|
|
1317
|
-
// export const runBackend = (name: string): string => {
|
|
1318
|
-
// return `Backend running successfully for ${name}.`;
|
|
1319
|
-
// };
|
|
1320
|
-
// console.log(runBackend("MonoDog"));
|