tlc-claude-code 1.4.8 → 1.4.9

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 (169) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +229 -14
  3. package/server/lib/compliance/control-mapper.js +401 -0
  4. package/server/lib/compliance/control-mapper.test.js +117 -0
  5. package/server/lib/compliance/evidence-linker.js +296 -0
  6. package/server/lib/compliance/evidence-linker.test.js +121 -0
  7. package/server/lib/compliance/gdpr-checklist.js +416 -0
  8. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  9. package/server/lib/compliance/hipaa-checklist.js +277 -0
  10. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  11. package/server/lib/compliance/iso27001-checklist.js +287 -0
  12. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  13. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  14. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  15. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  16. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  17. package/server/lib/compliance/trust-centre.js +187 -0
  18. package/server/lib/compliance/trust-centre.test.js +93 -0
  19. package/server/lib/dashboard/api-server.js +155 -0
  20. package/server/lib/dashboard/api-server.test.js +155 -0
  21. package/server/lib/dashboard/health-api.js +199 -0
  22. package/server/lib/dashboard/health-api.test.js +122 -0
  23. package/server/lib/dashboard/notes-api.js +234 -0
  24. package/server/lib/dashboard/notes-api.test.js +134 -0
  25. package/server/lib/dashboard/router-api.js +176 -0
  26. package/server/lib/dashboard/router-api.test.js +132 -0
  27. package/server/lib/dashboard/tasks-api.js +289 -0
  28. package/server/lib/dashboard/tasks-api.test.js +161 -0
  29. package/server/lib/dashboard/tlc-introspection.js +197 -0
  30. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  31. package/server/lib/dashboard/version-api.js +222 -0
  32. package/server/lib/dashboard/version-api.test.js +112 -0
  33. package/server/lib/dashboard/websocket-server.js +104 -0
  34. package/server/lib/dashboard/websocket-server.test.js +118 -0
  35. package/server/lib/deploy/branch-classifier.js +163 -0
  36. package/server/lib/deploy/branch-classifier.test.js +164 -0
  37. package/server/lib/deploy/deployment-approval.js +299 -0
  38. package/server/lib/deploy/deployment-approval.test.js +296 -0
  39. package/server/lib/deploy/deployment-audit.js +374 -0
  40. package/server/lib/deploy/deployment-audit.test.js +307 -0
  41. package/server/lib/deploy/deployment-executor.js +335 -0
  42. package/server/lib/deploy/deployment-executor.test.js +329 -0
  43. package/server/lib/deploy/deployment-rules.js +163 -0
  44. package/server/lib/deploy/deployment-rules.test.js +188 -0
  45. package/server/lib/deploy/rollback-manager.js +379 -0
  46. package/server/lib/deploy/rollback-manager.test.js +321 -0
  47. package/server/lib/deploy/security-gates.js +236 -0
  48. package/server/lib/deploy/security-gates.test.js +222 -0
  49. package/server/lib/k8s/gitops-config.js +188 -0
  50. package/server/lib/k8s/gitops-config.test.js +59 -0
  51. package/server/lib/k8s/helm-generator.js +196 -0
  52. package/server/lib/k8s/helm-generator.test.js +59 -0
  53. package/server/lib/k8s/kustomize-generator.js +176 -0
  54. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  55. package/server/lib/k8s/network-policy.js +114 -0
  56. package/server/lib/k8s/network-policy.test.js +53 -0
  57. package/server/lib/k8s/pod-security.js +114 -0
  58. package/server/lib/k8s/pod-security.test.js +55 -0
  59. package/server/lib/k8s/rbac-generator.js +132 -0
  60. package/server/lib/k8s/rbac-generator.test.js +57 -0
  61. package/server/lib/k8s/resource-manager.js +172 -0
  62. package/server/lib/k8s/resource-manager.test.js +60 -0
  63. package/server/lib/k8s/secrets-encryption.js +168 -0
  64. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  65. package/server/lib/monitoring/alert-manager.js +238 -0
  66. package/server/lib/monitoring/alert-manager.test.js +106 -0
  67. package/server/lib/monitoring/health-check.js +226 -0
  68. package/server/lib/monitoring/health-check.test.js +176 -0
  69. package/server/lib/monitoring/incident-manager.js +230 -0
  70. package/server/lib/monitoring/incident-manager.test.js +98 -0
  71. package/server/lib/monitoring/log-aggregator.js +147 -0
  72. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  73. package/server/lib/monitoring/metrics-collector.js +337 -0
  74. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  75. package/server/lib/monitoring/status-page.js +214 -0
  76. package/server/lib/monitoring/status-page.test.js +105 -0
  77. package/server/lib/monitoring/uptime-monitor.js +194 -0
  78. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  79. package/server/lib/network/fail2ban-config.js +294 -0
  80. package/server/lib/network/fail2ban-config.test.js +275 -0
  81. package/server/lib/network/firewall-manager.js +252 -0
  82. package/server/lib/network/firewall-manager.test.js +254 -0
  83. package/server/lib/network/geoip-filter.js +282 -0
  84. package/server/lib/network/geoip-filter.test.js +264 -0
  85. package/server/lib/network/rate-limiter.js +229 -0
  86. package/server/lib/network/rate-limiter.test.js +293 -0
  87. package/server/lib/network/request-validator.js +351 -0
  88. package/server/lib/network/request-validator.test.js +345 -0
  89. package/server/lib/network/security-headers.js +251 -0
  90. package/server/lib/network/security-headers.test.js +283 -0
  91. package/server/lib/network/tls-config.js +210 -0
  92. package/server/lib/network/tls-config.test.js +248 -0
  93. package/server/lib/security/auth-security.js +369 -0
  94. package/server/lib/security/auth-security.test.js +448 -0
  95. package/server/lib/security/cis-benchmark.js +152 -0
  96. package/server/lib/security/cis-benchmark.test.js +137 -0
  97. package/server/lib/security/compose-templates.js +312 -0
  98. package/server/lib/security/compose-templates.test.js +229 -0
  99. package/server/lib/security/container-runtime.js +456 -0
  100. package/server/lib/security/container-runtime.test.js +503 -0
  101. package/server/lib/security/cors-validator.js +278 -0
  102. package/server/lib/security/cors-validator.test.js +310 -0
  103. package/server/lib/security/crypto-utils.js +253 -0
  104. package/server/lib/security/crypto-utils.test.js +409 -0
  105. package/server/lib/security/dockerfile-linter.js +459 -0
  106. package/server/lib/security/dockerfile-linter.test.js +483 -0
  107. package/server/lib/security/dockerfile-templates.js +278 -0
  108. package/server/lib/security/dockerfile-templates.test.js +164 -0
  109. package/server/lib/security/error-sanitizer.js +426 -0
  110. package/server/lib/security/error-sanitizer.test.js +331 -0
  111. package/server/lib/security/headers-generator.js +368 -0
  112. package/server/lib/security/headers-generator.test.js +398 -0
  113. package/server/lib/security/image-scanner.js +83 -0
  114. package/server/lib/security/image-scanner.test.js +106 -0
  115. package/server/lib/security/input-validator.js +352 -0
  116. package/server/lib/security/input-validator.test.js +330 -0
  117. package/server/lib/security/network-policy.js +174 -0
  118. package/server/lib/security/network-policy.test.js +164 -0
  119. package/server/lib/security/output-encoder.js +237 -0
  120. package/server/lib/security/output-encoder.test.js +276 -0
  121. package/server/lib/security/path-validator.js +359 -0
  122. package/server/lib/security/path-validator.test.js +293 -0
  123. package/server/lib/security/query-builder.js +421 -0
  124. package/server/lib/security/query-builder.test.js +318 -0
  125. package/server/lib/security/secret-detector.js +290 -0
  126. package/server/lib/security/secret-detector.test.js +354 -0
  127. package/server/lib/security/secrets-validator.js +137 -0
  128. package/server/lib/security/secrets-validator.test.js +120 -0
  129. package/server/lib/security-testing/dast-runner.js +154 -0
  130. package/server/lib/security-testing/dast-runner.test.js +62 -0
  131. package/server/lib/security-testing/dependency-scanner.js +172 -0
  132. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  133. package/server/lib/security-testing/pentest-runner.js +230 -0
  134. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  135. package/server/lib/security-testing/sast-runner.js +136 -0
  136. package/server/lib/security-testing/sast-runner.test.js +62 -0
  137. package/server/lib/security-testing/secret-scanner.js +153 -0
  138. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  139. package/server/lib/security-testing/security-gate.js +216 -0
  140. package/server/lib/security-testing/security-gate.test.js +115 -0
  141. package/server/lib/security-testing/security-reporter.js +303 -0
  142. package/server/lib/security-testing/security-reporter.test.js +114 -0
  143. package/server/lib/standards/audit-checker.js +546 -0
  144. package/server/lib/standards/audit-checker.test.js +415 -0
  145. package/server/lib/standards/cleanup-executor.js +452 -0
  146. package/server/lib/standards/cleanup-executor.test.js +293 -0
  147. package/server/lib/standards/refactor-stepper.js +425 -0
  148. package/server/lib/standards/refactor-stepper.test.js +298 -0
  149. package/server/lib/standards/standards-injector.js +167 -0
  150. package/server/lib/standards/standards-injector.test.js +232 -0
  151. package/server/lib/user-management.test.js +284 -0
  152. package/server/lib/vps/backup-manager.js +157 -0
  153. package/server/lib/vps/backup-manager.test.js +59 -0
  154. package/server/lib/vps/caddy-config.js +159 -0
  155. package/server/lib/vps/caddy-config.test.js +48 -0
  156. package/server/lib/vps/compose-orchestrator.js +219 -0
  157. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  158. package/server/lib/vps/database-config.js +208 -0
  159. package/server/lib/vps/database-config.test.js +47 -0
  160. package/server/lib/vps/deploy-script.js +211 -0
  161. package/server/lib/vps/deploy-script.test.js +53 -0
  162. package/server/lib/vps/secrets-manager.js +148 -0
  163. package/server/lib/vps/secrets-manager.test.js +58 -0
  164. package/server/lib/vps/server-hardening.js +174 -0
  165. package/server/lib/vps/server-hardening.test.js +70 -0
  166. package/server/package-lock.json +19 -0
  167. package/server/package.json +1 -0
  168. package/server/templates/CLAUDE.md +37 -0
  169. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Dashboard API Server
3
+ * Express server mounting all dashboard APIs
4
+ */
5
+
6
+ import express from 'express';
7
+
8
+ /**
9
+ * Creates a standardized error response
10
+ * @param {string} message - Error message
11
+ * @param {number} statusCode - HTTP status code
12
+ * @returns {Object} Error response object
13
+ */
14
+ export function createErrorResponse(message, statusCode = 500) {
15
+ return {
16
+ error: message,
17
+ statusCode,
18
+ timestamp: new Date().toISOString()
19
+ };
20
+ }
21
+
22
+ /**
23
+ * CORS middleware for handling cross-origin requests
24
+ * @param {Object} req - Express request
25
+ * @param {Object} res - Express response
26
+ * @param {Function} next - Next middleware
27
+ * @param {Object} options - CORS options
28
+ */
29
+ export function corsMiddleware(req, res, next, options = {}) {
30
+ const origin = options.origin || '*';
31
+
32
+ res.header('Access-Control-Allow-Origin', origin);
33
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
34
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
35
+
36
+ if (req.method === 'OPTIONS') {
37
+ res.sendStatus(200);
38
+ return;
39
+ }
40
+
41
+ next();
42
+ }
43
+
44
+ /**
45
+ * Request logger middleware
46
+ * @param {Object} req - Express request
47
+ * @param {Object} res - Express response
48
+ * @param {Function} next - Next middleware
49
+ * @param {Object} options - Logger options
50
+ */
51
+ export function requestLogger(req, res, next, options = {}) {
52
+ const logger = options.logger || console;
53
+ const startTime = Date.now();
54
+
55
+ logger.info(`${req.method} ${req.url}`);
56
+
57
+ res.on('finish', () => {
58
+ const duration = Date.now() - startTime;
59
+ logger.info(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
60
+ });
61
+
62
+ next();
63
+ }
64
+
65
+ /**
66
+ * Error handler middleware
67
+ * @param {Error} error - Error object
68
+ * @param {Object} req - Express request
69
+ * @param {Object} res - Express response
70
+ * @param {Function} next - Next middleware
71
+ * @param {Object} options - Error handler options
72
+ */
73
+ export function handleError(error, req, res, next, options = {}) {
74
+ const statusCode = error.statusCode || 500;
75
+ const response = {
76
+ error: error.message || 'Internal server error'
77
+ };
78
+
79
+ if (options.env !== 'production') {
80
+ response.stack = error.stack;
81
+ }
82
+
83
+ res.status(statusCode).json(response);
84
+ }
85
+
86
+ /**
87
+ * Mounts all API routes on the Express app
88
+ * @param {Object} app - Express app or router
89
+ * @param {Object} options - Route options
90
+ */
91
+ export function mountRoutes(app, options = {}) {
92
+ // Tasks API
93
+ app.get('/api/tasks', (req, res) => {
94
+ res.json({ tasks: [] });
95
+ });
96
+
97
+ app.post('/api/tasks', (req, res) => {
98
+ res.status(201).json({ task: {} });
99
+ });
100
+
101
+ if (app.patch) {
102
+ app.patch('/api/tasks/:id', (req, res) => {
103
+ res.json({ task: {} });
104
+ });
105
+ }
106
+
107
+ if (app.delete) {
108
+ app.delete('/api/tasks/:id', (req, res) => {
109
+ res.status(204).send();
110
+ });
111
+ }
112
+
113
+ // Health API
114
+ app.get('/api/health', (req, res) => {
115
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
116
+ });
117
+
118
+ // Notes API
119
+ app.get('/api/notes', (req, res) => {
120
+ res.json({ notes: [] });
121
+ });
122
+
123
+ if (app.put) {
124
+ app.put('/api/notes', (req, res) => {
125
+ res.json({ note: {} });
126
+ });
127
+ }
128
+
129
+ // Router API
130
+ app.get('/api/router/status', (req, res) => {
131
+ res.json({ status: 'running' });
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Creates the Express API server
137
+ * @param {Object} options - Server options
138
+ * @param {string} options.basePath - Base path for serving files
139
+ * @returns {Object} Express app
140
+ */
141
+ export function createApiServer(options = {}) {
142
+ const app = express();
143
+
144
+ // Middleware
145
+ app.use(express.json());
146
+ app.use((req, res, next) => corsMiddleware(req, res, next));
147
+
148
+ // Mount routes
149
+ mountRoutes(app, options);
150
+
151
+ // Error handler
152
+ app.use((err, req, res, next) => handleError(err, req, res, next, options));
153
+
154
+ return app;
155
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Dashboard API Server Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { createApiServer, mountRoutes, handleError, corsMiddleware, requestLogger, createErrorResponse } from './api-server.js';
6
+
7
+ describe('api-server', () => {
8
+ describe('createApiServer', () => {
9
+ it('creates Express app', () => {
10
+ const app = createApiServer({ basePath: '/test' });
11
+ expect(app.listen).toBeDefined();
12
+ expect(app.use).toBeDefined();
13
+ });
14
+
15
+ it('mounts API routes', () => {
16
+ const app = createApiServer({ basePath: '/test' });
17
+ // Routes should be mounted at /api/*
18
+ expect(app._router).toBeDefined();
19
+ });
20
+ });
21
+
22
+ describe('mountRoutes', () => {
23
+ it('mounts tasks API', () => {
24
+ const mockApp = { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() };
25
+ mountRoutes(mockApp, { basePath: '/test' });
26
+ expect(mockApp.get).toHaveBeenCalledWith('/api/tasks', expect.any(Function));
27
+ expect(mockApp.post).toHaveBeenCalledWith('/api/tasks', expect.any(Function));
28
+ });
29
+
30
+ it('mounts health API', () => {
31
+ const mockApp = { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() };
32
+ mountRoutes(mockApp, { basePath: '/test' });
33
+ expect(mockApp.get).toHaveBeenCalledWith('/api/health', expect.any(Function));
34
+ });
35
+
36
+ it('mounts notes API', () => {
37
+ const mockApp = { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() };
38
+ mountRoutes(mockApp, { basePath: '/test' });
39
+ expect(mockApp.get).toHaveBeenCalledWith('/api/notes', expect.any(Function));
40
+ expect(mockApp.put).toHaveBeenCalledWith('/api/notes', expect.any(Function));
41
+ });
42
+
43
+ it('mounts router API', () => {
44
+ const mockApp = { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() };
45
+ mountRoutes(mockApp, { basePath: '/test' });
46
+ expect(mockApp.get).toHaveBeenCalledWith('/api/router/status', expect.any(Function));
47
+ });
48
+ });
49
+
50
+ describe('handleError', () => {
51
+ it('returns error response', () => {
52
+ const mockRes = {
53
+ status: vi.fn().mockReturnThis(),
54
+ json: vi.fn()
55
+ };
56
+ const error = new Error('Test error');
57
+ handleError(error, {}, mockRes, () => {});
58
+ expect(mockRes.status).toHaveBeenCalledWith(500);
59
+ expect(mockRes.json).toHaveBeenCalledWith(
60
+ expect.objectContaining({ error: expect.any(String) })
61
+ );
62
+ });
63
+
64
+ it('uses error status code', () => {
65
+ const mockRes = {
66
+ status: vi.fn().mockReturnThis(),
67
+ json: vi.fn()
68
+ };
69
+ const error = new Error('Not found');
70
+ error.statusCode = 404;
71
+ handleError(error, {}, mockRes, () => {});
72
+ expect(mockRes.status).toHaveBeenCalledWith(404);
73
+ });
74
+
75
+ it('hides stack trace in production', () => {
76
+ const mockRes = {
77
+ status: vi.fn().mockReturnThis(),
78
+ json: vi.fn()
79
+ };
80
+ const error = new Error('Error');
81
+ handleError(error, {}, mockRes, () => {}, { env: 'production' });
82
+ const response = mockRes.json.mock.calls[0][0];
83
+ expect(response.stack).toBeUndefined();
84
+ });
85
+ });
86
+
87
+ describe('corsMiddleware', () => {
88
+ it('sets CORS headers', () => {
89
+ const mockRes = {
90
+ header: vi.fn()
91
+ };
92
+ const mockNext = vi.fn();
93
+ corsMiddleware({}, mockRes, mockNext);
94
+ expect(mockRes.header).toHaveBeenCalledWith('Access-Control-Allow-Origin', expect.any(String));
95
+ expect(mockNext).toHaveBeenCalled();
96
+ });
97
+
98
+ it('handles preflight requests', () => {
99
+ const mockReq = { method: 'OPTIONS' };
100
+ const mockRes = {
101
+ header: vi.fn(),
102
+ sendStatus: vi.fn()
103
+ };
104
+ corsMiddleware(mockReq, mockRes, () => {});
105
+ expect(mockRes.sendStatus).toHaveBeenCalledWith(200);
106
+ });
107
+
108
+ it('allows configurable origins', () => {
109
+ const mockRes = { header: vi.fn() };
110
+ const mockNext = vi.fn();
111
+ corsMiddleware({}, mockRes, mockNext, { origin: 'http://localhost:3000' });
112
+ expect(mockRes.header).toHaveBeenCalledWith('Access-Control-Allow-Origin', 'http://localhost:3000');
113
+ });
114
+ });
115
+
116
+ describe('requestLogger', () => {
117
+ it('logs requests', () => {
118
+ const mockLogger = { info: vi.fn() };
119
+ const mockReq = { method: 'GET', url: '/api/tasks' };
120
+ const mockRes = { on: vi.fn() };
121
+ const mockNext = vi.fn();
122
+
123
+ requestLogger(mockReq, mockRes, mockNext, { logger: mockLogger });
124
+ expect(mockLogger.info).toHaveBeenCalled();
125
+ expect(mockNext).toHaveBeenCalled();
126
+ });
127
+
128
+ it('logs response time', () => {
129
+ const mockLogger = { info: vi.fn() };
130
+ const mockReq = { method: 'GET', url: '/test' };
131
+ let finishCallback;
132
+ const mockRes = {
133
+ on: vi.fn((event, cb) => { if (event === 'finish') finishCallback = cb; }),
134
+ statusCode: 200
135
+ };
136
+
137
+ requestLogger(mockReq, mockRes, () => {}, { logger: mockLogger });
138
+ finishCallback();
139
+ expect(mockLogger.info).toHaveBeenCalledTimes(2);
140
+ });
141
+ });
142
+
143
+ describe('createErrorResponse', () => {
144
+ it('creates standard error format', () => {
145
+ const response = createErrorResponse('Not found', 404);
146
+ expect(response.error).toBe('Not found');
147
+ expect(response.statusCode).toBe(404);
148
+ });
149
+
150
+ it('includes timestamp', () => {
151
+ const response = createErrorResponse('Error');
152
+ expect(response.timestamp).toBeDefined();
153
+ });
154
+ });
155
+ });
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Health API Module
3
+ * Health check endpoint
4
+ */
5
+ import { promises as defaultFs } from 'fs';
6
+ import os from 'os';
7
+ import path from 'path';
8
+
9
+ const startTime = Date.now();
10
+
11
+ /**
12
+ * Get health status
13
+ * @param {Object} options - Options
14
+ * @returns {Promise<Object>} Health status
15
+ */
16
+ export async function getHealthStatus(options = {}) {
17
+ const checkDeps = options.checkDeps || (() => Promise.resolve({ healthy: true }));
18
+
19
+ const depsResult = await checkDeps();
20
+
21
+ const status = depsResult.healthy ? 'healthy' : 'degraded';
22
+ const uptime = Math.floor((Date.now() - startTime) / 1000);
23
+
24
+ const result = {
25
+ status,
26
+ timestamp: new Date().toISOString(),
27
+ uptime
28
+ };
29
+
30
+ if (!depsResult.healthy && depsResult.issues) {
31
+ result.issues = depsResult.issues;
32
+ }
33
+
34
+ return result;
35
+ }
36
+
37
+ /**
38
+ * Get system metrics
39
+ * @returns {Object} System metrics
40
+ */
41
+ export function getSystemMetrics() {
42
+ const memUsage = process.memoryUsage();
43
+ const totalMem = os.totalmem();
44
+ const freeMem = os.freemem();
45
+ const cpus = os.cpus();
46
+
47
+ // Calculate CPU usage (simple average of all cores)
48
+ let cpuUsage = 0;
49
+ if (cpus.length > 0) {
50
+ const totalCpuTime = cpus.reduce((acc, cpu) => {
51
+ const total = Object.values(cpu.times).reduce((sum, time) => sum + time, 0);
52
+ const idle = cpu.times.idle;
53
+ return acc + ((total - idle) / total);
54
+ }, 0);
55
+ cpuUsage = totalCpuTime / cpus.length;
56
+ }
57
+
58
+ return {
59
+ memory: {
60
+ used: memUsage.heapUsed,
61
+ total: memUsage.heapTotal,
62
+ systemUsed: totalMem - freeMem,
63
+ systemTotal: totalMem
64
+ },
65
+ cpu: {
66
+ usage: cpuUsage,
67
+ cores: cpus.length,
68
+ model: cpus[0]?.model || 'unknown'
69
+ },
70
+ pid: process.pid,
71
+ nodeVersion: process.version,
72
+ platform: process.platform,
73
+ uptime: process.uptime()
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Check dependencies
79
+ * @param {Object} options - Options
80
+ * @returns {Promise<Object>} Dependency status
81
+ */
82
+ export async function checkDependencies(options = {}) {
83
+ const fs = options.fs || defaultFs;
84
+ const basePath = options.basePath || process.cwd();
85
+
86
+ const result = {
87
+ filesystem: false,
88
+ issues: []
89
+ };
90
+
91
+ // Check filesystem access
92
+ try {
93
+ await fs.access(basePath);
94
+ result.filesystem = true;
95
+ } catch {
96
+ result.filesystem = false;
97
+ result.issues.push('Filesystem not accessible');
98
+ }
99
+
100
+ // Aggregate health status
101
+ result.healthy = result.filesystem;
102
+
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * Run test suite
108
+ * @param {Object} options - Options
109
+ * @returns {Promise<Object>} Test results
110
+ */
111
+ export async function runTestSuite(options = {}) {
112
+ const exec = options.exec;
113
+
114
+ if (!exec) {
115
+ return { error: 'No exec function provided' };
116
+ }
117
+
118
+ try {
119
+ const { stdout, stderr } = await exec('npm test');
120
+
121
+ // Parse test output
122
+ let passed = 0;
123
+ let failed = 0;
124
+ let total = 0;
125
+
126
+ // Try to parse different test output formats
127
+ // Format: "Tests: X passed | Y failed"
128
+ const testMatch = stdout.match(/Tests?:\s*(\d+)\s*passed(?:\s*\|\s*(\d+)\s*failed)?/i);
129
+ if (testMatch) {
130
+ passed = parseInt(testMatch[1], 10);
131
+ failed = testMatch[2] ? parseInt(testMatch[2], 10) : 0;
132
+ total = passed + failed;
133
+ } else {
134
+ // Format: "X passed"
135
+ const passedMatch = stdout.match(/(\d+)\s*passed/i);
136
+ if (passedMatch) {
137
+ passed = parseInt(passedMatch[1], 10);
138
+ total = passed;
139
+ }
140
+
141
+ const failedMatch = stdout.match(/(\d+)\s*failed/i);
142
+ if (failedMatch) {
143
+ failed = parseInt(failedMatch[1], 10);
144
+ total = passed + failed;
145
+ }
146
+ }
147
+
148
+ return {
149
+ passed,
150
+ failed,
151
+ total,
152
+ stdout,
153
+ stderr
154
+ };
155
+ } catch (error) {
156
+ return {
157
+ error: error.message,
158
+ passed: 0,
159
+ failed: 0,
160
+ total: 0
161
+ };
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Create Health API handler
167
+ * @param {Object} options - Options
168
+ * @returns {Object} API handler
169
+ */
170
+ export function createHealthApi(options = {}) {
171
+ const { basePath = process.cwd(), fs: fileSystem = defaultFs, cacheMs = 5000 } = options;
172
+ const checkDeps = options.checkDeps || (() => checkDependencies({ fs: fileSystem, basePath }));
173
+
174
+ let cachedStatus = null;
175
+ let cacheTime = 0;
176
+
177
+ return {
178
+ async get() {
179
+ const now = Date.now();
180
+
181
+ if (cachedStatus && (now - cacheTime) < cacheMs) {
182
+ return cachedStatus;
183
+ }
184
+
185
+ cachedStatus = await getHealthStatus({ checkDeps });
186
+ cacheTime = now;
187
+
188
+ return cachedStatus;
189
+ },
190
+
191
+ getMetrics() {
192
+ return getSystemMetrics();
193
+ },
194
+
195
+ async runTests(execFn) {
196
+ return runTestSuite({ exec: execFn });
197
+ }
198
+ };
199
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Health API Module Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { getHealthStatus, getSystemMetrics, checkDependencies, runTestSuite, createHealthApi } from './health-api.js';
6
+
7
+ describe('health-api', () => {
8
+ describe('getHealthStatus', () => {
9
+ it('returns healthy status', async () => {
10
+ const status = await getHealthStatus({
11
+ checkDeps: vi.fn().mockResolvedValue({ healthy: true })
12
+ });
13
+ expect(status.status).toBe('healthy');
14
+ expect(status.timestamp).toBeDefined();
15
+ });
16
+
17
+ it('returns degraded on dependency issues', async () => {
18
+ const status = await getHealthStatus({
19
+ checkDeps: vi.fn().mockResolvedValue({ healthy: false, issues: ['db down'] })
20
+ });
21
+ expect(status.status).toBe('degraded');
22
+ expect(status.issues).toContain('db down');
23
+ });
24
+
25
+ it('includes uptime', async () => {
26
+ const status = await getHealthStatus({
27
+ checkDeps: vi.fn().mockResolvedValue({ healthy: true })
28
+ });
29
+ expect(status.uptime).toBeDefined();
30
+ expect(typeof status.uptime).toBe('number');
31
+ });
32
+ });
33
+
34
+ describe('getSystemMetrics', () => {
35
+ it('returns memory metrics', () => {
36
+ const metrics = getSystemMetrics();
37
+ expect(metrics.memory).toBeDefined();
38
+ expect(metrics.memory.used).toBeDefined();
39
+ expect(metrics.memory.total).toBeDefined();
40
+ });
41
+
42
+ it('returns cpu metrics', () => {
43
+ const metrics = getSystemMetrics();
44
+ expect(metrics.cpu).toBeDefined();
45
+ });
46
+
47
+ it('returns process info', () => {
48
+ const metrics = getSystemMetrics();
49
+ expect(metrics.pid).toBeDefined();
50
+ expect(metrics.nodeVersion).toBeDefined();
51
+ });
52
+ });
53
+
54
+ describe('checkDependencies', () => {
55
+ it('checks file system access', async () => {
56
+ const mockFs = {
57
+ access: vi.fn().mockResolvedValue(undefined)
58
+ };
59
+ const result = await checkDependencies({ fs: mockFs, basePath: '/test' });
60
+ expect(result.filesystem).toBe(true);
61
+ });
62
+
63
+ it('reports missing directories', async () => {
64
+ const mockFs = {
65
+ access: vi.fn().mockRejectedValue(new Error('ENOENT'))
66
+ };
67
+ const result = await checkDependencies({ fs: mockFs, basePath: '/test' });
68
+ expect(result.filesystem).toBe(false);
69
+ });
70
+
71
+ it('aggregates health status', async () => {
72
+ const mockFs = {
73
+ access: vi.fn().mockResolvedValue(undefined)
74
+ };
75
+ const result = await checkDependencies({ fs: mockFs, basePath: '/test' });
76
+ expect(result.healthy).toBe(true);
77
+ });
78
+ });
79
+
80
+ describe('runTestSuite', () => {
81
+ it('returns test results', async () => {
82
+ const mockExec = vi.fn().mockResolvedValue({
83
+ stdout: '5541 passed',
84
+ stderr: ''
85
+ });
86
+ const result = await runTestSuite({ exec: mockExec });
87
+ expect(result.passed).toBeDefined();
88
+ expect(result.total).toBeDefined();
89
+ });
90
+
91
+ it('parses test output', async () => {
92
+ const mockExec = vi.fn().mockResolvedValue({
93
+ stdout: 'Tests: 100 passed | 5 failed',
94
+ stderr: ''
95
+ });
96
+ const result = await runTestSuite({ exec: mockExec });
97
+ expect(result.passed).toBe(100);
98
+ expect(result.failed).toBe(5);
99
+ });
100
+
101
+ it('handles test errors', async () => {
102
+ const mockExec = vi.fn().mockRejectedValue(new Error('Command failed'));
103
+ const result = await runTestSuite({ exec: mockExec });
104
+ expect(result.error).toBeDefined();
105
+ });
106
+ });
107
+
108
+ describe('createHealthApi', () => {
109
+ it('creates API handler', () => {
110
+ const api = createHealthApi({ basePath: '/test' });
111
+ expect(api.get).toBeDefined();
112
+ });
113
+
114
+ it('caches health status briefly', async () => {
115
+ const mockCheck = vi.fn().mockResolvedValue({ healthy: true });
116
+ const api = createHealthApi({ checkDeps: mockCheck, cacheMs: 1000 });
117
+ await api.get();
118
+ await api.get();
119
+ expect(mockCheck).toHaveBeenCalledTimes(1);
120
+ });
121
+ });
122
+ });