@diegovelasquezweb/a11y-engine 0.7.2 → 0.7.4
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 +3 -25
- package/docs/api-reference.md +42 -3
- package/package.json +1 -1
- package/src/cli/audit.mjs +36 -4
- package/src/reports/renderers/html.mjs +1 -9
- package/src/source-patterns/source-scanner.mjs +44 -21
package/README.md
CHANGED
|
@@ -62,29 +62,7 @@ const payload = await runAudit({
|
|
|
62
62
|
});
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
**`runAudit` options**
|
|
68
|
-
|
|
69
|
-
| Option | Type | Description |
|
|
70
|
-
| :--- | :--- | :--- |
|
|
71
|
-
| `baseUrl` | `string` | Target URL to scan |
|
|
72
|
-
| `maxRoutes` | `number` | Maximum routes to discover and scan |
|
|
73
|
-
| `crawlDepth` | `number` | How many link levels to follow from the starting URL |
|
|
74
|
-
| `axeTags` | `string[]` | WCAG tag filters (e.g. `["wcag2a", "wcag2aa"]`) |
|
|
75
|
-
| `engines` | `{ axe?, cdp?, pa11y? }` | Enable or disable individual scan engines |
|
|
76
|
-
| `waitUntil` | `string` | Page load strategy: `"domcontentloaded"`, `"load"`, or `"networkidle"` |
|
|
77
|
-
| `timeoutMs` | `number` | Per-page timeout in milliseconds |
|
|
78
|
-
| `viewport` | `{ width, height }` | Browser viewport size |
|
|
79
|
-
| `colorScheme` | `string` | Emulated color scheme: `"light"` or `"dark"` |
|
|
80
|
-
| `projectDir` | `string` | Local project path for stack detection and source pattern scanning |
|
|
81
|
-
| `repoUrl` | `string` | GitHub repo URL for remote stack detection and source pattern scanning |
|
|
82
|
-
| `githubToken` | `string` | GitHub token for API access when using `repoUrl` |
|
|
83
|
-
| `skipPatterns` | `boolean` | Disable source pattern scanning |
|
|
84
|
-
| `ai` | `{ enabled?, apiKey?, githubToken?, model? }` | AI enrichment configuration |
|
|
85
|
-
| `onProgress` | `(step, status, extra?) => void` | Progress callback for UI updates |
|
|
86
|
-
|
|
87
|
-
See [API Reference](docs/api-reference.md) for the full `RunAuditOptions` contract.
|
|
65
|
+
See [API Reference](docs/api-reference.md) for options, progress steps, and return types.
|
|
88
66
|
|
|
89
67
|
#### getFindings
|
|
90
68
|
|
|
@@ -133,13 +111,13 @@ These functions render final artifacts from scan payload data.
|
|
|
133
111
|
|
|
134
112
|
### Knowledge API
|
|
135
113
|
|
|
136
|
-
These functions expose scanner help content, persona explanations, conformance levels, and UI copy so frontends or agents can render
|
|
114
|
+
These functions expose scanner help content, persona explanations, conformance levels, and UI copy so frontends or agents can render guidance from engine-owned data.
|
|
137
115
|
|
|
138
116
|
| Function | Returns | Description |
|
|
139
117
|
| :--- | :--- | :--- |
|
|
140
118
|
| `getScannerHelp(options?)` | `{ locale, version, title, engines, options }` | Scanner option and engine help metadata |
|
|
141
119
|
| `getPersonaReference(options?)` | `{ locale, version, personas }` | Persona labels, descriptions, and mapping hints |
|
|
142
|
-
| `getUiHelp(options?)` | `{ locale, version,
|
|
120
|
+
| `getUiHelp(options?)` | `{ locale, version, concepts, glossary }` | Shared concept definitions and glossary entries |
|
|
143
121
|
| `getConformanceLevels(options?)` | `{ locale, version, conformanceLevels }` | WCAG conformance level definitions with axe tag mappings |
|
|
144
122
|
| `getWcagPrinciples(options?)` | `{ locale, version, wcagPrinciples }` | The four WCAG principles with criterion prefix patterns |
|
|
145
123
|
| `getSeverityLevels(options?)` | `{ locale, version, severityLevels }` | Severity level definitions with labels and ordering |
|
package/docs/api-reference.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
### `runAudit(options)`
|
|
10
10
|
|
|
11
|
-
Runs route discovery, runtime scan, merge, and
|
|
11
|
+
Runs route discovery, runtime scan, merge, analyzer enrichment, and optional AI enrichment. Supports local project paths or remote GitHub repos for stack detection and source pattern scanning.
|
|
12
12
|
|
|
13
13
|
`options` (`RunAuditOptions`):
|
|
14
14
|
|
|
@@ -30,10 +30,28 @@ Runs route discovery, runtime scan, merge, and analyzer enrichment.
|
|
|
30
30
|
| `ignoreFindings` | `string[]` |
|
|
31
31
|
| `framework` | `string` |
|
|
32
32
|
| `projectDir` | `string` |
|
|
33
|
+
| `repoUrl` | `string` |
|
|
34
|
+
| `githubToken` | `string` |
|
|
33
35
|
| `skipPatterns` | `boolean` |
|
|
34
36
|
| `screenshotsDir` | `string` |
|
|
37
|
+
| `engines` | `{ axe?: boolean; cdp?: boolean; pa11y?: boolean }` |
|
|
38
|
+
| `ai` | `{ enabled?: boolean; apiKey?: string; githubToken?: string; model?: string }` |
|
|
35
39
|
| `onProgress` | `(step: string, status: string, extra?: Record<string, unknown>) => void` |
|
|
36
40
|
|
|
41
|
+
Progress steps emitted via `onProgress`:
|
|
42
|
+
|
|
43
|
+
| Step | When |
|
|
44
|
+
| :--- | :--- |
|
|
45
|
+
| `page` | Always — page load |
|
|
46
|
+
| `axe` | Always — axe-core scan |
|
|
47
|
+
| `cdp` | Always — CDP accessibility tree check |
|
|
48
|
+
| `pa11y` | Always — pa11y HTML CodeSniffer scan |
|
|
49
|
+
| `merge` | Always — finding deduplication |
|
|
50
|
+
| `intelligence` | Always — enrichment and WCAG mapping |
|
|
51
|
+
| `repo` | When `repoUrl` is set |
|
|
52
|
+
| `patterns` | When source scanning is active |
|
|
53
|
+
| `ai` | When AI enrichment is configured |
|
|
54
|
+
|
|
37
55
|
Returns: `Promise<ScanPayload>`
|
|
38
56
|
|
|
39
57
|
### `getFindings(input, options?)`
|
|
@@ -123,14 +141,35 @@ Returns: `PersonaReference` (`{ locale, version, personas }`)
|
|
|
123
141
|
- `options`: `KnowledgeOptions`
|
|
124
142
|
- `locale?: string`
|
|
125
143
|
|
|
126
|
-
Returns: `UiHelp` (`{ locale, version,
|
|
144
|
+
Returns: `UiHelp` (`{ locale, version, concepts, glossary }`)
|
|
145
|
+
|
|
146
|
+
### `getConformanceLevels(options?)`
|
|
147
|
+
|
|
148
|
+
- `options`: `KnowledgeOptions`
|
|
149
|
+
- `locale?: string`
|
|
150
|
+
|
|
151
|
+
Returns: `ConformanceLevelsResult` (`{ locale, version, conformanceLevels }`)
|
|
152
|
+
|
|
153
|
+
### `getWcagPrinciples(options?)`
|
|
154
|
+
|
|
155
|
+
- `options`: `KnowledgeOptions`
|
|
156
|
+
- `locale?: string`
|
|
157
|
+
|
|
158
|
+
Returns: `WcagPrinciplesResult` (`{ locale, version, wcagPrinciples }`)
|
|
159
|
+
|
|
160
|
+
### `getSeverityLevels(options?)`
|
|
161
|
+
|
|
162
|
+
- `options`: `KnowledgeOptions`
|
|
163
|
+
- `locale?: string`
|
|
164
|
+
|
|
165
|
+
Returns: `SeverityLevelsResult` (`{ locale, version, severityLevels }`)
|
|
127
166
|
|
|
128
167
|
### `getKnowledge(options?)`
|
|
129
168
|
|
|
130
169
|
- `options`: `KnowledgeOptions`
|
|
131
170
|
- `locale?: string`
|
|
132
171
|
|
|
133
|
-
Returns: `EngineKnowledge` (`{ locale, version, scanner, personas,
|
|
172
|
+
Returns: `EngineKnowledge` (`{ locale, version, scanner, personas, concepts, glossary, docs, conformanceLevels, wcagPrinciples, severityLevels }`)
|
|
134
173
|
|
|
135
174
|
---
|
|
136
175
|
|
package/package.json
CHANGED
package/src/cli/audit.mjs
CHANGED
|
@@ -140,6 +140,31 @@ async function main() {
|
|
|
140
140
|
const timeoutMs = getArgValue("timeout-ms") || DEFAULTS.timeoutMs;
|
|
141
141
|
const axeTags = getArgValue("axe-tags");
|
|
142
142
|
|
|
143
|
+
const repoUrl = getArgValue("repo-url");
|
|
144
|
+
const githubToken = getArgValue("github-token");
|
|
145
|
+
|
|
146
|
+
let remotePackageJson = null;
|
|
147
|
+
let detectedFramework = null;
|
|
148
|
+
if (repoUrl) {
|
|
149
|
+
try {
|
|
150
|
+
const { fetchPackageJson } = await import("../core/github-api.mjs");
|
|
151
|
+
remotePackageJson = await fetchPackageJson(repoUrl, githubToken || undefined);
|
|
152
|
+
if (remotePackageJson) {
|
|
153
|
+
const { detectProjectContext } = await import("../pipeline/dom-scanner.mjs");
|
|
154
|
+
const ctx = detectProjectContext(null, remotePackageJson);
|
|
155
|
+
if (ctx.framework) {
|
|
156
|
+
detectedFramework = ctx.framework;
|
|
157
|
+
log.info(`Detected framework from repository: ${ctx.framework}`);
|
|
158
|
+
}
|
|
159
|
+
if (ctx.uiLibraries.length) {
|
|
160
|
+
log.info(`Detected UI libraries: ${ctx.uiLibraries.join(", ")}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
log.warn(`Could not fetch package.json from repo: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
const sessionFile = getInternalPath("a11y-session.json");
|
|
144
169
|
let projectDir = getArgValue("project-dir");
|
|
145
170
|
if (projectDir) {
|
|
@@ -265,12 +290,19 @@ async function main() {
|
|
|
265
290
|
|
|
266
291
|
const analyzerArgs = [];
|
|
267
292
|
if (ignoreFindings) analyzerArgs.push("--ignore-findings", ignoreFindings);
|
|
268
|
-
|
|
293
|
+
const resolvedFramework = framework || detectedFramework;
|
|
294
|
+
if (resolvedFramework) analyzerArgs.push("--framework", resolvedFramework);
|
|
269
295
|
await runScript("../enrichment/analyzer.mjs", analyzerArgs);
|
|
270
296
|
|
|
271
|
-
if (projectDir && !skipPatterns) {
|
|
272
|
-
const patternArgs = [
|
|
273
|
-
|
|
297
|
+
if ((projectDir || repoUrl) && !skipPatterns) {
|
|
298
|
+
const patternArgs = [];
|
|
299
|
+
if (projectDir) {
|
|
300
|
+
patternArgs.push("--project-dir", path.resolve(projectDir));
|
|
301
|
+
} else {
|
|
302
|
+
patternArgs.push("--repo-url", repoUrl);
|
|
303
|
+
if (githubToken) patternArgs.push("--github-token", githubToken);
|
|
304
|
+
}
|
|
305
|
+
let resolvedFramework = framework || detectedFramework;
|
|
274
306
|
if (!resolvedFramework) {
|
|
275
307
|
try {
|
|
276
308
|
const findings = JSON.parse(fs.readFileSync(getInternalPath("a11y-findings.json"), "utf-8"));
|
|
@@ -107,15 +107,7 @@ export function buildIssueCard(finding) {
|
|
|
107
107
|
</div>`
|
|
108
108
|
: "";
|
|
109
109
|
|
|
110
|
-
const implNotesHtml =
|
|
111
|
-
? `<div class="mt-4 pt-3 border-t border-indigo-100/50">
|
|
112
|
-
<h4 class="text-[10px] font-black text-amber-700 uppercase tracking-widest mb-2 flex items-center gap-1.5">
|
|
113
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
114
|
-
Implementation Notes
|
|
115
|
-
</h4>
|
|
116
|
-
<p class="text-[12px] text-amber-900/80 leading-relaxed bg-amber-50/60 border border-amber-100/60 rounded-lg p-3">${escapeHtml(finding.fixDifficultyNotes)}</p>
|
|
117
|
-
</div>`
|
|
118
|
-
: "";
|
|
110
|
+
const implNotesHtml = "";
|
|
119
111
|
|
|
120
112
|
const problemPanelHtml = `
|
|
121
113
|
<div class="grid grid-cols-1 gap-6">
|
|
@@ -46,6 +46,8 @@ function parseArgs(argv) {
|
|
|
46
46
|
|
|
47
47
|
const args = {
|
|
48
48
|
projectDir: null,
|
|
49
|
+
repoUrl: null,
|
|
50
|
+
githubToken: null,
|
|
49
51
|
framework: null,
|
|
50
52
|
output: getInternalPath("a11y-pattern-findings.json"),
|
|
51
53
|
onlyPattern: null,
|
|
@@ -56,13 +58,15 @@ function parseArgs(argv) {
|
|
|
56
58
|
const value = argv[i + 1];
|
|
57
59
|
if (!key.startsWith("--") || value === undefined) continue;
|
|
58
60
|
if (key === "--project-dir") args.projectDir = value;
|
|
61
|
+
if (key === "--repo-url") args.repoUrl = value;
|
|
62
|
+
if (key === "--github-token") args.githubToken = value;
|
|
59
63
|
if (key === "--framework") args.framework = value;
|
|
60
64
|
if (key === "--output") args.output = value;
|
|
61
65
|
if (key === "--only-pattern") args.onlyPattern = value;
|
|
62
66
|
i++;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
if (!args.projectDir) throw new Error("Missing required --project-dir");
|
|
69
|
+
if (!args.projectDir && !args.repoUrl) throw new Error("Missing required --project-dir or --repo-url");
|
|
66
70
|
return args;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -320,7 +324,7 @@ export async function scanPatternRemote(pattern, repoUrl, githubToken, framework
|
|
|
320
324
|
/**
|
|
321
325
|
* Main execution function for the pattern scanner.
|
|
322
326
|
*/
|
|
323
|
-
function main() {
|
|
327
|
+
async function main() {
|
|
324
328
|
const args = parseArgs(process.argv.slice(2));
|
|
325
329
|
const { patterns } = loadAssetJson(
|
|
326
330
|
ASSET_PATHS.remediation.codePatterns,
|
|
@@ -339,23 +343,44 @@ function main() {
|
|
|
339
343
|
process.exit(0);
|
|
340
344
|
}
|
|
341
345
|
|
|
342
|
-
const scanDirs = resolveScanDirs(args.framework, args.projectDir);
|
|
343
|
-
log.info(`Scanning source code at: ${args.projectDir}`);
|
|
344
|
-
if (scanDirs.length > 1 || scanDirs[0] !== args.projectDir) {
|
|
345
|
-
log.info(` Scoped to: ${scanDirs.map((d) => relative(args.projectDir, d)).join(", ")}`);
|
|
346
|
-
}
|
|
347
|
-
log.info(`Running ${activePatterns.length} pattern(s)...`);
|
|
348
|
-
|
|
349
346
|
const allFindings = [];
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
347
|
+
|
|
348
|
+
if (args.repoUrl) {
|
|
349
|
+
// Remote scan via GitHub API — no clone needed
|
|
350
|
+
log.info(`Scanning source code at: ${args.repoUrl}`);
|
|
351
|
+
log.info(`Running ${activePatterns.length} pattern(s) via GitHub API...`);
|
|
352
|
+
|
|
353
|
+
for (const pattern of activePatterns) {
|
|
354
|
+
const findings = await scanPatternRemote(
|
|
355
|
+
pattern,
|
|
356
|
+
args.repoUrl,
|
|
357
|
+
args.githubToken || null,
|
|
358
|
+
args.framework || null,
|
|
359
|
+
);
|
|
360
|
+
if (findings.length > 0) {
|
|
361
|
+
log.info(` ${pattern.id}: ${findings.length} match(es)`);
|
|
362
|
+
}
|
|
363
|
+
allFindings.push(...findings);
|
|
354
364
|
}
|
|
355
|
-
|
|
356
|
-
|
|
365
|
+
} else {
|
|
366
|
+
// Local filesystem scan
|
|
367
|
+
const scanDirs = resolveScanDirs(args.framework, args.projectDir);
|
|
368
|
+
log.info(`Scanning source code at: ${args.projectDir}`);
|
|
369
|
+
if (scanDirs.length > 1 || scanDirs[0] !== args.projectDir) {
|
|
370
|
+
log.info(` Scoped to: ${scanDirs.map((d) => relative(args.projectDir, d)).join(", ")}`);
|
|
371
|
+
}
|
|
372
|
+
log.info(`Running ${activePatterns.length} pattern(s)...`);
|
|
373
|
+
|
|
374
|
+
for (const pattern of activePatterns) {
|
|
375
|
+
const findings = [];
|
|
376
|
+
for (const scanDir of scanDirs) {
|
|
377
|
+
findings.push(...scanPattern(pattern, scanDir, args.projectDir));
|
|
378
|
+
}
|
|
379
|
+
if (findings.length > 0) {
|
|
380
|
+
log.info(` ${pattern.id}: ${findings.length} match(es)`);
|
|
381
|
+
}
|
|
382
|
+
allFindings.push(...findings);
|
|
357
383
|
}
|
|
358
|
-
allFindings.push(...findings);
|
|
359
384
|
}
|
|
360
385
|
|
|
361
386
|
const confirmed = allFindings.filter((f) => f.status === "confirmed").length;
|
|
@@ -363,7 +388,7 @@ function main() {
|
|
|
363
388
|
|
|
364
389
|
writeJson(args.output, {
|
|
365
390
|
generated_at: new Date().toISOString(),
|
|
366
|
-
project_dir: args.projectDir,
|
|
391
|
+
project_dir: args.repoUrl || args.projectDir,
|
|
367
392
|
findings: allFindings,
|
|
368
393
|
summary: {
|
|
369
394
|
total: allFindings.length,
|
|
@@ -378,10 +403,8 @@ function main() {
|
|
|
378
403
|
}
|
|
379
404
|
|
|
380
405
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
381
|
-
|
|
382
|
-
main();
|
|
383
|
-
} catch (error) {
|
|
406
|
+
main().catch((error) => {
|
|
384
407
|
log.error(error.message);
|
|
385
408
|
process.exit(1);
|
|
386
|
-
}
|
|
409
|
+
});
|
|
387
410
|
}
|