@manojkmfsi/monodog 1.0.1

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