spec-up-t-healthcheck 1.0.0
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/README.md +216 -0
- package/bin/cli.js +193 -0
- package/bin/demo-html.js +186 -0
- package/bin/simple-test.js +79 -0
- package/lib/checks/external-specs-urls.js +484 -0
- package/lib/checks/gitignore.js +350 -0
- package/lib/checks/package-json.js +518 -0
- package/lib/checks/spec-files.js +263 -0
- package/lib/checks/specsjson.js +361 -0
- package/lib/file-opener.js +127 -0
- package/lib/formatters.js +176 -0
- package/lib/health-check-orchestrator.js +413 -0
- package/lib/health-check-registry.js +396 -0
- package/lib/health-check-utils.js +234 -0
- package/lib/health-checker.js +145 -0
- package/lib/html-formatter.js +626 -0
- package/lib/index.js +123 -0
- package/lib/providers.js +184 -0
- package/lib/web.js +70 -0
- package/package.json +91 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check registry system
|
|
3
|
+
*
|
|
4
|
+
* This module provides a registry system for discovering, registering, and managing
|
|
5
|
+
* health check modules. It enables dynamic loading of health checks and provides
|
|
6
|
+
* a centralized way to manage available checks without hardcoding dependencies.
|
|
7
|
+
*
|
|
8
|
+
* @author spec-up-t-healthcheck
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { isValidHealthCheckResult } from './health-check-utils.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} HealthCheckMetadata
|
|
15
|
+
* @property {string} id - Unique identifier for the health check
|
|
16
|
+
* @property {string} name - Human-readable name
|
|
17
|
+
* @property {string} description - Description of what the check validates
|
|
18
|
+
* @property {function} checkFunction - The actual health check function
|
|
19
|
+
* @property {string} [category='general'] - Category for grouping checks
|
|
20
|
+
* @property {number} [priority=100] - Execution priority (lower = higher priority)
|
|
21
|
+
* @property {string[]} [dependencies=[]] - IDs of checks that must run before this one
|
|
22
|
+
* @property {boolean} [enabled=true] - Whether the check is enabled by default
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Registry for managing health check modules.
|
|
27
|
+
* This class provides a centralized system for registering, discovering,
|
|
28
|
+
* and executing health checks in a modular fashion.
|
|
29
|
+
*/
|
|
30
|
+
export class HealthCheckRegistry {
|
|
31
|
+
constructor() {
|
|
32
|
+
/** @type {Map<string, HealthCheckMetadata>} */
|
|
33
|
+
this.checks = new Map();
|
|
34
|
+
|
|
35
|
+
/** @type {Set<string>} */
|
|
36
|
+
this.categories = new Set(['general']);
|
|
37
|
+
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
this.autoDiscovered = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Registers a health check with the registry.
|
|
44
|
+
*
|
|
45
|
+
* @param {HealthCheckMetadata} metadata - The health check metadata
|
|
46
|
+
* @throws {Error} If the check metadata is invalid or ID already exists
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```javascript
|
|
50
|
+
* registry.register({
|
|
51
|
+
* id: 'my-check',
|
|
52
|
+
* name: 'My Custom Check',
|
|
53
|
+
* description: 'Validates something important',
|
|
54
|
+
* checkFunction: async (provider) => { ... }
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
register(metadata) {
|
|
59
|
+
this.validateMetadata(metadata);
|
|
60
|
+
|
|
61
|
+
if (this.checks.has(metadata.id)) {
|
|
62
|
+
throw new Error(`Health check with ID '${metadata.id}' is already registered`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Set defaults for optional fields
|
|
66
|
+
const fullMetadata = {
|
|
67
|
+
category: 'general',
|
|
68
|
+
priority: 100,
|
|
69
|
+
dependencies: [],
|
|
70
|
+
enabled: true,
|
|
71
|
+
...metadata
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.checks.set(metadata.id, fullMetadata);
|
|
75
|
+
this.categories.add(fullMetadata.category);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates health check metadata structure.
|
|
80
|
+
*
|
|
81
|
+
* @param {HealthCheckMetadata} metadata - The metadata to validate
|
|
82
|
+
* @throws {Error} If metadata is invalid
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
validateMetadata(metadata) {
|
|
86
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
87
|
+
throw new Error('Health check metadata must be an object');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const requiredFields = ['id', 'name', 'description', 'checkFunction'];
|
|
91
|
+
for (const field of requiredFields) {
|
|
92
|
+
if (!metadata[field]) {
|
|
93
|
+
throw new Error(`Health check metadata missing required field: ${field}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof metadata.id !== 'string' || metadata.id.trim() === '') {
|
|
98
|
+
throw new Error('Health check ID must be a non-empty string');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof metadata.checkFunction !== 'function') {
|
|
102
|
+
throw new Error('Health check function must be a function');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Validate optional fields if present
|
|
106
|
+
if (metadata.priority !== undefined && (typeof metadata.priority !== 'number' || metadata.priority < 0)) {
|
|
107
|
+
throw new Error('Health check priority must be a non-negative number');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (metadata.dependencies && !Array.isArray(metadata.dependencies)) {
|
|
111
|
+
throw new Error('Health check dependencies must be an array');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Unregisters a health check from the registry.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} id - The ID of the health check to unregister
|
|
119
|
+
* @returns {boolean} True if the check was removed, false if it wasn't found
|
|
120
|
+
*/
|
|
121
|
+
unregister(id) {
|
|
122
|
+
return this.checks.delete(id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Gets metadata for a specific health check.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} id - The ID of the health check
|
|
129
|
+
* @returns {HealthCheckMetadata|undefined} The metadata or undefined if not found
|
|
130
|
+
*/
|
|
131
|
+
get(id) {
|
|
132
|
+
return this.checks.get(id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gets all registered health check IDs.
|
|
137
|
+
*
|
|
138
|
+
* @returns {string[]} Array of health check IDs
|
|
139
|
+
*/
|
|
140
|
+
getAllIds() {
|
|
141
|
+
return Array.from(this.checks.keys());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets health checks filtered by category.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} category - The category to filter by
|
|
148
|
+
* @returns {HealthCheckMetadata[]} Array of health checks in the category
|
|
149
|
+
*/
|
|
150
|
+
getByCategory(category) {
|
|
151
|
+
return Array.from(this.checks.values()).filter(check => check.category === category);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Gets all available categories.
|
|
156
|
+
*
|
|
157
|
+
* @returns {string[]} Array of category names
|
|
158
|
+
*/
|
|
159
|
+
getCategories() {
|
|
160
|
+
return Array.from(this.categories);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Checks if a health check is registered.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} id - The ID to check
|
|
167
|
+
* @returns {boolean} True if the check is registered
|
|
168
|
+
*/
|
|
169
|
+
has(id) {
|
|
170
|
+
return this.checks.has(id);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets health checks sorted by priority and dependencies.
|
|
175
|
+
*
|
|
176
|
+
* This method returns checks in an order that respects dependencies
|
|
177
|
+
* and priority settings, ensuring checks run in the correct sequence.
|
|
178
|
+
*
|
|
179
|
+
* @param {string[]} [requestedIds] - Specific check IDs to include (optional)
|
|
180
|
+
* @returns {HealthCheckMetadata[]} Ordered array of health checks
|
|
181
|
+
*/
|
|
182
|
+
getExecutionOrder(requestedIds) {
|
|
183
|
+
const availableChecks = requestedIds
|
|
184
|
+
? requestedIds.map(id => this.get(id)).filter(Boolean)
|
|
185
|
+
: Array.from(this.checks.values());
|
|
186
|
+
|
|
187
|
+
// Filter only enabled checks
|
|
188
|
+
const enabledChecks = availableChecks.filter(check => check.enabled);
|
|
189
|
+
|
|
190
|
+
// Sort by priority, then by ID for consistent ordering
|
|
191
|
+
return enabledChecks.sort((a, b) => {
|
|
192
|
+
if (a.priority !== b.priority) {
|
|
193
|
+
return a.priority - b.priority;
|
|
194
|
+
}
|
|
195
|
+
return a.id.localeCompare(b.id);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Executes a specific health check.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} id - The ID of the health check to execute
|
|
203
|
+
* @param {import('./providers.js').Provider} provider - The provider instance
|
|
204
|
+
* @returns {Promise<import('./health-check-utils.js').HealthCheckResult>} The check result
|
|
205
|
+
* @throws {Error} If the check is not registered or execution fails
|
|
206
|
+
*/
|
|
207
|
+
async execute(id, provider) {
|
|
208
|
+
const metadata = this.get(id);
|
|
209
|
+
if (!metadata) {
|
|
210
|
+
throw new Error(`Health check '${id}' is not registered`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!metadata.enabled) {
|
|
214
|
+
throw new Error(`Health check '${id}' is disabled`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const result = await metadata.checkFunction(provider);
|
|
219
|
+
|
|
220
|
+
// Validate the result structure
|
|
221
|
+
if (!isValidHealthCheckResult(result)) {
|
|
222
|
+
throw new Error(`Health check '${id}' returned invalid result structure`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw new Error(`Failed to execute health check '${id}': ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Automatically discovers and registers health checks from the checks directory.
|
|
233
|
+
*
|
|
234
|
+
* This method dynamically imports health check modules and registers them
|
|
235
|
+
* with the registry. It's designed to work with the standard module structure.
|
|
236
|
+
*/
|
|
237
|
+
async autoDiscover() {
|
|
238
|
+
if (this.autoDiscovered) {
|
|
239
|
+
return; // Avoid duplicate discovery
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// Import the built-in health checks
|
|
244
|
+
const packageJsonModule = await import('./checks/package-json.js');
|
|
245
|
+
const specFilesModule = await import('./checks/spec-files.js');
|
|
246
|
+
const specsJsonModule = await import('./checks/specsjson.js');
|
|
247
|
+
const externalSpecsUrlsModule = await import('./checks/external-specs-urls.js');
|
|
248
|
+
const gitignoreModule = await import('./checks/gitignore.js');
|
|
249
|
+
|
|
250
|
+
// Register package.json check
|
|
251
|
+
if (packageJsonModule.checkPackageJson && packageJsonModule.CHECK_ID) {
|
|
252
|
+
this.register({
|
|
253
|
+
id: packageJsonModule.CHECK_ID,
|
|
254
|
+
name: packageJsonModule.CHECK_NAME || 'Package.json Check',
|
|
255
|
+
description: packageJsonModule.CHECK_DESCRIPTION || 'Validates package.json file',
|
|
256
|
+
checkFunction: packageJsonModule.checkPackageJson,
|
|
257
|
+
category: 'configuration',
|
|
258
|
+
priority: 10 // High priority for configuration checks
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Register spec files check
|
|
263
|
+
if (specFilesModule.checkSpecFiles && specFilesModule.CHECK_ID) {
|
|
264
|
+
this.register({
|
|
265
|
+
id: specFilesModule.CHECK_ID,
|
|
266
|
+
name: specFilesModule.CHECK_NAME || 'Specification Files Check',
|
|
267
|
+
description: specFilesModule.CHECK_DESCRIPTION || 'Discovers specification files',
|
|
268
|
+
checkFunction: specFilesModule.checkSpecFiles,
|
|
269
|
+
category: 'content',
|
|
270
|
+
priority: 20 // Lower priority, content checks can run after configuration
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Register specs.json check
|
|
275
|
+
if (specsJsonModule.checkSpecsJson && specsJsonModule.CHECK_ID) {
|
|
276
|
+
this.register({
|
|
277
|
+
id: specsJsonModule.CHECK_ID,
|
|
278
|
+
name: specsJsonModule.CHECK_NAME || 'Specs.json Check',
|
|
279
|
+
description: specsJsonModule.CHECK_DESCRIPTION || 'Validates specs.json file',
|
|
280
|
+
checkFunction: specsJsonModule.checkSpecsJson,
|
|
281
|
+
category: 'configuration',
|
|
282
|
+
priority: 15 // Between package-json and spec-files
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Register external specs URLs check
|
|
287
|
+
if (externalSpecsUrlsModule.checkExternalSpecsUrls && externalSpecsUrlsModule.CHECK_ID) {
|
|
288
|
+
this.register({
|
|
289
|
+
id: externalSpecsUrlsModule.CHECK_ID,
|
|
290
|
+
name: externalSpecsUrlsModule.CHECK_NAME || 'External Specs URL Validation',
|
|
291
|
+
description: externalSpecsUrlsModule.CHECK_DESCRIPTION || 'Validates external specification URLs',
|
|
292
|
+
checkFunction: externalSpecsUrlsModule.checkExternalSpecsUrls,
|
|
293
|
+
category: 'external-references',
|
|
294
|
+
priority: 30 // Run after specs.json is validated
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Register .gitignore check
|
|
299
|
+
if (gitignoreModule.checkGitignore && gitignoreModule.CHECK_ID) {
|
|
300
|
+
this.register({
|
|
301
|
+
id: gitignoreModule.CHECK_ID,
|
|
302
|
+
name: gitignoreModule.CHECK_NAME || '.gitignore Validation',
|
|
303
|
+
description: gitignoreModule.CHECK_DESCRIPTION || 'Validates .gitignore file',
|
|
304
|
+
checkFunction: gitignoreModule.checkGitignore,
|
|
305
|
+
category: 'configuration',
|
|
306
|
+
priority: 12 // After package.json, before specs.json
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.autoDiscovered = true;
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.warn('Failed to auto-discover some health checks:', error.message);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Gets a summary of the registry state.
|
|
318
|
+
*
|
|
319
|
+
* @returns {Object} Summary information about registered checks
|
|
320
|
+
*/
|
|
321
|
+
getSummary() {
|
|
322
|
+
const checks = Array.from(this.checks.values());
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
totalChecks: checks.length,
|
|
326
|
+
enabledChecks: checks.filter(c => c.enabled).length,
|
|
327
|
+
disabledChecks: checks.filter(c => !c.enabled).length,
|
|
328
|
+
categories: this.getCategories(),
|
|
329
|
+
checksByCategory: Object.fromEntries(
|
|
330
|
+
this.getCategories().map(cat => [cat, this.getByCategory(cat).length])
|
|
331
|
+
)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Enables or disables a health check.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} id - The ID of the health check
|
|
339
|
+
* @param {boolean} enabled - Whether to enable or disable the check
|
|
340
|
+
* @returns {boolean} True if the check was found and updated
|
|
341
|
+
*/
|
|
342
|
+
setEnabled(id, enabled) {
|
|
343
|
+
const metadata = this.get(id);
|
|
344
|
+
if (metadata) {
|
|
345
|
+
metadata.enabled = enabled;
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Clears all registered health checks.
|
|
353
|
+
*
|
|
354
|
+
* This method is primarily useful for testing or when reinitializing
|
|
355
|
+
* the registry with a different set of checks.
|
|
356
|
+
*/
|
|
357
|
+
clear() {
|
|
358
|
+
this.checks.clear();
|
|
359
|
+
this.categories.clear();
|
|
360
|
+
this.categories.add('general');
|
|
361
|
+
this.autoDiscovered = false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Global registry instance for convenient access.
|
|
367
|
+
* Most applications should use this singleton instance.
|
|
368
|
+
* @type {HealthCheckRegistry}
|
|
369
|
+
*/
|
|
370
|
+
export const globalRegistry = new HealthCheckRegistry();
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Convenience function to register a health check with the global registry.
|
|
374
|
+
*
|
|
375
|
+
* @param {HealthCheckMetadata} metadata - The health check metadata
|
|
376
|
+
*/
|
|
377
|
+
export function registerHealthCheck(metadata) {
|
|
378
|
+
globalRegistry.register(metadata);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Convenience function to get a health check from the global registry.
|
|
383
|
+
*
|
|
384
|
+
* @param {string} id - The health check ID
|
|
385
|
+
* @returns {HealthCheckMetadata|undefined} The health check metadata
|
|
386
|
+
*/
|
|
387
|
+
export function getHealthCheck(id) {
|
|
388
|
+
return globalRegistry.get(id);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Convenience function to auto-discover health checks using the global registry.
|
|
393
|
+
*/
|
|
394
|
+
export async function autoDiscoverHealthChecks() {
|
|
395
|
+
await globalRegistry.autoDiscover();
|
|
396
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Health check utilities and common types for spec-up-t-healthcheck
|
|
3
|
+
*
|
|
4
|
+
* This module provides shared utilities, type definitions, and helper functions
|
|
5
|
+
* used across all health check modules. It ensures consistency in health check
|
|
6
|
+
* result formatting and provides a centralized location for common functionality.
|
|
7
|
+
*
|
|
8
|
+
* @author spec-up-t-healthcheck
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} HealthCheckResult
|
|
13
|
+
* @property {string} check - The name/identifier of the health check
|
|
14
|
+
* @property {'pass'|'fail'|'warn'|'skip'} status - The result status
|
|
15
|
+
* @property {string} message - Human-readable result message
|
|
16
|
+
* @property {string} timestamp - ISO timestamp when the check was performed
|
|
17
|
+
* @property {Object} [details={}] - Additional details about the check result
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} HealthCheckSummary
|
|
22
|
+
* @property {number} total - Total number of checks performed
|
|
23
|
+
* @property {number} passed - Number of checks that passed
|
|
24
|
+
* @property {number} failed - Number of checks that failed
|
|
25
|
+
* @property {number} warnings - Number of checks with warnings
|
|
26
|
+
* @property {number} skipped - Number of checks that were skipped
|
|
27
|
+
* @property {number} score - Overall health score as a percentage (0-100)
|
|
28
|
+
* @property {boolean} hasErrors - Whether any checks failed
|
|
29
|
+
* @property {boolean} hasWarnings - Whether any checks had warnings
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} HealthCheckReport
|
|
34
|
+
* @property {HealthCheckResult[]} results - Array of individual check results
|
|
35
|
+
* @property {HealthCheckSummary} summary - Aggregated summary of all check results
|
|
36
|
+
* @property {string} timestamp - ISO timestamp when the report was generated
|
|
37
|
+
* @property {Object} provider - Information about the provider used for checks
|
|
38
|
+
* @property {string} provider.type - The type of provider ('local', 'remote', etc.)
|
|
39
|
+
* @property {string} [provider.repoPath] - The repository path (for local providers)
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {function(import('./providers.js').Provider): Promise<HealthCheckResult>} HealthCheckFunction
|
|
44
|
+
* @description A function that performs a health check using a provider and returns a result
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Valid status values for health check results.
|
|
49
|
+
* @type {readonly string[]}
|
|
50
|
+
*/
|
|
51
|
+
export const HEALTH_CHECK_STATUSES = Object.freeze(['pass', 'fail', 'warn', 'skip']);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a standardized health check result object.
|
|
55
|
+
*
|
|
56
|
+
* This utility function ensures consistent structure across all health check results,
|
|
57
|
+
* automatically adding timestamps and providing a standard format for reporting.
|
|
58
|
+
* The function validates input parameters to maintain data integrity.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} check - The identifier/name of the health check being performed
|
|
61
|
+
* @param {'pass'|'fail'|'warn'|'skip'} status - The status of the health check
|
|
62
|
+
* @param {string} message - A human-readable message describing the result
|
|
63
|
+
* @param {Object} [details={}] - Optional additional details about the check
|
|
64
|
+
* @returns {HealthCheckResult} A standardized health check result object
|
|
65
|
+
* @throws {Error} If check name or message is empty, or status is invalid
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```javascript
|
|
69
|
+
* const result = createHealthCheckResult(
|
|
70
|
+
* 'package-json',
|
|
71
|
+
* 'pass',
|
|
72
|
+
* 'package.json is valid',
|
|
73
|
+
* { packageData: { name: 'my-spec', version: '1.0.0' } }
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function createHealthCheckResult(check, status, message, details = {}) {
|
|
78
|
+
// Input validation to ensure data integrity
|
|
79
|
+
if (typeof check !== 'string' || check.trim() === '') {
|
|
80
|
+
throw new Error('Check name must be a non-empty string');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof message !== 'string' || message.trim() === '') {
|
|
84
|
+
throw new Error('Message must be a non-empty string');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!HEALTH_CHECK_STATUSES.includes(status)) {
|
|
88
|
+
throw new Error(`Status must be one of: ${HEALTH_CHECK_STATUSES.join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (details !== null && typeof details !== 'object') {
|
|
92
|
+
throw new Error('Details must be an object or null');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
check: check.trim(),
|
|
97
|
+
status,
|
|
98
|
+
message: message.trim(),
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
details: details || {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calculates summary statistics from an array of health check results.
|
|
106
|
+
*
|
|
107
|
+
* This function aggregates individual health check results into a comprehensive
|
|
108
|
+
* summary that includes counts, percentages, and boolean flags for quick
|
|
109
|
+
* assessment of overall repository health.
|
|
110
|
+
*
|
|
111
|
+
* @param {HealthCheckResult[]} results - Array of health check results to summarize
|
|
112
|
+
* @returns {HealthCheckSummary} Aggregated summary statistics
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```javascript
|
|
116
|
+
* const results = [
|
|
117
|
+
* createHealthCheckResult('check1', 'pass', 'All good'),
|
|
118
|
+
* createHealthCheckResult('check2', 'fail', 'Found issue')
|
|
119
|
+
* ];
|
|
120
|
+
* const summary = calculateSummary(results);
|
|
121
|
+
* console.log(summary.score); // 50 (50% passed)
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function calculateSummary(results) {
|
|
125
|
+
if (!Array.isArray(results)) {
|
|
126
|
+
throw new Error('Results must be an array');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const summary = {
|
|
130
|
+
total: results.length,
|
|
131
|
+
passed: results.filter(r => r.status === 'pass').length,
|
|
132
|
+
failed: results.filter(r => r.status === 'fail').length,
|
|
133
|
+
warnings: results.filter(r => r.status === 'warn').length,
|
|
134
|
+
skipped: results.filter(r => r.status === 'skip').length
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Calculate health score as percentage of passed checks
|
|
138
|
+
summary.score = summary.total > 0 ? Math.round((summary.passed / summary.total) * 100) : 0;
|
|
139
|
+
|
|
140
|
+
// Boolean flags for quick status assessment
|
|
141
|
+
summary.hasErrors = summary.failed > 0;
|
|
142
|
+
summary.hasWarnings = summary.warnings > 0;
|
|
143
|
+
|
|
144
|
+
return summary;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validates that an object conforms to the HealthCheckResult interface.
|
|
149
|
+
*
|
|
150
|
+
* This function performs runtime validation of health check result objects
|
|
151
|
+
* to ensure they meet the expected structure and data types. Useful for
|
|
152
|
+
* validating results from external or dynamic sources.
|
|
153
|
+
*
|
|
154
|
+
* @param {any} result - The object to validate
|
|
155
|
+
* @returns {boolean} True if the object is a valid HealthCheckResult
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```javascript
|
|
159
|
+
* const result = { check: 'test', status: 'pass', message: 'OK', timestamp: '...' };
|
|
160
|
+
* if (isValidHealthCheckResult(result)) {
|
|
161
|
+
* // Safe to use as HealthCheckResult
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function isValidHealthCheckResult(result) {
|
|
166
|
+
if (!result || typeof result !== 'object') {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const requiredFields = ['check', 'status', 'message', 'timestamp'];
|
|
171
|
+
|
|
172
|
+
// Check all required fields are present and have correct types
|
|
173
|
+
for (const field of requiredFields) {
|
|
174
|
+
if (!(field in result) || typeof result[field] !== 'string') {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Validate status is a known value
|
|
180
|
+
if (!HEALTH_CHECK_STATUSES.includes(result.status)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Validate timestamp is a valid ISO string
|
|
185
|
+
if (isNaN(Date.parse(result.timestamp))) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Details field is optional but must be an object if present
|
|
190
|
+
if ('details' in result && (result.details === null || typeof result.details !== 'object')) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Creates a standardized error result for health checks that encounter exceptions.
|
|
199
|
+
*
|
|
200
|
+
* This utility function provides a consistent way to handle and report errors
|
|
201
|
+
* that occur during health check execution, ensuring proper error information
|
|
202
|
+
* is captured and formatted for reporting.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} check - The identifier of the health check that encountered an error
|
|
205
|
+
* @param {Error|string} error - The error object or error message
|
|
206
|
+
* @param {Object} [additionalDetails={}] - Additional context about the error
|
|
207
|
+
* @returns {HealthCheckResult} A standardized error result
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```javascript
|
|
211
|
+
* try {
|
|
212
|
+
* // Health check logic
|
|
213
|
+
* } catch (error) {
|
|
214
|
+
* return createErrorResult('my-check', error, { context: 'additional info' });
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export function createErrorResult(check, error, additionalDetails = {}) {
|
|
219
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
220
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
221
|
+
|
|
222
|
+
const details = {
|
|
223
|
+
error: errorMessage,
|
|
224
|
+
...(errorStack && { stack: errorStack }),
|
|
225
|
+
...additionalDetails
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return createHealthCheckResult(
|
|
229
|
+
check,
|
|
230
|
+
'fail',
|
|
231
|
+
`Error during health check: ${errorMessage}`,
|
|
232
|
+
details
|
|
233
|
+
);
|
|
234
|
+
}
|