@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,483 @@
1
+ // import { Package } from '@prisma/client';
2
+ import * as fs from 'fs';
3
+ import path from 'path';
4
+ import { appConfig } from '../config-loader';
5
+
6
+ export interface PackageInfo {
7
+ name: string;
8
+ version: string;
9
+ type: string; //'app' | 'lib' | 'tool';
10
+ path: string;
11
+ dependencies: Record<string, string>;
12
+ devDependencies: Record<string, string>;
13
+ peerDependencies: Record<string, string>;
14
+ scripts: Record<string, string>;
15
+ maintainers: string[];
16
+ description?: string;
17
+ license?: string;
18
+ repository?: Record<string, string>;
19
+ }
20
+
21
+ export interface DependencyInfo {
22
+ // name: string;
23
+ // currentVersion: string;
24
+ // latestVersion?: string;
25
+ // status: 'up-to-date' | 'outdated' | 'major-update' | 'unknown';
26
+ // type: 'production' | 'development';
27
+ name: string;
28
+ version: string;
29
+ type: 'dependency' | 'devDependency' | 'peerDependency';
30
+ latest?: string;
31
+ status?: 'up-to-date' | 'outdated' | 'major-update' | 'unknown';
32
+ outdated?: boolean;
33
+ }
34
+
35
+ export interface PackageHealth {
36
+ buildStatus: 'success' | 'failed' | 'running' | 'unknown';
37
+ testCoverage: number;
38
+ lintStatus: 'pass' | 'fail' | 'unknown';
39
+ securityAudit: 'pass' | 'fail' | 'unknown';
40
+ overallScore: number;
41
+ }
42
+
43
+ export interface MonorepoStats {
44
+ totalPackages: number;
45
+ apps: number;
46
+ libraries: number;
47
+ tools: number;
48
+ healthyPackages: number;
49
+ warningPackages: number;
50
+ errorPackages: number;
51
+ outdatedDependencies: number;
52
+ totalDependencies: number;
53
+ }
54
+
55
+ /**
56
+ * Resolves simple workspace globs (like 'packages/*', 'apps/*') into actual package directory paths.
57
+ * Note: This implementation only handles the 'folder/*' pattern and is not a full glob resolver.
58
+ */
59
+ export function resolveWorkspaceGlobs(rootDir: string, globs: string[]): string[] {
60
+ const resolvedPaths: string[] = [];
61
+
62
+ for (const glob of globs) {
63
+ if (glob.endsWith('/*')) {
64
+ const baseDirName = glob.slice(0, -2); // e.g., 'packages'
65
+ const baseDirPath = path.join(rootDir, baseDirName);
66
+
67
+ if (fs.existsSync(baseDirPath) && fs.statSync(baseDirPath).isDirectory()) {
68
+ const subDirs = fs.readdirSync(baseDirPath, { withFileTypes: true })
69
+ .filter(dirent => dirent.isDirectory())
70
+ .map(dirent => path.join(baseDirName, dirent.name)); // e.g., 'packages/my-utils'
71
+
72
+ resolvedPaths.push(...subDirs);
73
+ }
74
+ } else {
75
+ // Handle non-glob paths (e.g., 'packages/my-package') if it's explicitly listed
76
+ const directPath = path.join(rootDir, glob);
77
+ if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
78
+ resolvedPaths.push(glob);
79
+ }
80
+ }
81
+ }
82
+ return resolvedPaths;
83
+ }
84
+
85
+ /**
86
+ * Reads the root package.json and extracts the 'workspaces' field (array of globs).
87
+ */
88
+ export function getWorkspacesFromRoot(rootDir: string): string[] | undefined {
89
+ const packageJsonPath = path.join(rootDir, 'package.json');
90
+ if (!fs.existsSync(packageJsonPath)) {
91
+ console.warn(`\n⚠️ Warning: No package.json found at root directory: ${rootDir}`);
92
+ return undefined;
93
+ }
94
+
95
+ try {
96
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
97
+
98
+ // Handle both standard array and object format (used by yarn/pnpm)
99
+ if (Array.isArray(packageJson.workspaces)) {
100
+ return packageJson.workspaces;
101
+ } else if (packageJson.workspaces && Array.isArray(packageJson.workspaces.packages)) {
102
+ return packageJson.workspaces.packages;
103
+ }
104
+ } catch (e) {
105
+ console.error(`\n❌ Error parsing package.json at ${packageJsonPath}. Skipping workspace detection.`);
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ /**
111
+ * Scans the monorepo and returns information about all packages
112
+ */
113
+ function scanMonorepo(rootDir: string): PackageInfo[] {
114
+ const packages: PackageInfo[] = [];
115
+ console.log('rootDir:', rootDir);
116
+ const workspacesGlobs = appConfig.workspaces;
117
+ // Use provided workspaces globs if given, otherwise attempt to detect from root package.json
118
+ const detectedWorkspacesGlobs = workspacesGlobs.length > 0 ? workspacesGlobs : getWorkspacesFromRoot(rootDir);
119
+ if (detectedWorkspacesGlobs && detectedWorkspacesGlobs.length > 0) {
120
+ if (workspacesGlobs.length) {
121
+ console.log(`\n✅ Using provided workspaces globs: ${detectedWorkspacesGlobs.join(', ')}`);
122
+ } else {
123
+ console.log(`\n✅ Detected Monorepo Workspaces Globs: ${detectedWorkspacesGlobs.join(', ')}`);
124
+ }
125
+
126
+ // 1. Resolve the globs into concrete package directory paths
127
+ const resolvedPackagePaths = resolveWorkspaceGlobs(rootDir, detectedWorkspacesGlobs);
128
+
129
+ console.log(`[DEBUG] Resolved package directories (Total ${resolvedPackagePaths.length}):`);
130
+ console.warn(resolvedPackagePaths.length < workspacesGlobs.length ? 'Some workspaces globs provided are invalid.' : '');
131
+
132
+ // 2. Integration of the requested loop structure for package scanning
133
+ for (const workspacePath of resolvedPackagePaths) {
134
+ const fullPackagePath = path.join(rootDir, workspacePath);
135
+ // The package name would be read from the package.json inside this path
136
+ const packageName = path.basename(fullPackagePath);
137
+
138
+ console.log(`- Scanning path: ${workspacePath} (Package: ${packageName})`);
139
+
140
+ const packageInfo = parsePackageInfo(fullPackagePath, packageName);
141
+ if (packageInfo) {
142
+ packages.push(packageInfo);
143
+ }
144
+ }
145
+ } else {
146
+ console.warn('\n⚠️ No workspace globs provided or detected. Returning empty package list.');
147
+ }
148
+
149
+ return packages;
150
+ }
151
+
152
+ /*** Parses package.json and determines package type */
153
+ export function parsePackageInfo(
154
+ packagePath: string,
155
+ packageName: string,
156
+ forcedType?: 'app' | 'lib' | 'tool'
157
+ ): PackageInfo | null {
158
+ const packageJsonPath = path.join(packagePath, 'package.json');
159
+
160
+ if (!fs.existsSync(packageJsonPath)) {
161
+ return null;
162
+ }
163
+
164
+ try {
165
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
166
+
167
+ // Determine package type
168
+ let packageType: 'app' | 'lib' | 'tool' = 'lib';
169
+ if (forcedType) {
170
+ packageType = forcedType;
171
+ } else if (packageJson.scripts && packageJson.scripts.start) {
172
+ packageType = 'app';
173
+ } else if (packageJson.keywords && packageJson.keywords.includes('tool')) {
174
+ packageType = 'tool';
175
+ }
176
+
177
+ return {
178
+ name: packageJson.name || packageName,
179
+ version: packageJson.version || '0.0.0',
180
+ type: packageType,
181
+ path: packagePath,
182
+ dependencies: packageJson.dependencies || {},
183
+ devDependencies: packageJson.devDependencies || {},
184
+ peerDependencies: packageJson.peerDependencies || {},
185
+ scripts: packageJson.scripts || {},
186
+ maintainers: packageJson.maintainers || [],
187
+ description: packageJson.description,
188
+ license: packageJson.license,
189
+ repository: packageJson.repository || {},
190
+ };
191
+ } catch (error) {
192
+ console.error(`Error parsing package.json for ${packageName}:`, error);
193
+ return null;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Analyzes dependencies and determines their status
199
+ */
200
+ // function analyzeDependencies(
201
+ // dependencies: Record<string, string>,
202
+ // type: 'production' | 'development' = 'production'
203
+ // ): DependencyInfo[] {
204
+ // return Object.entries(dependencies).map(([name, version]) => ({
205
+ // name,
206
+ // currentVersion: version,
207
+ // status: 'unknown', // Would be determined by npm registry check
208
+ // type,
209
+ // }));
210
+ // }
211
+
212
+ /**
213
+ * Calculates package health score based on various metrics
214
+ */
215
+ function calculatePackageHealth(
216
+ buildStatus: PackageHealth['buildStatus'],
217
+ testCoverage: number,
218
+ lintStatus: PackageHealth['lintStatus'],
219
+ securityAudit: PackageHealth['securityAudit']
220
+ ): PackageHealth {
221
+ let score = 0;
222
+
223
+ // Build status (30 points)
224
+ switch (buildStatus) {
225
+ case 'success':
226
+ score += 30;
227
+ break;
228
+ case 'running':
229
+ score += 15;
230
+ break;
231
+ case 'failed':
232
+ score += 0;
233
+ break;
234
+ default:
235
+ score += 10;
236
+ }
237
+
238
+ // Test coverage (25 points)
239
+ score += Math.min(25, (testCoverage / 100) * 25);
240
+
241
+ // Lint status (25 points)
242
+ switch (lintStatus) {
243
+ case 'pass':
244
+ score += 25;
245
+ break;
246
+ case 'fail':
247
+ score += 0;
248
+ break;
249
+ default:
250
+ score += 10;
251
+ }
252
+
253
+ // Security audit (20 points)
254
+ switch (securityAudit) {
255
+ case 'pass':
256
+ score += 20;
257
+ break;
258
+ case 'fail':
259
+ score += 0;
260
+ break;
261
+ default:
262
+ score += 10;
263
+ }
264
+
265
+ return {
266
+ buildStatus,
267
+ testCoverage,
268
+ lintStatus,
269
+ securityAudit,
270
+ overallScore: Math.round(score),
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Generates comprehensive monorepo statistics
276
+ */
277
+ function generateMonorepoStats(packages: PackageInfo[]): MonorepoStats {
278
+ const stats: MonorepoStats = {
279
+ totalPackages: packages.length,
280
+ apps: packages.filter(p => p.type === 'app').length,
281
+ libraries: packages.filter(p => p.type === 'lib').length,
282
+ tools: packages.filter(p => p.type === 'tool').length,
283
+ healthyPackages: 0,
284
+ warningPackages: 0,
285
+ errorPackages: 0,
286
+ outdatedDependencies: 0,
287
+ totalDependencies: 0,
288
+ };
289
+
290
+ // Calculate dependency counts
291
+ packages.forEach(pkg => {
292
+ stats.totalDependencies += Object.keys(pkg.dependencies).length;
293
+ stats.totalDependencies += Object.keys(pkg.devDependencies).length;
294
+ stats.totalDependencies += Object.keys(pkg.peerDependencies ?? {}).length;
295
+ });
296
+
297
+ return stats;
298
+ }
299
+
300
+ /**
301
+ * Finds circular dependencies in the monorepo
302
+ */
303
+ function findCircularDependencies(packages: PackageInfo[]): string[][] {
304
+ const graph = new Map<string, string[]>();
305
+ const visited = new Set<string>();
306
+ const recursionStack = new Set<string>();
307
+ const circularDeps: string[][] = [];
308
+
309
+ // Build dependency graph
310
+ packages.forEach(pkg => {
311
+ graph.set(pkg.name, Object.keys(pkg.dependencies));
312
+ });
313
+
314
+ function dfs(node: string, path: string[]): void {
315
+ if (recursionStack.has(node)) {
316
+ const cycleStart = path.indexOf(node);
317
+ circularDeps.push(path.slice(cycleStart));
318
+ return;
319
+ }
320
+
321
+ if (visited.has(node)) {
322
+ return;
323
+ }
324
+
325
+ visited.add(node);
326
+ recursionStack.add(node);
327
+ path.push(node);
328
+
329
+ const dependencies = graph.get(node) || [];
330
+ for (const dep of dependencies) {
331
+ if (graph.has(dep)) {
332
+ dfs(dep, [...path]);
333
+ }
334
+ }
335
+
336
+ recursionStack.delete(node);
337
+ }
338
+
339
+ for (const node of graph.keys()) {
340
+ if (!visited.has(node)) {
341
+ dfs(node, []);
342
+ }
343
+ }
344
+
345
+ return circularDeps;
346
+ }
347
+
348
+ /**
349
+ * Generates a dependency graph for visualization
350
+ */
351
+ function generateDependencyGraph(packages: PackageInfo[]) {
352
+ const nodes = packages.map(pkg => ({
353
+ // id: pkg.name,
354
+ label: pkg.name,
355
+ type: pkg.type,
356
+ version: pkg.version,
357
+ dependencies: Object.keys(pkg.dependencies).length,
358
+ }));
359
+
360
+ const edges: Array<{ from: string; to: string; type: string }> = [];
361
+
362
+ packages.forEach(pkg => {
363
+ Object.keys(pkg.dependencies).forEach(depName => {
364
+ // Only include internal dependencies
365
+ if (packages.some(p => p.name === depName)) {
366
+ edges.push({
367
+ from: pkg.name,
368
+ to: depName,
369
+ type: 'internal',
370
+ });
371
+ }
372
+ });
373
+ });
374
+
375
+ return { nodes, edges };
376
+ }
377
+
378
+ /**
379
+ * Checks if a package has outdated dependencies
380
+ */
381
+ function checkOutdatedDependencies(packageInfo: PackageInfo): DependencyInfo[] {
382
+ const outdated: DependencyInfo[] = [];
383
+
384
+ // This would typically involve checking against npm registry
385
+ // For now, we'll simulate with some basic checks
386
+ Object.entries(packageInfo.dependencies).forEach(([name, version]) => {
387
+ if (version.startsWith('^') || version.startsWith('~')) {
388
+ // Could be outdated, would need registry check
389
+ outdated.push({
390
+ name,
391
+ version: version,
392
+ status: 'unknown',
393
+ type: 'dependency',
394
+ });
395
+ }
396
+ });
397
+
398
+ return outdated;
399
+ }
400
+
401
+ /**
402
+ * Formats version numbers for comparison
403
+ */
404
+ // function parseVersion(version: string): number[] {
405
+ // return version
406
+ // .replace(/^[^0-9]*/, '')
407
+ // .split('.')
408
+ // .map(Number);
409
+ // }
410
+
411
+ /**
412
+ * Compares two version strings
413
+ */
414
+ // function compareVersions(v1: string, v2: string): number {
415
+ // const parsed1 = parseVersion(v1);
416
+ // const parsed2 = parseVersion(v2);
417
+
418
+ // for (let i = 0; i < Math.max(parsed1.length, parsed2.length); i++) {
419
+ // const num1 = parsed1[i] || 0;
420
+ // const num2 = parsed2[i] || 0;
421
+
422
+ // if (num1 > num2) return 1;
423
+ // if (num1 < num2) return -1;
424
+ // }
425
+
426
+ // return 0;
427
+ // }
428
+
429
+ /**
430
+ * Gets package size information
431
+ */
432
+ function getPackageSize(packagePath: string): {
433
+ size: number;
434
+ files: number;
435
+ } {
436
+ try {
437
+ let totalSize = 0;
438
+ let fileCount = 0;
439
+
440
+ const calculateSize = (dirPath: string): void => {
441
+ const items = fs.readdirSync(dirPath, { withFileTypes: true });
442
+
443
+ for (const item of items) {
444
+ const fullPath = path.join(dirPath, item.name);
445
+
446
+ if (item.isDirectory()) {
447
+ // Skip node_modules and other build artifacts
448
+ if (!['node_modules', 'dist', 'build', '.git'].includes(item.name)) {
449
+ calculateSize(fullPath);
450
+ }
451
+ } else {
452
+ try {
453
+ const stats = fs.statSync(fullPath);
454
+ totalSize += stats.size;
455
+ fileCount++;
456
+ } catch (error) {
457
+ // Skip files we can't read
458
+ }
459
+ }
460
+ }
461
+ };
462
+
463
+ calculateSize(packagePath);
464
+
465
+ return {
466
+ size: totalSize,
467
+ files: fileCount,
468
+ };
469
+ } catch (error) {
470
+ return { size: 0, files: 0 };
471
+ }
472
+ }
473
+
474
+ export {
475
+ scanMonorepo,
476
+ generateMonorepoStats,
477
+ findCircularDependencies,
478
+ generateDependencyGraph,
479
+ checkOutdatedDependencies,
480
+ getPackageSize,
481
+ // analyzeDependencies,
482
+ calculatePackageHealth,
483
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "allowJs": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "__tests__", "dist"]
16
+ }