@monodog/backend 1.5.2 → 1.5.3

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