@manojkmfsi/monodog 1.1.7 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/package.json +1 -1
  4. package/src/config/swagger-config.ts +0 -344
  5. package/src/config-loader.ts +0 -97
  6. package/src/constants/index.ts +0 -13
  7. package/src/constants/middleware.ts +0 -83
  8. package/src/constants/port.ts +0 -20
  9. package/src/constants/security.ts +0 -78
  10. package/src/controllers/commit-controller.ts +0 -31
  11. package/src/controllers/config-controller.ts +0 -42
  12. package/src/controllers/health-controller.ts +0 -24
  13. package/src/controllers/package-controller.ts +0 -67
  14. package/src/get-db-url.ts +0 -9
  15. package/src/index.ts +0 -9
  16. package/src/middleware/dashboard-startup.ts +0 -161
  17. package/src/middleware/error-handler.ts +0 -50
  18. package/src/middleware/index.ts +0 -20
  19. package/src/middleware/logger.ts +0 -65
  20. package/src/middleware/security.ts +0 -90
  21. package/src/middleware/server-startup.ts +0 -153
  22. package/src/middleware/swagger-middleware.ts +0 -58
  23. package/src/repositories/commit-repository.ts +0 -108
  24. package/src/repositories/dependency-repository.ts +0 -110
  25. package/src/repositories/index.ts +0 -10
  26. package/src/repositories/package-health-repository.ts +0 -75
  27. package/src/repositories/package-repository.ts +0 -142
  28. package/src/repositories/prisma-client.ts +0 -25
  29. package/src/routes/commit-routes.ts +0 -10
  30. package/src/routes/config-routes.ts +0 -14
  31. package/src/routes/health-routes.ts +0 -14
  32. package/src/routes/package-routes.ts +0 -22
  33. package/src/serve.ts +0 -41
  34. package/src/services/commit-service.ts +0 -44
  35. package/src/services/config-service.ts +0 -391
  36. package/src/services/git-service.ts +0 -140
  37. package/src/services/health-service.ts +0 -162
  38. package/src/services/package-service.ts +0 -228
  39. package/src/setup.ts +0 -78
  40. package/src/types/ci.ts +0 -122
  41. package/src/types/config.ts +0 -22
  42. package/src/types/database.ts +0 -67
  43. package/src/types/git.ts +0 -33
  44. package/src/types/health.ts +0 -11
  45. package/src/types/index.ts +0 -23
  46. package/src/types/package.ts +0 -39
  47. package/src/types/scanner.ts +0 -31
  48. package/src/types/swagger-jsdoc.d.ts +0 -15
  49. package/src/utils/ci-status.ts +0 -535
  50. package/src/utils/db-utils.ts +0 -92
  51. package/src/utils/health-utils.ts +0 -67
  52. package/src/utils/monorepo-scanner.ts +0 -576
  53. package/src/utils/utilities.ts +0 -427
@@ -1,391 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- import { AppLogger } from '../middleware/logger';
5
- import { PackageRepository } from '../repositories';
6
-
7
- /**
8
- * Configuration file interface
9
- */
10
- interface ConfigFile {
11
- id: string;
12
- name: string;
13
- path: string;
14
- type: string;
15
- content: string;
16
- size: number;
17
- lastModified: string;
18
- hasSecrets: boolean;
19
- }
20
-
21
- interface TransformedConfigFile extends ConfigFile {
22
- isEditable: boolean;
23
- }
24
-
25
- // Helper function to scan for configuration files
26
- async function scanConfigFiles(rootDir: string): Promise<any[]> {
27
- const configPatterns = [
28
- // Root level config files
29
- 'package.json',
30
- 'pnpm-workspace.yaml',
31
- 'pnpm-lock.yaml',
32
- 'turbo.json',
33
- 'tsconfig.json',
34
- '.eslintrc.*',
35
- '.prettierrc',
36
- '.prettierignore',
37
- '.editorconfig',
38
- '.nvmrc',
39
- '.gitignore',
40
- 'commitlint.config.js',
41
- '.releaserc.json',
42
- 'env.example',
43
-
44
- // App-specific config files
45
- 'vite.config.*',
46
- 'tailwind.config.*',
47
- 'postcss.config.*',
48
- 'components.json',
49
-
50
- // Package-specific config files
51
- 'jest.config.*',
52
- '.env*',
53
- 'dockerfile*',
54
- ];
55
-
56
- const configFiles: ConfigFile[] = [];
57
- const scannedPaths = new Set();
58
-
59
- function scanDirectory(dir: string, depth = 0) {
60
- if (scannedPaths.has(dir) || depth > 8) return; // Limit depth for safety
61
- scannedPaths.add(dir);
62
-
63
- try {
64
- const items = fs.readdirSync(dir, { withFileTypes: true });
65
-
66
- for (const item of items) {
67
- const fullPath = path.join(dir, item.name);
68
-
69
- if (item.isDirectory()) {
70
- // Skip node_modules and other non-source directories
71
- if (!shouldSkipDirectory(item.name, depth)) {
72
- scanDirectory(fullPath, depth + 1);
73
- }
74
- } else {
75
- // Check if file matches config patterns
76
- if (isConfigFile(item.name)) {
77
- try {
78
- const stats = fs.statSync(fullPath);
79
- const content = fs.readFileSync(fullPath, 'utf8');
80
- const relativePath =
81
- fullPath.replace(rootDir, '').replace(/\\/g, '/') ||
82
- `/${item.name}`;
83
-
84
- configFiles.push({
85
- id: relativePath,
86
- name: item.name,
87
- path: relativePath,
88
- type: getFileType(item.name),
89
- content: content,
90
- size: stats.size,
91
- lastModified: stats.mtime.toISOString(),
92
- hasSecrets: containsSecrets(content, item.name),
93
- });
94
- } catch (error) {
95
- AppLogger.warn(`Could not read file: ${fullPath}`);
96
- }
97
- }
98
- }
99
- }
100
- } catch (error) {
101
- AppLogger.warn(`Could not scan directory: ${dir}`);
102
- }
103
- }
104
-
105
- function shouldSkipDirectory(dirName: string, depth: number): boolean {
106
- const skipDirs = [
107
- 'node_modules',
108
- 'dist',
109
- 'build',
110
- '.git',
111
- '.next',
112
- '.vscode',
113
- '.turbo',
114
- '.husky',
115
- '.github',
116
- '.vite',
117
- 'migrations',
118
- 'coverage',
119
- '.cache',
120
- 'tmp',
121
- 'temp',
122
- ];
123
-
124
- // At root level, be more permissive
125
- if (depth === 0) {
126
- return skipDirs.includes(dirName);
127
- }
128
-
129
- // Deeper levels, skip more directories
130
- return skipDirs.includes(dirName) || dirName.startsWith('.');
131
- }
132
-
133
- function isConfigFile(filename: string): boolean {
134
- return configPatterns.some(pattern => {
135
- if (pattern.includes('*')) {
136
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
137
- return regex.test(filename.toLowerCase());
138
- }
139
- return filename.toLowerCase() === pattern.toLowerCase();
140
- });
141
- }
142
-
143
- AppLogger.info(`Scanning for config files in: ${rootDir}`);
144
-
145
- // Start scanning from root
146
- scanDirectory(rootDir);
147
-
148
- // Sort files by path for consistent ordering
149
- configFiles.sort((a, b) => a.path.localeCompare(b.path));
150
-
151
- AppLogger.info(`Found ${configFiles.length} configuration files`);
152
-
153
- // Log some sample files for debugging
154
- if (configFiles.length > 0) {
155
- AppLogger.info('Sample config files found');
156
- configFiles.slice(0, 5).forEach(file => {
157
- AppLogger.debug(` - ${file.path} (${file.type})`);
158
- });
159
- if (configFiles.length > 5) {
160
- AppLogger.debug(` ... and ${configFiles.length - 5} more`);
161
- }
162
- }
163
-
164
- return configFiles;
165
- }
166
-
167
- function containsSecrets(content: string, filename: string): boolean {
168
- // Only check .env files and files that might contain secrets
169
- if (!filename.includes('.env') && !filename.includes('config')) {
170
- return false;
171
- }
172
-
173
- const secretPatterns = [
174
- /password\s*=\s*[^\s]/i,
175
- /secret\s*=\s*[^\s]/i,
176
- /key\s*=\s*[^\s]/i,
177
- /token\s*=\s*[^\s]/i,
178
- /auth\s*=\s*[^\s]/i,
179
- /credential\s*=\s*[^\s]/i,
180
- /api_key\s*=\s*[^\s]/i,
181
- /private_key\s*=\s*[^\s]/i,
182
- /DATABASE_URL/i,
183
- /JWT_SECRET/i,
184
- /GITHUB_TOKEN/i,
185
- ];
186
-
187
- return secretPatterns.some(pattern => pattern.test(content));
188
- }
189
-
190
- function getFileType(filename: string): string {
191
- const extension = filename.split('.').pop()?.toLowerCase();
192
- switch (extension) {
193
- case 'json':
194
- return 'json';
195
- case 'yaml':
196
- case 'yml':
197
- return 'yaml';
198
- case 'js':
199
- return 'javascript';
200
- case 'ts':
201
- return 'typescript';
202
- case 'env':
203
- return 'env';
204
- case 'md':
205
- return 'markdown';
206
- case 'cjs':
207
- return 'javascript';
208
- case 'config':
209
- return 'text';
210
- case 'example':
211
- return filename.includes('.env') ? 'env' : 'text';
212
- default:
213
- return 'text';
214
- }
215
- }
216
-
217
- export const getConfigurationFilesService = async (rootDir: string) => {
218
-
219
- const configFiles = await scanConfigFiles(rootDir);
220
-
221
- // Transform to match frontend ConfigFile interface
222
- const transformedFiles = configFiles.map(file => ({
223
- id: file.id,
224
- name: file.name,
225
- path: file.path,
226
- type: file.type,
227
- content: file.content,
228
- size: file.size,
229
- lastModified: file.lastModified,
230
- hasSecrets: file.hasSecrets,
231
- isEditable: true,
232
- }));
233
-
234
- return {
235
- success: true,
236
- files: transformedFiles,
237
- total: transformedFiles.length,
238
- timestamp: Date.now(),
239
- };
240
- }
241
-
242
- export const updateConfigFileService = async (id: string, rootDir: string, content: string) => {
243
- const filePath = path.join(
244
- rootDir,
245
- id.startsWith('/') ? id.slice(1) : id
246
- );
247
-
248
- AppLogger.debug('Saving file: ' + filePath);
249
- AppLogger.debug('Root directory: ' + rootDir);
250
-
251
- // Security check: ensure the file is within the project directory
252
- if (!filePath.startsWith(rootDir)) {
253
- throw new Error('Invalid file path');
254
- }
255
-
256
- // Check if file exists and is writable
257
- try {
258
- await fs.promises.access(filePath, fs.constants.W_OK);
259
- } catch (error) {
260
- throw new Error('File is not writable or does not exist ' + filePath);
261
- }
262
- // Write the new content
263
- await fs.promises.writeFile(filePath, content, 'utf8');
264
-
265
- // Get file stats for updated information
266
- const stats = await fs.promises.stat(filePath);
267
- const filename = path.basename(filePath);
268
-
269
- const updatedFile = {
270
- id: id,
271
- name: filename,
272
- path: id,
273
- type: getFileType(filename),
274
- content: content,
275
- size: stats.size,
276
- lastModified: stats.mtime.toISOString(),
277
- hasSecrets: containsSecrets(content, filename),
278
- isEditable: true,
279
- };
280
-
281
- return {
282
- success: true,
283
- file: updatedFile,
284
- message: 'File saved successfully',
285
- };
286
- }
287
-
288
- export const updatePackageConfigurationService = async (packagePath: string, packageName: string, config: string) => {
289
- let newConfig;
290
- try {
291
- newConfig = JSON.parse(config);
292
- } catch (error) {
293
- AppLogger.error('JSON parsing error', error as Error);
294
- throw new Error('Invalid JSON format: ' + (error instanceof Error ? error.message : 'Unknown error'));
295
- }
296
-
297
- const packageJsonPath = path.join(packagePath, 'package.json');
298
-
299
- // Security check: ensure the path is valid
300
- if (!fs.existsSync(packagePath)) {
301
- throw new Error('Package directory not found: ' + packagePath);
302
- }
303
-
304
- // Check if package.json exists
305
- if (!fs.existsSync(packageJsonPath)) {
306
- throw new Error('package.json not found in directory: ' + packageJsonPath);
307
- }
308
-
309
- // Read the existing package.json to preserve all fields
310
- const existingContent = await fs.promises.readFile(packageJsonPath, 'utf8');
311
- let existingConfig;
312
- try {
313
- existingConfig = JSON.parse(existingContent);
314
- } catch (error) {
315
- throw new Error('Failed to parse existing package.json: ' + (error instanceof Error ? error.message : 'Invalid JSON'));
316
- }
317
-
318
- // Merge the new configuration with existing configuration
319
- const mergedConfig = {
320
- ...existingConfig,
321
- name: newConfig.name || existingConfig.name,
322
- version: newConfig.version || existingConfig.version,
323
- description:
324
- newConfig.description !== undefined
325
- ? newConfig.description
326
- : existingConfig.description,
327
- license:
328
- newConfig.license !== undefined
329
- ? newConfig.license
330
- : existingConfig.license,
331
- repository: newConfig.repository || existingConfig.repository,
332
- scripts: newConfig.scripts || existingConfig.scripts,
333
- dependencies: newConfig.dependencies || existingConfig.dependencies,
334
- devDependencies:
335
- newConfig.devDependencies || existingConfig.devDependencies,
336
- peerDependencies:
337
- newConfig.peerDependencies || existingConfig.peerDependencies,
338
- };
339
-
340
- // Write the merged configuration back
341
- const formattedConfig = JSON.stringify(mergedConfig, null, 2);
342
- await fs.promises.writeFile(packageJsonPath, formattedConfig, 'utf8');
343
-
344
- // Update the package in the database - use correct field names based on your Prisma schema
345
- const updateData: Record<string, unknown> = {
346
- lastUpdated: new Date(),
347
- };
348
-
349
- // Only update fields that exist in your Prisma schema
350
- if (newConfig.version) updateData.version = newConfig.version;
351
- if (newConfig.description !== undefined)
352
- updateData.description = newConfig.description || '';
353
- if (newConfig.license !== undefined)
354
- updateData.license = newConfig.license || '';
355
- if (newConfig.scripts)
356
- updateData.scripts = JSON.stringify(newConfig.scripts);
357
- if (newConfig.repository)
358
- updateData.repository = JSON.stringify(newConfig.repository);
359
- if (newConfig.dependencies)
360
- updateData.dependencies = JSON.stringify(newConfig.dependencies);
361
- if (newConfig.devDependencies)
362
- updateData.devDependencies = JSON.stringify(newConfig.devDependencies);
363
- if (newConfig.peerDependencies)
364
- updateData.peerDependencies = JSON.stringify(newConfig.peerDependencies);
365
-
366
- AppLogger.debug('Updating database with:', updateData);
367
-
368
- const updatedPackage = await PackageRepository.updateConfig(packageName, updateData);
369
-
370
- // Transform the response
371
- const transformedPackage = {
372
- ...updatedPackage,
373
- maintainers: updatedPackage.maintainers
374
- ? JSON.parse(updatedPackage.maintainers)
375
- : [],
376
- scripts: updatedPackage.scripts ? JSON.parse(updatedPackage.scripts) : {},
377
- repository: updatedPackage.repository
378
- ? JSON.parse(updatedPackage.repository)
379
- : {},
380
- dependencies: updatedPackage.dependencies
381
- ? JSON.parse(updatedPackage.dependencies)
382
- : {},
383
- devDependencies: updatedPackage.devDependencies
384
- ? JSON.parse(updatedPackage.devDependencies)
385
- : {},
386
- peerDependencies: updatedPackage.peerDependencies
387
- ? JSON.parse(updatedPackage.peerDependencies)
388
- : {},
389
- };
390
- return transformedPackage;
391
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * GitService.ts
3
- *
4
- * This service executes native 'git' commands using Node.js's child_process
5
- * to retrieve the commit history of the local repository.
6
- */
7
-
8
- import { exec } from 'child_process';
9
- import { promisify } from 'util';
10
- import path from 'path';
11
-
12
- import { AppLogger } from '../middleware/logger';
13
- import type { Commit } from '../types';
14
- import { VALID_COMMIT_TYPES } from '../types';
15
-
16
- // Promisify the standard 'exec' function for easy async/await usage
17
- const execPromise = promisify(exec);
18
-
19
- export class GitService {
20
- private repoPath: string;
21
-
22
- constructor(repoPath: string = process.cwd()) {
23
- this.repoPath = repoPath;
24
- }
25
-
26
- /**
27
- * Retrieves commit history for the repository, optionally filtered by a package path.
28
- */
29
- public async getAllCommits(pathFilter?: string): Promise<Commit[]> {
30
- try {
31
-
32
- let pathArgument = '';
33
- if (pathFilter) {
34
- // Normalize the path and ensure it's relative to the repo root
35
- const normalizedPath = this.normalizePath(pathFilter);
36
- if (normalizedPath) {
37
- pathArgument = ` -C ${normalizedPath}`;
38
- }
39
- }
40
-
41
- // First, validate we're in a git repo
42
- await this.validateGitRepository(pathArgument);
43
-
44
- AppLogger.debug(`Executing Git command in: ${this.repoPath}`);
45
- // Use a simpler git log format
46
- const command = `git log --pretty=format:"%H|%an|%ad|%s" --date=iso-strict ${pathArgument}`;
47
- AppLogger.debug(`Git command: ${command}`);
48
-
49
- const { stdout, stderr } = await execPromise(command, {
50
- cwd: this.repoPath,
51
- maxBuffer: 1024 * 5000,
52
- });
53
-
54
- if (stderr) {
55
- AppLogger.warn('Git stderr: ' + stderr);
56
- }
57
-
58
- if (!stdout.trim()) {
59
- AppLogger.debug('No commits found for path: ' + pathFilter);
60
- return [];
61
- }
62
-
63
- // Parse the output
64
- const commits: Commit[] = [];
65
- const lines = stdout.trim().split('\n');
66
-
67
- for (const line of lines) {
68
- try {
69
- const [hash, author, date, message] = line.split('|');
70
-
71
- const commit: Commit = {
72
- hash: hash.trim(),
73
- author: author.trim(),
74
- packageName: pathFilter || 'root',
75
- date: new Date(date.trim()),
76
- message: message.trim(),
77
- type: this.extractCommitType(message.trim()),
78
- };
79
-
80
- commits.push(commit);
81
- } catch (parseError) {
82
- AppLogger.error('Failed to parse commit line: ' + line, parseError as Error);
83
- }
84
- }
85
-
86
- AppLogger.debug(`Successfully parsed ${commits.length} commits`);
87
- return commits;
88
- } catch (error) {
89
- AppLogger.error('Error in getAllCommits', error as Error);
90
- throw error;
91
- }
92
- }
93
-
94
- /**
95
- * Normalize path to be relative to git repo root
96
- */
97
- private normalizePath(inputPath: string): string {
98
- // If it's an absolute path, make it relative to repo root
99
- if (path.isAbsolute(inputPath)) {
100
- return path.relative(this.repoPath, inputPath);
101
- }
102
-
103
- // If it's already relative, return as-is
104
- return inputPath;
105
- }
106
-
107
- /**
108
- * Extract commit type from message
109
- */
110
- private extractCommitType(message: string): string {
111
- try {
112
- const typeMatch = message.match(/^(\w+)(\([^)]+\))?!?:/);
113
- if (typeMatch) {
114
- const rawType = typeMatch[1].toLowerCase();
115
- if (VALID_COMMIT_TYPES.includes(rawType)) {
116
- return rawType;
117
- }
118
- }
119
- return 'other';
120
- } catch (error) {
121
- return 'other';
122
- }
123
- }
124
-
125
- /**
126
- * Validate that we're in a git repository
127
- */
128
- private async validateGitRepository(pathArgument: string): Promise<void> {
129
- try {
130
- await execPromise('git '+pathArgument+' rev-parse --is-inside-work-tree', {
131
- cwd: this.repoPath,
132
- });
133
- AppLogger.debug('Valid git repository');
134
- } catch (error) {
135
- throw new Error(
136
- 'Not a git repository (or any of the parent directories)'
137
- );
138
- }
139
- }
140
- }
@@ -1,162 +0,0 @@
1
- import {
2
- scanMonorepo,
3
- calculatePackageHealth,
4
- } from '../utils/utilities';
5
-
6
- import {
7
- funCheckBuildStatus,
8
- funCheckLintStatus,
9
- funCheckSecurityAudit,
10
- } from '../utils/monorepo-scanner';
11
-
12
- import { AppLogger } from '../middleware/logger';
13
- import { PackageHealthRepository, PackageRepository } from '../repositories';
14
- import type { TransformedPackageHealth, HealthResponse, PackageHealthModel } from '../types/database';
15
-
16
- // Track in-flight health refresh requests to prevent duplicates
17
- let inFlightHealthRefresh: Promise<HealthResponse> | null = null;
18
-
19
- export const getHealthSummaryService = async (): Promise<HealthResponse> => {
20
- const packageHealthData = await PackageHealthRepository.findAll() as PackageHealthModel[];
21
- AppLogger.debug('packageHealthData count: ' + packageHealthData.length);
22
-
23
- // Transform the data to match the expected frontend format
24
- const packages = packageHealthData.map((pkg: PackageHealthModel) => {
25
- const health = {
26
- buildStatus: pkg.packageBuildStatus,
27
- testCoverage: pkg.packageTestCoverage,
28
- lintStatus: pkg.packageLintStatus,
29
- securityAudit: pkg.packageSecurity,
30
- overallScore: pkg.packageOverallScore,
31
- };
32
-
33
- return {
34
- packageName: pkg.packageName,
35
- health: health,
36
- isHealthy: pkg.packageOverallScore >= 80,
37
- };
38
- });
39
-
40
- // Calculate summary statistics
41
- const total = packages.length;
42
- const healthy = packages.filter((pkg: TransformedPackageHealth) => pkg.isHealthy).length;
43
- const unhealthy = packages.filter((pkg: TransformedPackageHealth) => !pkg.isHealthy).length;
44
- const averageScore =
45
- packages.length > 0
46
- ? packages.reduce((sum: number, pkg: TransformedPackageHealth) => sum + pkg.health.overallScore, 0) /
47
- packages.length
48
- : 0;
49
-
50
- return {
51
- packages: packages,
52
- summary: {
53
- total: total,
54
- healthy: healthy,
55
- unhealthy: unhealthy,
56
- averageScore: averageScore,
57
- },
58
- };
59
- }
60
-
61
- export const healthRefreshService = async (rootDir: string) => {
62
- // If a health refresh is already in progress, return the in-flight promise
63
- if (inFlightHealthRefresh) {
64
- AppLogger.info('Health refresh already in progress, returning cached promise');
65
- return inFlightHealthRefresh;
66
- }
67
-
68
- // Create and store the health refresh promise
69
- inFlightHealthRefresh = (async () => {
70
- try {
71
- const packages = scanMonorepo(rootDir);
72
- AppLogger.debug('packages count: ' + packages.length);
73
- const healthMetrics = await Promise.all(
74
- packages.map(async pkg => {
75
- try {
76
- // Await each health check function since they return promises
77
- const buildStatus = await funCheckBuildStatus(pkg);
78
- const testCoverage = 0; //await funCheckTestCoverage(pkg); // skip test coverage for now
79
- const lintStatus = await funCheckLintStatus(pkg);
80
- const securityAudit = await funCheckSecurityAudit(pkg);
81
- // Calculate overall health score
82
- const overallScore = calculatePackageHealth(
83
- buildStatus,
84
- testCoverage,
85
- lintStatus,
86
- securityAudit
87
- );
88
-
89
- const health = {
90
- buildStatus: buildStatus,
91
- testCoverage: testCoverage,
92
- lintStatus: lintStatus,
93
- securityAudit: securityAudit,
94
- overallScore: overallScore.overallScore,
95
- };
96
- const packageStatus =
97
- health.overallScore >= 80
98
- ? 'healthy'
99
- : health.overallScore >= 60 && health.overallScore < 80
100
- ? 'warning'
101
- : 'error';
102
-
103
- AppLogger.debug(`${pkg.name}: ${packageStatus}`, health);
104
-
105
- await PackageHealthRepository.upsert({
106
- packageName: pkg.name,
107
- packageOverallScore: overallScore.overallScore,
108
- packageBuildStatus: buildStatus,
109
- packageTestCoverage: testCoverage,
110
- packageLintStatus: lintStatus,
111
- packageSecurity: securityAudit,
112
- packageDependencies: '',
113
- });
114
- // update related package status as well
115
- await PackageRepository.updateStatus(pkg.name, packageStatus);
116
- return {
117
- packageName: pkg.name,
118
- health,
119
- isHealthy: health.overallScore >= 80,
120
- };
121
- } catch (error) {
122
- return {
123
- packageName: pkg.name,
124
- health: {
125
- "buildStatus": "",
126
- "testCoverage": 0,
127
- "lintStatus": "",
128
- "securityAudit": "",
129
- "overallScore": 0
130
- },
131
- isHealthy: false,
132
- error: 'Failed to fetch health metrics1',
133
- };
134
- }
135
- })
136
- );
137
-
138
- const result: HealthResponse = {
139
- packages: healthMetrics.filter(h => !h.error),
140
- summary: {
141
- total: packages.length,
142
- healthy: healthMetrics.filter(h => h.isHealthy).length,
143
- unhealthy: healthMetrics.filter(h => !h.isHealthy).length,
144
- averageScore:
145
- healthMetrics.filter(h => h.health).length > 0
146
- ? healthMetrics
147
- .filter(h => h.health)
148
- .reduce((sum, h) => sum + h.health!.overallScore, 0) /
149
- healthMetrics.filter(h => h.health).length
150
- : 0,
151
- },
152
- };
153
-
154
- return result;
155
- } finally {
156
- // Clear the in-flight promise after completion
157
- inFlightHealthRefresh = null;
158
- }
159
- })();
160
-
161
- return inFlightHealthRefresh;
162
- }