@manojkmfsi/monodog 1.0.25 → 1.1.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/config/swagger-config.js +345 -0
- package/dist/config-loader.js +2 -2
- package/dist/constants/index.js +26 -0
- package/dist/constants/middleware.js +71 -0
- package/dist/constants/port.js +20 -0
- package/dist/constants/security.js +67 -0
- package/dist/middleware/dashboard-startup.js +15 -18
- package/dist/middleware/security.js +10 -9
- package/dist/middleware/server-startup.js +12 -11
- package/dist/middleware/swagger-middleware.js +54 -0
- package/dist/routes/health-routes.js +1 -1
- package/dist/routes/package-routes.js +1 -1
- package/dist/services/health-service.js +84 -64
- package/dist/services/package-service.js +23 -1
- package/monodog-config.example.json +2 -2
- package/monodog-config.json +5 -4
- package/monodog-dashboard/dist/assets/{index-746f6c13.js → index-45e19f29.js} +1 -1
- package/monodog-dashboard/dist/index.html +1 -1
- package/package.json +5 -2
- package/src/config/swagger-config.ts +344 -0
- package/src/config-loader.ts +2 -2
- package/src/constants/index.ts +13 -0
- package/src/constants/middleware.ts +83 -0
- package/src/constants/port.ts +20 -0
- package/src/constants/security.ts +78 -0
- package/src/middleware/dashboard-startup.ts +30 -18
- package/src/middleware/security.ts +18 -9
- package/src/middleware/server-startup.ts +22 -11
- package/src/middleware/swagger-middleware.ts +57 -0
- package/src/routes/health-routes.ts +1 -1
- package/src/routes/package-routes.ts +1 -1
- package/src/services/health-service.ts +103 -79
- package/src/services/package-service.ts +27 -1
- package/src/types/swagger-jsdoc.d.ts +15 -0
|
@@ -14,6 +14,7 @@ exports.buildApiUrl = buildApiUrl;
|
|
|
14
14
|
exports.buildDashboardUrl = buildDashboardUrl;
|
|
15
15
|
const helmet_1 = __importDefault(require("helmet"));
|
|
16
16
|
const cors_1 = __importDefault(require("cors"));
|
|
17
|
+
const constants_1 = require("../constants");
|
|
17
18
|
/**
|
|
18
19
|
* Create Helmet security middleware with Content Security Policy
|
|
19
20
|
*/
|
|
@@ -36,8 +37,8 @@ function createApiCorsMiddleware(dashboardUrl) {
|
|
|
36
37
|
const corsOptions = {
|
|
37
38
|
origin: dashboardUrl,
|
|
38
39
|
credentials: true,
|
|
39
|
-
methods: [
|
|
40
|
-
allowedHeaders: [
|
|
40
|
+
methods: [...constants_1.CORS_API_METHODS],
|
|
41
|
+
allowedHeaders: [...constants_1.CORS_ALLOWED_HEADERS],
|
|
41
42
|
};
|
|
42
43
|
return (0, cors_1.default)(corsOptions);
|
|
43
44
|
}
|
|
@@ -55,8 +56,8 @@ function createDashboardCorsMiddleware() {
|
|
|
55
56
|
*/
|
|
56
57
|
function createTimeoutMiddleware() {
|
|
57
58
|
return (req, res, next) => {
|
|
58
|
-
req.setTimeout(
|
|
59
|
-
res.setTimeout(
|
|
59
|
+
req.setTimeout(constants_1.REQUEST_TIMEOUT);
|
|
60
|
+
res.setTimeout(constants_1.RESPONSE_TIMEOUT);
|
|
60
61
|
next();
|
|
61
62
|
};
|
|
62
63
|
}
|
|
@@ -64,15 +65,15 @@ function createTimeoutMiddleware() {
|
|
|
64
65
|
* Build API URL based on config
|
|
65
66
|
*/
|
|
66
67
|
function buildApiUrl(host, port) {
|
|
67
|
-
const apiHost = host ===
|
|
68
|
-
return
|
|
68
|
+
const apiHost = host === constants_1.WILDCARD_ADDRESS ? constants_1.DEFAULT_LOCALHOST : host;
|
|
69
|
+
return `${constants_1.HTTP_PROTOCOL}${apiHost}:${port}`;
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
72
|
* Build dashboard URL based on config
|
|
72
73
|
*/
|
|
73
74
|
function buildDashboardUrl(config) {
|
|
74
|
-
const dashboardHost = config.dashboard.host ===
|
|
75
|
-
?
|
|
75
|
+
const dashboardHost = config.dashboard.host === constants_1.WILDCARD_ADDRESS
|
|
76
|
+
? constants_1.DEFAULT_LOCALHOST
|
|
76
77
|
: config.dashboard.host;
|
|
77
|
-
return
|
|
78
|
+
return `${constants_1.HTTP_PROTOCOL}${dashboardHost}:${config.dashboard.port}`;
|
|
78
79
|
}
|
|
@@ -13,20 +13,19 @@ const logger_1 = require("./logger");
|
|
|
13
13
|
const config_loader_1 = require("../config-loader");
|
|
14
14
|
const error_handler_1 = require("./error-handler");
|
|
15
15
|
const security_1 = require("./security");
|
|
16
|
+
const swagger_middleware_1 = require("./swagger-middleware");
|
|
16
17
|
const package_routes_1 = __importDefault(require("../routes/package-routes"));
|
|
17
18
|
const commit_routes_1 = __importDefault(require("../routes/commit-routes"));
|
|
18
19
|
const health_routes_1 = __importDefault(require("../routes/health-routes"));
|
|
19
20
|
const config_routes_1 = __importDefault(require("../routes/config-routes"));
|
|
20
|
-
|
|
21
|
-
const PORT_MIN = 1024;
|
|
22
|
-
const PORT_MAX = 65535;
|
|
21
|
+
const constants_1 = require("../constants");
|
|
23
22
|
/**
|
|
24
23
|
* Validate port number
|
|
25
24
|
*/
|
|
26
25
|
function validatePort(port) {
|
|
27
26
|
const portNum = typeof port === 'string' ? parseInt(port, 10) : port;
|
|
28
|
-
if (isNaN(portNum) || portNum < PORT_MIN || portNum > PORT_MAX) {
|
|
29
|
-
throw new Error(
|
|
27
|
+
if (isNaN(portNum) || portNum < constants_1.PORT_MIN || portNum > constants_1.PORT_MAX) {
|
|
28
|
+
throw new Error((0, constants_1.PORT_VALIDATION_ERROR_MESSAGE)(constants_1.PORT_MIN, constants_1.PORT_MAX));
|
|
30
29
|
}
|
|
31
30
|
return portNum;
|
|
32
31
|
}
|
|
@@ -45,9 +44,11 @@ function createApp(rootPath) {
|
|
|
45
44
|
app.use((0, security_1.createHelmetMiddleware)(apiUrl));
|
|
46
45
|
app.use((0, security_1.createApiCorsMiddleware)(dashboardUrl));
|
|
47
46
|
// Body parser
|
|
48
|
-
app.use((0, body_parser_1.json)({ limit:
|
|
47
|
+
app.use((0, body_parser_1.json)({ limit: constants_1.BODY_PARSER_LIMIT }));
|
|
49
48
|
// HTTP request logging with Morgan
|
|
50
49
|
app.use(logger_1.httpLogger);
|
|
50
|
+
// Setup Swagger documentation
|
|
51
|
+
(0, swagger_middleware_1.setupSwaggerDocs)(app);
|
|
51
52
|
// Routes
|
|
52
53
|
app.use('/api/packages', package_routes_1.default);
|
|
53
54
|
app.use('/api/commits/', commit_routes_1.default);
|
|
@@ -71,7 +72,7 @@ function startServer(rootPath) {
|
|
|
71
72
|
logger_1.AppLogger.info(`Analyzing monorepo at root: ${rootPath}`);
|
|
72
73
|
const app = createApp(rootPath);
|
|
73
74
|
const server = app.listen(validatedPort, host, () => {
|
|
74
|
-
console.log(
|
|
75
|
+
console.log((0, constants_1.SUCCESS_SERVER_START)(host, validatedPort));
|
|
75
76
|
logger_1.AppLogger.info('API endpoints available:', {
|
|
76
77
|
endpoints: [
|
|
77
78
|
'GET /api/health',
|
|
@@ -88,11 +89,11 @@ function startServer(rootPath) {
|
|
|
88
89
|
});
|
|
89
90
|
server.on('error', (err) => {
|
|
90
91
|
if (err.code === 'EADDRINUSE') {
|
|
91
|
-
logger_1.AppLogger.error(
|
|
92
|
+
logger_1.AppLogger.error((0, constants_1.ERROR_PORT_IN_USE)(validatedPort), err);
|
|
92
93
|
process.exit(1);
|
|
93
94
|
}
|
|
94
95
|
else if (err.code === 'EACCES') {
|
|
95
|
-
logger_1.AppLogger.error(
|
|
96
|
+
logger_1.AppLogger.error((0, constants_1.ERROR_PERMISSION_DENIED)(validatedPort), err);
|
|
96
97
|
process.exit(1);
|
|
97
98
|
}
|
|
98
99
|
else {
|
|
@@ -102,9 +103,9 @@ function startServer(rootPath) {
|
|
|
102
103
|
});
|
|
103
104
|
// Graceful shutdown
|
|
104
105
|
process.on('SIGTERM', () => {
|
|
105
|
-
logger_1.AppLogger.info(
|
|
106
|
+
logger_1.AppLogger.info(constants_1.MESSAGE_GRACEFUL_SHUTDOWN);
|
|
106
107
|
server.close(() => {
|
|
107
|
-
logger_1.AppLogger.info(
|
|
108
|
+
logger_1.AppLogger.info(constants_1.MESSAGE_SERVER_CLOSED);
|
|
108
109
|
process.exit(0);
|
|
109
110
|
});
|
|
110
111
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Swagger Documentation Middleware
|
|
4
|
+
* Sets up Swagger UI for API documentation
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.setupSwaggerDocs = setupSwaggerDocs;
|
|
11
|
+
const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
|
|
12
|
+
const swagger_jsdoc_1 = __importDefault(require("swagger-jsdoc"));
|
|
13
|
+
const swagger_config_1 = require("../config/swagger-config");
|
|
14
|
+
/**
|
|
15
|
+
* Setup Swagger documentation endpoint
|
|
16
|
+
* @param app Express application instance
|
|
17
|
+
*/
|
|
18
|
+
function setupSwaggerDocs(app) {
|
|
19
|
+
try {
|
|
20
|
+
const specs = (0, swagger_jsdoc_1.default)(swagger_config_1.swaggerOptions);
|
|
21
|
+
// Serve raw Swagger JSON FIRST (before the middleware catches all /api-docs paths)
|
|
22
|
+
app.get('/api-docs/swagger.json', (_req, res) => {
|
|
23
|
+
res.setHeader('Content-Type', 'application/json');
|
|
24
|
+
res.send(specs);
|
|
25
|
+
});
|
|
26
|
+
// Serve Swagger UI at /api-docs
|
|
27
|
+
app.use('/api-docs', swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(specs, {
|
|
28
|
+
swaggerOptions: {
|
|
29
|
+
url: '/api-docs/swagger.json',
|
|
30
|
+
persistAuthorization: true,
|
|
31
|
+
displayOperationId: true,
|
|
32
|
+
filter: true,
|
|
33
|
+
showExtensions: true,
|
|
34
|
+
},
|
|
35
|
+
customCss: `
|
|
36
|
+
.swagger-ui .topbar {
|
|
37
|
+
background-color: #2c3e50;
|
|
38
|
+
}
|
|
39
|
+
.swagger-ui .info .title {
|
|
40
|
+
color: #2c3e50;
|
|
41
|
+
font-weight: bold;
|
|
42
|
+
}
|
|
43
|
+
.swagger-ui .btn-box .btn {
|
|
44
|
+
background-color: #2c3e50;
|
|
45
|
+
}
|
|
46
|
+
`,
|
|
47
|
+
customCssUrl: 'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
|
|
48
|
+
}));
|
|
49
|
+
console.log('Swagger documentation available at /api-docs');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error('Failed to setup Swagger documentation:', error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -8,7 +8,7 @@ const health_controller_1 = require("../controllers/health-controller");
|
|
|
8
8
|
const healthRouter = express_1.default.Router();
|
|
9
9
|
healthRouter
|
|
10
10
|
.route('/refresh')
|
|
11
|
-
.
|
|
11
|
+
.post(health_controller_1.refreshHealth);
|
|
12
12
|
healthRouter
|
|
13
13
|
.route('/packages')
|
|
14
14
|
.get(health_controller_1.getPackagesHealth);
|
|
@@ -8,7 +8,7 @@ const package_controller_1 = require("../controllers/package-controller");
|
|
|
8
8
|
const packageRouter = express_1.default.Router();
|
|
9
9
|
packageRouter
|
|
10
10
|
.route('/refresh')
|
|
11
|
-
.
|
|
11
|
+
.post(package_controller_1.refreshPackages);
|
|
12
12
|
packageRouter
|
|
13
13
|
.route('/update-config')
|
|
14
14
|
.put(package_controller_1.updatePackageConfig);
|
|
@@ -4,6 +4,8 @@ exports.healthRefreshService = exports.getHealthSummaryService = void 0;
|
|
|
4
4
|
const utilities_1 = require("../utils/utilities");
|
|
5
5
|
const monorepo_scanner_1 = require("../utils/monorepo-scanner");
|
|
6
6
|
const repositories_1 = require("../repositories");
|
|
7
|
+
// Track in-flight health refresh requests to prevent duplicates
|
|
8
|
+
let inFlightHealthRefresh = null;
|
|
7
9
|
const getHealthSummaryService = async () => {
|
|
8
10
|
const packageHealthData = await repositories_1.PackageHealthRepository.findAll();
|
|
9
11
|
console.log('packageHealthData -->', packageHealthData.length);
|
|
@@ -42,73 +44,91 @@ const getHealthSummaryService = async () => {
|
|
|
42
44
|
};
|
|
43
45
|
exports.getHealthSummaryService = getHealthSummaryService;
|
|
44
46
|
const healthRefreshService = async (rootDir) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// If a health refresh is already in progress, return the in-flight promise
|
|
48
|
+
if (inFlightHealthRefresh) {
|
|
49
|
+
console.log('Health refresh already in progress, returning cached promise');
|
|
50
|
+
return inFlightHealthRefresh;
|
|
51
|
+
}
|
|
52
|
+
// Create and store the health refresh promise
|
|
53
|
+
inFlightHealthRefresh = (async () => {
|
|
48
54
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
55
|
+
const packages = (0, utilities_1.scanMonorepo)(rootDir);
|
|
56
|
+
console.log('packages -->', packages.length);
|
|
57
|
+
const healthMetrics = await Promise.all(packages.map(async (pkg) => {
|
|
58
|
+
try {
|
|
59
|
+
// Await each health check function since they return promises
|
|
60
|
+
const buildStatus = await (0, monorepo_scanner_1.funCheckBuildStatus)(pkg);
|
|
61
|
+
const testCoverage = 0; //await funCheckTestCoverage(pkg); // skip test coverage for now
|
|
62
|
+
const lintStatus = await (0, monorepo_scanner_1.funCheckLintStatus)(pkg);
|
|
63
|
+
const securityAudit = await (0, monorepo_scanner_1.funCheckSecurityAudit)(pkg);
|
|
64
|
+
// Calculate overall health score
|
|
65
|
+
const overallScore = (0, utilities_1.calculatePackageHealth)(buildStatus, testCoverage, lintStatus, securityAudit);
|
|
66
|
+
const health = {
|
|
67
|
+
buildStatus: buildStatus,
|
|
68
|
+
testCoverage: testCoverage,
|
|
69
|
+
lintStatus: lintStatus,
|
|
70
|
+
securityAudit: securityAudit,
|
|
71
|
+
overallScore: overallScore.overallScore,
|
|
72
|
+
};
|
|
73
|
+
const packageStatus = health.overallScore >= 80
|
|
74
|
+
? 'healthy'
|
|
75
|
+
: health.overallScore >= 60 && health.overallScore < 80
|
|
76
|
+
? 'warning'
|
|
77
|
+
: 'error';
|
|
78
|
+
console.log(pkg.name, '-->', health, packageStatus);
|
|
79
|
+
await repositories_1.PackageHealthRepository.upsert({
|
|
80
|
+
packageName: pkg.name,
|
|
81
|
+
packageOverallScore: overallScore.overallScore,
|
|
82
|
+
packageBuildStatus: buildStatus,
|
|
83
|
+
packageTestCoverage: testCoverage,
|
|
84
|
+
packageLintStatus: lintStatus,
|
|
85
|
+
packageSecurity: securityAudit,
|
|
86
|
+
packageDependencies: '',
|
|
87
|
+
});
|
|
88
|
+
// update related package status as well
|
|
89
|
+
await repositories_1.PackageRepository.updateStatus(pkg.name, packageStatus);
|
|
90
|
+
return {
|
|
91
|
+
packageName: pkg.name,
|
|
92
|
+
health,
|
|
93
|
+
isHealthy: health.overallScore >= 80,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
packageName: pkg.name,
|
|
99
|
+
health: {
|
|
100
|
+
"buildStatus": "",
|
|
101
|
+
"testCoverage": 0,
|
|
102
|
+
"lintStatus": "",
|
|
103
|
+
"securityAudit": "",
|
|
104
|
+
"overallScore": 0
|
|
105
|
+
},
|
|
106
|
+
isHealthy: false,
|
|
107
|
+
error: 'Failed to fetch health metrics1',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}));
|
|
111
|
+
const result = {
|
|
112
|
+
packages: healthMetrics.filter(h => !h.error),
|
|
113
|
+
summary: {
|
|
114
|
+
total: packages.length,
|
|
115
|
+
healthy: healthMetrics.filter(h => h.isHealthy).length,
|
|
116
|
+
unhealthy: healthMetrics.filter(h => !h.isHealthy).length,
|
|
117
|
+
averageScore: healthMetrics.filter(h => h.health).length > 0
|
|
118
|
+
? healthMetrics
|
|
119
|
+
.filter(h => h.health)
|
|
120
|
+
.reduce((sum, h) => sum + h.health.overallScore, 0) /
|
|
121
|
+
healthMetrics.filter(h => h.health).length
|
|
122
|
+
: 0,
|
|
95
123
|
},
|
|
96
|
-
isHealthy: false,
|
|
97
|
-
error: 'Failed to fetch health metrics1',
|
|
98
124
|
};
|
|
125
|
+
return result;
|
|
99
126
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
unhealthy: healthMetrics.filter(h => !h.isHealthy).length,
|
|
107
|
-
averageScore: healthMetrics
|
|
108
|
-
.filter(h => h.health)
|
|
109
|
-
.reduce((sum, h) => sum + h.health.overallScore, 0) /
|
|
110
|
-
healthMetrics.filter(h => h.health).length,
|
|
111
|
-
},
|
|
112
|
-
};
|
|
127
|
+
finally {
|
|
128
|
+
// Clear the in-flight promise after completion
|
|
129
|
+
inFlightHealthRefresh = null;
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
return inFlightHealthRefresh;
|
|
113
133
|
};
|
|
114
134
|
exports.healthRefreshService = healthRefreshService;
|
|
@@ -128,7 +128,29 @@ const refreshPackagesService = async (rootPath) => {
|
|
|
128
128
|
for (const pkg of packages) {
|
|
129
129
|
await storePackage(pkg);
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
// Return transformed packages like getPackagesService
|
|
132
|
+
const dbPackages = await repositories_1.PackageRepository.findAll();
|
|
133
|
+
const transformedPackages = dbPackages.map((pkg) => {
|
|
134
|
+
const transformedPkg = { ...pkg };
|
|
135
|
+
transformedPkg.maintainers = pkg.maintainers
|
|
136
|
+
? JSON.parse(pkg.maintainers)
|
|
137
|
+
: [];
|
|
138
|
+
transformedPkg.scripts = pkg.scripts ? JSON.parse(pkg.scripts) : {};
|
|
139
|
+
transformedPkg.repository = pkg.repository
|
|
140
|
+
? JSON.parse(pkg.repository)
|
|
141
|
+
: {};
|
|
142
|
+
transformedPkg.dependencies = pkg.dependencies
|
|
143
|
+
? JSON.parse(pkg.dependencies)
|
|
144
|
+
: [];
|
|
145
|
+
transformedPkg.devDependencies = pkg.devDependencies
|
|
146
|
+
? JSON.parse(pkg.devDependencies)
|
|
147
|
+
: [];
|
|
148
|
+
transformedPkg.peerDependencies = pkg.peerDependencies
|
|
149
|
+
? JSON.parse(pkg.peerDependencies)
|
|
150
|
+
: [];
|
|
151
|
+
return transformedPkg;
|
|
152
|
+
});
|
|
153
|
+
return transformedPackages;
|
|
132
154
|
};
|
|
133
155
|
exports.refreshPackagesService = refreshPackagesService;
|
|
134
156
|
const getPackageDetailService = async (name) => {
|
package/monodog-config.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"workspaces": [
|
|
2
|
+
"workspaces": [
|
|
3
|
+
],
|
|
3
4
|
"database": {
|
|
4
5
|
"path": "file:./monodog.db"
|
|
5
6
|
},
|
|
6
7
|
"dashboard": {
|
|
7
|
-
"host": "
|
|
8
|
+
"host": "localhost",
|
|
8
9
|
"port": "3010"
|
|
9
10
|
},
|
|
10
11
|
"server": {
|
|
11
|
-
"host": "
|
|
12
|
+
"host": "localhost",
|
|
12
13
|
"port": 8999
|
|
13
14
|
}
|
|
14
|
-
}
|
|
15
|
+
}
|