wcag-scanner 1.0.0 → 1.2.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/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +183 -0
- package/dist/index.d.ts +44 -6
- package/dist/index.js +87 -5
- package/dist/middleware/express.d.ts +25 -0
- package/dist/middleware/express.js +209 -0
- package/dist/middleware/index.d.ts +8 -0
- package/dist/middleware/index.js +11 -0
- package/dist/reporters/console.d.ts +12 -0
- package/dist/reporters/console.js +210 -0
- package/dist/reporters/html.d.ts +12 -0
- package/dist/reporters/html.js +633 -0
- package/dist/reporters/index.d.ts +21 -0
- package/dist/reporters/index.js +36 -0
- package/dist/reporters/json.d.ts +12 -0
- package/dist/reporters/json.js +55 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +7 -1
|
@@ -0,0 +1,183 @@
|
|
|
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 http_1 = __importDefault(require("http"));
|
|
12
|
+
const https_1 = __importDefault(require("https"));
|
|
13
|
+
const url_1 = require("url");
|
|
14
|
+
// Try to load package.json for version information
|
|
15
|
+
let version = '0.1.0';
|
|
16
|
+
try {
|
|
17
|
+
const packagePath = path_1.default.join(__dirname, '../../package.json');
|
|
18
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, 'utf8'));
|
|
19
|
+
version = packageJson.version;
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
// Ignore package.json load error
|
|
23
|
+
}
|
|
24
|
+
commander_1.program
|
|
25
|
+
.name('wcag-scanner')
|
|
26
|
+
.description('Scan HTML files and websites for WCAG accessibility violations')
|
|
27
|
+
.version(version);
|
|
28
|
+
// Command to scan HTML file
|
|
29
|
+
commander_1.program
|
|
30
|
+
.command('scan <filePath>')
|
|
31
|
+
.description('Scan a local HTML file for accessibility violations')
|
|
32
|
+
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
33
|
+
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
34
|
+
.option('-o, --output <file>', 'Save results to file')
|
|
35
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
36
|
+
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
37
|
+
.action(async (filePath, options) => {
|
|
38
|
+
try {
|
|
39
|
+
console.log(`Scanning file: ${filePath}`);
|
|
40
|
+
const scannerOptions = {
|
|
41
|
+
level: options.level,
|
|
42
|
+
verbose: options.verbose || false,
|
|
43
|
+
rules: options.rules ? options.rules.split(',') : undefined
|
|
44
|
+
};
|
|
45
|
+
const results = await (0, __1.scanFile)(filePath, scannerOptions);
|
|
46
|
+
// Generate report
|
|
47
|
+
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
48
|
+
// Output to console if not html format
|
|
49
|
+
if (options.format !== 'html') {
|
|
50
|
+
console.log(report);
|
|
51
|
+
}
|
|
52
|
+
// Save to file if specified
|
|
53
|
+
if (options.output) {
|
|
54
|
+
console.log(`Saving report to: ${options.output}`);
|
|
55
|
+
(0, __1.saveReport)(report, options.output);
|
|
56
|
+
}
|
|
57
|
+
// Exit with appropriate code based on violations
|
|
58
|
+
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error('Error scanning file:', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// Command to scan URL
|
|
66
|
+
commander_1.program
|
|
67
|
+
.command('url <url>')
|
|
68
|
+
.description('Scan a website URL for accessibility issues')
|
|
69
|
+
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
70
|
+
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
71
|
+
.option('-o, --output <file>', 'Save results to file')
|
|
72
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
73
|
+
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
74
|
+
.action(async (urlString, options) => {
|
|
75
|
+
try {
|
|
76
|
+
// Add protocol if missing
|
|
77
|
+
if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
|
|
78
|
+
urlString = 'https://' + urlString;
|
|
79
|
+
console.log(`Added protocol to URL: ${urlString}`);
|
|
80
|
+
}
|
|
81
|
+
console.log(`Scanning URL: ${urlString}`);
|
|
82
|
+
// Fetch the URL content
|
|
83
|
+
const html = await fetchUrl(urlString);
|
|
84
|
+
const scannerOptions = {
|
|
85
|
+
level: options.level,
|
|
86
|
+
verbose: options.verbose || false,
|
|
87
|
+
baseUrl: urlString,
|
|
88
|
+
rules: options.rules ? options.rules.split(',') : undefined
|
|
89
|
+
};
|
|
90
|
+
const results = await (0, __1.scanHtml)(html, scannerOptions);
|
|
91
|
+
// Generate report
|
|
92
|
+
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
93
|
+
// Output to console if not html format
|
|
94
|
+
if (options.format !== 'html') {
|
|
95
|
+
console.log(report);
|
|
96
|
+
}
|
|
97
|
+
// Save to file if specified
|
|
98
|
+
if (options.output) {
|
|
99
|
+
console.log(`Saving report to: ${options.output}`);
|
|
100
|
+
(0, __1.saveReport)(report, options.output);
|
|
101
|
+
}
|
|
102
|
+
// Exit with appropriate code based on violations
|
|
103
|
+
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error('Error scanning URL:', error);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Command to scan HTML from stdin
|
|
111
|
+
commander_1.program
|
|
112
|
+
.command('stdin')
|
|
113
|
+
.description('Scan HTML input from stdin')
|
|
114
|
+
.option('-l, --level <level>', 'WCAG level (A, AA, AAA)', 'AA')
|
|
115
|
+
.option('-f, --format <format>', 'Output format (json, console, html)', 'console')
|
|
116
|
+
.option('-o, --output <file>', 'Save results to file')
|
|
117
|
+
.option('-v, --verbose', 'Show verbose output')
|
|
118
|
+
.option('-r, --rules <rules>', 'Comma-separated list of rules to check')
|
|
119
|
+
.action(async (options) => {
|
|
120
|
+
try {
|
|
121
|
+
console.log('Reading HTML from stdin...');
|
|
122
|
+
let html = '';
|
|
123
|
+
process.stdin.on('data', (chunk) => {
|
|
124
|
+
html += chunk;
|
|
125
|
+
});
|
|
126
|
+
process.stdin.on('end', async () => {
|
|
127
|
+
console.log(`Received ${html.length} bytes of HTML`);
|
|
128
|
+
const scannerOptions = {
|
|
129
|
+
level: options.level,
|
|
130
|
+
verbose: options.verbose || false,
|
|
131
|
+
rules: options.rules ? options.rules.split(',') : undefined
|
|
132
|
+
};
|
|
133
|
+
const results = await (0, __1.scanHtml)(html, scannerOptions);
|
|
134
|
+
// Generate report
|
|
135
|
+
const report = (0, __1.formatReport)(results, options.format, scannerOptions);
|
|
136
|
+
// Output to console if not html format
|
|
137
|
+
if (options.format !== 'html') {
|
|
138
|
+
console.log(report);
|
|
139
|
+
}
|
|
140
|
+
// Save to file if specified
|
|
141
|
+
if (options.output) {
|
|
142
|
+
console.log(`Saving report to: ${options.output}`);
|
|
143
|
+
(0, __1.saveReport)(report, options.output);
|
|
144
|
+
}
|
|
145
|
+
// Exit with appropriate code based on violations
|
|
146
|
+
process.exit(results.violations.length > 0 ? 1 : 0);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('Error scanning HTML:', error);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// Helper function to fetch URL content
|
|
155
|
+
async function fetchUrl(urlString) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const url = new url_1.URL(urlString);
|
|
158
|
+
const client = url.protocol === 'https:' ? https_1.default : http_1.default;
|
|
159
|
+
const req = client.get(urlString, (res) => {
|
|
160
|
+
if (res.statusCode !== 200) {
|
|
161
|
+
reject(new Error(`Request failed with status code ${res.statusCode}`));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
let data = '';
|
|
165
|
+
res.on('data', (chunk) => {
|
|
166
|
+
data += chunk;
|
|
167
|
+
});
|
|
168
|
+
res.on('end', () => {
|
|
169
|
+
resolve(data);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
req.on('error', (err) => {
|
|
173
|
+
reject(err);
|
|
174
|
+
});
|
|
175
|
+
req.end();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// If no arguments provided, show help
|
|
179
|
+
if (process.argv.length <= 2) {
|
|
180
|
+
commander_1.program.help();
|
|
181
|
+
}
|
|
182
|
+
// Parse command line arguments
|
|
183
|
+
commander_1.program.parse(process.argv);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,56 @@
|
|
|
1
|
-
import { WCAGScanner } from
|
|
2
|
-
import { ScannerOptions, ScanResults } from
|
|
1
|
+
import { WCAGScanner } from './scanner';
|
|
2
|
+
import { ScannerOptions, ScanResults } from './types';
|
|
3
|
+
import { ReporterFormat } from './reporters';
|
|
4
|
+
import middleware from './middleware';
|
|
3
5
|
/**
|
|
4
6
|
* Scan HTML string for WCAG violations
|
|
5
7
|
* @param html HTML content to scan
|
|
6
8
|
* @param options Scanner options
|
|
7
|
-
* @
|
|
9
|
+
* @returns Promise<ScanResults> Scan results
|
|
8
10
|
*/
|
|
9
|
-
export declare function
|
|
11
|
+
export declare function scanHtml(html: string, options?: ScannerOptions): Promise<ScanResults>;
|
|
10
12
|
/**
|
|
11
13
|
* Scan HTML file for WCAG violations
|
|
12
14
|
* @param filePath Path to HTML file
|
|
13
15
|
* @param options Scanner options
|
|
14
|
-
* @
|
|
16
|
+
* @returns Promise<ScanResults> Scan results
|
|
15
17
|
*/
|
|
16
18
|
export declare function scanFile(filePath: string, options?: ScannerOptions): Promise<ScanResults>;
|
|
19
|
+
/**
|
|
20
|
+
* Scan a URL for WCAG violations
|
|
21
|
+
* @param urlString URL to scan
|
|
22
|
+
* @param options Scanner options
|
|
23
|
+
* @returns Promise<ScanResults> Scan results
|
|
24
|
+
*/
|
|
25
|
+
export declare function scanUrl(urlString: string, options?: ScannerOptions): Promise<ScanResults>;
|
|
26
|
+
/**
|
|
27
|
+
* Generate a report from scan results
|
|
28
|
+
* @param results Scan results
|
|
29
|
+
* @param format Report format
|
|
30
|
+
* @param options Scanner options
|
|
31
|
+
* @returns Report string
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatReport(results: ScanResults, format?: ReporterFormat, options?: ScannerOptions): string;
|
|
34
|
+
/**
|
|
35
|
+
* Save report to a file
|
|
36
|
+
* @param report Report string
|
|
37
|
+
* @param filePath Output file path
|
|
38
|
+
*/
|
|
39
|
+
export declare function saveReport(report: string, filePath: string): void;
|
|
17
40
|
export { WCAGScanner };
|
|
18
|
-
export * from
|
|
41
|
+
export * from './types';
|
|
42
|
+
export { ReporterFormat };
|
|
43
|
+
export { middleware };
|
|
44
|
+
declare const _default: {
|
|
45
|
+
scanHtml: typeof scanHtml;
|
|
46
|
+
scanFile: typeof scanFile;
|
|
47
|
+
scanUrl: typeof scanUrl;
|
|
48
|
+
formatReport: typeof formatReport;
|
|
49
|
+
saveReport: typeof saveReport;
|
|
50
|
+
middleware: {
|
|
51
|
+
express: {
|
|
52
|
+
createMiddleware: typeof import("./middleware/express").createMiddleware;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -17,20 +17,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.WCAGScanner = void 0;
|
|
21
|
-
exports.
|
|
20
|
+
exports.middleware = exports.WCAGScanner = void 0;
|
|
21
|
+
exports.scanHtml = scanHtml;
|
|
22
22
|
exports.scanFile = scanFile;
|
|
23
|
+
exports.scanUrl = scanUrl;
|
|
24
|
+
exports.formatReport = formatReport;
|
|
25
|
+
exports.saveReport = saveReport;
|
|
23
26
|
const scanner_1 = require("./scanner");
|
|
24
27
|
Object.defineProperty(exports, "WCAGScanner", { enumerable: true, get: function () { return scanner_1.WCAGScanner; } });
|
|
28
|
+
const reporters_1 = require("./reporters");
|
|
29
|
+
const middleware_1 = __importDefault(require("./middleware"));
|
|
30
|
+
exports.middleware = middleware_1.default;
|
|
25
31
|
const fs_1 = __importDefault(require("fs"));
|
|
26
32
|
const path_1 = __importDefault(require("path"));
|
|
33
|
+
const url_1 = require("url");
|
|
34
|
+
const http_1 = __importDefault(require("http"));
|
|
35
|
+
const https_1 = __importDefault(require("https"));
|
|
27
36
|
/**
|
|
28
37
|
* Scan HTML string for WCAG violations
|
|
29
38
|
* @param html HTML content to scan
|
|
30
39
|
* @param options Scanner options
|
|
31
|
-
* @
|
|
40
|
+
* @returns Promise<ScanResults> Scan results
|
|
32
41
|
*/
|
|
33
|
-
async function
|
|
42
|
+
async function scanHtml(html, options = {}) {
|
|
34
43
|
const scanner = new scanner_1.WCAGScanner(options);
|
|
35
44
|
await scanner.loadHTML(html);
|
|
36
45
|
return scanner.scan();
|
|
@@ -39,7 +48,7 @@ async function scanHTML(html, options = {}) {
|
|
|
39
48
|
* Scan HTML file for WCAG violations
|
|
40
49
|
* @param filePath Path to HTML file
|
|
41
50
|
* @param options Scanner options
|
|
42
|
-
* @
|
|
51
|
+
* @returns Promise<ScanResults> Scan results
|
|
43
52
|
*/
|
|
44
53
|
async function scanFile(filePath, options = {}) {
|
|
45
54
|
const html = fs_1.default.readFileSync(path_1.default.resolve(filePath), 'utf8');
|
|
@@ -48,4 +57,77 @@ async function scanFile(filePath, options = {}) {
|
|
|
48
57
|
await scanner.loadHTML(html, baseUrl);
|
|
49
58
|
return scanner.scan();
|
|
50
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Scan a URL for WCAG violations
|
|
62
|
+
* @param urlString URL to scan
|
|
63
|
+
* @param options Scanner options
|
|
64
|
+
* @returns Promise<ScanResults> Scan results
|
|
65
|
+
*/
|
|
66
|
+
async function scanUrl(urlString, options = {}) {
|
|
67
|
+
// Add protocol if missing
|
|
68
|
+
if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
|
|
69
|
+
urlString = 'https://' + urlString;
|
|
70
|
+
}
|
|
71
|
+
const html = await fetchUrl(urlString);
|
|
72
|
+
const scannerOptions = {
|
|
73
|
+
...options,
|
|
74
|
+
baseUrl: urlString
|
|
75
|
+
};
|
|
76
|
+
return scanHtml(html, scannerOptions);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate a report from scan results
|
|
80
|
+
* @param results Scan results
|
|
81
|
+
* @param format Report format
|
|
82
|
+
* @param options Scanner options
|
|
83
|
+
* @returns Report string
|
|
84
|
+
*/
|
|
85
|
+
function formatReport(results, format = 'json', options = {}) {
|
|
86
|
+
return (0, reporters_1.generateReport)(results, format, options);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Save report to a file
|
|
90
|
+
* @param report Report string
|
|
91
|
+
* @param filePath Output file path
|
|
92
|
+
*/
|
|
93
|
+
function saveReport(report, filePath) {
|
|
94
|
+
fs_1.default.writeFileSync(filePath, report);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Helper function to fetch URL content
|
|
98
|
+
* @param urlString URL to fetch
|
|
99
|
+
* @returns Promise<string> HTML content
|
|
100
|
+
*/
|
|
101
|
+
async function fetchUrl(urlString) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const url = new url_1.URL(urlString);
|
|
104
|
+
const client = url.protocol === 'https:' ? https_1.default : http_1.default;
|
|
105
|
+
const req = client.get(urlString, (res) => {
|
|
106
|
+
if (res.statusCode !== 200) {
|
|
107
|
+
reject(new Error(`Request failed with status code ${res.statusCode}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let data = '';
|
|
111
|
+
res.on('data', (chunk) => {
|
|
112
|
+
data += chunk;
|
|
113
|
+
});
|
|
114
|
+
res.on('end', () => {
|
|
115
|
+
resolve(data);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
req.on('error', (err) => {
|
|
119
|
+
reject(err);
|
|
120
|
+
});
|
|
121
|
+
req.end();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
51
124
|
__exportStar(require("./types"), exports);
|
|
125
|
+
// Default export with all main functions
|
|
126
|
+
exports.default = {
|
|
127
|
+
scanHtml,
|
|
128
|
+
scanFile,
|
|
129
|
+
scanUrl,
|
|
130
|
+
formatReport,
|
|
131
|
+
saveReport,
|
|
132
|
+
middleware: middleware_1.default
|
|
133
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { ScannerOptions, ScanResults } from '../index';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the Express middleware
|
|
5
|
+
*/
|
|
6
|
+
export interface ExpressMiddlewareOptions extends ScannerOptions {
|
|
7
|
+
/** Enable/disable the middleware */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** Custom header name for violation count */
|
|
10
|
+
headerName?: string;
|
|
11
|
+
/** Add inline report to HTML responses */
|
|
12
|
+
inlineReport?: boolean;
|
|
13
|
+
/** Callback function to handle violations */
|
|
14
|
+
onViolation?: (results: ScanResults, req: Request, res: Response) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create Express middleware for scanning HTML responses for accessibility issues
|
|
18
|
+
* @param options Middleware options
|
|
19
|
+
* @returns Express middleware function
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMiddleware(options?: ExpressMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
22
|
+
declare const _default: {
|
|
23
|
+
createMiddleware: typeof createMiddleware;
|
|
24
|
+
};
|
|
25
|
+
export default _default;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMiddleware = createMiddleware;
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
/**
|
|
6
|
+
* Create Express middleware for scanning HTML responses for accessibility issues
|
|
7
|
+
* @param options Middleware options
|
|
8
|
+
* @returns Express middleware function
|
|
9
|
+
*/
|
|
10
|
+
function createMiddleware(options = {}) {
|
|
11
|
+
const defaultOptions = {
|
|
12
|
+
enabled: process.env.NODE_ENV !== 'production', // Disable in production by default
|
|
13
|
+
level: 'AA',
|
|
14
|
+
headerName: 'X-WCAG-Violations',
|
|
15
|
+
inlineReport: false,
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
return async function wcagScannerMiddleware(req, res, next) {
|
|
19
|
+
// Skip if disabled or non-HTML request
|
|
20
|
+
if (!defaultOptions.enabled || !shouldProcessRequest(req)) {
|
|
21
|
+
return next();
|
|
22
|
+
}
|
|
23
|
+
// Store original send method
|
|
24
|
+
const originalSend = res.send;
|
|
25
|
+
// Override send method to intercept HTML responses
|
|
26
|
+
res.send = function (body) {
|
|
27
|
+
// Only process HTML responses
|
|
28
|
+
if (typeof body === 'string' && isHtmlResponse(res)) {
|
|
29
|
+
try {
|
|
30
|
+
// Create scanner
|
|
31
|
+
const scanner = new index_1.WCAGScanner(defaultOptions);
|
|
32
|
+
// Run scan asynchronously (we can't make res.send async)
|
|
33
|
+
scanner.loadHTML(body).then(() => {
|
|
34
|
+
return scanner.scan();
|
|
35
|
+
}).then((results) => {
|
|
36
|
+
// Add violation count header
|
|
37
|
+
res.setHeader(defaultOptions.headerName || 'X-WCAG-Violations', results.violations.length.toString());
|
|
38
|
+
// Call violation handler if provided
|
|
39
|
+
if (defaultOptions.onViolation && results.violations.length > 0) {
|
|
40
|
+
defaultOptions.onViolation(results, req, res);
|
|
41
|
+
}
|
|
42
|
+
// Add inline report if enabled
|
|
43
|
+
if (defaultOptions.inlineReport && results.violations.length > 0) {
|
|
44
|
+
body = insertInlineReport(body, results);
|
|
45
|
+
}
|
|
46
|
+
// Send modified response
|
|
47
|
+
originalSend.call(res, body);
|
|
48
|
+
}).catch((error) => {
|
|
49
|
+
console.error('Error in WCAG scanner middleware:', error);
|
|
50
|
+
// Send original response if there's an error
|
|
51
|
+
originalSend.call(res, body);
|
|
52
|
+
});
|
|
53
|
+
// Return a dummy response to prevent Express from sending twice
|
|
54
|
+
return res;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('Error in WCAG scanner middleware:', error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Call original send for non-HTML responses
|
|
61
|
+
return originalSend.call(this, body);
|
|
62
|
+
};
|
|
63
|
+
next();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Determine if the request should be processed
|
|
68
|
+
* @param req Express request object
|
|
69
|
+
* @returns True if request should be processed
|
|
70
|
+
*/
|
|
71
|
+
function shouldProcessRequest(req) {
|
|
72
|
+
// Skip non-GET requests
|
|
73
|
+
if (req.method !== 'GET') {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Skip API routes
|
|
77
|
+
if (req.path.startsWith('/api/')) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// Skip if client doesn't want HTML
|
|
81
|
+
const accept = req.headers.accept || '';
|
|
82
|
+
if (!accept.includes('html') && !accept.includes('*/*')) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if response is HTML
|
|
89
|
+
* @param res Express response object
|
|
90
|
+
* @returns True if response is HTML
|
|
91
|
+
*/
|
|
92
|
+
function isHtmlResponse(res) {
|
|
93
|
+
const contentType = res.get('Content-Type') || '';
|
|
94
|
+
return contentType.includes('html');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Insert inline accessibility report into HTML
|
|
98
|
+
* @param html Original HTML
|
|
99
|
+
* @param results Scan results
|
|
100
|
+
* @returns HTML with inline report
|
|
101
|
+
*/
|
|
102
|
+
function insertInlineReport(html, results) {
|
|
103
|
+
// Skip if no violations
|
|
104
|
+
if (results.violations.length === 0) {
|
|
105
|
+
return html;
|
|
106
|
+
}
|
|
107
|
+
// Create simple inline report
|
|
108
|
+
const report = `
|
|
109
|
+
<div id="wcag-scanner-report" style="
|
|
110
|
+
position: fixed;
|
|
111
|
+
bottom: 20px;
|
|
112
|
+
right: 20px;
|
|
113
|
+
z-index: 9999;
|
|
114
|
+
background: white;
|
|
115
|
+
border: 2px solid #e53935;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
padding: 20px;
|
|
118
|
+
max-width: 400px;
|
|
119
|
+
max-height: 80vh;
|
|
120
|
+
overflow: auto;
|
|
121
|
+
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
|
122
|
+
font-family: sans-serif;
|
|
123
|
+
color: #333;
|
|
124
|
+
">
|
|
125
|
+
<div style="
|
|
126
|
+
display: flex;
|
|
127
|
+
justify-content: space-between;
|
|
128
|
+
align-items: center;
|
|
129
|
+
margin-bottom: 15px;
|
|
130
|
+
border-bottom: 1px solid #eee;
|
|
131
|
+
padding-bottom: 10px;
|
|
132
|
+
">
|
|
133
|
+
<h3 style="margin: 0; color: #e53935;">
|
|
134
|
+
${results.violations.length} Accessibility Issues Found
|
|
135
|
+
</h3>
|
|
136
|
+
<button onclick="document.getElementById('wcag-scanner-report').style.display='none'" style="
|
|
137
|
+
background: #f5f5f5;
|
|
138
|
+
border: none;
|
|
139
|
+
border-radius: 4px;
|
|
140
|
+
padding: 5px 10px;
|
|
141
|
+
cursor: pointer;
|
|
142
|
+
">
|
|
143
|
+
Close
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<ul style="
|
|
148
|
+
list-style-type: none;
|
|
149
|
+
padding: 0;
|
|
150
|
+
margin: 0;
|
|
151
|
+
">
|
|
152
|
+
${results.violations.slice(0, 5).map(violation => `
|
|
153
|
+
<li style="
|
|
154
|
+
margin-bottom: 15px;
|
|
155
|
+
padding-bottom: 15px;
|
|
156
|
+
border-bottom: 1px solid #eee;
|
|
157
|
+
">
|
|
158
|
+
<div style="
|
|
159
|
+
font-weight: bold;
|
|
160
|
+
margin-bottom: 5px;
|
|
161
|
+
">
|
|
162
|
+
${violation.description}
|
|
163
|
+
</div>
|
|
164
|
+
${violation.help ? `
|
|
165
|
+
<div style="
|
|
166
|
+
font-size: 14px;
|
|
167
|
+
margin-bottom: 8px;
|
|
168
|
+
">
|
|
169
|
+
${violation.help}
|
|
170
|
+
</div>
|
|
171
|
+
` : ''}
|
|
172
|
+
${violation.element ? `
|
|
173
|
+
<div style="
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
color: #666;
|
|
176
|
+
">
|
|
177
|
+
${violation.element.tagName}
|
|
178
|
+
${violation.element.id ? `#${violation.element.id}` : ''}
|
|
179
|
+
</div>
|
|
180
|
+
` : ''}
|
|
181
|
+
</li>
|
|
182
|
+
`).join('')}
|
|
183
|
+
${results.violations.length > 5 ? `
|
|
184
|
+
<li style="text-align: center; font-size: 14px; color: #666;">
|
|
185
|
+
+${results.violations.length - 5} more issues
|
|
186
|
+
</li>
|
|
187
|
+
` : ''}
|
|
188
|
+
</ul>
|
|
189
|
+
<div style="
|
|
190
|
+
margin-top: 15px;
|
|
191
|
+
font-size: 12px;
|
|
192
|
+
text-align: right;
|
|
193
|
+
color: #666;
|
|
194
|
+
">
|
|
195
|
+
Generated by WCAG Scanner
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
// Insert before </body> if it exists, otherwise append to the end
|
|
200
|
+
if (html.includes('</body>')) {
|
|
201
|
+
return html.replace('</body>', `${report}</body>`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return html + report;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.default = {
|
|
208
|
+
createMiddleware
|
|
209
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
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.express = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("./express"));
|
|
8
|
+
exports.express = express_1.default;
|
|
9
|
+
exports.default = {
|
|
10
|
+
express: express_1.default
|
|
11
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ScanResults, ScannerOptions } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Format scan results for console output
|
|
4
|
+
* @param results Scanner results object
|
|
5
|
+
* @param options Options used for the scan
|
|
6
|
+
* @returns Formatted console output string
|
|
7
|
+
*/
|
|
8
|
+
export declare function format(results: ScanResults, options?: ScannerOptions): string;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
format: typeof format;
|
|
11
|
+
};
|
|
12
|
+
export default _default;
|