@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/CHANGELOG.md +7 -0
- package/LICENCE +21 -0
- package/README.md +19 -19
- package/dist/cli.js +20 -20
- package/dist/config-loader.js +13 -13
- package/dist/index.js +59 -15
- package/dist/utils/helpers.js +50 -9
- package/monodog-conf.example.json +11 -11
- package/package.json +17 -16
- package/src/cli.ts +190 -154
- package/src/config-loader.ts +57 -50
- package/src/get-db-url.ts +0 -1
- package/src/index.ts +1366 -1331
- package/src/types/monorepo-scanner.d.ts +7 -8
- package/src/utils/helpers.js +169 -165
- package/src/utils/helpers.ts +82 -77
- package/tsconfig.json +0 -1
package/src/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import 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
|
|
27
|
-
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
87
|
-
throw new Error('Error ' + error);
|
|
98
|
+
dbPackages = await prisma.package.findMany();
|
|
88
99
|
}
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
216
|
+
// Get commit details
|
|
217
|
+
app.get('/api/commits/:packagePath', async (_req, res) => {
|
|
218
|
+
try {
|
|
219
|
+
const { packagePath } = _req.params;
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
221
|
+
// Decode the package path
|
|
222
|
+
const decodedPath = decodeURIComponent(packagePath);
|
|
214
223
|
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
console.log('🔍 Fetching commits for path:', decodedPath);
|
|
225
|
+
console.log('📁 Current working directory:', process.cwd());
|
|
217
226
|
|
|
218
|
-
|
|
227
|
+
const gitService = new GitService();
|
|
219
228
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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(
|
|
239
|
-
console.log('✅
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
257
|
+
const commits = await gitService.getAllCommits(relativePath);
|
|
249
258
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
} catch (error) {
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
branch
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
346
|
+
const result = await ciStatusManager.triggerBuild(
|
|
347
|
+
packageName,
|
|
348
|
+
providerName || 'github',
|
|
349
|
+
branch || 'main'
|
|
350
|
+
);
|
|
365
351
|
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const { force } = _req.body;
|
|
395
|
+
if (!provider) {
|
|
396
|
+
return res.status(400).json({ error: 'Provider is required' });
|
|
397
|
+
}
|
|
404
398
|
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const { format } = _req.params;
|
|
437
|
-
const { filename } = _req.query;
|
|
414
|
+
if (force) {
|
|
415
|
+
scanner.clearCache();
|
|
416
|
+
}
|
|
438
417
|
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
}
|
|
452
|
-
|
|
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
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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:
|
|
562
|
-
testCoverage:
|
|
563
|
-
lintStatus:
|
|
564
|
-
securityAudit:
|
|
565
|
-
overallScore:
|
|
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
|
-
|
|
569
|
-
packageName:
|
|
570
|
-
health
|
|
571
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
742
|
+
// Filter by type
|
|
743
|
+
if (type && type !== 'all') {
|
|
744
|
+
filtered = filtered.filter((pkg: any) => pkg.type === type);
|
|
745
|
+
}
|
|
732
746
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
779
|
-
|
|
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
|
-
|
|
783
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
//
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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
|
-
//
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
895
|
-
|
|
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
|
-
|
|
933
|
-
|
|
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
|
-
|
|
936
|
-
|
|
949
|
+
for (const item of items) {
|
|
950
|
+
const fullPath = path.join(dir, item.name);
|
|
937
951
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
-
|
|
994
|
-
|
|
995
|
-
return skipDirs.includes(dirName);
|
|
1012
|
+
// Deeper levels, skip more directories
|
|
1013
|
+
return skipDirs.includes(dirName) || dirName.startsWith('.');
|
|
996
1014
|
}
|
|
997
1015
|
|
|
998
|
-
|
|
999
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1028
|
+
// Start scanning from root
|
|
1029
|
+
scanDirectory(rootDir);
|
|
1013
1030
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1031
|
+
// Sort files by path for consistent ordering
|
|
1032
|
+
configFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
1016
1033
|
|
|
1017
|
-
|
|
1018
|
-
configFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
1034
|
+
console.log(`📁 Found ${configFiles.length} configuration files`);
|
|
1019
1035
|
|
|
1020
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
-
|
|
1180
|
-
|
|
1196
|
+
console.log('💾 Saving file:', filePath);
|
|
1197
|
+
console.log('📁 Root directory:', rootDir);
|
|
1181
1198
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
1243
|
+
console.error('Error saving configuration file:', error);
|
|
1244
|
+
res.status(500).json({
|
|
1195
1245
|
success: false,
|
|
1196
|
-
error: '
|
|
1246
|
+
error: 'Failed to save configuration file',
|
|
1197
1247
|
});
|
|
1198
1248
|
}
|
|
1249
|
+
});
|
|
1199
1250
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
1235
|
-
|
|
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
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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
|
-
|
|
1247
|
-
console.log('📁 Package path:', packagePath);
|
|
1279
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
1248
1280
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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: '
|
|
1289
|
-
message:
|
|
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
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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(
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
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
|
-
|
|
1556
|
+
next();
|
|
1526
1557
|
} else {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
// Start the server
|
|
1537
|
-
const PORT = parseInt(port ? port.toString() :
|
|
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
|
-
|