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,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview External Specs URL Validator
|
|
3
|
+
*
|
|
4
|
+
* This module validates external specification references in specs.json,
|
|
5
|
+
* specifically checking the structure and accessibility of URLs.
|
|
6
|
+
* It verifies both gh_page and url fields exist, have correct formats,
|
|
7
|
+
* and return HTTP 200 responses.
|
|
8
|
+
*
|
|
9
|
+
* @author spec-up-t-healthcheck
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import axios from 'axios';
|
|
13
|
+
import { createHealthCheckResult, createErrorResult } from '../health-check-utils.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The identifier for this health check
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
export const CHECK_ID = 'external-specs-urls';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Human-readable name for this health check
|
|
23
|
+
* @type {string}
|
|
24
|
+
*/
|
|
25
|
+
export const CHECK_NAME = 'External Specs URL Validation';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Description of what this health check validates
|
|
29
|
+
* @type {string}
|
|
30
|
+
*/
|
|
31
|
+
export const CHECK_DESCRIPTION = 'Validates external specification URLs exist, have correct structure, and are accessible';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Timeout for HTTP requests in milliseconds
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
const HTTP_TIMEOUT = 10000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Maximum number of redirects to follow
|
|
41
|
+
* @type {number}
|
|
42
|
+
*/
|
|
43
|
+
const MAX_REDIRECTS = 5;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validates that a string is a properly formatted URL
|
|
47
|
+
*
|
|
48
|
+
* @param {string} urlString - The URL string to validate
|
|
49
|
+
* @returns {{isValid: boolean, message?: string}} Validation result
|
|
50
|
+
*/
|
|
51
|
+
function validateUrlStructure(urlString) {
|
|
52
|
+
if (!urlString || typeof urlString !== 'string') {
|
|
53
|
+
return { isValid: false, message: 'URL is missing or not a string' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (urlString.trim() === '') {
|
|
57
|
+
return { isValid: false, message: 'URL is empty' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(urlString);
|
|
62
|
+
|
|
63
|
+
// Check protocol
|
|
64
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
65
|
+
return {
|
|
66
|
+
isValid: false,
|
|
67
|
+
message: `URL must use http or https protocol, found: ${url.protocol}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check for hostname
|
|
72
|
+
if (!url.hostname) {
|
|
73
|
+
return { isValid: false, message: 'URL must have a hostname' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { isValid: true };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
isValid: false,
|
|
80
|
+
message: `Invalid URL format: ${error.message}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validates that a URL matches GitHub Pages URL pattern
|
|
87
|
+
*
|
|
88
|
+
* @param {string} urlString - The URL string to validate
|
|
89
|
+
* @returns {{isValid: boolean, message?: string}} Validation result
|
|
90
|
+
*/
|
|
91
|
+
function validateGitHubPagesStructure(urlString) {
|
|
92
|
+
const structureCheck = validateUrlStructure(urlString);
|
|
93
|
+
if (!structureCheck.isValid) {
|
|
94
|
+
return structureCheck;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const url = new URL(urlString);
|
|
99
|
+
const hostname = url.hostname.toLowerCase();
|
|
100
|
+
|
|
101
|
+
// Check if it's a GitHub Pages URL
|
|
102
|
+
if (hostname.endsWith('.github.io')) {
|
|
103
|
+
return { isValid: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Custom domain could also be used for GitHub Pages
|
|
107
|
+
// We'll accept any valid URL but note it's not standard GitHub Pages
|
|
108
|
+
return {
|
|
109
|
+
isValid: true,
|
|
110
|
+
message: `Valid URL (${urlString}) but not a standard GitHub Pages domain (.github.io)`
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
isValid: false,
|
|
115
|
+
message: `Error validating GitHub Pages URL: ${error.message}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validates that a URL matches GitHub repository URL pattern
|
|
122
|
+
*
|
|
123
|
+
* @param {string} urlString - The URL string to validate
|
|
124
|
+
* @returns {{isValid: boolean, message?: string}} Validation result
|
|
125
|
+
*/
|
|
126
|
+
function validateGitHubRepoStructure(urlString) {
|
|
127
|
+
const structureCheck = validateUrlStructure(urlString);
|
|
128
|
+
if (!structureCheck.isValid) {
|
|
129
|
+
return structureCheck;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(urlString);
|
|
134
|
+
const hostname = url.hostname.toLowerCase();
|
|
135
|
+
|
|
136
|
+
// Check if it's a GitHub URL
|
|
137
|
+
if (hostname === 'github.com' || hostname === 'www.github.com') {
|
|
138
|
+
// Check basic path structure (should have at least /owner/repo)
|
|
139
|
+
const pathParts = url.pathname.split('/').filter(part => part.length > 0);
|
|
140
|
+
|
|
141
|
+
if (pathParts.length < 2) {
|
|
142
|
+
return {
|
|
143
|
+
isValid: false,
|
|
144
|
+
message: 'GitHub URL should have format: https://github.com/{owner}/{repo}'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { isValid: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
isValid: false,
|
|
153
|
+
message: 'URL is not a GitHub repository URL (should be github.com)'
|
|
154
|
+
};
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
isValid: false,
|
|
158
|
+
message: `Error validating GitHub repository URL: ${error.message}`
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Checks if a URL is accessible and returns HTTP 200
|
|
165
|
+
*
|
|
166
|
+
* @param {string} url - The URL to check
|
|
167
|
+
* @param {string} fieldName - Name of the field being checked (for error messages)
|
|
168
|
+
* @returns {Promise<{isAccessible: boolean, statusCode?: number, message?: string}>}
|
|
169
|
+
*/
|
|
170
|
+
async function checkUrlAccessibility(url, fieldName) {
|
|
171
|
+
// First, try HEAD request (more efficient)
|
|
172
|
+
const headResult = await attemptHeadRequest(url, fieldName);
|
|
173
|
+
if (headResult !== null) {
|
|
174
|
+
return headResult;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If HEAD fails, try GET request (some servers don't support HEAD)
|
|
178
|
+
const getResult = await attemptGetRequest(url, fieldName);
|
|
179
|
+
return getResult;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Attempts to check URL accessibility using HEAD request
|
|
184
|
+
*
|
|
185
|
+
* @param {string} url - The URL to check
|
|
186
|
+
* @param {string} fieldName - Name of the field being checked
|
|
187
|
+
* @returns {Promise<Object|null>} Result object or null if HEAD is not supported
|
|
188
|
+
*/
|
|
189
|
+
async function attemptHeadRequest(url, fieldName) {
|
|
190
|
+
try {
|
|
191
|
+
const response = await axios.head(url, {
|
|
192
|
+
timeout: HTTP_TIMEOUT,
|
|
193
|
+
maxRedirects: MAX_REDIRECTS,
|
|
194
|
+
validateStatus: (status) => status < 500
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return createAccessibilityResult(response.status, fieldName);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Return null to signal that GET should be attempted
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Attempts to check URL accessibility using GET request
|
|
206
|
+
*
|
|
207
|
+
* @param {string} url - The URL to check
|
|
208
|
+
* @param {string} fieldName - Name of the field being checked
|
|
209
|
+
* @returns {Promise<Object>} Result object
|
|
210
|
+
*/
|
|
211
|
+
async function attemptGetRequest(url, fieldName) {
|
|
212
|
+
try {
|
|
213
|
+
const response = await axios.get(url, {
|
|
214
|
+
timeout: HTTP_TIMEOUT,
|
|
215
|
+
maxRedirects: MAX_REDIRECTS,
|
|
216
|
+
validateStatus: (status) => status < 500
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return createAccessibilityResult(response.status, fieldName);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
isAccessible: false,
|
|
223
|
+
message: `${fieldName} is not accessible: ${error.code || error.message}`
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Creates a standardized accessibility result based on HTTP status code
|
|
230
|
+
*
|
|
231
|
+
* @param {number} statusCode - HTTP status code
|
|
232
|
+
* @param {string} fieldName - Name of the field being checked
|
|
233
|
+
* @returns {Object} Accessibility result
|
|
234
|
+
*/
|
|
235
|
+
function createAccessibilityResult(statusCode, fieldName) {
|
|
236
|
+
if (statusCode === 200) {
|
|
237
|
+
return {
|
|
238
|
+
isAccessible: true,
|
|
239
|
+
statusCode: 200
|
|
240
|
+
};
|
|
241
|
+
} else {
|
|
242
|
+
return {
|
|
243
|
+
isAccessible: false,
|
|
244
|
+
statusCode: statusCode,
|
|
245
|
+
message: `${fieldName} returned HTTP ${statusCode}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validates a single external spec entry
|
|
252
|
+
*
|
|
253
|
+
* @param {Object} spec - The external spec object to validate
|
|
254
|
+
* @param {number} index - Index of the spec in the array
|
|
255
|
+
* @param {boolean} checkAccessibility - Whether to check URL accessibility
|
|
256
|
+
* @returns {Promise<Object>} Validation results for this spec
|
|
257
|
+
*/
|
|
258
|
+
async function validateExternalSpec(spec, index, checkAccessibility = true) {
|
|
259
|
+
const results = {
|
|
260
|
+
specIndex: index,
|
|
261
|
+
specId: spec.external_spec || `[spec ${index}]`,
|
|
262
|
+
errors: [],
|
|
263
|
+
warnings: [],
|
|
264
|
+
success: []
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Check gh_page field existence
|
|
268
|
+
if (!('gh_page' in spec)) {
|
|
269
|
+
results.errors.push('Field "gh_page" is missing');
|
|
270
|
+
} else if (!spec.gh_page) {
|
|
271
|
+
results.errors.push('Field "gh_page" is empty');
|
|
272
|
+
} else {
|
|
273
|
+
results.success.push('Field "gh_page" exists');
|
|
274
|
+
|
|
275
|
+
// Validate gh_page structure
|
|
276
|
+
const ghPageStructure = validateGitHubPagesStructure(spec.gh_page);
|
|
277
|
+
if (!ghPageStructure.isValid) {
|
|
278
|
+
results.errors.push(`gh_page structure invalid: ${ghPageStructure.message}`);
|
|
279
|
+
} else {
|
|
280
|
+
results.success.push('Field "gh_page" has valid URL structure');
|
|
281
|
+
|
|
282
|
+
if (ghPageStructure.message) {
|
|
283
|
+
results.warnings.push(ghPageStructure.message);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check gh_page accessibility
|
|
287
|
+
if (checkAccessibility) {
|
|
288
|
+
const accessibility = await checkUrlAccessibility(spec.gh_page, 'gh_page');
|
|
289
|
+
if (accessibility.isAccessible) {
|
|
290
|
+
results.success.push(`gh_page is accessible (HTTP ${accessibility.statusCode})`);
|
|
291
|
+
} else {
|
|
292
|
+
results.errors.push(accessibility.message || 'gh_page is not accessible');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check url field existence
|
|
299
|
+
if (!('url' in spec)) {
|
|
300
|
+
results.errors.push('Field "url" is missing');
|
|
301
|
+
} else if (!spec.url) {
|
|
302
|
+
results.errors.push('Field "url" is empty');
|
|
303
|
+
} else {
|
|
304
|
+
results.success.push('Field "url" exists');
|
|
305
|
+
|
|
306
|
+
// Validate url structure
|
|
307
|
+
const urlStructure = validateGitHubRepoStructure(spec.url);
|
|
308
|
+
if (!urlStructure.isValid) {
|
|
309
|
+
results.errors.push(`url structure invalid: ${urlStructure.message}`);
|
|
310
|
+
} else {
|
|
311
|
+
results.success.push('Field "url" has valid GitHub repository structure');
|
|
312
|
+
|
|
313
|
+
// Check url accessibility
|
|
314
|
+
if (checkAccessibility) {
|
|
315
|
+
const accessibility = await checkUrlAccessibility(spec.url, 'url');
|
|
316
|
+
if (accessibility.isAccessible) {
|
|
317
|
+
results.success.push(`url is accessible (HTTP ${accessibility.statusCode})`);
|
|
318
|
+
} else {
|
|
319
|
+
results.errors.push(accessibility.message || 'url is not accessible');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return results;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Validates external specification URLs in specs.json
|
|
330
|
+
*
|
|
331
|
+
* This health check performs comprehensive validation of external spec references:
|
|
332
|
+
* 1. Checks if gh_page field exists and is not empty
|
|
333
|
+
* 2. Validates gh_page URL structure (proper format for GitHub Pages)
|
|
334
|
+
* 3. Checks if gh_page URL is accessible (returns HTTP 200)
|
|
335
|
+
* 4. Checks if url field exists and is not empty
|
|
336
|
+
* 5. Validates url structure (proper format for GitHub repository)
|
|
337
|
+
* 6. Checks if url is accessible (returns HTTP 200)
|
|
338
|
+
*
|
|
339
|
+
* @param {import('../providers.js').Provider} provider - The provider instance for file operations
|
|
340
|
+
* @param {Object} options - Validation options
|
|
341
|
+
* @param {boolean} options.checkAccessibility - Whether to check URL accessibility (default: true)
|
|
342
|
+
* @returns {Promise<import('../health-check-utils.js').HealthCheckResult>} The health check result
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```javascript
|
|
346
|
+
* const provider = createLocalProvider('/path/to/repo');
|
|
347
|
+
* const result = await checkExternalSpecsUrls(provider);
|
|
348
|
+
* console.log(result.status); // 'pass', 'fail', or 'warn'
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
export async function checkExternalSpecsUrls(provider, options = {}) {
|
|
352
|
+
const { checkAccessibility = true } = options;
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
// Check if specs.json exists
|
|
356
|
+
const exists = await provider.fileExists('specs.json');
|
|
357
|
+
if (!exists) {
|
|
358
|
+
return createHealthCheckResult(
|
|
359
|
+
CHECK_NAME,
|
|
360
|
+
'fail',
|
|
361
|
+
'specs.json not found - cannot validate external specs',
|
|
362
|
+
{
|
|
363
|
+
suggestions: [
|
|
364
|
+
'Create a specs.json file in the repository root',
|
|
365
|
+
'Run the specs-json health check first'
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Read and parse specs.json
|
|
372
|
+
const content = await provider.readFile('specs.json');
|
|
373
|
+
let specsData;
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
specsData = JSON.parse(content);
|
|
377
|
+
} catch (parseError) {
|
|
378
|
+
return createHealthCheckResult(
|
|
379
|
+
CHECK_NAME,
|
|
380
|
+
'fail',
|
|
381
|
+
'specs.json contains invalid JSON',
|
|
382
|
+
{ parseError: parseError.message }
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check if specs array exists
|
|
387
|
+
if (!specsData.specs || !Array.isArray(specsData.specs) || specsData.specs.length === 0) {
|
|
388
|
+
return createHealthCheckResult(
|
|
389
|
+
CHECK_NAME,
|
|
390
|
+
'fail',
|
|
391
|
+
'No specs found in specs.json',
|
|
392
|
+
{ details: 'specs.json must contain a "specs" array with at least one entry' }
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const spec = specsData.specs[0];
|
|
397
|
+
|
|
398
|
+
// Check if external_specs exists
|
|
399
|
+
if (!spec.external_specs) {
|
|
400
|
+
return createHealthCheckResult(
|
|
401
|
+
CHECK_NAME,
|
|
402
|
+
'pass',
|
|
403
|
+
'No external_specs defined (this is acceptable)',
|
|
404
|
+
{
|
|
405
|
+
info: 'This specification does not reference external specifications',
|
|
406
|
+
note: 'If you want to add external specs, add an "external_specs" array to your spec'
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Validate external_specs is an array
|
|
412
|
+
if (!Array.isArray(spec.external_specs)) {
|
|
413
|
+
return createHealthCheckResult(
|
|
414
|
+
CHECK_NAME,
|
|
415
|
+
'fail',
|
|
416
|
+
'external_specs must be an array',
|
|
417
|
+
{ actualType: typeof spec.external_specs }
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// If empty array, that's acceptable
|
|
422
|
+
if (spec.external_specs.length === 0) {
|
|
423
|
+
return createHealthCheckResult(
|
|
424
|
+
CHECK_NAME,
|
|
425
|
+
'pass',
|
|
426
|
+
'external_specs array is empty (this is acceptable)',
|
|
427
|
+
{ info: 'No external specifications are configured' }
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Validate each external spec
|
|
432
|
+
const allResults = [];
|
|
433
|
+
const totalErrors = [];
|
|
434
|
+
const totalWarnings = [];
|
|
435
|
+
const totalSuccess = [];
|
|
436
|
+
|
|
437
|
+
for (let i = 0; i < spec.external_specs.length; i++) {
|
|
438
|
+
const extSpec = spec.external_specs[i];
|
|
439
|
+
const result = await validateExternalSpec(extSpec, i, checkAccessibility);
|
|
440
|
+
allResults.push(result);
|
|
441
|
+
|
|
442
|
+
totalErrors.push(...result.errors.map(err => `${result.specId}: ${err}`));
|
|
443
|
+
totalWarnings.push(...result.warnings.map(warn => `${result.specId}: ${warn}`));
|
|
444
|
+
totalSuccess.push(...result.success.map(succ => `${result.specId}: ${succ}`));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Determine overall status
|
|
448
|
+
let status = 'pass';
|
|
449
|
+
let message = `All ${spec.external_specs.length} external spec(s) validated successfully`;
|
|
450
|
+
|
|
451
|
+
if (totalErrors.length > 0) {
|
|
452
|
+
status = 'fail';
|
|
453
|
+
message = `Found ${totalErrors.length} error(s) in external specs`;
|
|
454
|
+
} else if (totalWarnings.length > 0) {
|
|
455
|
+
status = 'warn';
|
|
456
|
+
message = `External specs validated with ${totalWarnings.length} warning(s)`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return createHealthCheckResult(
|
|
460
|
+
CHECK_NAME,
|
|
461
|
+
status,
|
|
462
|
+
message,
|
|
463
|
+
{
|
|
464
|
+
totalSpecs: spec.external_specs.length,
|
|
465
|
+
errors: totalErrors,
|
|
466
|
+
warnings: totalWarnings,
|
|
467
|
+
success: totalSuccess,
|
|
468
|
+
detailedResults: allResults,
|
|
469
|
+
accessibilityChecked: checkAccessibility
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
} catch (error) {
|
|
474
|
+
return createErrorResult(CHECK_NAME, error);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Export as default for backward compatibility
|
|
479
|
+
export default {
|
|
480
|
+
CHECK_ID,
|
|
481
|
+
CHECK_NAME,
|
|
482
|
+
CHECK_DESCRIPTION,
|
|
483
|
+
checkExternalSpecsUrls
|
|
484
|
+
};
|