ally-a11y 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACCESSIBILITY.md +205 -0
- package/LICENSE +21 -0
- package/README.md +940 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +528 -0
- package/dist/commands/audit-palette.d.ts +18 -0
- package/dist/commands/audit-palette.js +613 -0
- package/dist/commands/auto-pr.d.ts +19 -0
- package/dist/commands/auto-pr.js +434 -0
- package/dist/commands/badge.d.ts +11 -0
- package/dist/commands/badge.js +143 -0
- package/dist/commands/completion.d.ts +4 -0
- package/dist/commands/completion.js +185 -0
- package/dist/commands/crawl.d.ts +12 -0
- package/dist/commands/crawl.js +249 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +233 -0
- package/dist/commands/explain.d.ts +12 -0
- package/dist/commands/explain.js +233 -0
- package/dist/commands/fix.d.ts +13 -0
- package/dist/commands/fix.js +668 -0
- package/dist/commands/health.d.ts +11 -0
- package/dist/commands/health.js +367 -0
- package/dist/commands/history.d.ts +10 -0
- package/dist/commands/history.js +191 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +164 -0
- package/dist/commands/learn.d.ts +8 -0
- package/dist/commands/learn.js +592 -0
- package/dist/commands/pr-check.d.ts +12 -0
- package/dist/commands/pr-check.js +270 -0
- package/dist/commands/report.d.ts +11 -0
- package/dist/commands/report.js +375 -0
- package/dist/commands/scan-storybook.d.ts +18 -0
- package/dist/commands/scan-storybook.js +402 -0
- package/dist/commands/scan.d.ts +25 -0
- package/dist/commands/scan.js +673 -0
- package/dist/commands/stats.d.ts +5 -0
- package/dist/commands/stats.js +137 -0
- package/dist/commands/tree.d.ts +12 -0
- package/dist/commands/tree.js +635 -0
- package/dist/commands/triage.d.ts +13 -0
- package/dist/commands/triage.js +327 -0
- package/dist/commands/watch.d.ts +17 -0
- package/dist/commands/watch.js +302 -0
- package/dist/types/index.d.ts +60 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/baseline.d.ts +62 -0
- package/dist/utils/baseline.js +169 -0
- package/dist/utils/browser.d.ts +78 -0
- package/dist/utils/browser.js +239 -0
- package/dist/utils/cache.d.ts +76 -0
- package/dist/utils/cache.js +178 -0
- package/dist/utils/config.d.ts +102 -0
- package/dist/utils/config.js +237 -0
- package/dist/utils/converters.d.ts +77 -0
- package/dist/utils/converters.js +200 -0
- package/dist/utils/copilot.d.ts +36 -0
- package/dist/utils/copilot.js +139 -0
- package/dist/utils/detect.d.ts +22 -0
- package/dist/utils/detect.js +197 -0
- package/dist/utils/enhanced-errors.d.ts +46 -0
- package/dist/utils/enhanced-errors.js +295 -0
- package/dist/utils/errors.d.ts +31 -0
- package/dist/utils/errors.js +149 -0
- package/dist/utils/fix-patterns.d.ts +56 -0
- package/dist/utils/fix-patterns.js +529 -0
- package/dist/utils/history-tracking.d.ts +94 -0
- package/dist/utils/history-tracking.js +230 -0
- package/dist/utils/history.d.ts +42 -0
- package/dist/utils/history.js +255 -0
- package/dist/utils/impact-scores.d.ts +44 -0
- package/dist/utils/impact-scores.js +257 -0
- package/dist/utils/retry.d.ts +24 -0
- package/dist/utils/retry.js +76 -0
- package/dist/utils/scanner.d.ts +74 -0
- package/dist/utils/scanner.js +606 -0
- package/dist/utils/scanner.test.d.ts +4 -0
- package/dist/utils/scanner.test.js +162 -0
- package/dist/utils/ui.d.ts +44 -0
- package/dist/utils/ui.js +276 -0
- package/mcp-server/dist/index.d.ts +8 -0
- package/mcp-server/dist/index.js +1923 -0
- package/package.json +88 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ally - Your codebase's accessibility ally
|
|
4
|
+
*
|
|
5
|
+
* Scans, explains, and fixes accessibility issues using GitHub Copilot CLI.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { handleErrorWithEnhancement } from './utils/enhanced-errors.js';
|
|
10
|
+
// Read version from package.json to stay in sync
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version } = require('../package.json');
|
|
13
|
+
import { scanCommand } from './commands/scan.js';
|
|
14
|
+
import { explainCommand } from './commands/explain.js';
|
|
15
|
+
import { fixCommand } from './commands/fix.js';
|
|
16
|
+
import { reportCommand } from './commands/report.js';
|
|
17
|
+
import { initCommand } from './commands/init.js';
|
|
18
|
+
import { statsCommand } from './commands/stats.js';
|
|
19
|
+
import { badgeCommand } from './commands/badge.js';
|
|
20
|
+
import { watchCommand } from './commands/watch.js';
|
|
21
|
+
import { learnCommand } from './commands/learn.js';
|
|
22
|
+
import { crawlCommand } from './commands/crawl.js';
|
|
23
|
+
import { treeCommand } from './commands/tree.js';
|
|
24
|
+
import { triageCommand } from './commands/triage.js';
|
|
25
|
+
import { prCheckCommand } from './commands/pr-check.js';
|
|
26
|
+
import { completionCommand } from './commands/completion.js';
|
|
27
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
28
|
+
import { healthCommand } from './commands/health.js';
|
|
29
|
+
import { scanStorybookCommand } from './commands/scan-storybook.js';
|
|
30
|
+
import { auditPaletteCommand } from './commands/audit-palette.js';
|
|
31
|
+
const program = new Command();
|
|
32
|
+
program
|
|
33
|
+
.name('ally')
|
|
34
|
+
.description('Your codebase\'s accessibility ally. Scans, explains, and fixes a11y issues using GitHub Copilot CLI.')
|
|
35
|
+
.version(version);
|
|
36
|
+
// ally scan [path]
|
|
37
|
+
program
|
|
38
|
+
.command('scan [path]')
|
|
39
|
+
.description('Scan files for accessibility violations')
|
|
40
|
+
.option('-o, --output <dir>', 'Output directory for results', '.ally')
|
|
41
|
+
.option('-u, --url <url>', 'Scan a URL instead of files')
|
|
42
|
+
.option('-j, --json', 'Output raw JSON results')
|
|
43
|
+
.option('-f, --format <type>', 'Output format: json, sarif, junit, or csv')
|
|
44
|
+
.option('-v, --verbose', 'Show all violations including minor ones')
|
|
45
|
+
.option('-t, --threshold <number>', 'Exit with error if violations exceed threshold (for CI)')
|
|
46
|
+
.option('--ci', 'CI mode: minimal output, exit code based on violations')
|
|
47
|
+
.option('-F, --fail-on <severities>', 'Fail only on specified severities (comma-separated: critical,serious,moderate,minor)')
|
|
48
|
+
.option('-S, --simulate <type>', 'Simulate color blindness (deuteranopia, protanopia, tritanopia)', (value) => {
|
|
49
|
+
const valid = ['deuteranopia', 'protanopia', 'tritanopia'];
|
|
50
|
+
if (!valid.includes(value)) {
|
|
51
|
+
throw new Error(`Invalid simulation type: ${value}. Valid options: ${valid.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
})
|
|
55
|
+
.option('-s, --standard <level>', 'WCAG standard to test against (default: wcag22aa)', (value) => {
|
|
56
|
+
const valid = ['wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa', 'wcag22aa', 'section508', 'best-practice'];
|
|
57
|
+
if (!valid.includes(value)) {
|
|
58
|
+
throw new Error(`Invalid standard: ${value}. Valid options: ${valid.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
})
|
|
62
|
+
.option('-T, --timeout <ms>', 'Page load timeout in milliseconds (default: 30000)')
|
|
63
|
+
.option('-B, --browser <browser>', 'Browser engine: chromium, firefox, webkit (default: chromium)', (value) => {
|
|
64
|
+
const valid = ['chromium', 'firefox', 'webkit'];
|
|
65
|
+
if (!valid.includes(value)) {
|
|
66
|
+
throw new Error(`Invalid browser: ${value}. Valid options: ${valid.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
})
|
|
70
|
+
.option('--experimental-apca', 'Show APCA Lc values alongside WCAG 2.x contrast ratios (experimental)')
|
|
71
|
+
.option('--max-files <number>', 'Limit scan to first N files (useful for large projects)')
|
|
72
|
+
.option('--baseline', 'Set current scan as accessibility baseline (for regression detection)')
|
|
73
|
+
.option('--compare-baseline', 'Compare against saved baseline and show improvements/regressions')
|
|
74
|
+
.option('--fail-on-regression', 'Exit with error if accessibility regressions detected (with --compare-baseline)')
|
|
75
|
+
.option('--no-cache', 'Do not use cache, rescan all files')
|
|
76
|
+
.action(async (path, options) => {
|
|
77
|
+
try {
|
|
78
|
+
// Parse timeout if provided
|
|
79
|
+
if (options.timeout !== undefined) {
|
|
80
|
+
const timeout = parseInt(options.timeout, 10);
|
|
81
|
+
if (isNaN(timeout) || timeout < 1000) {
|
|
82
|
+
console.error('Error: --timeout must be at least 1000 (1 second)');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
options.timeout = timeout;
|
|
86
|
+
}
|
|
87
|
+
// Parse threshold if provided
|
|
88
|
+
if (options.threshold !== undefined) {
|
|
89
|
+
const threshold = parseInt(options.threshold, 10);
|
|
90
|
+
if (isNaN(threshold) || threshold < 0) {
|
|
91
|
+
console.error('Error: --threshold must be a non-negative number');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
options.threshold = threshold;
|
|
95
|
+
}
|
|
96
|
+
// Parse max-files if provided
|
|
97
|
+
if (options.maxFiles !== undefined) {
|
|
98
|
+
const maxFiles = parseInt(options.maxFiles, 10);
|
|
99
|
+
if (isNaN(maxFiles) || maxFiles < 1) {
|
|
100
|
+
console.error('Error: --max-files must be at least 1');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
options.maxFiles = maxFiles;
|
|
104
|
+
}
|
|
105
|
+
const report = await scanCommand(path, options);
|
|
106
|
+
// CI threshold check
|
|
107
|
+
if (options.threshold !== undefined && report) {
|
|
108
|
+
// If --fail-on is set, filter violations before checking threshold
|
|
109
|
+
let violationCount;
|
|
110
|
+
if (options.failOn) {
|
|
111
|
+
const severities = options.failOn.split(',').map((s) => s.trim().toLowerCase());
|
|
112
|
+
const validSeverities = ['critical', 'serious', 'moderate', 'minor'];
|
|
113
|
+
const invalidSeverities = severities.filter((s) => !validSeverities.includes(s));
|
|
114
|
+
if (invalidSeverities.length > 0) {
|
|
115
|
+
console.error(`Error: Invalid severity values: ${invalidSeverities.join(', ')}`);
|
|
116
|
+
console.error(`Valid values are: ${validSeverities.join(', ')}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
// Count violations matching specified severities
|
|
120
|
+
violationCount = report.results.reduce((count, result) => {
|
|
121
|
+
return count + result.violations.filter(v => severities.includes(v.impact)).length;
|
|
122
|
+
}, 0);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
violationCount = report.summary.totalViolations;
|
|
126
|
+
}
|
|
127
|
+
if (violationCount > options.threshold) {
|
|
128
|
+
const severityNote = options.failOn ? ` (filtered by: ${options.failOn})` : '';
|
|
129
|
+
console.error(`\nCI FAILED: ${violationCount} violations${severityNote} exceed threshold of ${options.threshold}`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof Error) {
|
|
136
|
+
handleErrorWithEnhancement(error);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error('Scan failed:', error);
|
|
140
|
+
}
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// ally explain
|
|
145
|
+
program
|
|
146
|
+
.command('explain')
|
|
147
|
+
.description('Get plain-language explanations of violations using Copilot')
|
|
148
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
149
|
+
.option('-s, --severity <level>', 'Filter by severity (critical, serious, moderate, minor)')
|
|
150
|
+
.option('-l, --limit <number>', 'Maximum number of issues to explain', '10')
|
|
151
|
+
.option('--ai', 'Use GitHub Copilot CLI for AI-powered explanations')
|
|
152
|
+
.action(async (options) => {
|
|
153
|
+
try {
|
|
154
|
+
const limit = parseInt(options.limit, 10);
|
|
155
|
+
if (isNaN(limit) || limit < 1) {
|
|
156
|
+
console.error('Error: --limit must be a positive number');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
await explainCommand({
|
|
160
|
+
...options,
|
|
161
|
+
limit,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error('Explain failed:', error);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// ally fix
|
|
170
|
+
program
|
|
171
|
+
.command('fix')
|
|
172
|
+
.description('Fix accessibility issues using Copilot CLI agentic mode')
|
|
173
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
174
|
+
.option('-s, --severity <level>', 'Filter by severity (critical, serious, moderate, minor)')
|
|
175
|
+
.option('-a, --auto', 'Automatically apply all fixes without prompting')
|
|
176
|
+
.option('-d, --dry-run', 'Show what would be fixed without making changes')
|
|
177
|
+
.option('-y, --yes', 'Skip interactive review, apply all suggested fixes')
|
|
178
|
+
.action(async (options) => {
|
|
179
|
+
try {
|
|
180
|
+
await fixCommand(options);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error('Fix failed:', error);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// ally report
|
|
188
|
+
program
|
|
189
|
+
.command('report')
|
|
190
|
+
.description('Generate accessibility report (ACCESSIBILITY.md)')
|
|
191
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
192
|
+
.option('-o, --output <file>', 'Output file path', 'ACCESSIBILITY.md')
|
|
193
|
+
.option('-f, --format <type>', 'Report format (markdown, json, html)', 'markdown')
|
|
194
|
+
.action(async (options) => {
|
|
195
|
+
try {
|
|
196
|
+
await reportCommand(options);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error('Report generation failed:', error);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// ally init
|
|
204
|
+
program
|
|
205
|
+
.command('init')
|
|
206
|
+
.description('Initialize ally in your project')
|
|
207
|
+
.option('-f, --force', 'Overwrite existing configuration')
|
|
208
|
+
.option('-H, --hooks', 'Set up pre-commit hooks for accessibility checks')
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
try {
|
|
211
|
+
await initCommand(options);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error('Initialization failed:', error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// ally stats
|
|
219
|
+
program
|
|
220
|
+
.command('stats')
|
|
221
|
+
.description('Show accessibility progress dashboard')
|
|
222
|
+
.action(async () => {
|
|
223
|
+
try {
|
|
224
|
+
await statsCommand();
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
console.error('Stats failed:', error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// ally history
|
|
232
|
+
program
|
|
233
|
+
.command('history')
|
|
234
|
+
.description('View scan history and progress over time')
|
|
235
|
+
.option('-l, --limit <number>', 'Number of recent scans to show', '10')
|
|
236
|
+
.option('-b, --branch <name>', 'Filter by git branch')
|
|
237
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
238
|
+
.action(async (options) => {
|
|
239
|
+
try {
|
|
240
|
+
const { historyCommand } = await import('./commands/history.js');
|
|
241
|
+
const limit = parseInt(options.limit, 10);
|
|
242
|
+
await historyCommand({ limit, branch: options.branch, verbose: options.verbose });
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error('History failed:', error);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
// ally badge
|
|
250
|
+
program
|
|
251
|
+
.command('badge')
|
|
252
|
+
.description('Generate accessibility score badge for README')
|
|
253
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
254
|
+
.option('-f, --format <type>', 'Output format (url, markdown, svg)', 'url')
|
|
255
|
+
.option('-o, --output <file>', 'Save badge to file (primarily for SVG)')
|
|
256
|
+
.action(async (options) => {
|
|
257
|
+
try {
|
|
258
|
+
await badgeCommand(options);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error('Badge generation failed:', error);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
// ally watch
|
|
266
|
+
program
|
|
267
|
+
.command('watch [path]')
|
|
268
|
+
.description('Watch for file changes and run accessibility scans continuously')
|
|
269
|
+
.option('-d, --debounce <ms>', 'Debounce time in milliseconds', '500')
|
|
270
|
+
.option('--clear', 'Clear console between scans')
|
|
271
|
+
.option('--fix-on-save', 'Automatically apply high-confidence fixes (≥90%) when files change')
|
|
272
|
+
.action(async (path, options) => {
|
|
273
|
+
try {
|
|
274
|
+
const debounce = parseInt(options.debounce, 10);
|
|
275
|
+
if (isNaN(debounce) || debounce < 0) {
|
|
276
|
+
console.error('Error: --debounce must be a non-negative number');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
await watchCommand(path, {
|
|
280
|
+
...options,
|
|
281
|
+
debounce,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
console.error('Watch failed:', error);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// ally learn
|
|
290
|
+
program
|
|
291
|
+
.command('learn [violation-id]')
|
|
292
|
+
.description('Learn about WCAG accessibility criteria in depth')
|
|
293
|
+
.option('-l, --list', 'List all known violation types')
|
|
294
|
+
.action(async (violationId, options) => {
|
|
295
|
+
try {
|
|
296
|
+
await learnCommand(violationId, options);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
console.error('Learn failed:', error);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
// ally crawl
|
|
304
|
+
program
|
|
305
|
+
.command('crawl <url>')
|
|
306
|
+
.description('Crawl entire website by following links and scan each page')
|
|
307
|
+
.option('-d, --depth <number>', 'Maximum crawl depth', '2')
|
|
308
|
+
.option('-l, --limit <number>', 'Maximum pages to scan', '10')
|
|
309
|
+
.option('--same-origin', 'Only follow links to same origin (default: true)', true)
|
|
310
|
+
.option('--no-same-origin', 'Follow links to any origin')
|
|
311
|
+
.option('-o, --output <dir>', 'Output directory for results', '.ally')
|
|
312
|
+
.action(async (url, options) => {
|
|
313
|
+
try {
|
|
314
|
+
// Parse numeric options
|
|
315
|
+
const depth = parseInt(options.depth, 10);
|
|
316
|
+
if (isNaN(depth) || depth < 0) {
|
|
317
|
+
console.error('Error: --depth must be a non-negative number');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
const limit = parseInt(options.limit, 10);
|
|
321
|
+
if (isNaN(limit) || limit < 1) {
|
|
322
|
+
console.error('Error: --limit must be a positive number');
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
await crawlCommand(url, {
|
|
326
|
+
depth,
|
|
327
|
+
limit,
|
|
328
|
+
sameOrigin: options.sameOrigin,
|
|
329
|
+
output: options.output,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('Crawl failed:', error);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
// ally tree
|
|
338
|
+
program
|
|
339
|
+
.command('tree <url>')
|
|
340
|
+
.description('Display accessibility tree for a URL')
|
|
341
|
+
.option('-d, --depth <number>', 'Maximum tree depth to display', '5')
|
|
342
|
+
.option('-r, --role <role>', 'Filter to specific ARIA role')
|
|
343
|
+
.option('-j, --json', 'Output as JSON')
|
|
344
|
+
.option('-s, --speak', 'Simulate screen reader announcement')
|
|
345
|
+
.option('--no-audio', 'Print announcement but skip TTS playback (use with --speak)')
|
|
346
|
+
.action(async (url, options) => {
|
|
347
|
+
try {
|
|
348
|
+
const depth = parseInt(options.depth, 10);
|
|
349
|
+
if (isNaN(depth) || depth < 1) {
|
|
350
|
+
console.error('Error: --depth must be a positive number');
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
await treeCommand(url, {
|
|
354
|
+
depth,
|
|
355
|
+
role: options.role,
|
|
356
|
+
json: options.json,
|
|
357
|
+
speak: options.speak,
|
|
358
|
+
noAudio: !options.audio, // Commander converts --no-audio to audio: false
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.error('Tree failed:', error);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// ally triage
|
|
367
|
+
program
|
|
368
|
+
.command('triage')
|
|
369
|
+
.description('Interactively categorize and prioritize accessibility issues')
|
|
370
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
371
|
+
.action(async (options) => {
|
|
372
|
+
try {
|
|
373
|
+
await triageCommand(options);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error('Triage failed:', error);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
// ally pr-check
|
|
381
|
+
program
|
|
382
|
+
.command('pr-check')
|
|
383
|
+
.description('Post accessibility results to GitHub PR')
|
|
384
|
+
.option('-i, --input <file>', 'Path to scan results', '.ally/scan.json')
|
|
385
|
+
.option('-p, --pr <number>', 'PR number (auto-detected in GitHub Actions)')
|
|
386
|
+
.option('--no-comment', 'Skip posting PR comment')
|
|
387
|
+
.option('-F, --fail-on <severities>', 'Fail on specified severities (comma-separated)')
|
|
388
|
+
.action(async (options) => {
|
|
389
|
+
try {
|
|
390
|
+
const prNumber = options.pr ? parseInt(options.pr, 10) : undefined;
|
|
391
|
+
await prCheckCommand({
|
|
392
|
+
...options,
|
|
393
|
+
pr: prNumber,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error('PR check failed:', error);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
// ally completion
|
|
402
|
+
program
|
|
403
|
+
.command('completion [shell]')
|
|
404
|
+
.description('Generate shell completion script (bash, zsh, fish)')
|
|
405
|
+
.action(async (shell) => {
|
|
406
|
+
try {
|
|
407
|
+
await completionCommand(shell);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
console.error('Completion failed:', error);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// ally doctor
|
|
415
|
+
program
|
|
416
|
+
.command('doctor')
|
|
417
|
+
.description('Diagnose installation and configuration issues')
|
|
418
|
+
.action(async () => {
|
|
419
|
+
try {
|
|
420
|
+
await doctorCommand();
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
console.error('Doctor failed:', error);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
// ally health
|
|
428
|
+
program
|
|
429
|
+
.command('health')
|
|
430
|
+
.description('Quick accessibility health check (like npm audit)')
|
|
431
|
+
.option('-p, --path <path>', 'Path to scan', '.')
|
|
432
|
+
.option('-s, --standard <level>', 'WCAG standard to test against (default: wcag22aa)', (value) => {
|
|
433
|
+
const valid = ['wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa', 'wcag22aa', 'section508', 'best-practice'];
|
|
434
|
+
if (!valid.includes(value)) {
|
|
435
|
+
throw new Error(`Invalid standard: ${value}. Valid options: ${valid.join(', ')}`);
|
|
436
|
+
}
|
|
437
|
+
return value;
|
|
438
|
+
})
|
|
439
|
+
.option('-i, --input <file>', 'Use existing scan results instead of scanning')
|
|
440
|
+
.action(async (options) => {
|
|
441
|
+
try {
|
|
442
|
+
await healthCommand(options);
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
console.error('Health check failed:', error);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
// ally scan-storybook
|
|
450
|
+
program
|
|
451
|
+
.command('scan-storybook')
|
|
452
|
+
.description('Scan Storybook stories for accessibility issues')
|
|
453
|
+
.option('-u, --url <url>', 'Storybook URL (default: http://localhost:6006)', 'http://localhost:6006')
|
|
454
|
+
.option('-T, --timeout <ms>', 'Page load timeout in milliseconds (default: 10000)', '10000')
|
|
455
|
+
.option('-f, --filter <pattern>', 'Filter stories by name pattern')
|
|
456
|
+
.option('-F, --format <format>', 'Output format (default, json, csv)', 'default')
|
|
457
|
+
.option('-o, --output <dir>', 'Output directory for results', '.ally')
|
|
458
|
+
.option('-s, --standard <level>', 'WCAG standard to test against (default: wcag22aa)', (value) => {
|
|
459
|
+
const valid = ['wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa', 'wcag22aa', 'section508', 'best-practice'];
|
|
460
|
+
if (!valid.includes(value)) {
|
|
461
|
+
throw new Error(`Invalid standard: ${value}. Valid options: ${valid.join(', ')}`);
|
|
462
|
+
}
|
|
463
|
+
return value;
|
|
464
|
+
})
|
|
465
|
+
.action(async (options) => {
|
|
466
|
+
try {
|
|
467
|
+
// Parse timeout if provided
|
|
468
|
+
const timeout = parseInt(options.timeout, 10);
|
|
469
|
+
if (isNaN(timeout) || timeout < 1000) {
|
|
470
|
+
console.error('Error: --timeout must be at least 1000 (1 second)');
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
// Validate format
|
|
474
|
+
const validFormats = ['default', 'json', 'csv'];
|
|
475
|
+
if (!validFormats.includes(options.format)) {
|
|
476
|
+
console.error(`Error: --format must be one of: ${validFormats.join(', ')}`);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
await scanStorybookCommand({
|
|
480
|
+
url: options.url,
|
|
481
|
+
timeout,
|
|
482
|
+
filter: options.filter,
|
|
483
|
+
format: options.format,
|
|
484
|
+
output: options.output,
|
|
485
|
+
standard: options.standard,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
console.error('Storybook scan failed:', error);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
// ally audit-palette
|
|
494
|
+
program
|
|
495
|
+
.command('audit-palette <file>')
|
|
496
|
+
.description('Audit a design system color palette for contrast issues')
|
|
497
|
+
.option('-f, --format <format>', 'Output format (default, json, csv)', 'default')
|
|
498
|
+
.option('-l, --level <level>', 'Minimum WCAG level to pass (aa, aaa)', 'aa')
|
|
499
|
+
.option('--large-text', 'Use large text thresholds (3:1 for AA)')
|
|
500
|
+
.option('--apca', 'Include APCA contrast values in output')
|
|
501
|
+
.action(async (file, options) => {
|
|
502
|
+
try {
|
|
503
|
+
// Validate level
|
|
504
|
+
const validLevels = ['aa', 'aaa'];
|
|
505
|
+
if (!validLevels.includes(options.level)) {
|
|
506
|
+
console.error(`Error: --level must be one of: ${validLevels.join(', ')}`);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
// Validate format
|
|
510
|
+
const validFormats = ['default', 'json', 'csv'];
|
|
511
|
+
if (!validFormats.includes(options.format)) {
|
|
512
|
+
console.error(`Error: --format must be one of: ${validFormats.join(', ')}`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
await auditPaletteCommand(file, {
|
|
516
|
+
format: options.format,
|
|
517
|
+
level: options.level,
|
|
518
|
+
largeText: options.largeText,
|
|
519
|
+
apca: options.apca,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
console.error('Palette audit failed:', error);
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
// Parse and run
|
|
528
|
+
program.parse();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ally audit-palette command - Audits a design system color palette for contrast issues
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Tailwind config files (tailwind.config.js, tailwind.config.ts)
|
|
6
|
+
* - CSS variable files (*.css)
|
|
7
|
+
* - JSON palette files (*.json)
|
|
8
|
+
*/
|
|
9
|
+
type OutputFormat = 'default' | 'json' | 'csv';
|
|
10
|
+
type WcagLevel = 'aa' | 'aaa';
|
|
11
|
+
interface AuditPaletteOptions {
|
|
12
|
+
format?: OutputFormat;
|
|
13
|
+
level?: WcagLevel;
|
|
14
|
+
largeText?: boolean;
|
|
15
|
+
apca?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function auditPaletteCommand(filePath: string, options?: AuditPaletteOptions): Promise<void>;
|
|
18
|
+
export default auditPaletteCommand;
|