equall-cli 0.1.2 → 0.1.3
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 +53 -6
- package/dist/{chunk-JMFHJMBB.js → chunk-5YPLWCOT.js} +55 -6
- package/dist/cli.js +220 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,17 +11,26 @@ equall scan .
|
|
|
11
11
|
```
|
|
12
12
|
◆ EQUALL — Accessibility Score
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
56 ~A WCAG 2.2
|
|
15
15
|
|
|
16
16
|
POUR Breakdown
|
|
17
|
-
P Perceivable
|
|
18
|
-
O Operable
|
|
17
|
+
P Perceivable ██████████████████░░ 89
|
|
18
|
+
O Operable ███████████████░░░░░ 76
|
|
19
19
|
U Understandable ░░░░░░░░░░░░░░░░░░░░ n/a
|
|
20
|
-
R Robust
|
|
20
|
+
R Robust █████████████████░░░ 85
|
|
21
21
|
|
|
22
22
|
Summary
|
|
23
|
-
33 files scanned ·
|
|
24
|
-
2 critical
|
|
23
|
+
33 files scanned · 15 WCAG violations · 19 best-practice issues
|
|
24
|
+
2 critical 11 serious 19 moderate 0 minor
|
|
25
|
+
2 issues ignored via equall-ignore
|
|
26
|
+
Score 56/100 · 18/30 Level A criteria checked (60%) · 5 failed
|
|
27
|
+
29/57 Level AA criteria checked (51%) · 6 failed
|
|
28
|
+
|
|
29
|
+
ⓘ You're failing 5 Level A criteria (1.3.1, 2.1.1, 2.4.2, 2.4.4, 4.1.2).
|
|
30
|
+
Level A is the legal minimum — fix these first.
|
|
31
|
+
|
|
32
|
+
Scanners: axe-core@4.11.1 (23 issues), eslint-jsx-a11y@6.10.2 (13 issues)
|
|
33
|
+
Completed in 0.8s
|
|
25
34
|
```
|
|
26
35
|
|
|
27
36
|
## Install
|
|
@@ -102,6 +111,40 @@ The POUR metrics (Perceivable, Operable, Understandable, Robust) strictly follow
|
|
|
102
111
|
### Conformance Level
|
|
103
112
|
Conformance (A / AA / AAA) is evaluated strictly against your `--level` target. If you target `AA`, any `AAA` rules incidentally flagged by the scanners will not downgrade your conformance status.
|
|
104
113
|
|
|
114
|
+
## Ignoring issues
|
|
115
|
+
|
|
116
|
+
Some issues are false positives (e.g. an orphan `<li>` in a component that's always rendered inside a `<ul>`). Suppress them with inline comments:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// equall-ignore-next-line
|
|
120
|
+
<li>{item.name}</li>
|
|
121
|
+
|
|
122
|
+
// equall-ignore-next-line jsx-a11y/alt-text
|
|
123
|
+
<img src={logo} />
|
|
124
|
+
|
|
125
|
+
{/* equall-ignore-next-line */}
|
|
126
|
+
<div onClick={handler}>...</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<!-- equall-ignore-next-line -->
|
|
131
|
+
<img src="decorative.png" />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Add `// equall-ignore-file` in the first 5 lines to ignore an entire file.
|
|
135
|
+
|
|
136
|
+
Or use the CLI to inject/manage comments without opening the file:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
equall ignore src/Modal.tsx:89 # ignore all rules at line 89
|
|
140
|
+
equall ignore src/Modal.tsx:89 jsx-a11y/alt-text # ignore a specific rule
|
|
141
|
+
equall ignore . # list all ignores
|
|
142
|
+
equall ignore --remove src/Modal.tsx:89 # remove an ignore
|
|
143
|
+
equall ignore --clear # remove all ignores
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Ignored issues are excluded from the score. Use `equall scan . -i` to show them, or `--json` to get them with `"ignored": true`.
|
|
147
|
+
|
|
105
148
|
## Programmatic API
|
|
106
149
|
|
|
107
150
|
```typescript
|
|
@@ -124,6 +167,10 @@ console.log(result.issues.length) // 12
|
|
|
124
167
|
- `1` — score < 50 (useful for CI gates)
|
|
125
168
|
- `2` — scan error
|
|
126
169
|
|
|
170
|
+
## Contributing
|
|
171
|
+
|
|
172
|
+
Issues and PRs welcome — https://github.com/GotaBird/equall/issues
|
|
173
|
+
|
|
127
174
|
## License
|
|
128
175
|
|
|
129
176
|
MIT
|
|
@@ -39268,7 +39268,8 @@ function computeSummary(issues, filesScanned) {
|
|
|
39268
39268
|
by_severity: bySeverity,
|
|
39269
39269
|
by_scanner: byScanner,
|
|
39270
39270
|
criteria_tested: [...criteriaSet].sort(),
|
|
39271
|
-
criteria_failed: [...failedCriteriaSet].sort()
|
|
39271
|
+
criteria_failed: [...failedCriteriaSet].sort(),
|
|
39272
|
+
ignored_count: 0
|
|
39272
39273
|
};
|
|
39273
39274
|
}
|
|
39274
39275
|
function computeScore(issues, filesScanned) {
|
|
@@ -40373,9 +40374,9 @@ async function runScan(options = {}) {
|
|
|
40373
40374
|
);
|
|
40374
40375
|
const allIssues = [];
|
|
40375
40376
|
const scannersUsed = [];
|
|
40376
|
-
for (const
|
|
40377
|
-
if (
|
|
40378
|
-
const { scanner, issues } =
|
|
40377
|
+
for (const result2 of scannerResults) {
|
|
40378
|
+
if (result2.status === "fulfilled") {
|
|
40379
|
+
const { scanner, issues } = result2.value;
|
|
40379
40380
|
allIssues.push(...issues);
|
|
40380
40381
|
scannersUsed.push({
|
|
40381
40382
|
name: scanner.name,
|
|
@@ -40385,16 +40386,63 @@ async function runScan(options = {}) {
|
|
|
40385
40386
|
issues_found: issues.length
|
|
40386
40387
|
});
|
|
40387
40388
|
} else {
|
|
40388
|
-
const err =
|
|
40389
|
+
const err = result2.reason instanceof Error ? result2.reason.message : String(result2.reason);
|
|
40389
40390
|
console.warn(` [scanner] Failed: ${err.slice(0, 120)}`);
|
|
40390
40391
|
}
|
|
40391
40392
|
}
|
|
40392
40393
|
const deduped = deduplicateIssues(allIssues);
|
|
40394
|
+
const { active, ignored } = applyIgnoreComments(deduped, files);
|
|
40393
40395
|
const criteriaCovered = [...new Set(scanners.flatMap((s) => s.coveredCriteria))].sort();
|
|
40394
40396
|
const WCAG_TOTAL = { A: 30, AA: 57, AAA: 78 };
|
|
40395
40397
|
const criteriaTotal = WCAG_TOTAL[scanOptions.wcag_level] ?? 57;
|
|
40396
40398
|
const durationMs = Date.now() - startTime;
|
|
40397
|
-
|
|
40399
|
+
const result = computeScanResult(active, files.length, scannersUsed, durationMs, scanOptions.wcag_level, criteriaCovered, criteriaTotal);
|
|
40400
|
+
result.issues = [...active, ...ignored];
|
|
40401
|
+
result.summary.ignored_count = ignored.length;
|
|
40402
|
+
return result;
|
|
40403
|
+
}
|
|
40404
|
+
function applyIgnoreComments(issues, files) {
|
|
40405
|
+
const fileContentMap = /* @__PURE__ */ new Map();
|
|
40406
|
+
for (const file of files) {
|
|
40407
|
+
const lines = file.content.split("\n");
|
|
40408
|
+
fileContentMap.set(file.path, lines);
|
|
40409
|
+
}
|
|
40410
|
+
const ignoredFiles = /* @__PURE__ */ new Set();
|
|
40411
|
+
for (const [filePath, lines] of fileContentMap) {
|
|
40412
|
+
const header = lines.slice(0, 5);
|
|
40413
|
+
if (header.some((line) => line.includes("equall-ignore-file"))) {
|
|
40414
|
+
ignoredFiles.add(filePath);
|
|
40415
|
+
}
|
|
40416
|
+
}
|
|
40417
|
+
const active = [];
|
|
40418
|
+
const ignored = [];
|
|
40419
|
+
for (const issue of issues) {
|
|
40420
|
+
if (ignoredFiles.has(issue.file_path)) {
|
|
40421
|
+
ignored.push({ ...issue, ignored: true });
|
|
40422
|
+
continue;
|
|
40423
|
+
}
|
|
40424
|
+
if (issue.line != null && issue.line > 1) {
|
|
40425
|
+
const lines = fileContentMap.get(issue.file_path);
|
|
40426
|
+
if (lines) {
|
|
40427
|
+
const prevLine = lines[issue.line - 2] ?? "";
|
|
40428
|
+
if (prevLine.includes("equall-ignore-next-line")) {
|
|
40429
|
+
const stripped = prevLine.replace(/\s*(?:-->|\*\/\}?)\s*$/g, "");
|
|
40430
|
+
const match = stripped.match(/equall-ignore-next-line\s+([\w\-/.]+)/);
|
|
40431
|
+
if (match) {
|
|
40432
|
+
if (match[1] === issue.scanner_rule_id) {
|
|
40433
|
+
ignored.push({ ...issue, ignored: true });
|
|
40434
|
+
continue;
|
|
40435
|
+
}
|
|
40436
|
+
} else {
|
|
40437
|
+
ignored.push({ ...issue, ignored: true });
|
|
40438
|
+
continue;
|
|
40439
|
+
}
|
|
40440
|
+
}
|
|
40441
|
+
}
|
|
40442
|
+
}
|
|
40443
|
+
active.push(issue);
|
|
40444
|
+
}
|
|
40445
|
+
return { active, ignored };
|
|
40398
40446
|
}
|
|
40399
40447
|
function deduplicateIssues(issues) {
|
|
40400
40448
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -40416,6 +40464,7 @@ export {
|
|
|
40416
40464
|
__commonJS,
|
|
40417
40465
|
__export,
|
|
40418
40466
|
__toESM,
|
|
40467
|
+
globby,
|
|
40419
40468
|
computeScanResult,
|
|
40420
40469
|
runScan
|
|
40421
40470
|
};
|
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
__export,
|
|
6
6
|
__require,
|
|
7
7
|
__toESM,
|
|
8
|
+
globby,
|
|
8
9
|
runScan
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5YPLWCOT.js";
|
|
10
11
|
|
|
11
12
|
// node_modules/commander/lib/error.js
|
|
12
13
|
var require_error = __commonJS({
|
|
@@ -3009,7 +3010,7 @@ var require_commander = __commonJS({
|
|
|
3009
3010
|
});
|
|
3010
3011
|
|
|
3011
3012
|
// src/cli.ts
|
|
3012
|
-
import { resolve, basename } from "path";
|
|
3013
|
+
import { resolve as resolve2, basename } from "path";
|
|
3013
3014
|
|
|
3014
3015
|
// node_modules/commander/esm.mjs
|
|
3015
3016
|
var import_index = __toESM(require_commander(), 1);
|
|
@@ -6512,7 +6513,7 @@ function bar(value, width = 20) {
|
|
|
6512
6513
|
const color = scoreColor(value);
|
|
6513
6514
|
return `${color}${"\u2588".repeat(filled)}${DIM}${"\u2591".repeat(empty)}${RESET} ${color}${value}${RESET}`;
|
|
6514
6515
|
}
|
|
6515
|
-
function printResult(result) {
|
|
6516
|
+
function printResult(result, options = {}) {
|
|
6516
6517
|
const { score, conformance_level, pour_scores, summary, scanners_used, duration_ms } = result;
|
|
6517
6518
|
console.log();
|
|
6518
6519
|
console.log(`${BOLD} \u25C6 EQUALL \u2014 Accessibility Score${RESET}`);
|
|
@@ -6531,6 +6532,9 @@ function printResult(result) {
|
|
|
6531
6532
|
const bpIssuesCount = result.issues.length - wcagIssuesCount;
|
|
6532
6533
|
console.log(` ${summary.files_scanned} files scanned \xB7 ${wcagIssuesCount} WCAG violations \xB7 ${bpIssuesCount} best-practice issues`);
|
|
6533
6534
|
console.log(` ${RED}${summary.by_severity.critical} critical${RESET} ${YELLOW}${summary.by_severity.serious} serious${RESET} ${CYAN}${summary.by_severity.moderate} moderate${RESET} ${DIM}${summary.by_severity.minor} minor${RESET}`);
|
|
6535
|
+
if (summary.ignored_count > 0) {
|
|
6536
|
+
console.log(` ${DIM}${summary.ignored_count} issue${summary.ignored_count > 1 ? "s" : ""} ignored via equall-ignore${RESET}`);
|
|
6537
|
+
}
|
|
6534
6538
|
if (result.criteria_total > 0) {
|
|
6535
6539
|
const covered = result.criteria_covered.length;
|
|
6536
6540
|
const total = result.criteria_total;
|
|
@@ -6559,8 +6563,9 @@ function printResult(result) {
|
|
|
6559
6563
|
console.log();
|
|
6560
6564
|
printCoaching(result);
|
|
6561
6565
|
console.log();
|
|
6562
|
-
const
|
|
6563
|
-
const
|
|
6566
|
+
const visibleIssues = result.issues.filter((i) => !i.ignored);
|
|
6567
|
+
const wcagIssues = visibleIssues.filter((i) => i.wcag_criteria.length > 0);
|
|
6568
|
+
const bpIssues = visibleIssues.filter((i) => i.wcag_criteria.length === 0);
|
|
6564
6569
|
if (wcagIssues.length > 0) {
|
|
6565
6570
|
console.log(` ${BOLD}WCAG Violations${RESET}`);
|
|
6566
6571
|
const grouped = groupByCriterion(wcagIssues);
|
|
@@ -6597,6 +6602,17 @@ function printResult(result) {
|
|
|
6597
6602
|
}
|
|
6598
6603
|
console.log();
|
|
6599
6604
|
}
|
|
6605
|
+
if (options.showIgnored) {
|
|
6606
|
+
const ignoredIssues = result.issues.filter((i) => i.ignored);
|
|
6607
|
+
if (ignoredIssues.length > 0) {
|
|
6608
|
+
console.log(` ${BOLD}Ignored${RESET}`);
|
|
6609
|
+
for (const issue of ignoredIssues) {
|
|
6610
|
+
const location = issue.line ? `:${issue.line}` : "";
|
|
6611
|
+
console.log(` ${DIM}\u2298${RESET} ${DIM}${issue.file_path}${location}${RESET} ${issue.scanner_rule_id}`);
|
|
6612
|
+
}
|
|
6613
|
+
console.log();
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6600
6616
|
console.log(` ${DIM}Scanners: ${scanners_used.map((s) => `${s.name}@${s.version} (${s.issues_found} issues)`).join(", ")}${RESET}`);
|
|
6601
6617
|
console.log(` ${DIM}Completed in ${(duration_ms / 1e3).toFixed(1)}s${RESET}`);
|
|
6602
6618
|
console.log();
|
|
@@ -6660,16 +6676,148 @@ function printJson(result) {
|
|
|
6660
6676
|
console.log(JSON.stringify(result, null, 2));
|
|
6661
6677
|
}
|
|
6662
6678
|
|
|
6679
|
+
// src/ignores.ts
|
|
6680
|
+
import { readFile, writeFile } from "fs/promises";
|
|
6681
|
+
import { resolve } from "path";
|
|
6682
|
+
var IGNORE_PATTERN = /equall-ignore-(next-line|file)(?:\s+(\S+))?/;
|
|
6683
|
+
async function findIgnores(rootPath) {
|
|
6684
|
+
const paths = await globby(
|
|
6685
|
+
["**/*.{html,htm,jsx,tsx,vue,svelte,astro}"],
|
|
6686
|
+
{
|
|
6687
|
+
cwd: rootPath,
|
|
6688
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"],
|
|
6689
|
+
absolute: false,
|
|
6690
|
+
gitignore: true
|
|
6691
|
+
}
|
|
6692
|
+
);
|
|
6693
|
+
const entries = [];
|
|
6694
|
+
for (const relativePath of paths) {
|
|
6695
|
+
const absolutePath = resolve(rootPath, relativePath);
|
|
6696
|
+
let content;
|
|
6697
|
+
try {
|
|
6698
|
+
content = await readFile(absolutePath, "utf-8");
|
|
6699
|
+
} catch {
|
|
6700
|
+
continue;
|
|
6701
|
+
}
|
|
6702
|
+
const lines = content.split("\n");
|
|
6703
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6704
|
+
const match = lines[i].match(IGNORE_PATTERN);
|
|
6705
|
+
if (!match) continue;
|
|
6706
|
+
const type = match[1];
|
|
6707
|
+
if (type === "file" && i >= 5) continue;
|
|
6708
|
+
entries.push({
|
|
6709
|
+
file_path: relativePath,
|
|
6710
|
+
line: i + 1,
|
|
6711
|
+
type,
|
|
6712
|
+
rule_id: match[2] ?? null,
|
|
6713
|
+
raw: lines[i].trim()
|
|
6714
|
+
});
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
return entries.sort((a, b) => a.file_path.localeCompare(b.file_path) || a.line - b.line);
|
|
6718
|
+
}
|
|
6719
|
+
async function removeIgnore(rootPath, target) {
|
|
6720
|
+
const ignores = await findIgnores(rootPath);
|
|
6721
|
+
const colonIdx = target.lastIndexOf(":");
|
|
6722
|
+
let targetFile;
|
|
6723
|
+
let targetLine = null;
|
|
6724
|
+
if (colonIdx > 0) {
|
|
6725
|
+
const maybeLine = parseInt(target.slice(colonIdx + 1), 10);
|
|
6726
|
+
if (!isNaN(maybeLine)) {
|
|
6727
|
+
targetFile = target.slice(0, colonIdx);
|
|
6728
|
+
targetLine = maybeLine;
|
|
6729
|
+
} else {
|
|
6730
|
+
targetFile = target;
|
|
6731
|
+
}
|
|
6732
|
+
} else {
|
|
6733
|
+
targetFile = target;
|
|
6734
|
+
}
|
|
6735
|
+
const matches = ignores.filter((e) => {
|
|
6736
|
+
if (e.file_path !== targetFile) return false;
|
|
6737
|
+
if (targetLine !== null) return e.line === targetLine;
|
|
6738
|
+
return true;
|
|
6739
|
+
});
|
|
6740
|
+
if (matches.length === 0) {
|
|
6741
|
+
return { removed: [], notFound: true };
|
|
6742
|
+
}
|
|
6743
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6744
|
+
for (const m of matches) {
|
|
6745
|
+
const lines = byFile.get(m.file_path) ?? [];
|
|
6746
|
+
lines.push(m.line);
|
|
6747
|
+
byFile.set(m.file_path, lines);
|
|
6748
|
+
}
|
|
6749
|
+
for (const [filePath, lineNumbers] of byFile) {
|
|
6750
|
+
const absolutePath = resolve(rootPath, filePath);
|
|
6751
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
6752
|
+
const lines = content.split("\n");
|
|
6753
|
+
const toRemove = new Set(lineNumbers.map((l) => l - 1));
|
|
6754
|
+
const newLines = lines.filter((_, i) => !toRemove.has(i));
|
|
6755
|
+
await writeFile(absolutePath, newLines.join("\n"), "utf-8");
|
|
6756
|
+
}
|
|
6757
|
+
return { removed: matches, notFound: false };
|
|
6758
|
+
}
|
|
6759
|
+
async function addIgnore(rootPath, target, ruleId) {
|
|
6760
|
+
const colonIdx = target.lastIndexOf(":");
|
|
6761
|
+
if (colonIdx <= 0) return null;
|
|
6762
|
+
const targetFile = target.slice(0, colonIdx);
|
|
6763
|
+
const targetLine = parseInt(target.slice(colonIdx + 1), 10);
|
|
6764
|
+
if (isNaN(targetLine) || targetLine < 1) return null;
|
|
6765
|
+
const absolutePath = resolve(rootPath, targetFile);
|
|
6766
|
+
let content;
|
|
6767
|
+
try {
|
|
6768
|
+
content = await readFile(absolutePath, "utf-8");
|
|
6769
|
+
} catch {
|
|
6770
|
+
return null;
|
|
6771
|
+
}
|
|
6772
|
+
const lines = content.split("\n");
|
|
6773
|
+
if (targetLine > lines.length) return null;
|
|
6774
|
+
const targetContent = lines[targetLine - 1];
|
|
6775
|
+
const indent = targetContent.match(/^(\s*)/)?.[1] ?? "";
|
|
6776
|
+
const ext = targetFile.split(".").pop()?.toLowerCase();
|
|
6777
|
+
const ruleSuffix = ruleId ? ` ${ruleId}` : "";
|
|
6778
|
+
let comment;
|
|
6779
|
+
if (ext === "html" || ext === "htm") {
|
|
6780
|
+
comment = `${indent}<!-- equall-ignore-next-line${ruleSuffix} -->`;
|
|
6781
|
+
} else if (ext === "jsx" || ext === "tsx") {
|
|
6782
|
+
const inJsx = targetContent.trimStart().startsWith("<") || targetContent.includes(">");
|
|
6783
|
+
comment = inJsx ? `${indent}{/* equall-ignore-next-line${ruleSuffix} */}` : `${indent}// equall-ignore-next-line${ruleSuffix}`;
|
|
6784
|
+
} else {
|
|
6785
|
+
comment = `${indent}// equall-ignore-next-line${ruleSuffix}`;
|
|
6786
|
+
}
|
|
6787
|
+
lines.splice(targetLine - 1, 0, comment);
|
|
6788
|
+
await writeFile(absolutePath, lines.join("\n"), "utf-8");
|
|
6789
|
+
return { file: targetFile, line: targetLine, comment };
|
|
6790
|
+
}
|
|
6791
|
+
async function clearAllIgnores(rootPath) {
|
|
6792
|
+
const ignores = await findIgnores(rootPath);
|
|
6793
|
+
if (ignores.length === 0) return [];
|
|
6794
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6795
|
+
for (const entry of ignores) {
|
|
6796
|
+
const lines = byFile.get(entry.file_path) ?? [];
|
|
6797
|
+
lines.push(entry.line);
|
|
6798
|
+
byFile.set(entry.file_path, lines);
|
|
6799
|
+
}
|
|
6800
|
+
for (const [filePath, lineNumbers] of byFile) {
|
|
6801
|
+
const absolutePath = resolve(rootPath, filePath);
|
|
6802
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
6803
|
+
const lines = content.split("\n");
|
|
6804
|
+
const toRemove = new Set(lineNumbers.map((l) => l - 1));
|
|
6805
|
+
const newLines = lines.filter((_, i) => !toRemove.has(i));
|
|
6806
|
+
await writeFile(absolutePath, newLines.join("\n"), "utf-8");
|
|
6807
|
+
}
|
|
6808
|
+
return ignores;
|
|
6809
|
+
}
|
|
6810
|
+
|
|
6663
6811
|
// src/cli.ts
|
|
6664
6812
|
var program2 = new Command();
|
|
6665
6813
|
program2.name("equall").description("Open-source accessibility scoring \u2014 aggregates axe-core, eslint-plugin-jsx-a11y, and more.").version("0.1.0");
|
|
6666
|
-
program2.command("scan").description("Scan a project for accessibility issues").argument("[path]", "Path to project root", ".").option("-l, --level <level>", "WCAG conformance target: A, AA, or AAA", "AA").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--json", "Output results as JSON").option("--no-color", "Disable colored output").action(async (path, opts) => {
|
|
6814
|
+
program2.command("scan").description("Scan a project for accessibility issues").argument("[path]", "Path to project root", ".").option("-l, --level <level>", "WCAG conformance target: A, AA, or AAA", "AA").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--json", "Output results as JSON").option("-i, --show-ignored", "Show ignored issues in output").option("--no-color", "Disable colored output").action(async (path, opts) => {
|
|
6667
6815
|
const level = opts.level.toUpperCase();
|
|
6668
6816
|
if (!["A", "AA", "AAA"].includes(level)) {
|
|
6669
6817
|
console.error(`Invalid level "${opts.level}". Use A, AA, or AAA.`);
|
|
6670
6818
|
process.exit(1);
|
|
6671
6819
|
}
|
|
6672
|
-
const displayName = basename(
|
|
6820
|
+
const displayName = basename(resolve2(path));
|
|
6673
6821
|
const spinner = opts.json ? null : ora({ text: `Scanning ${displayName}`, indent: 2 }).start();
|
|
6674
6822
|
try {
|
|
6675
6823
|
const result = await runScan({
|
|
@@ -6691,7 +6839,7 @@ program2.command("scan").description("Scan a project for accessibility issues").
|
|
|
6691
6839
|
if (opts.json) {
|
|
6692
6840
|
printJson(result);
|
|
6693
6841
|
} else {
|
|
6694
|
-
printResult(result);
|
|
6842
|
+
printResult(result, { showIgnored: opts.showIgnored });
|
|
6695
6843
|
}
|
|
6696
6844
|
if (result.score < 50) process.exit(1);
|
|
6697
6845
|
} catch (error2) {
|
|
@@ -6703,4 +6851,68 @@ program2.command("scan").description("Scan a project for accessibility issues").
|
|
|
6703
6851
|
process.exit(2);
|
|
6704
6852
|
}
|
|
6705
6853
|
});
|
|
6854
|
+
var DIM2 = "\x1B[2m";
|
|
6855
|
+
var BOLD2 = "\x1B[1m";
|
|
6856
|
+
var RESET2 = "\x1B[0m";
|
|
6857
|
+
var GREEN2 = "\x1B[32m";
|
|
6858
|
+
var YELLOW2 = "\x1B[33m";
|
|
6859
|
+
program2.command("ignore").description("Add, list, or remove equall-ignore comments").argument("[target]", "File:line to ignore (e.g. src/Modal.tsx:89)").argument("[rule-id]", "Optional rule ID (e.g. jsx-a11y/alt-text)").option("-p, --path <path>", "Path to project root", ".").option("--remove <target>", "Remove ignore at file:line or all ignores in a file").option("--clear", "Remove all equall-ignore comments from the project").option("--list", "List all equall-ignore comments").action(async (target, ruleId, opts) => {
|
|
6860
|
+
const rootPath = resolve2(opts.path);
|
|
6861
|
+
if (opts.clear) {
|
|
6862
|
+
const removed = await clearAllIgnores(rootPath);
|
|
6863
|
+
if (removed.length === 0) {
|
|
6864
|
+
console.log("\n No equall-ignore comments found.\n");
|
|
6865
|
+
} else {
|
|
6866
|
+
console.log(`
|
|
6867
|
+
${GREEN2}Removed ${removed.length} ignore comment${removed.length > 1 ? "s" : ""}${RESET2}
|
|
6868
|
+
`);
|
|
6869
|
+
}
|
|
6870
|
+
return;
|
|
6871
|
+
}
|
|
6872
|
+
if (opts.remove) {
|
|
6873
|
+
const { removed, notFound } = await removeIgnore(rootPath, opts.remove);
|
|
6874
|
+
if (notFound) {
|
|
6875
|
+
console.error(`
|
|
6876
|
+
No equall-ignore found at ${opts.remove}
|
|
6877
|
+
`);
|
|
6878
|
+
process.exit(1);
|
|
6879
|
+
}
|
|
6880
|
+
for (const entry of removed) {
|
|
6881
|
+
const location = entry.type === "file" ? "" : `:${entry.line}`;
|
|
6882
|
+
console.log(` ${GREEN2}Removed${RESET2} ${entry.file_path}${location} ${DIM2}${entry.raw}${RESET2}`);
|
|
6883
|
+
}
|
|
6884
|
+
console.log();
|
|
6885
|
+
return;
|
|
6886
|
+
}
|
|
6887
|
+
if (target && target.includes(":")) {
|
|
6888
|
+
const result = await addIgnore(rootPath, target, ruleId);
|
|
6889
|
+
if (!result) {
|
|
6890
|
+
console.error(`
|
|
6891
|
+
Could not add ignore at ${target}. Check that the file and line exist.
|
|
6892
|
+
`);
|
|
6893
|
+
process.exit(1);
|
|
6894
|
+
}
|
|
6895
|
+
console.log(`
|
|
6896
|
+
${GREEN2}Added${RESET2} ${result.file}:${result.line}`);
|
|
6897
|
+
console.log(` ${DIM2}${result.comment.trim()}${RESET2}
|
|
6898
|
+
`);
|
|
6899
|
+
return;
|
|
6900
|
+
}
|
|
6901
|
+
const listPath = target ? resolve2(target) : rootPath;
|
|
6902
|
+
const ignores = await findIgnores(listPath);
|
|
6903
|
+
if (ignores.length === 0) {
|
|
6904
|
+
console.log("\n No equall-ignore comments found.\n");
|
|
6905
|
+
return;
|
|
6906
|
+
}
|
|
6907
|
+
console.log(`
|
|
6908
|
+
${BOLD2}${ignores.length} ignore${ignores.length > 1 ? "s" : ""}${RESET2}
|
|
6909
|
+
`);
|
|
6910
|
+
for (const entry of ignores) {
|
|
6911
|
+
const location = `:${entry.line}`;
|
|
6912
|
+
const type = entry.type === "file" ? `${YELLOW2}equall-ignore-file${RESET2}` : `equall-ignore-next-line`;
|
|
6913
|
+
const rule = entry.rule_id ? ` ${DIM2}${entry.rule_id}${RESET2}` : ` ${DIM2}(all rules)${RESET2}`;
|
|
6914
|
+
console.log(` ${entry.file_path}${location}${" ".repeat(Math.max(1, 40 - entry.file_path.length - location.length))}${type}${rule}`);
|
|
6915
|
+
}
|
|
6916
|
+
console.log();
|
|
6917
|
+
});
|
|
6706
6918
|
program2.parse();
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface GladosIssue {
|
|
|
12
12
|
message: string;
|
|
13
13
|
help_url: string | null;
|
|
14
14
|
suggestion: string | null;
|
|
15
|
+
ignored?: boolean;
|
|
15
16
|
}
|
|
16
17
|
type WcagLevel = 'A' | 'AA' | 'AAA';
|
|
17
18
|
type PourPrinciple = 'perceivable' | 'operable' | 'understandable' | 'robust';
|
|
@@ -66,6 +67,7 @@ interface ScanSummary {
|
|
|
66
67
|
by_scanner: Record<string, number>;
|
|
67
68
|
criteria_tested: string[];
|
|
68
69
|
criteria_failed: string[];
|
|
70
|
+
ignored_count: number;
|
|
69
71
|
}
|
|
70
72
|
interface ScannerInfo {
|
|
71
73
|
name: string;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "equall-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Open-source accessibility scoring CLI — aggregates axe-core, eslint-plugin-jsx-a11y, and more into a unified score.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=18.0.0"
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|