spec-up-t-healthcheck 1.0.0 â 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/README.md +5 -0
- package/lib/checks/console-messages.js +211 -0
- package/lib/checks/external-specs-urls.js +93 -16
- package/lib/checks/gitignore.js +1 -1
- package/lib/checks/link-checker.js +361 -0
- package/lib/checks/markdown-tables.js +463 -0
- package/lib/checks/package-json.js +1 -1
- package/lib/checks/spec-directory-and-files.js +356 -0
- package/lib/checks/specsjson.js +258 -5
- package/lib/formatters/result-details-formatter.js +559 -0
- package/lib/formatters.js +14 -0
- package/lib/health-check-registry.js +70 -3
- package/lib/health-checker.js +13 -3
- package/lib/html-formatter.js +139 -168
- package/lib/index.js +1 -1
- package/lib/providers-browser.js +73 -0
- package/lib/providers.js +24 -0
- package/lib/web.js +10 -6
- package/package.json +5 -17
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared result details formatter for health check results
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions to format health check result details into HTML.
|
|
5
|
+
* It works in both Node.js and browser environments, enabling consistent display
|
|
6
|
+
* of health check results in CLI tools, HTML reports, and web applications.
|
|
7
|
+
*
|
|
8
|
+
* The module formats:
|
|
9
|
+
* - Errors array with red styling
|
|
10
|
+
* - Warnings array with yellow/orange styling
|
|
11
|
+
* - Success messages array with green styling
|
|
12
|
+
* - Additional metadata (missingFields, count, packageData)
|
|
13
|
+
*
|
|
14
|
+
* URLs in messages are automatically converted to clickable links.
|
|
15
|
+
*
|
|
16
|
+
* @author spec-up-t-healthcheck
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Escapes HTML special characters to prevent XSS attacks.
|
|
21
|
+
* This is critical for security when displaying user-generated content.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|any} text - Text to escape
|
|
24
|
+
* @returns {string} HTML-escaped text
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```javascript
|
|
28
|
+
* escapeHtml('<script>alert("xss")</script>');
|
|
29
|
+
* // Returns: '<script>alert("xss")</script>'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function escapeHtml(text) {
|
|
33
|
+
if (typeof text !== 'string') {
|
|
34
|
+
return String(text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const map = {
|
|
38
|
+
'&': '&',
|
|
39
|
+
'<': '<',
|
|
40
|
+
'>': '>',
|
|
41
|
+
'"': '"',
|
|
42
|
+
"'": '''
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Converts URLs in text to clickable links that open in a new tab.
|
|
50
|
+
* The text is first escaped for HTML safety, then URLs are converted to links.
|
|
51
|
+
*
|
|
52
|
+
* This function enhances user experience by making URLs in error messages,
|
|
53
|
+
* warnings, and other feedback immediately actionable.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} text - Text potentially containing URLs
|
|
56
|
+
* @returns {string} HTML string with clickable links
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```javascript
|
|
60
|
+
* linkifyUrls('Check https://example.com for details');
|
|
61
|
+
* // Returns: 'Check <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for details'
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function linkifyUrls(text) {
|
|
65
|
+
if (typeof text !== 'string') {
|
|
66
|
+
return escapeHtml(String(text));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// First escape the text for HTML safety
|
|
70
|
+
const escaped = escapeHtml(text);
|
|
71
|
+
|
|
72
|
+
// URL regex pattern that matches http://, https://, and www. URLs
|
|
73
|
+
// Excludes common trailing punctuation like ), >, ", and whitespace
|
|
74
|
+
const urlPattern = /(https?:\/\/[^\s<>"()]+|www\.[^\s<>"()]+)/g;
|
|
75
|
+
|
|
76
|
+
// Replace URLs with clickable links
|
|
77
|
+
return escaped.replace(urlPattern, (url) => {
|
|
78
|
+
// Ensure the URL has a protocol for the href attribute
|
|
79
|
+
const href = url.startsWith('http') ? url : `https://${url}`;
|
|
80
|
+
|
|
81
|
+
// Create an anchor tag that opens in a new tab with security attributes
|
|
82
|
+
return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-decoration-underline">${url}</a>`;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Formats result details into HTML.
|
|
88
|
+
*
|
|
89
|
+
* This is the main formatter function that converts a health check result's
|
|
90
|
+
* details object into a formatted HTML string. It handles:
|
|
91
|
+
* - Errors: Displayed in red with bullet points
|
|
92
|
+
* - Warnings: Displayed in yellow/orange with bullet points
|
|
93
|
+
* - Success: Displayed in green with bullet points (can be hidden for brevity)
|
|
94
|
+
* - Info: Displayed in muted gray with bullet points (contextual information)
|
|
95
|
+
* - Metadata: Missing fields, counts, package data
|
|
96
|
+
* - Console Messages: Special table format for console output (timestamp, type, message, operation, additionalData)
|
|
97
|
+
*
|
|
98
|
+
* All URLs in messages are automatically converted to clickable links.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} details - The details object from a health check result
|
|
101
|
+
* @param {string[]} [details.errors] - Array of error messages
|
|
102
|
+
* @param {string[]} [details.warnings] - Array of warning messages
|
|
103
|
+
* @param {string[]} [details.success] - Array of success messages
|
|
104
|
+
* @param {string[]} [details.info] - Array of informational messages
|
|
105
|
+
* @param {string[]} [details.missingFields] - Array of missing field names
|
|
106
|
+
* @param {number} [details.count] - Count of items found
|
|
107
|
+
* @param {Object} [details.packageData] - Package metadata
|
|
108
|
+
* @param {string} [details.packageData.name] - Package name
|
|
109
|
+
* @param {string} [details.packageData.version] - Package version
|
|
110
|
+
* @param {Object} [details.analysis] - Console message analysis data
|
|
111
|
+
* @param {Array} [details.allMessages] - All console messages for table display
|
|
112
|
+
* @returns {string} Formatted HTML string
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```javascript
|
|
116
|
+
* const details = {
|
|
117
|
+
* errors: ['Field "title" is missing'],
|
|
118
|
+
* warnings: ['Field "favicon" is recommended'],
|
|
119
|
+
* success: ['Field "author" exists'],
|
|
120
|
+
* info: ['URL accessibility checks skipped (browser environment)']
|
|
121
|
+
* };
|
|
122
|
+
* const html = formatResultDetails(details);
|
|
123
|
+
* // Returns formatted HTML with error, warning, success, and info lists
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export function formatResultDetails(details) {
|
|
127
|
+
let html = '';
|
|
128
|
+
|
|
129
|
+
// Special handling for console-messages check with errors/warnings
|
|
130
|
+
// Show compact error/warning list first, then full messages table
|
|
131
|
+
if (details.analysis && details.allMessages) {
|
|
132
|
+
// Show errors first (compact format for quick scanning)
|
|
133
|
+
if (details.errors && details.errors.length > 0) {
|
|
134
|
+
const isConsoleMessageFormat = typeof details.errors[0] === 'object' && details.errors[0].timestamp;
|
|
135
|
+
if (isConsoleMessageFormat) {
|
|
136
|
+
html += formatConsoleMessageList('Errors', details.errors, 'danger');
|
|
137
|
+
if (details.errorsNote) {
|
|
138
|
+
html += `<div class="mt-1"><small class="text-muted">${escapeHtml(details.errorsNote)}</small></div>`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Show warnings (compact format)
|
|
144
|
+
if (details.warnings && details.warnings.length > 0) {
|
|
145
|
+
const isConsoleMessageFormat = typeof details.warnings[0] === 'object' && details.warnings[0].timestamp;
|
|
146
|
+
if (isConsoleMessageFormat) {
|
|
147
|
+
html += formatConsoleMessageList('Warnings', details.warnings, 'warning');
|
|
148
|
+
if (details.warningsNote) {
|
|
149
|
+
html += `<div class="mt-1"><small class="text-muted">${escapeHtml(details.warningsNote)}</small></div>`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add the full messages table (with all message types)
|
|
155
|
+
html += formatConsoleMessagesTable(details);
|
|
156
|
+
return html;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Special handling for markdown-tables check
|
|
160
|
+
if (details.details && Array.isArray(details.details)) {
|
|
161
|
+
html += formatMarkdownTablesDetails(details);
|
|
162
|
+
return html;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Display errors array with clickable URLs
|
|
166
|
+
// Errors are shown with strong red styling to draw immediate attention
|
|
167
|
+
if (details.errors && details.errors.length > 0) {
|
|
168
|
+
// Check if errors are objects (from console-messages) or strings (from other checks)
|
|
169
|
+
const isConsoleMessageFormat = typeof details.errors[0] === 'object' && details.errors[0].timestamp;
|
|
170
|
+
|
|
171
|
+
if (isConsoleMessageFormat) {
|
|
172
|
+
html += formatConsoleMessageList('Errors', details.errors, 'danger');
|
|
173
|
+
if (details.errorsNote) {
|
|
174
|
+
html += `<small class="text-muted">${escapeHtml(details.errorsNote)}</small>`;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
html += `<div class="mt-2 detail-errors"><strong class="text-danger">Errors:</strong><ul class="mb-0 mt-1">`;
|
|
178
|
+
details.errors.forEach(error => {
|
|
179
|
+
html += `<li class="text-danger">${linkifyUrls(error)}</li>`;
|
|
180
|
+
});
|
|
181
|
+
html += `</ul></div>`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Display warnings array with clickable URLs
|
|
186
|
+
// Warnings indicate potential issues that should be addressed but aren't critical
|
|
187
|
+
if (details.warnings && details.warnings.length > 0) {
|
|
188
|
+
// Check if warnings are objects (from console-messages) or strings (from other checks)
|
|
189
|
+
const isConsoleMessageFormat = typeof details.warnings[0] === 'object' && details.warnings[0].timestamp;
|
|
190
|
+
|
|
191
|
+
if (isConsoleMessageFormat) {
|
|
192
|
+
html += formatConsoleMessageList('Warnings', details.warnings, 'warning');
|
|
193
|
+
if (details.warningsNote) {
|
|
194
|
+
html += `<small class="text-muted">${escapeHtml(details.warningsNote)}</small>`;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
html += `<div class="mt-2 detail-warnings"><strong class="text-warning">Warnings:</strong><ul class="mb-0 mt-1">`;
|
|
198
|
+
details.warnings.forEach(warning => {
|
|
199
|
+
html += `<li class="text-warning">${linkifyUrls(warning)}</li>`;
|
|
200
|
+
});
|
|
201
|
+
html += `</ul></div>`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Display success messages array with clickable URLs
|
|
206
|
+
// Add detail-success class so these can be hidden when "Show passing checks" is disabled
|
|
207
|
+
// This helps users focus on issues while still providing complete information when needed
|
|
208
|
+
if (details.success && details.success.length > 0) {
|
|
209
|
+
html += `<div class="mt-2 detail-success"><strong class="text-success">Success:</strong><ul class="mb-0 mt-1">`;
|
|
210
|
+
details.success.forEach(success => {
|
|
211
|
+
html += `<li class="text-success">${linkifyUrls(success)}</li>`;
|
|
212
|
+
});
|
|
213
|
+
html += `</ul></div>`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Display informational messages array with clickable URLs
|
|
217
|
+
// Info messages provide additional context without indicating success or failure
|
|
218
|
+
if (details.info && details.info.length > 0) {
|
|
219
|
+
html += `<div class="mt-2 detail-info"><strong class="text-info">Info:</strong><ul class="mb-0 mt-1">`;
|
|
220
|
+
details.info.forEach(info => {
|
|
221
|
+
html += `<li class="text-muted">${linkifyUrls(info)}</li>`;
|
|
222
|
+
});
|
|
223
|
+
html += `</ul></div>`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Display missing fields (existing functionality for backward compatibility)
|
|
227
|
+
// Provides a quick summary of what's missing in validation results
|
|
228
|
+
if (details.missingFields && details.missingFields.length > 0) {
|
|
229
|
+
html += `<br><small class="text-muted">Missing fields: ${details.missingFields.map(escapeHtml).join(', ')}</small>`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Display count (existing functionality)
|
|
233
|
+
// Shows the number of items found in checks that count resources
|
|
234
|
+
if (details.count !== undefined) {
|
|
235
|
+
html += `<br><small class="text-muted">Files found: ${details.count}</small>`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Display package data (existing functionality)
|
|
239
|
+
// Shows package name and version for package.json validation
|
|
240
|
+
if (details.packageData) {
|
|
241
|
+
html += `<br><small class="text-muted">Package: ${escapeHtml(details.packageData.name)}@${escapeHtml(details.packageData.version)}</small>`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return html;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Formats console messages in a compact list format.
|
|
249
|
+
* This is used when showing errors/warnings from console-messages check.
|
|
250
|
+
*
|
|
251
|
+
* @param {string} title - Title for the list (e.g., "Errors", "Warnings")
|
|
252
|
+
* @param {Array<Object>} messages - Array of console message objects
|
|
253
|
+
* @param {string} colorClass - Bootstrap color class ('danger', 'warning', etc.)
|
|
254
|
+
* @returns {string} HTML string with formatted list
|
|
255
|
+
*/
|
|
256
|
+
export function formatConsoleMessageList(title, messages, colorClass) {
|
|
257
|
+
let html = `<div class="mt-2"><strong class="text-${colorClass}">${escapeHtml(title)}:</strong>`;
|
|
258
|
+
html += `<div class="table-responsive mt-2"><table class="table-sm" style="width: 100%; background-color: transparent;">`;
|
|
259
|
+
html += `<thead>`;
|
|
260
|
+
html += `<tr>`;
|
|
261
|
+
// html += `<th style="width: 150px; background-color: transparent">Timestamp</th>`;
|
|
262
|
+
html += `<th class="d-none" style="background-color: transparent">Message</th>`;
|
|
263
|
+
html += `</tr>`;
|
|
264
|
+
html += `</thead>`;
|
|
265
|
+
html += `<tbody>`;
|
|
266
|
+
|
|
267
|
+
messages.forEach(msg => {
|
|
268
|
+
const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString() : 'N/A';
|
|
269
|
+
const message = msg.message || '';
|
|
270
|
+
const additionalData = msg.additionalData ? ` [${Array.isArray(msg.additionalData) ? msg.additionalData.join(', ') : msg.additionalData}]` : '';
|
|
271
|
+
|
|
272
|
+
html += `<tr>`;
|
|
273
|
+
// html += `<td><small>${escapeHtml(timestamp)}</small></td>`;
|
|
274
|
+
html += `<td><small>${linkifyUrls(message + additionalData)}</small></td>`;
|
|
275
|
+
html += `</tr>`;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
html += `</tbody></table></div></div>`;
|
|
279
|
+
return html;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Formats all console messages in a detailed table format.
|
|
284
|
+
* This is used when the check includes allMessages for verbose display.
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} details - Details object containing console message data
|
|
287
|
+
* @returns {string} HTML string with formatted table
|
|
288
|
+
*/
|
|
289
|
+
export function formatConsoleMessagesTable(details) {
|
|
290
|
+
const { analysis, allMessages, metadata } = details;
|
|
291
|
+
|
|
292
|
+
let html = '<div class="mt-3">';
|
|
293
|
+
|
|
294
|
+
// // Add a clear heading for the full messages table
|
|
295
|
+
// html += `<div class="mb-3">`;
|
|
296
|
+
// html += `<strong class="text-primary">All Console Messages (${allMessages.length}):</strong><br>`;
|
|
297
|
+
// html += `<small class="text-muted">Complete log of all operations with timestamp, type, and details</small>`;
|
|
298
|
+
// html += `</div>`;
|
|
299
|
+
|
|
300
|
+
// // Add summary statistics
|
|
301
|
+
// if (analysis) {
|
|
302
|
+
// html += `<div class="mb-3">`;
|
|
303
|
+
// html += `<strong>Summary:</strong><br>`;
|
|
304
|
+
// html += `<small class="text-muted">`;
|
|
305
|
+
// html += `Total: ${analysis.totalMessages || 0} | `;
|
|
306
|
+
// html += `<span class="text-success">Success: ${analysis.successCount || 0}</span> | `;
|
|
307
|
+
// html += `<span class="text-danger">Errors: ${analysis.errorCount || 0}</span> | `;
|
|
308
|
+
// html += `<span class="text-warning">Warnings: ${analysis.warningCount || 0}</span>`;
|
|
309
|
+
// html += `</small>`;
|
|
310
|
+
// html += `</div>`;
|
|
311
|
+
// }
|
|
312
|
+
|
|
313
|
+
// // Add operations info if available
|
|
314
|
+
// if (metadata && metadata.operations && metadata.operations.length > 0) {
|
|
315
|
+
// html += `<div class="mb-2">`;
|
|
316
|
+
// html += `<small class="text-muted">Operations: ${metadata.operations.map(escapeHtml).join(', ')}</small>`;
|
|
317
|
+
// html += `</div>`;
|
|
318
|
+
// }
|
|
319
|
+
|
|
320
|
+
// Create the messages table (without Operation column)
|
|
321
|
+
html += `<div class="table-responsive">`;
|
|
322
|
+
html += `<table class="table-sm table-striped" style="width: 100%;">`;
|
|
323
|
+
html += `<thead class="table-light">`;
|
|
324
|
+
html += `<tr>`;
|
|
325
|
+
// html += `<th style="width: 150px;">Timestamp</th>`;
|
|
326
|
+
// html += `<th style="width: 80px;">Type</th>`;
|
|
327
|
+
html += `<th class="d-none">Message</th>`;
|
|
328
|
+
// html += `<th style="width: 150px;">Additional Data</th>`;
|
|
329
|
+
html += `</tr>`;
|
|
330
|
+
html += `</thead>`;
|
|
331
|
+
html += `<tbody>`;
|
|
332
|
+
|
|
333
|
+
// Format each message as a table row
|
|
334
|
+
allMessages.forEach(msg => {
|
|
335
|
+
const timestamp = msg.timestamp ? new Date(msg.timestamp).toLocaleString() : 'N/A';
|
|
336
|
+
const type = msg.type || 'info';
|
|
337
|
+
const message = msg.message || '';
|
|
338
|
+
const additionalData = msg.additionalData
|
|
339
|
+
? (Array.isArray(msg.additionalData) ? msg.additionalData.join(', ') : String(msg.additionalData))
|
|
340
|
+
: '';
|
|
341
|
+
|
|
342
|
+
// Apply color based on message type
|
|
343
|
+
let typeClass = 'text-muted';
|
|
344
|
+
let typeBadge = 'secondary';
|
|
345
|
+
switch (type) {
|
|
346
|
+
case 'error':
|
|
347
|
+
typeClass = 'text-danger';
|
|
348
|
+
typeBadge = 'danger';
|
|
349
|
+
break;
|
|
350
|
+
case 'warn':
|
|
351
|
+
typeClass = 'text-warning';
|
|
352
|
+
typeBadge = 'warning';
|
|
353
|
+
break;
|
|
354
|
+
case 'success':
|
|
355
|
+
typeClass = 'text-success';
|
|
356
|
+
typeBadge = 'success';
|
|
357
|
+
break;
|
|
358
|
+
case 'info':
|
|
359
|
+
typeClass = 'text-info';
|
|
360
|
+
typeBadge = 'info';
|
|
361
|
+
break;
|
|
362
|
+
case 'highlight':
|
|
363
|
+
typeClass = 'text-primary';
|
|
364
|
+
typeBadge = 'primary';
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
html += `<tr>`;
|
|
369
|
+
// html += `<td><small>${escapeHtml(timestamp)}</small></td>`;
|
|
370
|
+
// html += `<td><span class="badge bg-${typeBadge}">${escapeHtml(type)}</span></td>`;
|
|
371
|
+
html += `<td><small>${linkifyUrls(message)}</small></td>`;
|
|
372
|
+
// html += `<td><small class="text-muted">${escapeHtml(additionalData)}</small></td>`;
|
|
373
|
+
html += `</tr>`;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
html += `</tbody></table></div></div>`;
|
|
377
|
+
|
|
378
|
+
return html;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Formats markdown tables details into HTML.
|
|
383
|
+
*
|
|
384
|
+
* @param {Object} details - Details object containing markdown tables data
|
|
385
|
+
* @returns {string} HTML string with formatted table issues
|
|
386
|
+
*/
|
|
387
|
+
export function formatMarkdownTablesDetails(details) {
|
|
388
|
+
const { details: fileResults } = details;
|
|
389
|
+
|
|
390
|
+
let html = '<div class="mt-2">';
|
|
391
|
+
html += '<div class="table-responsive">';
|
|
392
|
+
html += '<table class="table table-sm table-striped">';
|
|
393
|
+
html += '<thead class="table-light">';
|
|
394
|
+
html += '<tr>';
|
|
395
|
+
html += '<th>File</th>';
|
|
396
|
+
html += '<th>Line</th>';
|
|
397
|
+
html += '<th>Issue</th>';
|
|
398
|
+
html += '<th>Content</th>';
|
|
399
|
+
html += '</tr>';
|
|
400
|
+
html += '</thead>';
|
|
401
|
+
html += '<tbody>';
|
|
402
|
+
|
|
403
|
+
fileResults.forEach(fileResult => {
|
|
404
|
+
fileResult.tables.forEach(table => {
|
|
405
|
+
table.issues.forEach(issue => {
|
|
406
|
+
const severityClass = issue.severity === 'error' ? 'text-danger' :
|
|
407
|
+
issue.severity === 'warning' ? 'text-warning' : 'text-info';
|
|
408
|
+
const severityIcon = issue.severity === 'error' ? 'bi-exclamation-triangle-fill' :
|
|
409
|
+
issue.severity === 'warning' ? 'bi-exclamation-circle-fill' : 'bi-info-circle-fill';
|
|
410
|
+
|
|
411
|
+
html += '<tr>';
|
|
412
|
+
html += `<td><small><code>${escapeHtml(fileResult.file)}</code></small></td>`;
|
|
413
|
+
html += `<td><small>${issue.line}</small></td>`;
|
|
414
|
+
html += `<td><small class="${severityClass}"><i class="bi ${severityIcon}"></i> ${escapeHtml(issue.message)}</small></td>`;
|
|
415
|
+
html += `<td><small class="text-muted">${escapeHtml(issue.content || '')}</small></td>`;
|
|
416
|
+
html += '</tr>';
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
html += '</tbody>';
|
|
422
|
+
html += '</table>';
|
|
423
|
+
html += '</div>';
|
|
424
|
+
html += '</div>';
|
|
425
|
+
|
|
426
|
+
return html;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Formats a single health check result into a table row HTML string.
|
|
431
|
+
*
|
|
432
|
+
* This function is useful for building tables of health check results.
|
|
433
|
+
* It includes status badges, check names, messages, and formatted details.
|
|
434
|
+
*
|
|
435
|
+
* @param {Object} result - A health check result object
|
|
436
|
+
* @param {string} result.status - Status of the check ('pass', 'fail', 'warn', 'skip')
|
|
437
|
+
* @param {string} result.check - Name of the health check
|
|
438
|
+
* @param {string} result.message - Primary message describing the result
|
|
439
|
+
* @param {Object} [result.details] - Additional details to format
|
|
440
|
+
* @returns {string} HTML table row string
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* ```javascript
|
|
444
|
+
* const result = {
|
|
445
|
+
* status: 'fail',
|
|
446
|
+
* check: 'specs.json validation',
|
|
447
|
+
* message: 'specs.json has 2 error(s)',
|
|
448
|
+
* details: {
|
|
449
|
+
* errors: ['Field "title" is missing', 'Field "author" is empty']
|
|
450
|
+
* }
|
|
451
|
+
* };
|
|
452
|
+
* const rowHtml = formatResultAsTableRow(result);
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
export function formatResultAsTableRow(result) {
|
|
456
|
+
const { statusClass, statusIcon, statusText } = getStatusDisplay(result);
|
|
457
|
+
const rowClass = getRowClass(result);
|
|
458
|
+
|
|
459
|
+
let detailsHtml = '';
|
|
460
|
+
if (result.details && Object.keys(result.details).length > 0) {
|
|
461
|
+
detailsHtml = formatResultDetails(result.details);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return `<tr data-status="${result.status}" class="check-row ${rowClass}">
|
|
465
|
+
<td class="${statusClass} status-badge">
|
|
466
|
+
<i class="bi ${statusIcon} status-icon"></i>
|
|
467
|
+
<span>${statusText}</span>
|
|
468
|
+
</td>
|
|
469
|
+
<td>${escapeHtml(result.check)}</td>
|
|
470
|
+
<td>
|
|
471
|
+
${escapeHtml(result.message)}
|
|
472
|
+
${detailsHtml}
|
|
473
|
+
</td>
|
|
474
|
+
</tr>`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Gets display properties (class, icon, text) for a result status.
|
|
479
|
+
*
|
|
480
|
+
* This helper function centralizes the mapping between status values
|
|
481
|
+
* and their visual representation, ensuring consistency across the UI.
|
|
482
|
+
*
|
|
483
|
+
* @param {Object} result - The health check result
|
|
484
|
+
* @param {string} result.status - Status value ('pass', 'fail', 'warn', 'skip')
|
|
485
|
+
* @returns {{statusClass: string, statusIcon: string, statusText: string}}
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```javascript
|
|
489
|
+
* const display = getStatusDisplay({ status: 'fail' });
|
|
490
|
+
* // Returns: {
|
|
491
|
+
* // statusClass: 'text-danger',
|
|
492
|
+
* // statusIcon: 'bi-x-circle-fill',
|
|
493
|
+
* // statusText: 'Fail'
|
|
494
|
+
* // }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
export function getStatusDisplay(result) {
|
|
498
|
+
switch (result.status) {
|
|
499
|
+
case 'pass':
|
|
500
|
+
return {
|
|
501
|
+
statusClass: 'text-success',
|
|
502
|
+
statusIcon: 'bi-check-circle-fill',
|
|
503
|
+
statusText: 'Pass'
|
|
504
|
+
};
|
|
505
|
+
case 'fail':
|
|
506
|
+
return {
|
|
507
|
+
statusClass: 'text-danger',
|
|
508
|
+
statusIcon: 'bi-x-circle-fill',
|
|
509
|
+
statusText: 'Fail'
|
|
510
|
+
};
|
|
511
|
+
case 'warn':
|
|
512
|
+
return {
|
|
513
|
+
statusClass: 'text-warning',
|
|
514
|
+
statusIcon: 'bi-exclamation-triangle-fill',
|
|
515
|
+
statusText: 'Warning'
|
|
516
|
+
};
|
|
517
|
+
case 'skip':
|
|
518
|
+
return {
|
|
519
|
+
statusClass: 'text-muted',
|
|
520
|
+
statusIcon: 'bi-dash-circle',
|
|
521
|
+
statusText: 'Skipped'
|
|
522
|
+
};
|
|
523
|
+
default:
|
|
524
|
+
return {
|
|
525
|
+
statusClass: 'text-secondary',
|
|
526
|
+
statusIcon: 'bi-question-circle',
|
|
527
|
+
statusText: 'Unknown'
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Gets the appropriate CSS class for a table row based on the result status.
|
|
534
|
+
*
|
|
535
|
+
* This provides Bootstrap table row styling to visually distinguish
|
|
536
|
+
* different result statuses in tabular displays.
|
|
537
|
+
*
|
|
538
|
+
* @param {Object} result - The health check result
|
|
539
|
+
* @param {string} result.status - Status value ('pass', 'fail', 'warn')
|
|
540
|
+
* @returns {string} CSS class name
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```javascript
|
|
544
|
+
* const rowClass = getRowClass({ status: 'fail' });
|
|
545
|
+
* // Returns: 'table-danger'
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
export function getRowClass(result) {
|
|
549
|
+
switch (result.status) {
|
|
550
|
+
case 'fail':
|
|
551
|
+
return 'table-danger';
|
|
552
|
+
case 'warn':
|
|
553
|
+
return 'table-warning';
|
|
554
|
+
case 'pass':
|
|
555
|
+
return 'table-success';
|
|
556
|
+
default:
|
|
557
|
+
return '';
|
|
558
|
+
}
|
|
559
|
+
}
|
package/lib/formatters.js
CHANGED
|
@@ -98,6 +98,20 @@ export function formatResultsAsText(healthCheckOutput, useColors = false) {
|
|
|
98
98
|
if (result.details.packageData) {
|
|
99
99
|
output.push(` Package: ${result.details.packageData.name}@${result.details.packageData.version}`);
|
|
100
100
|
}
|
|
101
|
+
// Special handling for markdown-tables check
|
|
102
|
+
if (result.check === 'markdown-tables' && result.details.details) {
|
|
103
|
+
const fileResults = result.details.details;
|
|
104
|
+
fileResults.forEach(fileResult => {
|
|
105
|
+
output.push(` đ ${fileResult.file}:`);
|
|
106
|
+
fileResult.tables.forEach(table => {
|
|
107
|
+
output.push(` Table at line ${table.startLine}:`);
|
|
108
|
+
table.issues.forEach(issue => {
|
|
109
|
+
const severityIcon = issue.severity === 'error' ? 'â' : issue.severity === 'warning' ? 'â ī¸' : 'âšī¸';
|
|
110
|
+
output.push(` ${severityIcon} Line ${issue.line}: ${issue.message}`);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
output.push('');
|