@manojkmfsi/monodog 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.eslintrc.cjs +15 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/CHANGELOG.md +79 -0
  4. package/LICENCE +21 -0
  5. package/README.md +55 -0
  6. package/dist/config-loader.js +116 -0
  7. package/dist/get-db-url.js +10 -0
  8. package/dist/gitService.js +242 -0
  9. package/dist/index.js +1370 -0
  10. package/dist/serve.js +103 -0
  11. package/dist/setup.js +155 -0
  12. package/dist/utils/ci-status.js +446 -0
  13. package/dist/utils/helpers.js +237 -0
  14. package/dist/utils/monorepo-scanner.js +486 -0
  15. package/dist/utils/utilities.js +414 -0
  16. package/monodog-conf.example.json +16 -0
  17. package/monodog-conf.json +16 -0
  18. package/monodog-dashboard/README.md +58 -0
  19. package/monodog-dashboard/dist/assets/index-2d967652.js +71 -0
  20. package/monodog-dashboard/dist/assets/index-504dc418.css +1 -0
  21. package/monodog-dashboard/dist/index.html +15 -0
  22. package/package.json +50 -0
  23. package/prisma/migrations/20251017041048_init/migration.sql +19 -0
  24. package/prisma/migrations/20251017083007_add_package/migration.sql +21 -0
  25. package/prisma/migrations/20251021083705_alter_package/migration.sql +37 -0
  26. package/prisma/migrations/20251022085155_test/migration.sql +2 -0
  27. package/prisma/migrations/20251022160841_/migration.sql +35 -0
  28. package/prisma/migrations/20251023130158_rename_column_name/migration.sql +34 -0
  29. package/prisma/migrations/20251023174837_/migration.sql +34 -0
  30. package/prisma/migrations/20251023175830_uodate_schema/migration.sql +32 -0
  31. package/prisma/migrations/20251024103700_add_dependency_info/migration.sql +13 -0
  32. package/prisma/migrations/20251025192150_add_dependency_info/migration.sql +19 -0
  33. package/prisma/migrations/20251025192342_add_dependency_info/migration.sql +40 -0
  34. package/prisma/migrations/20251025204613_add_dependency_info/migration.sql +8 -0
  35. package/prisma/migrations/20251026071336_add_dependency_info/migration.sql +25 -0
  36. package/prisma/migrations/20251027062626_add_commit/migration.sql +10 -0
  37. package/prisma/migrations/20251027062748_add_commit/migration.sql +23 -0
  38. package/prisma/migrations/20251027092741_add_commit/migration.sql +17 -0
  39. package/prisma/migrations/20251027112736_add_health_status/migration.sql +16 -0
  40. package/prisma/migrations/20251027140546_init_packages/migration.sql +16 -0
  41. package/prisma/migrations/20251029073436_added_package_heath_key/migration.sql +34 -0
  42. package/prisma/migrations/20251029073830_added_package_health_key/migration.sql +49 -0
  43. package/prisma/migrations/20251111091920_/migration.sql +16 -0
  44. package/prisma/migrations/20251211155036_cascade_on_auto_delete/migration.sql +48 -0
  45. package/prisma/migrations/migration_lock.toml +3 -0
  46. package/prisma/schema.prisma +114 -0
  47. package/release.config.js +41 -0
  48. package/src/config-loader.ts +119 -0
  49. package/src/get-db-url.ts +11 -0
  50. package/src/gitService.ts +277 -0
  51. package/src/index.ts +1554 -0
  52. package/src/serve.ts +87 -0
  53. package/src/setup.ts +164 -0
  54. package/src/types/monorepo-scanner.d.ts +32 -0
  55. package/src/utils/ci-status.ts +639 -0
  56. package/src/utils/helpers.js +203 -0
  57. package/src/utils/helpers.js.map +1 -0
  58. package/src/utils/helpers.ts +238 -0
  59. package/src/utils/monorepo-scanner.ts +599 -0
  60. package/src/utils/utilities.ts +483 -0
  61. package/tsconfig.json +16 -0
@@ -0,0 +1,599 @@
1
+ // Monorepo Scanner
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { execSync } from 'child_process';
5
+ import {
6
+ PackageInfo,
7
+ DependencyInfo,
8
+ PackageHealth,
9
+ MonorepoStats,
10
+ scanMonorepo,
11
+ // analyzeDependencies,
12
+ calculatePackageHealth,
13
+ generateMonorepoStats,
14
+ findCircularDependencies,
15
+ generateDependencyGraph,
16
+ checkOutdatedDependencies,
17
+ getPackageSize,
18
+ } from './utilities';
19
+
20
+ export interface ScanResult {
21
+ packages: PackageInfo[];
22
+ stats: MonorepoStats;
23
+ dependencyGraph: any;
24
+ circularDependencies: string[][];
25
+ outdatedPackages: string[];
26
+ scanTimestamp: Date;
27
+ scanDuration: number;
28
+ }
29
+
30
+ export interface PackageReport {
31
+ package: PackageInfo;
32
+ health: PackageHealth;
33
+ size: { size: number; files: number };
34
+ outdatedDeps: DependencyInfo[];
35
+ lastModified: Date;
36
+ gitInfo?: {
37
+ lastCommit: string;
38
+ lastCommitDate: Date;
39
+ author: string;
40
+ branch: string;
41
+ };
42
+ }
43
+
44
+ export class MonorepoScanner {
45
+ private rootDir: string;
46
+ private cache: Map<string, any> = new Map();
47
+ private cacheExpiry = 5 * 60 * 1000; // 5 minutes
48
+
49
+ constructor(rootDir: string = process.cwd()) {
50
+ this.rootDir = rootDir;
51
+ }
52
+
53
+ /**
54
+ * Performs a complete scan of the monorepo
55
+ */
56
+ async scan(): Promise<ScanResult> {
57
+ const startTime = Date.now();
58
+
59
+ try {
60
+ // Check cache first
61
+ const cacheKey = 'full-scan';
62
+ const cached = this.getFromCache(cacheKey);
63
+ if (cached) {
64
+ return cached;
65
+ }
66
+
67
+ console.log('🔍 Starting monorepo scan...');
68
+
69
+ // Scan all packages
70
+ const packages = scanMonorepo(this.rootDir);
71
+ console.log(`📦 Found ${packages.length} packages`);
72
+
73
+ // Generate statistics
74
+ const stats = generateMonorepoStats(packages);
75
+
76
+ // Generate dependency graph
77
+ const dependencyGraph = generateDependencyGraph(packages);
78
+
79
+ // Find circular dependencies
80
+ const circularDependencies = findCircularDependencies(packages);
81
+
82
+ // Check for outdated packages
83
+ const outdatedPackages = this.findOutdatedPackages(packages);
84
+
85
+ const result: ScanResult = {
86
+ packages,
87
+ stats,
88
+ dependencyGraph,
89
+ circularDependencies,
90
+ outdatedPackages,
91
+ scanTimestamp: new Date(),
92
+ scanDuration: Date.now() - startTime,
93
+ };
94
+
95
+ // Cache the result
96
+ this.setCache(cacheKey, result);
97
+
98
+ console.log(`✅ Scan completed in ${result.scanDuration}ms`);
99
+ return result;
100
+ } catch (error) {
101
+ console.error('❌ Error during scan:', error);
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Generates detailed reports for all packages
108
+ */
109
+ async generatePackageReports(): Promise<PackageReport[]> {
110
+ const packages = scanMonorepo(this.rootDir);
111
+ const reports: PackageReport[] = [];
112
+
113
+ for (const pkg of packages) {
114
+ try {
115
+ const report = await this.generatePackageReport(pkg);
116
+ reports.push(report);
117
+ } catch (error) {
118
+ console.error(`Error generating report for ${pkg.name}:`, error);
119
+ }
120
+ }
121
+
122
+ return reports;
123
+ }
124
+
125
+ /**
126
+ * Generates a detailed report for a specific package
127
+ */
128
+ async generatePackageReport(pkg: PackageInfo): Promise<PackageReport> {
129
+ const health = await this.assessPackageHealth(pkg);
130
+ const size = getPackageSize(pkg.path);
131
+ const outdatedDeps = checkOutdatedDependencies(pkg);
132
+ const lastModified = this.getLastModified(pkg.path);
133
+ const gitInfo = await this.getGitInfo(pkg.path);
134
+
135
+ return {
136
+ package: pkg,
137
+ health,
138
+ size,
139
+ outdatedDeps,
140
+ lastModified,
141
+ gitInfo,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Assesses the health of a package
147
+ */
148
+ private async assessPackageHealth(pkg: PackageInfo): Promise<PackageHealth> {
149
+ // Check build status
150
+ const buildStatus = await this.checkBuildStatus(pkg);
151
+
152
+ // Check test coverage
153
+ const testCoverage = await this.checkTestCoverage(pkg);
154
+
155
+ // Check lint status
156
+ const lintStatus = await this.checkLintStatus(pkg);
157
+
158
+ // Check security audit
159
+ const securityAudit = await this.checkSecurityAudit(pkg);
160
+
161
+ return calculatePackageHealth(
162
+ buildStatus,
163
+ testCoverage,
164
+ lintStatus,
165
+ securityAudit
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Checks if a package builds successfully
171
+ */
172
+ async checkBuildStatus(
173
+ pkg: PackageInfo
174
+ ): Promise<PackageHealth['buildStatus']> {
175
+ try {
176
+ if (pkg.scripts.build) {
177
+ // Try to run build command
178
+ execSync('npm run build', {
179
+ cwd: pkg.path,
180
+ stdio: 'pipe',
181
+ timeout: 30000,
182
+ });
183
+ return 'success';
184
+ }
185
+ return 'unknown';
186
+ } catch (error) {
187
+ return 'failed';
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Checks test coverage for a package
193
+ */
194
+ // async checkTestCoverage(pkg: PackageInfo): Promise<number> {
195
+ // try {
196
+ // if (pkg.scripts.test) {
197
+ // // This would typically run tests and parse coverage reports
198
+ // // For now, return a mock value
199
+ // return Math.floor(Math.random() * 100);
200
+ // }
201
+ // return 0;
202
+ // } catch (error) {
203
+ // return 0;
204
+ // }
205
+ // }
206
+ async checkTestCoverage(pkg: PackageInfo): Promise<number> {
207
+ try {
208
+ // First, check for existing coverage reports
209
+ const coveragePaths = [
210
+ path.join(pkg.path, 'coverage', 'coverage-summary.json'),
211
+ path.join(pkg.path, 'coverage', 'lcov.info'),
212
+ path.join(pkg.path, 'coverage', 'clover.xml'),
213
+ path.join(pkg.path, 'coverage.json'),
214
+ ];
215
+
216
+ // Look for any coverage file that exists
217
+ for (const coveragePath of coveragePaths) {
218
+ if (fs.existsSync(coveragePath)) {
219
+ if (coveragePath.endsWith('coverage-summary.json')) {
220
+ try {
221
+ const coverage = JSON.parse(
222
+ fs.readFileSync(coveragePath, 'utf8')
223
+ );
224
+ return (
225
+ coverage.total?.lines?.pct ||
226
+ coverage.total?.statements?.pct ||
227
+ 0
228
+ );
229
+ } catch (error) {
230
+ console.warn(
231
+ `Error parsing coverage file for ${pkg.name}:`,
232
+ error
233
+ );
234
+ }
235
+ }
236
+ // If we find any coverage file but can't parse it, assume coverage exists
237
+ return 50; // Default coverage if files exist but can't parse
238
+ }
239
+ }
240
+
241
+ // If no coverage files found and package has test script
242
+ if (pkg.scripts.test) {
243
+ // Return a reasonable default based on whether tests are likely to have coverage
244
+ const hasCoverageSetup =
245
+ pkg.scripts.test.includes('--coverage') ||
246
+ pkg.scripts.test.includes('coverage') ||
247
+ pkg.devDependencies?.jest ||
248
+ pkg.devDependencies?.nyc ||
249
+ pkg.devDependencies?.['@types/jest'];
250
+
251
+ return hasCoverageSetup ? 30 : 0; // Reasonable defaults
252
+ }
253
+
254
+ return 0;
255
+ } catch (error) {
256
+ console.warn(`Error checking coverage for ${pkg.name}:`, error);
257
+ return 0;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Checks lint status for a package
263
+ */
264
+ async checkLintStatus(
265
+ pkg: PackageInfo
266
+ ): Promise<PackageHealth['lintStatus']> {
267
+ try {
268
+ if (pkg.scripts.lint) {
269
+ // Try to run lint command
270
+ execSync('npm run lint', {
271
+ cwd: pkg.path,
272
+ stdio: 'pipe',
273
+ timeout: 10000,
274
+ });
275
+ return 'pass';
276
+ }
277
+ return 'unknown';
278
+ } catch (error) {
279
+ return 'fail';
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Checks security audit for a package
285
+ */
286
+ async checkSecurityAudit(
287
+ pkg: PackageInfo
288
+ ): Promise<PackageHealth['securityAudit']> {
289
+ try {
290
+ // Run npm audit
291
+ const result = execSync('npm audit --json', {
292
+ cwd: pkg.path,
293
+ stdio: 'pipe',
294
+ timeout: 15000,
295
+ });
296
+
297
+ const audit = JSON.parse(result.toString());
298
+ return audit.metadata.vulnerabilities.total === 0 ? 'pass' : 'fail';
299
+ } catch (error) {
300
+ return 'unknown';
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Finds packages with outdated dependencies
306
+ */
307
+ private findOutdatedPackages(packages: PackageInfo[]): string[] {
308
+ const outdated: string[] = [];
309
+
310
+ for (const pkg of packages) {
311
+ const outdatedDeps = checkOutdatedDependencies(pkg);
312
+ if (outdatedDeps.length > 0) {
313
+ outdated.push(pkg.name);
314
+ }
315
+ }
316
+
317
+ return outdated;
318
+ }
319
+
320
+ /**
321
+ * Gets the last modified date of a package
322
+ */
323
+ private getLastModified(packagePath: string): Date {
324
+ try {
325
+ const stats = fs.statSync(packagePath);
326
+ return stats.mtime;
327
+ } catch (error) {
328
+ return new Date();
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Gets git information for a package
334
+ */
335
+ private async getGitInfo(
336
+ packagePath: string
337
+ ): Promise<PackageReport['gitInfo'] | undefined> {
338
+ try {
339
+ // Check if this is a git repository
340
+ const gitPath = path.join(packagePath, '.git');
341
+ if (!fs.existsSync(gitPath)) {
342
+ return undefined;
343
+ }
344
+
345
+ // Get last commit info
346
+ const lastCommit = execSync('git rev-parse HEAD', {
347
+ cwd: packagePath,
348
+ stdio: 'pipe',
349
+ })
350
+ .toString()
351
+ .trim();
352
+
353
+ const lastCommitDate = new Date(
354
+ execSync('git log -1 --format=%cd', {
355
+ cwd: packagePath,
356
+ stdio: 'pipe',
357
+ })
358
+ .toString()
359
+ .trim()
360
+ );
361
+
362
+ const author = execSync('git log -1 --format=%an', {
363
+ cwd: packagePath,
364
+ stdio: 'pipe',
365
+ })
366
+ .toString()
367
+ .trim();
368
+
369
+ const branch = execSync('git branch --show-current', {
370
+ cwd: packagePath,
371
+ stdio: 'pipe',
372
+ })
373
+ .toString()
374
+ .trim();
375
+
376
+ return {
377
+ lastCommit: lastCommit.substring(0, 7),
378
+ lastCommitDate,
379
+ author,
380
+ branch,
381
+ };
382
+ } catch (error) {
383
+ return undefined;
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Scans for specific file types in packages
389
+ */
390
+ scanForFileTypes(fileTypes: string[]): Record<string, string[]> {
391
+ const results: Record<string, string[]> = {};
392
+
393
+ for (const fileType of fileTypes) {
394
+ results[fileType] = [];
395
+ }
396
+
397
+ const packages = scanMonorepo(this.rootDir);
398
+
399
+ for (const pkg of packages) {
400
+ this.scanPackageForFiles(pkg.path, fileTypes, results);
401
+ }
402
+
403
+ return results;
404
+ }
405
+
406
+ /**
407
+ * Recursively scans a package directory for specific file types
408
+ */
409
+ private scanPackageForFiles(
410
+ packagePath: string,
411
+ fileTypes: string[],
412
+ results: Record<string, string[]>
413
+ ): void {
414
+ try {
415
+ const items = fs.readdirSync(packagePath, { withFileTypes: true });
416
+
417
+ for (const item of items) {
418
+ const fullPath = path.join(packagePath, item.name);
419
+
420
+ if (item.isDirectory()) {
421
+ // Skip certain directories
422
+ if (!['node_modules', 'dist', 'build', '.git'].includes(item.name)) {
423
+ this.scanPackageForFiles(fullPath, fileTypes, results);
424
+ }
425
+ } else {
426
+ // Check file extension
427
+ const ext = path.extname(item.name);
428
+ if (fileTypes.includes(ext)) {
429
+ results[ext].push(fullPath);
430
+ }
431
+ }
432
+ }
433
+ } catch (error) {
434
+ // Skip directories we can't read
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Gets cache value if not expired
440
+ */
441
+ private getFromCache(key: string): any {
442
+ const cached = this.cache.get(key);
443
+ if (cached && Date.now() - cached.timestamp < this.cacheExpiry) {
444
+ return cached.data;
445
+ }
446
+ return null;
447
+ }
448
+
449
+ /**
450
+ * Sets cache value with timestamp
451
+ */
452
+ private setCache(key: string, data: any): void {
453
+ this.cache.set(key, {
454
+ data,
455
+ timestamp: Date.now(),
456
+ });
457
+ }
458
+
459
+ /**
460
+ * Clears the cache
461
+ */
462
+ clearCache(): void {
463
+ this.cache.clear();
464
+ }
465
+
466
+ /**
467
+ * Exports scan results to various formats
468
+ */
469
+ exportResults(results: ScanResult, format: 'json' | 'csv' | 'html'): string {
470
+ switch (format) {
471
+ case 'json':
472
+ return JSON.stringify(results, null, 2);
473
+ case 'csv':
474
+ return this.exportToCSV(results);
475
+ case 'html':
476
+ return this.exportToHTML(results);
477
+ default:
478
+ throw new Error(`Unsupported format: ${format}`);
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Exports results to CSV format
484
+ */
485
+ private exportToCSV(results: ScanResult): string {
486
+ const headers = [
487
+ 'Package',
488
+ 'Type',
489
+ 'Version',
490
+ 'Dependencies',
491
+ 'Health Score',
492
+ ];
493
+ const rows = results.packages.map(pkg => [
494
+ pkg.name,
495
+ pkg.type,
496
+ pkg.version,
497
+ Object.keys(pkg.dependencies).length,
498
+ 'N/A', // Would need health calculation
499
+ ]);
500
+
501
+ return [headers, ...rows]
502
+ .map(row => row.map(cell => `"${cell}"`).join(','))
503
+ .join('\n');
504
+ }
505
+
506
+ /**
507
+ * Exports results to HTML format
508
+ */
509
+ private exportToHTML(results: ScanResult): string {
510
+ return `
511
+ <!DOCTYPE html>
512
+ <html>
513
+ <head>
514
+ <title>Monorepo Scan Report</title>
515
+ <style>
516
+ body { font-family: Arial, sans-serif; margin: 20px; }
517
+ table { border-collapse: collapse; width: 100%; }
518
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
519
+ th { background-color: #f2f2f2; }
520
+ </style>
521
+ </head>
522
+ <body>
523
+ <h1>Monorepo Scan Report</h1>
524
+ <p>Generated: ${results.scanTimestamp}</p>
525
+ <p>Duration: ${results.scanDuration}ms</p>
526
+
527
+ <h2>Summary</h2>
528
+ <ul>
529
+ <li>Total Packages: ${results.stats.totalPackages}</li>
530
+ <li>Applications: ${results.stats.apps}</li>
531
+ <li>Libraries: ${results.stats.libraries}</li>
532
+ <li>Tools: ${results.stats.tools}</li>
533
+ </ul>
534
+
535
+ <h2>Packages</h2>
536
+ <table>
537
+ <tr>
538
+ <th>Name</th>
539
+ <th>Type</th>
540
+ <th>Version</th>
541
+ <th>Dependencies</th>
542
+ </tr>
543
+ ${results.packages
544
+ .map(
545
+ pkg => `
546
+ <tr>
547
+ <td>${pkg.name}</td>
548
+ <td>${pkg.type}</td>
549
+ <td>${pkg.version}</td>
550
+ <td>${Object.keys(pkg.dependencies).length}</td>
551
+ </tr>
552
+ `
553
+ )
554
+ .join('')}
555
+ </table>
556
+ </body>
557
+ </html>
558
+ `;
559
+ }
560
+ }
561
+
562
+ // Export default instance
563
+ export const scanner = new MonorepoScanner();
564
+
565
+ // Export convenience functions
566
+ export async function quickScan(): Promise<ScanResult> {
567
+ return scanner.scan();
568
+ }
569
+
570
+ export async function generateReports(): Promise<PackageReport[]> {
571
+ return scanner.generatePackageReports();
572
+ }
573
+
574
+ export function scanForFiles(fileTypes: string[]): Record<string, string[]> {
575
+ return scanner.scanForFileTypes(fileTypes);
576
+ }
577
+
578
+ // Fix these function signatures - they should accept single PackageInfo objects
579
+ export async function funCheckBuildStatus(
580
+ pkg: PackageInfo
581
+ ): Promise<PackageHealth['buildStatus']> {
582
+ return scanner.checkBuildStatus(pkg);
583
+ }
584
+
585
+ export async function funCheckTestCoverage(pkg: PackageInfo): Promise<number> {
586
+ return scanner.checkTestCoverage(pkg);
587
+ }
588
+
589
+ export async function funCheckLintStatus(
590
+ pkg: PackageInfo
591
+ ): Promise<PackageHealth['lintStatus']> {
592
+ return scanner.checkLintStatus(pkg);
593
+ }
594
+
595
+ export async function funCheckSecurityAudit(
596
+ pkg: PackageInfo
597
+ ): Promise<PackageHealth['securityAudit']> {
598
+ return scanner.checkSecurityAudit(pkg);
599
+ }