wcag-scanner 1.2.65 → 1.2.67
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 +107 -83
- package/dist/index.d.ts +5 -24
- package/dist/index.js +11 -126
- package/dist/middleware/express.js +42 -18
- package/dist/react/WcagDevOverlay.d.ts +2 -0
- package/dist/react/WcagDevOverlay.js +282 -154
- package/dist/react/browserScanner.d.ts +11 -1
- package/dist/react/browserScanner.js +107 -22
- package/dist/react/gemini.d.ts +12 -0
- package/dist/react/gemini.js +60 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +10 -1
- package/dist/rules/backgroundImages.d.ts +8 -0
- package/dist/rules/backgroundImages.js +116 -0
- package/dist/rules/contrast.js +52 -23
- package/dist/rules/images.js +0 -91
- package/dist/rules/presets.d.ts +8 -0
- package/dist/rules/presets.js +19 -0
- package/dist/scanner.js +14 -12
- package/dist/types/index.d.ts +3 -0
- package/package.json +16 -15
- package/dist/ai/suggestions.d.ts +0 -11
- package/dist/ai/suggestions.js +0 -103
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -160
- package/dist/wasm/index.d.ts +0 -19
- package/dist/wasm/index.js +0 -53
package/dist/rules/images.js
CHANGED
|
@@ -21,8 +21,6 @@ exports.default = {
|
|
|
21
21
|
checkImageElements(document, results);
|
|
22
22
|
// Check <svg> elements
|
|
23
23
|
checkSvgElements(document, results);
|
|
24
|
-
// Check background images
|
|
25
|
-
checkBackgroundImages(document, window, results);
|
|
26
24
|
// Check image maps
|
|
27
25
|
checkImageMaps(document, results);
|
|
28
26
|
return results;
|
|
@@ -187,54 +185,6 @@ function checkSvgElements(document, results) {
|
|
|
187
185
|
}
|
|
188
186
|
});
|
|
189
187
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Check background images for accessibility issues
|
|
192
|
-
* @param document DOM document
|
|
193
|
-
* @param window Window object
|
|
194
|
-
* @param results Scan results
|
|
195
|
-
*/
|
|
196
|
-
function checkBackgroundImages(document, window, results) {
|
|
197
|
-
// Find elements with background image
|
|
198
|
-
const allElements = document.querySelectorAll('*');
|
|
199
|
-
allElements.forEach(element => {
|
|
200
|
-
var _a, _b, _c;
|
|
201
|
-
const style = window.getComputedStyle(element);
|
|
202
|
-
const backgroundImage = style.backgroundImage;
|
|
203
|
-
// Skip if no background image or if it's "none"
|
|
204
|
-
if (!backgroundImage || backgroundImage === 'none') {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
// Skip if the element is hidden/decorative
|
|
208
|
-
if (isElementHidden(element) || element.getAttribute('aria-hidden') === 'true') {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const info = {
|
|
212
|
-
tagName: element.tagName.toLowerCase(),
|
|
213
|
-
id: element.id || null,
|
|
214
|
-
className: ((_a = element.className) === null || _a === void 0 ? void 0 : _a.toString()) || null,
|
|
215
|
-
textContent: ((_b = element.textContent) === null || _b === void 0 ? void 0 : _b.substring(0, 50)) || null
|
|
216
|
-
};
|
|
217
|
-
// Check if meaningful background image has text alternative
|
|
218
|
-
const hasTextContent = ((_c = element.textContent) === null || _c === void 0 ? void 0 : _c.trim()) !== '';
|
|
219
|
-
const hasAriaLabel = element.hasAttribute('aria-label');
|
|
220
|
-
const hasAriaLabelledby = element.hasAttribute('aria-labelledby');
|
|
221
|
-
const hasTitle = element.hasAttribute('title');
|
|
222
|
-
if (!hasTextContent && !hasAriaLabel && !hasAriaLabelledby && !hasTitle) {
|
|
223
|
-
// Only warn if the background image looks like content (not decoration)
|
|
224
|
-
if (backgroundImage.includes('url(') && !isBackgroundLikelyDecorative(element)) {
|
|
225
|
-
results.warnings.push({
|
|
226
|
-
rule: 'background-image',
|
|
227
|
-
element: info,
|
|
228
|
-
impact: 'moderate',
|
|
229
|
-
description: 'Element with background image may need text alternative',
|
|
230
|
-
snippet: element.outerHTML.slice(0, 150) + (element.outerHTML.length > 150 ? '...' : ''),
|
|
231
|
-
wcag: ['1.1.1'],
|
|
232
|
-
help: 'If the background image conveys meaning, add text alternative via aria-label or text content'
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
188
|
/**
|
|
239
189
|
* Check image maps for accessibility
|
|
240
190
|
* @param document DOM document
|
|
@@ -293,47 +243,6 @@ function checkImageMaps(document, results) {
|
|
|
293
243
|
});
|
|
294
244
|
});
|
|
295
245
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Check if an element is hidden
|
|
298
|
-
* @param element Element to check
|
|
299
|
-
*/
|
|
300
|
-
function isElementHidden(element) {
|
|
301
|
-
// This is a simplified check - a real implementation would be more comprehensive
|
|
302
|
-
if (element.hasAttribute('hidden') || element.getAttribute('aria-hidden') === 'true') {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
// Check computed style if available
|
|
306
|
-
try {
|
|
307
|
-
const style = getComputedStyle(element);
|
|
308
|
-
return style.display === 'none' || style.visibility === 'hidden';
|
|
309
|
-
}
|
|
310
|
-
catch (e) {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Check if a background image is likely decorative
|
|
316
|
-
* @param element Element to check
|
|
317
|
-
*/
|
|
318
|
-
function isBackgroundLikelyDecorative(element) {
|
|
319
|
-
var _a, _b;
|
|
320
|
-
// Common decorative pattern elements
|
|
321
|
-
const decorativeElements = ['header', 'footer', 'section', 'article', 'div'];
|
|
322
|
-
const tagName = ((_a = element.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
323
|
-
if (decorativeElements.includes(tagName)) {
|
|
324
|
-
// Check for certain classes that might indicate decorative backgrounds
|
|
325
|
-
const className = ((_b = element.className) === null || _b === void 0 ? void 0 : _b.toString()) || '';
|
|
326
|
-
if (className.includes('background') ||
|
|
327
|
-
className.includes('banner') ||
|
|
328
|
-
className.includes('hero') ||
|
|
329
|
-
className.includes('container') ||
|
|
330
|
-
className.includes('wrapper') ||
|
|
331
|
-
className.includes('section')) {
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
246
|
/**
|
|
338
247
|
* Determine if an image is likely decorative
|
|
339
248
|
* @param img Image to check
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ScannerOptions } from '../types';
|
|
2
|
+
export declare const FAST_RULES: readonly ["images", "contrast", "forms", "aria", "structure", "keyboard"];
|
|
3
|
+
export declare const FULL_RULES: readonly ["images", "contrast", "forms", "aria", "structure", "keyboard", "backgroundImages"];
|
|
4
|
+
export declare const RULE_PRESETS: {
|
|
5
|
+
readonly fast: readonly ["images", "contrast", "forms", "aria", "structure", "keyboard"];
|
|
6
|
+
readonly full: readonly ["images", "contrast", "forms", "aria", "structure", "keyboard", "backgroundImages"];
|
|
7
|
+
};
|
|
8
|
+
export declare function resolveRuleNames(options: ScannerOptions, fallback?: readonly string[]): string[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RULE_PRESETS = exports.FULL_RULES = exports.FAST_RULES = void 0;
|
|
4
|
+
exports.resolveRuleNames = resolveRuleNames;
|
|
5
|
+
exports.FAST_RULES = ['images', 'contrast', 'forms', 'aria', 'structure', 'keyboard'];
|
|
6
|
+
exports.FULL_RULES = [...exports.FAST_RULES, 'backgroundImages'];
|
|
7
|
+
exports.RULE_PRESETS = {
|
|
8
|
+
fast: [...exports.FAST_RULES],
|
|
9
|
+
full: [...exports.FULL_RULES],
|
|
10
|
+
};
|
|
11
|
+
function resolveRuleNames(options, fallback = exports.FAST_RULES) {
|
|
12
|
+
if (options.rules && options.rules.length > 0) {
|
|
13
|
+
return options.rules;
|
|
14
|
+
}
|
|
15
|
+
if (options.preset === 'full') {
|
|
16
|
+
return [...exports.FULL_RULES];
|
|
17
|
+
}
|
|
18
|
+
return [...fallback];
|
|
19
|
+
}
|
package/dist/scanner.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.WCAGScanner = void 0;
|
|
|
7
7
|
const jsdom_1 = require("jsdom");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const presets_1 = require("./rules/presets");
|
|
10
11
|
/**
|
|
11
12
|
* Main WCAG Scanner class
|
|
12
13
|
*/
|
|
@@ -18,7 +19,7 @@ class WCAGScanner {
|
|
|
18
19
|
constructor(options = {}) {
|
|
19
20
|
this.rules = new Map();
|
|
20
21
|
this.options = {
|
|
21
|
-
|
|
22
|
+
preset: 'fast',
|
|
22
23
|
level: 'AA',
|
|
23
24
|
ai: true,
|
|
24
25
|
...options
|
|
@@ -36,18 +37,15 @@ class WCAGScanner {
|
|
|
36
37
|
* @returns Promise<boolean> True if loaded successfully
|
|
37
38
|
*/
|
|
38
39
|
async loadHTML(html, baseUrl = 'https://example.org') {
|
|
40
|
+
var _a;
|
|
39
41
|
try {
|
|
42
|
+
(_a = this.dom) === null || _a === void 0 ? void 0 : _a.window.close();
|
|
40
43
|
// Create virtual DOM with robust error handling
|
|
41
44
|
this.dom = new jsdom_1.JSDOM(html, {
|
|
42
45
|
url: baseUrl,
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
runScripts: 'outside-only',
|
|
47
|
+
pretendToBeVisual: true,
|
|
45
48
|
beforeParse(window) {
|
|
46
|
-
// Silence script errors
|
|
47
|
-
window.addEventListener('error', (event) => {
|
|
48
|
-
console.log(`Ignored script error: ${event.message}`);
|
|
49
|
-
event.preventDefault();
|
|
50
|
-
});
|
|
51
49
|
// Mock modern browser APIs that may be missing in JSDOM
|
|
52
50
|
if (!window.ReadableStream) {
|
|
53
51
|
window.ReadableStream = class MockReadableStream {
|
|
@@ -67,8 +65,6 @@ class WCAGScanner {
|
|
|
67
65
|
});
|
|
68
66
|
this.document = this.dom.window.document;
|
|
69
67
|
this.window = this.dom.window;
|
|
70
|
-
// Wait for resources to load
|
|
71
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
72
68
|
return true;
|
|
73
69
|
}
|
|
74
70
|
catch (error) {
|
|
@@ -103,6 +99,8 @@ class WCAGScanner {
|
|
|
103
99
|
.filter(file => {
|
|
104
100
|
if (file.endsWith('.d.ts') || file.endsWith('.d.js'))
|
|
105
101
|
return false;
|
|
102
|
+
if (path_1.default.basename(file, path_1.default.extname(file)) === 'presets')
|
|
103
|
+
return false;
|
|
106
104
|
return file.endsWith('.js') || file.endsWith('.ts');
|
|
107
105
|
});
|
|
108
106
|
for (const file of ruleFiles) {
|
|
@@ -147,7 +145,7 @@ class WCAGScanner {
|
|
|
147
145
|
await this.loadRules();
|
|
148
146
|
}
|
|
149
147
|
// Run each enabled rule
|
|
150
|
-
const enabledRules = this.options.
|
|
148
|
+
const enabledRules = (0, presets_1.resolveRuleNames)(this.options, presets_1.FAST_RULES);
|
|
151
149
|
for (const ruleName of enabledRules) {
|
|
152
150
|
const rule = this.rules.get(ruleName);
|
|
153
151
|
if (rule) {
|
|
@@ -170,7 +168,11 @@ class WCAGScanner {
|
|
|
170
168
|
* @returns ScanResults Current scan results
|
|
171
169
|
*/
|
|
172
170
|
getResults() {
|
|
173
|
-
return
|
|
171
|
+
return {
|
|
172
|
+
passes: [...this.results.passes],
|
|
173
|
+
violations: [...this.results.violations],
|
|
174
|
+
warnings: [...this.results.warnings]
|
|
175
|
+
};
|
|
174
176
|
}
|
|
175
177
|
/**
|
|
176
178
|
* Update scanner options
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
export type RulePreset = 'fast' | 'full';
|
|
1
2
|
/**
|
|
2
3
|
* Scanner configuration options
|
|
3
4
|
*/
|
|
4
5
|
export interface ScannerOptions {
|
|
5
6
|
/** WCAG level to check against (A, AA or AAA) */
|
|
6
7
|
level?: 'A' | 'AA' | 'AAA';
|
|
8
|
+
/** Built-in rule preset */
|
|
9
|
+
preset?: RulePreset;
|
|
7
10
|
/** Specific rules to check */
|
|
8
11
|
rules?: string[];
|
|
9
12
|
/** Enable AI-powered suggestions */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wcag-scanner",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.67",
|
|
4
4
|
"description": "Scan HTML for WCAG accessibility violations with AI-powered fix suggestions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,24 +10,22 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"scrapper/pkg",
|
|
14
13
|
"README.md",
|
|
15
14
|
"LICENSE"
|
|
16
15
|
],
|
|
17
16
|
"engines": {
|
|
18
17
|
"node": ">=16.0.0"
|
|
19
18
|
},
|
|
20
|
-
"bin": {
|
|
21
|
-
"wcag-scanner": "dist/cli/index.js"
|
|
22
|
-
},
|
|
23
19
|
"scripts": {
|
|
24
20
|
"test": "jest",
|
|
25
21
|
"test:coverage": "jest --coverage",
|
|
26
|
-
"build
|
|
27
|
-
"
|
|
22
|
+
"build": "rimraf dist && tsc",
|
|
23
|
+
"profile:aria": "npm run build && node scripts/profile-aria.js",
|
|
24
|
+
"profile:contrast": "npm run build && node scripts/profile-contrast.js",
|
|
25
|
+
"profile:forms": "npm run build && node scripts/profile-forms.js",
|
|
26
|
+
"profile:images": "npm run build && node scripts/profile-images.js",
|
|
28
27
|
"prepublishOnly": "npm run build",
|
|
29
|
-
"lint": "eslint src/**/*.ts"
|
|
30
|
-
"codecov": "codecov"
|
|
28
|
+
"lint": "eslint src/**/*.ts"
|
|
31
29
|
},
|
|
32
30
|
"repository": {
|
|
33
31
|
"type": "git",
|
|
@@ -51,10 +49,15 @@
|
|
|
51
49
|
"react-dom": ">=17.0.0"
|
|
52
50
|
},
|
|
53
51
|
"peerDependenciesMeta": {
|
|
54
|
-
"react": {
|
|
55
|
-
|
|
52
|
+
"react": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"react-dom": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
56
58
|
},
|
|
57
59
|
"devDependencies": {
|
|
60
|
+
"@testing-library/react": "^16.3.2",
|
|
58
61
|
"@types/express": "^5.0.0",
|
|
59
62
|
"@types/jest": "^29.5.14",
|
|
60
63
|
"@types/jsdom": "^21.1.7",
|
|
@@ -63,19 +66,17 @@
|
|
|
63
66
|
"@types/react-dom": "^18.3.7",
|
|
64
67
|
"@typescript-eslint/eslint-plugin": "^8.24.0",
|
|
65
68
|
"@typescript-eslint/parser": "^8.24.0",
|
|
66
|
-
"codecov": "^3.6.2",
|
|
67
69
|
"eslint": "^9.21.0",
|
|
68
70
|
"jest": "^29.7.0",
|
|
71
|
+
"react": "^19.2.4",
|
|
72
|
+
"react-dom": "^19.2.4",
|
|
69
73
|
"rimraf": "^6.0.1",
|
|
70
74
|
"ts-jest": "^29.2.6",
|
|
71
75
|
"typescript": "^5.7.2"
|
|
72
76
|
},
|
|
73
77
|
"dependencies": {
|
|
74
|
-
"@google/generative-ai": "^0.23.0",
|
|
75
78
|
"color-diff": "^1.4.0",
|
|
76
|
-
"commander": "^13.1.0",
|
|
77
79
|
"crypto": "^1.0.1",
|
|
78
|
-
"dotenv": "^16.4.7",
|
|
79
80
|
"express": "^4.21.2",
|
|
80
81
|
"jsdom": "^26.0.0"
|
|
81
82
|
}
|
package/dist/ai/suggestions.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Violation, FixSuggestion } from "../types";
|
|
2
|
-
/**
|
|
3
|
-
* Generate fix suggestions for accessibility violations
|
|
4
|
-
* @param violation WCAG violation to fix
|
|
5
|
-
* @returns Promise<FixSuggestion> Suggested fix
|
|
6
|
-
*/
|
|
7
|
-
export declare function generateFixSuggestion(violation: Violation): Promise<FixSuggestion>;
|
|
8
|
-
declare const _default: {
|
|
9
|
-
generateFixSuggestion: typeof generateFixSuggestion;
|
|
10
|
-
};
|
|
11
|
-
export default _default;
|
package/dist/ai/suggestions.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateFixSuggestion = generateFixSuggestion;
|
|
7
|
-
const generative_ai_1 = require("@google/generative-ai");
|
|
8
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
-
/**
|
|
10
|
-
* @TODO working on AI-powered suggestions, it's still working in progress.
|
|
11
|
-
*/
|
|
12
|
-
// Load environment variables from .env file
|
|
13
|
-
dotenv_1.default.config();
|
|
14
|
-
// Get API key from environment
|
|
15
|
-
const apiKey = process.env.GEMINI_API_KEY;
|
|
16
|
-
// Initialize Gemini if API key is available
|
|
17
|
-
let genAI = null;
|
|
18
|
-
let model = null;
|
|
19
|
-
if (apiKey) {
|
|
20
|
-
try {
|
|
21
|
-
genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
|
|
22
|
-
model = genAI.getGenerativeModel({ model: 'gemini-1.0-pro' });
|
|
23
|
-
console.log('Gemini AI initialized');
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.error('Error initializing Gemini AI:', error);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Generate fix suggestions for accessibility violations
|
|
31
|
-
* @param violation WCAG violation to fix
|
|
32
|
-
* @returns Promise<FixSuggestion> Suggested fix
|
|
33
|
-
*/
|
|
34
|
-
async function generateFixSuggestion(violation) {
|
|
35
|
-
// Ig Gemini is not available, use rule-based suggestion
|
|
36
|
-
if (!model) {
|
|
37
|
-
console.log('Gemini AI not available, using rule-based suggestion');
|
|
38
|
-
return generateRuleBasedSuggestion(violation);
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
// Create prompt for Gemini
|
|
42
|
-
const prompt = `
|
|
43
|
-
As a web accessibility expert, I need a fix for this WCAG issue:
|
|
44
|
-
Rule: ${violation.rule || ''}
|
|
45
|
-
Description: ${violation.description || ''}
|
|
46
|
-
|
|
47
|
-
HTML code with issue:
|
|
48
|
-
${violation.snippet || ''}
|
|
49
|
-
|
|
50
|
-
Please provide a corrected version of the code and a brief explanation.
|
|
51
|
-
`;
|
|
52
|
-
// Generate content with Gemini
|
|
53
|
-
const result = await model.generateContent(prompt);
|
|
54
|
-
const response = result.response.text();
|
|
55
|
-
// Extract code and explanation from response
|
|
56
|
-
const codeMatch = response.match(/```(?:html)?\s*([\s\S]*?)\s*```/);
|
|
57
|
-
const code = codeMatch ? codeMatch[1].trim() : '';
|
|
58
|
-
// Remove code block for clean explanation
|
|
59
|
-
const explanation = response
|
|
60
|
-
.replace(/```(?:html)?\s*[\s\S]*?\s*```/g, '')
|
|
61
|
-
.trim();
|
|
62
|
-
return {
|
|
63
|
-
code: code || 'Unable to generate specific code fix',
|
|
64
|
-
description: 'AI-suggested fix',
|
|
65
|
-
explanation: explanation || 'Fix the accessibility issue as suggested by the AI'
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
console.error('Error generating fix suggestion with Gemini AI:', error);
|
|
70
|
-
return generateRuleBasedSuggestion(violation);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Generate basic rule-based suggestion as fallback
|
|
75
|
-
* @param violation WCAG violation
|
|
76
|
-
* @returns FixSuggestion
|
|
77
|
-
*/
|
|
78
|
-
function generateRuleBasedSuggestion(violation) {
|
|
79
|
-
var _a;
|
|
80
|
-
const rule = violation.rule || '';
|
|
81
|
-
if (rule.includes('img-alt')) {
|
|
82
|
-
return {
|
|
83
|
-
code: ((_a = violation.snippet) === null || _a === void 0 ? void 0 : _a.replace(/<img/i, '<img alt="Descriptive text"')) || '',
|
|
84
|
-
description: 'Add alt text to image',
|
|
85
|
-
explanation: 'Images need alternative text for screen readers'
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (rule.includes('contrast')) {
|
|
89
|
-
return {
|
|
90
|
-
code: '/* Increase color contrast to at least 4.5:1 ratio */',
|
|
91
|
-
description: 'Increase color contrast',
|
|
92
|
-
explanation: 'Text needs sufficient contrast with its background'
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
code: '',
|
|
97
|
-
description: 'Fix needed',
|
|
98
|
-
explanation: violation.help || 'Fix this issue to improve accessibility'
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
exports.default = {
|
|
102
|
-
generateFixSuggestion
|
|
103
|
-
};
|
package/dist/cli/index.d.ts
DELETED
package/dist/cli/index.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const commander_1 = require("commander");
|
|
8
|
-
const __1 = require("..");
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const __2 = require("..");
|
|
12
|
-
// Try to load package.json for version information
|
|
13
|
-
let version = '0.1.0';
|
|
14
|
-
try {
|
|
15
|
-
const packagePath = path_1.default.join(__dirname, '../../package.json');
|
|
16
|
-
const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, 'utf8'));
|
|
17
|
-
version = packageJson.version;
|
|
18
|
-
}
|
|
19
|
-
catch (e) {
|
|
20
|
-
// Ignore package.json load error
|
|
21
|
-
}
|
|
22
|
-
commander_1.program
|
|
23
|
-
.name('wcag-scanner')
|
|
24
|
-
.description('Scan HTML files and websites for WCAG accessibility violations')
|
|
25
|
-
.version(version);
|
|
26
|
-
// Command to scan HTML file
|
|
27
|
-
commander_1.program
|
|
28
|
-
.command('scan <filePath>')
|
|
29
|
-
.description('Scan a local HTML file for accessibility violations')
|
|
30
|
-
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
31
|
-
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
32
|
-
.option('-o, --output <file>', 'Save results to file')
|
|
33
|
-
.option('-v, --verbose', 'Show verbose output')
|
|
34
|
-
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
35
|
-
.action(async (filePath, options) => {
|
|
36
|
-
try {
|
|
37
|
-
console.log(`Scanning file: ${filePath}`);
|
|
38
|
-
const scannerOptions = {
|
|
39
|
-
level: options.level,
|
|
40
|
-
verbose: options.verbose || false,
|
|
41
|
-
rules: options.rules ? options.rules.split(',') : undefined
|
|
42
|
-
};
|
|
43
|
-
const results = await (0, __1.scanFile)(filePath, scannerOptions);
|
|
44
|
-
// Generate report
|
|
45
|
-
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
46
|
-
// Output to console if not html format
|
|
47
|
-
if (options.format !== 'html') {
|
|
48
|
-
console.log(report);
|
|
49
|
-
}
|
|
50
|
-
// Save to file if specified
|
|
51
|
-
if (options.output) {
|
|
52
|
-
console.log(`Saving report to: ${options.output}`);
|
|
53
|
-
(0, __1.saveReport)(report, options.output);
|
|
54
|
-
}
|
|
55
|
-
// Exit with appropriate code based on violations
|
|
56
|
-
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
console.error('Error scanning file:', error);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
// Command to scan URL
|
|
64
|
-
commander_1.program
|
|
65
|
-
.command('url <url>')
|
|
66
|
-
.description('Scan a website URL for accessibility issues')
|
|
67
|
-
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
68
|
-
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
69
|
-
.option('-o, --output <file>', 'Save results to file')
|
|
70
|
-
.option('-v, --verbose', 'Show verbose output')
|
|
71
|
-
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
72
|
-
.option('--ai', 'Enable AI-powered fix suggestions')
|
|
73
|
-
.action(async (urlString, options) => {
|
|
74
|
-
try {
|
|
75
|
-
// Add protocol if missing
|
|
76
|
-
if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
|
|
77
|
-
urlString = 'https://' + urlString;
|
|
78
|
-
console.log(`Added protocol to URL: ${urlString}`);
|
|
79
|
-
}
|
|
80
|
-
console.log(`Scanning URL: ${urlString}`);
|
|
81
|
-
// Create scanner options
|
|
82
|
-
const scannerOptions = {
|
|
83
|
-
level: options.level,
|
|
84
|
-
verbose: options.verbose || false,
|
|
85
|
-
baseUrl: urlString,
|
|
86
|
-
rules: options.rules ? options.rules.split(',') : undefined,
|
|
87
|
-
ai: options.ai || false,
|
|
88
|
-
ignoreScriptErrors: true
|
|
89
|
-
};
|
|
90
|
-
// Use the scanUrl function that uses WASM internally
|
|
91
|
-
const results = await (0, __2.scanUrl)(urlString, scannerOptions);
|
|
92
|
-
// Generate report
|
|
93
|
-
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
94
|
-
// Output to console if not html format
|
|
95
|
-
if (options.format !== 'html') {
|
|
96
|
-
console.log(report);
|
|
97
|
-
}
|
|
98
|
-
// Save to file if specified
|
|
99
|
-
if (options.output) {
|
|
100
|
-
console.log(`Saving report to: ${options.output}`);
|
|
101
|
-
(0, __1.saveReport)(report, options.output);
|
|
102
|
-
}
|
|
103
|
-
// Exit with appropriate code based on violations
|
|
104
|
-
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
console.error('Error scanning URL:', error);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
// Command to scan HTML from stdin
|
|
112
|
-
commander_1.program
|
|
113
|
-
.command('stdin')
|
|
114
|
-
.description('Scan HTML input from stdin')
|
|
115
|
-
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
116
|
-
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
117
|
-
.option('-o, --output <file>', 'Save results to file')
|
|
118
|
-
.option('-v, --verbose', 'Show verbose output')
|
|
119
|
-
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
120
|
-
.action(async (options) => {
|
|
121
|
-
try {
|
|
122
|
-
console.log('Reading HTML from stdin...');
|
|
123
|
-
let html = '';
|
|
124
|
-
process.stdin.on('data', (chunk) => {
|
|
125
|
-
html += chunk;
|
|
126
|
-
});
|
|
127
|
-
process.stdin.on('end', async () => {
|
|
128
|
-
console.log(`Received ${html.length} bytes of HTML`);
|
|
129
|
-
const scannerOptions = {
|
|
130
|
-
level: options.level,
|
|
131
|
-
verbose: options.verbose || false,
|
|
132
|
-
rules: options.rules ? options.rules.split(',') : undefined
|
|
133
|
-
};
|
|
134
|
-
const results = await (0, __1.scanHtml)(html, scannerOptions);
|
|
135
|
-
// Generate report
|
|
136
|
-
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
137
|
-
// Output to console if not html format
|
|
138
|
-
if (options.format !== 'html') {
|
|
139
|
-
console.log(report);
|
|
140
|
-
}
|
|
141
|
-
// Save to file if specified
|
|
142
|
-
if (options.output) {
|
|
143
|
-
console.log(`Saving report to: ${options.output}`);
|
|
144
|
-
(0, __1.saveReport)(report, options.output);
|
|
145
|
-
}
|
|
146
|
-
// Exit with appropriate code based on violations
|
|
147
|
-
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
console.error('Error scanning HTML:', error);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
// If no arguments provided, show help
|
|
156
|
-
if (process.argv.length <= 2) {
|
|
157
|
-
commander_1.program.help();
|
|
158
|
-
}
|
|
159
|
-
// Parse command line arguments
|
|
160
|
-
commander_1.program.parse(process.argv);
|
package/dist/wasm/index.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WASM based URL scraper using Rust
|
|
3
|
-
*/
|
|
4
|
-
export declare class WsamScraper {
|
|
5
|
-
private wasmModule;
|
|
6
|
-
private initialized;
|
|
7
|
-
/**
|
|
8
|
-
* Initialize the WASM scraper
|
|
9
|
-
*/
|
|
10
|
-
initialize(): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Scrape a URL using Rust/WASM
|
|
13
|
-
* @param url URL to scrape
|
|
14
|
-
* @returns Promise<string> HTML content
|
|
15
|
-
*/
|
|
16
|
-
scrapeUrl(url: string): Promise<string>;
|
|
17
|
-
}
|
|
18
|
-
declare const _default: WsamScraper;
|
|
19
|
-
export default _default;
|
package/dist/wasm/index.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WsamScraper = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* WASM based URL scraper using Rust
|
|
6
|
-
*/
|
|
7
|
-
class WsamScraper {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.initialized = false;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Initialize the WASM scraper
|
|
13
|
-
*/
|
|
14
|
-
async initialize() {
|
|
15
|
-
if (this.initialized)
|
|
16
|
-
return;
|
|
17
|
-
try {
|
|
18
|
-
// scrapper/pkg is generated by `npm run build:wasm` (wasm-pack build)
|
|
19
|
-
this.wasmModule = require('../../scrapper/pkg/scrapper');
|
|
20
|
-
this.wasmModule.init_panic_hook();
|
|
21
|
-
this.initialized = true;
|
|
22
|
-
console.log("WASM scraper initialized successfully");
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
console.error("Failed to initialize WASM scraper:", error);
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Scrape a URL using Rust/WASM
|
|
31
|
-
* @param url URL to scrape
|
|
32
|
-
* @returns Promise<string> HTML content
|
|
33
|
-
*/
|
|
34
|
-
async scrapeUrl(url) {
|
|
35
|
-
if (!this.initialized) {
|
|
36
|
-
await this.initialize();
|
|
37
|
-
}
|
|
38
|
-
console.log(`Scraping URL using Rust/WASM: ${url}`);
|
|
39
|
-
try {
|
|
40
|
-
const uint8Array = await this.wasmModule.scrape_url(url);
|
|
41
|
-
const decoder = new TextDecoder('utf-8');
|
|
42
|
-
const html = decoder.decode(uint8Array);
|
|
43
|
-
console.log(`Successfully scraped ${html.length} bytes of HTML content`);
|
|
44
|
-
return html;
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
console.error("Error in WASM scraper:", error);
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
exports.WsamScraper = WsamScraper;
|
|
53
|
-
exports.default = new WsamScraper;
|