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.
Files changed (84) hide show
  1. package/ACCESSIBILITY.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +940 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.js +528 -0
  6. package/dist/commands/audit-palette.d.ts +18 -0
  7. package/dist/commands/audit-palette.js +613 -0
  8. package/dist/commands/auto-pr.d.ts +19 -0
  9. package/dist/commands/auto-pr.js +434 -0
  10. package/dist/commands/badge.d.ts +11 -0
  11. package/dist/commands/badge.js +143 -0
  12. package/dist/commands/completion.d.ts +4 -0
  13. package/dist/commands/completion.js +185 -0
  14. package/dist/commands/crawl.d.ts +12 -0
  15. package/dist/commands/crawl.js +249 -0
  16. package/dist/commands/doctor.d.ts +5 -0
  17. package/dist/commands/doctor.js +233 -0
  18. package/dist/commands/explain.d.ts +12 -0
  19. package/dist/commands/explain.js +233 -0
  20. package/dist/commands/fix.d.ts +13 -0
  21. package/dist/commands/fix.js +668 -0
  22. package/dist/commands/health.d.ts +11 -0
  23. package/dist/commands/health.js +367 -0
  24. package/dist/commands/history.d.ts +10 -0
  25. package/dist/commands/history.js +191 -0
  26. package/dist/commands/init.d.ts +9 -0
  27. package/dist/commands/init.js +164 -0
  28. package/dist/commands/learn.d.ts +8 -0
  29. package/dist/commands/learn.js +592 -0
  30. package/dist/commands/pr-check.d.ts +12 -0
  31. package/dist/commands/pr-check.js +270 -0
  32. package/dist/commands/report.d.ts +11 -0
  33. package/dist/commands/report.js +375 -0
  34. package/dist/commands/scan-storybook.d.ts +18 -0
  35. package/dist/commands/scan-storybook.js +402 -0
  36. package/dist/commands/scan.d.ts +25 -0
  37. package/dist/commands/scan.js +673 -0
  38. package/dist/commands/stats.d.ts +5 -0
  39. package/dist/commands/stats.js +137 -0
  40. package/dist/commands/tree.d.ts +12 -0
  41. package/dist/commands/tree.js +635 -0
  42. package/dist/commands/triage.d.ts +13 -0
  43. package/dist/commands/triage.js +327 -0
  44. package/dist/commands/watch.d.ts +17 -0
  45. package/dist/commands/watch.js +302 -0
  46. package/dist/types/index.d.ts +60 -0
  47. package/dist/types/index.js +4 -0
  48. package/dist/utils/baseline.d.ts +62 -0
  49. package/dist/utils/baseline.js +169 -0
  50. package/dist/utils/browser.d.ts +78 -0
  51. package/dist/utils/browser.js +239 -0
  52. package/dist/utils/cache.d.ts +76 -0
  53. package/dist/utils/cache.js +178 -0
  54. package/dist/utils/config.d.ts +102 -0
  55. package/dist/utils/config.js +237 -0
  56. package/dist/utils/converters.d.ts +77 -0
  57. package/dist/utils/converters.js +200 -0
  58. package/dist/utils/copilot.d.ts +36 -0
  59. package/dist/utils/copilot.js +139 -0
  60. package/dist/utils/detect.d.ts +22 -0
  61. package/dist/utils/detect.js +197 -0
  62. package/dist/utils/enhanced-errors.d.ts +46 -0
  63. package/dist/utils/enhanced-errors.js +295 -0
  64. package/dist/utils/errors.d.ts +31 -0
  65. package/dist/utils/errors.js +149 -0
  66. package/dist/utils/fix-patterns.d.ts +56 -0
  67. package/dist/utils/fix-patterns.js +529 -0
  68. package/dist/utils/history-tracking.d.ts +94 -0
  69. package/dist/utils/history-tracking.js +230 -0
  70. package/dist/utils/history.d.ts +42 -0
  71. package/dist/utils/history.js +255 -0
  72. package/dist/utils/impact-scores.d.ts +44 -0
  73. package/dist/utils/impact-scores.js +257 -0
  74. package/dist/utils/retry.d.ts +24 -0
  75. package/dist/utils/retry.js +76 -0
  76. package/dist/utils/scanner.d.ts +74 -0
  77. package/dist/utils/scanner.js +606 -0
  78. package/dist/utils/scanner.test.d.ts +4 -0
  79. package/dist/utils/scanner.test.js +162 -0
  80. package/dist/utils/ui.d.ts +44 -0
  81. package/dist/utils/ui.js +276 -0
  82. package/mcp-server/dist/index.d.ts +8 -0
  83. package/mcp-server/dist/index.js +1923 -0
  84. package/package.json +88 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * ally learn command - Educational command that explains WCAG criteria in depth
3
+ */
4
+ import chalk from 'chalk';
5
+ import boxen from 'boxen';
6
+ import { printBanner, printInfo, printError, } from '../utils/ui.js';
7
+ // Built-in knowledge base for top 15 accessibility violations
8
+ const VIOLATIONS_DB = {
9
+ 'image-alt': {
10
+ id: 'image-alt',
11
+ name: 'Image Alternative Text',
12
+ wcagCriterion: '1.1.1',
13
+ wcagName: 'Non-text Content',
14
+ level: 'A',
15
+ explanation: 'Images must have alternative text that describes their content or purpose. ' +
16
+ 'This text is read aloud by screen readers and displayed when images fail to load. ' +
17
+ 'The alt text should convey the same information or function as the image.',
18
+ whyItMatters: 'Screen reader users cannot see images. Without alt text, they miss critical information ' +
19
+ 'or context. This affects approximately 2.2 billion people with visual impairments worldwide. ' +
20
+ 'Alt text also helps when images fail to load due to slow connections or technical issues.',
21
+ commonFixes: [
22
+ 'Add descriptive alt text that conveys the image\'s meaning',
23
+ 'For decorative images, use alt="" (empty alt)',
24
+ 'For complex images like charts, provide detailed descriptions',
25
+ 'Avoid phrases like "image of" or "picture of" - screen readers already announce it\'s an image',
26
+ ],
27
+ codeExample: {
28
+ bad: '<img src="logo.png">',
29
+ good: '<img src="logo.png" alt="Acme Corporation logo">',
30
+ },
31
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html',
32
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/image-alt',
33
+ },
34
+ 'button-name': {
35
+ id: 'button-name',
36
+ name: 'Button Accessible Name',
37
+ wcagCriterion: '4.1.2',
38
+ wcagName: 'Name, Role, Value',
39
+ level: 'A',
40
+ explanation: 'Buttons must have an accessible name that describes their purpose. ' +
41
+ 'This can come from visible text, aria-label, aria-labelledby, or the button\'s title attribute. ' +
42
+ 'Icon-only buttons are especially prone to this issue.',
43
+ whyItMatters: 'When a button lacks an accessible name, screen reader users hear only "button" with no indication ' +
44
+ 'of what clicking it will do. This makes the interface unusable for keyboard and screen reader users.',
45
+ commonFixes: [
46
+ 'Add visible text inside the button',
47
+ 'Use aria-label for icon-only buttons',
48
+ 'Use aria-labelledby to reference existing text',
49
+ 'Ensure button images have alt text',
50
+ ],
51
+ codeExample: {
52
+ bad: '<button><svg class="icon-search"></svg></button>',
53
+ good: '<button aria-label="Search"><svg class="icon-search"></svg></button>',
54
+ },
55
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html',
56
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/button-name',
57
+ },
58
+ 'link-name': {
59
+ id: 'link-name',
60
+ name: 'Link Accessible Name',
61
+ wcagCriterion: '2.4.4',
62
+ wcagName: 'Link Purpose (In Context)',
63
+ level: 'A',
64
+ explanation: 'Links must have text that describes where they lead. The link text should make sense ' +
65
+ 'even when read out of context, as screen reader users often navigate by links alone. ' +
66
+ 'Links wrapped around images need alt text to serve as the link name.',
67
+ whyItMatters: 'Screen reader users can pull up a list of all links on a page to quickly navigate. ' +
68
+ 'If links say "click here" or "read more," users have no idea where each link leads. ' +
69
+ 'Descriptive link text benefits everyone by clearly indicating destinations.',
70
+ commonFixes: [
71
+ 'Use descriptive text that indicates the link destination',
72
+ 'Avoid generic phrases like "click here," "learn more," or "read more"',
73
+ 'For image links, ensure the image has descriptive alt text',
74
+ 'If context is needed, use aria-label or visually-hidden text',
75
+ ],
76
+ codeExample: {
77
+ bad: '<a href="/pricing">Click here</a> for pricing.',
78
+ good: '<a href="/pricing">View our pricing plans</a>',
79
+ },
80
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html',
81
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/link-name',
82
+ },
83
+ 'label': {
84
+ id: 'label',
85
+ name: 'Form Input Labels',
86
+ wcagCriterion: '1.3.1',
87
+ wcagName: 'Info and Relationships',
88
+ level: 'A',
89
+ explanation: 'Every form input needs a label that describes what information should be entered. ' +
90
+ 'Labels must be programmatically associated with their inputs using for/id attributes ' +
91
+ 'or by wrapping the input inside the label element.',
92
+ whyItMatters: 'Without labels, screen reader users hear only "edit text" or "checkbox" with no context. ' +
93
+ 'They cannot know what information to enter. Labels also increase the clickable area for ' +
94
+ 'the input, helping users with motor impairments.',
95
+ commonFixes: [
96
+ 'Add a <label> element with for="input-id" matching the input\'s id',
97
+ 'Wrap the input inside the label: <label>Email <input type="email"></label>',
98
+ 'Use aria-label or aria-labelledby for complex UI patterns',
99
+ 'Placeholder text alone is NOT sufficient - it disappears when typing',
100
+ ],
101
+ codeExample: {
102
+ bad: '<input type="email" placeholder="Email">',
103
+ good: '<label for="email">Email address</label>\n<input type="email" id="email">',
104
+ },
105
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
106
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/label',
107
+ },
108
+ 'color-contrast': {
109
+ id: 'color-contrast',
110
+ name: 'Color Contrast',
111
+ wcagCriterion: '1.4.3',
112
+ wcagName: 'Contrast (Minimum)',
113
+ level: 'AA',
114
+ explanation: 'Text must have sufficient contrast against its background. Normal text requires ' +
115
+ 'at least 4.5:1 contrast ratio, while large text (18pt or 14pt bold) requires 3:1. ' +
116
+ 'This ensures text is readable for people with low vision or color blindness.',
117
+ whyItMatters: 'Approximately 300 million people have color blindness. Many more have low vision or ' +
118
+ 'read in challenging lighting conditions. Insufficient contrast makes text difficult ' +
119
+ 'or impossible to read, excluding these users from your content.',
120
+ commonFixes: [
121
+ 'Use a contrast checker tool to verify your color combinations',
122
+ 'Increase the difference between text and background colors',
123
+ 'Avoid light gray text on white backgrounds',
124
+ 'Be careful with text over images - consider adding a semi-transparent overlay',
125
+ ],
126
+ codeExample: {
127
+ bad: 'color: #767676; background: #ffffff; /* 4.48:1 - fails WCAG AA */',
128
+ good: 'color: #595959; background: #ffffff; /* 7:1 - passes WCAG AA */',
129
+ },
130
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html',
131
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/color-contrast',
132
+ },
133
+ 'html-has-lang': {
134
+ id: 'html-has-lang',
135
+ name: 'HTML Language Attribute',
136
+ wcagCriterion: '3.1.1',
137
+ wcagName: 'Language of Page',
138
+ level: 'A',
139
+ explanation: 'The <html> element must have a lang attribute that identifies the page\'s primary language. ' +
140
+ 'This helps screen readers use the correct pronunciation rules and helps browsers offer translation. ' +
141
+ 'Use standard language codes like "en" for English, "es" for Spanish, "fr" for French.',
142
+ whyItMatters: 'Screen readers use the language attribute to switch pronunciation. Without it, an English screen ' +
143
+ 'reader might try to read French text with English pronunciation rules, making content unintelligible. ' +
144
+ 'This is critical for multilingual sites and users.',
145
+ commonFixes: [
146
+ 'Add lang="en" (or appropriate code) to the <html> element',
147
+ 'Use valid IETF language tags (en, en-US, es, fr, de, zh, etc.)',
148
+ 'For content in multiple languages, use lang on specific elements',
149
+ ],
150
+ codeExample: {
151
+ bad: '<!DOCTYPE html>\n<html>',
152
+ good: '<!DOCTYPE html>\n<html lang="en">',
153
+ },
154
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/language-of-page.html',
155
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/html-has-lang',
156
+ },
157
+ 'document-title': {
158
+ id: 'document-title',
159
+ name: 'Document Title',
160
+ wcagCriterion: '2.4.2',
161
+ wcagName: 'Page Titled',
162
+ level: 'A',
163
+ explanation: 'Every page needs a <title> element that describes its topic or purpose. ' +
164
+ 'The title appears in browser tabs, bookmarks, and search results. ' +
165
+ 'It\'s the first thing screen readers announce when a page loads.',
166
+ whyItMatters: 'Screen reader users rely on page titles to know what page they\'ve landed on. ' +
167
+ 'When users have many tabs open, descriptive titles help everyone find the right one. ' +
168
+ 'Search engines also use titles to understand and rank your content.',
169
+ commonFixes: [
170
+ 'Add a unique, descriptive <title> element to every page',
171
+ 'Put the most specific info first: "Product Name - Company Name"',
172
+ 'Avoid titles like "Untitled" or just your company name',
173
+ 'Update titles dynamically for SPAs when content changes',
174
+ ],
175
+ codeExample: {
176
+ bad: '<title>Page</title>',
177
+ good: '<title>Contact Us - Acme Corporation</title>',
178
+ },
179
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/page-titled.html',
180
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/document-title',
181
+ },
182
+ 'list': {
183
+ id: 'list',
184
+ name: 'List Structure',
185
+ wcagCriterion: '1.3.1',
186
+ wcagName: 'Info and Relationships',
187
+ level: 'A',
188
+ explanation: '<ul> and <ol> elements must only contain <li>, <script>, or <template> elements. ' +
189
+ 'Lists provide semantic structure that helps assistive technology communicate the ' +
190
+ 'number of items and navigate between them.',
191
+ whyItMatters: 'Screen readers announce lists with item counts like "list, 5 items". Users can jump ' +
192
+ 'between list items quickly. Invalid list structure breaks this functionality and makes ' +
193
+ 'navigation confusing.',
194
+ commonFixes: [
195
+ 'Ensure only <li> elements are direct children of <ul> or <ol>',
196
+ 'Move any wrapper divs inside the <li> elements, not between them',
197
+ 'Use proper list markup for navigation menus, not just styled divs',
198
+ ],
199
+ codeExample: {
200
+ bad: '<ul>\n <div><li>Item 1</li></div>\n <div><li>Item 2</li></div>\n</ul>',
201
+ good: '<ul>\n <li>Item 1</li>\n <li>Item 2</li>\n</ul>',
202
+ },
203
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
204
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/list',
205
+ },
206
+ 'listitem': {
207
+ id: 'listitem',
208
+ name: 'List Item Structure',
209
+ wcagCriterion: '1.3.1',
210
+ wcagName: 'Info and Relationships',
211
+ level: 'A',
212
+ explanation: '<li> elements must be contained within a <ul> or <ol> parent element. ' +
213
+ 'Orphaned list items lose their semantic meaning and are not announced ' +
214
+ 'properly by screen readers.',
215
+ whyItMatters: 'Without the proper parent container, screen readers cannot convey that items are ' +
216
+ 'part of a list. Users miss important context about content relationships and cannot ' +
217
+ 'use list navigation features.',
218
+ commonFixes: [
219
+ 'Wrap <li> elements in a <ul> (unordered) or <ol> (ordered) list',
220
+ 'Use <ul> for items without sequence, <ol> for numbered/ordered items',
221
+ 'Check that no <li> elements are placed outside list containers',
222
+ ],
223
+ codeExample: {
224
+ bad: '<div>\n <li>Orphaned item</li>\n</div>',
225
+ good: '<ul>\n <li>Properly nested item</li>\n</ul>',
226
+ },
227
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
228
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/listitem',
229
+ },
230
+ 'frame-title': {
231
+ id: 'frame-title',
232
+ name: 'Frame Title',
233
+ wcagCriterion: '4.1.2',
234
+ wcagName: 'Name, Role, Value',
235
+ level: 'A',
236
+ explanation: '<iframe> and <frame> elements must have a title attribute that describes their content. ' +
237
+ 'This helps users understand what content is embedded without having to enter the frame. ' +
238
+ 'The title should be concise but descriptive.',
239
+ whyItMatters: 'Screen reader users encounter iframes as navigation landmarks. Without a title, users hear ' +
240
+ 'just "frame" with no indication of its content. They may waste time entering frames that ' +
241
+ 'don\'t interest them or miss important embedded content.',
242
+ commonFixes: [
243
+ 'Add a title attribute describing the iframe\'s content',
244
+ 'Use descriptive titles like "Product demo video" not "iframe"',
245
+ 'For decorative iframes, consider if they should be hidden from screen readers',
246
+ ],
247
+ codeExample: {
248
+ bad: '<iframe src="https://www.youtube.com/embed/xyz"></iframe>',
249
+ good: '<iframe src="https://www.youtube.com/embed/xyz" title="Product demonstration video"></iframe>',
250
+ },
251
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html',
252
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/frame-title',
253
+ },
254
+ 'input-image-alt': {
255
+ id: 'input-image-alt',
256
+ name: 'Image Button Alternative Text',
257
+ wcagCriterion: '1.1.1',
258
+ wcagName: 'Non-text Content',
259
+ level: 'A',
260
+ explanation: '<input type="image"> elements must have an alt attribute that describes the button\'s ' +
261
+ 'action. These are submit buttons that use images, and the alt text serves as both the ' +
262
+ 'accessible name and description of what happens when clicked.',
263
+ whyItMatters: 'Screen reader users need to know what action an image button performs. Without alt text, ' +
264
+ 'they only hear "image button" with no indication of its purpose, making forms unusable.',
265
+ commonFixes: [
266
+ 'Add alt attribute describing the button\'s action',
267
+ 'Use action words like "Search," "Submit," or "Add to cart"',
268
+ 'Consider using <button> with an <img> inside for more flexibility',
269
+ ],
270
+ codeExample: {
271
+ bad: '<input type="image" src="search-icon.png">',
272
+ good: '<input type="image" src="search-icon.png" alt="Search">',
273
+ },
274
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html',
275
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/input-image-alt',
276
+ },
277
+ 'meta-viewport': {
278
+ id: 'meta-viewport',
279
+ name: 'Viewport Zoom',
280
+ wcagCriterion: '1.4.4',
281
+ wcagName: 'Resize Text',
282
+ level: 'AA',
283
+ explanation: 'The viewport meta tag must not prevent users from zooming. Settings like ' +
284
+ 'user-scalable="no" or maximum-scale=1.0 disable pinch-to-zoom functionality. ' +
285
+ 'Users must be able to zoom to at least 200%.',
286
+ whyItMatters: 'Many users with low vision rely on zooming to read text comfortably. Disabling zoom ' +
287
+ 'forces them to use external tools or leaves them unable to read content at all. ' +
288
+ 'Mobile users especially depend on pinch-to-zoom.',
289
+ commonFixes: [
290
+ 'Remove user-scalable="no" from viewport meta tag',
291
+ 'Remove or increase maximum-scale (allow at least 2.0)',
292
+ 'Set initial-scale=1.0 but allow user scaling',
293
+ ],
294
+ codeExample: {
295
+ bad: '<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0">',
296
+ good: '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
297
+ },
298
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html',
299
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/meta-viewport',
300
+ },
301
+ 'region': {
302
+ id: 'region',
303
+ name: 'Page Regions',
304
+ wcagCriterion: '1.3.1',
305
+ wcagName: 'Info and Relationships',
306
+ level: 'A',
307
+ explanation: 'All page content should be contained within landmark regions. Landmarks include ' +
308
+ '<header>, <nav>, <main>, <aside>, <footer>, and elements with ARIA roles like ' +
309
+ 'role="banner", role="navigation", role="main", etc.',
310
+ whyItMatters: 'Screen reader users can navigate directly between landmark regions, skipping repetitive ' +
311
+ 'content. Without landmarks, users must read through the entire page linearly to find ' +
312
+ 'what they need. This is like having a book with no chapters or table of contents.',
313
+ commonFixes: [
314
+ 'Wrap header content in <header> element',
315
+ 'Use <nav> for navigation menus',
316
+ 'Put main content in <main> element (only one per page)',
317
+ 'Use <aside> for sidebar content',
318
+ 'Wrap footer content in <footer> element',
319
+ ],
320
+ codeExample: {
321
+ bad: '<div class="header">...</div>\n<div class="content">...</div>\n<div class="footer">...</div>',
322
+ good: '<header>...</header>\n<main>...</main>\n<footer>...</footer>',
323
+ },
324
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
325
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/region',
326
+ },
327
+ 'aria-required-children': {
328
+ id: 'aria-required-children',
329
+ name: 'ARIA Required Children',
330
+ wcagCriterion: '1.3.1',
331
+ wcagName: 'Info and Relationships',
332
+ level: 'A',
333
+ explanation: 'Certain ARIA roles require specific child roles to function properly. For example, ' +
334
+ 'role="list" requires children with role="listitem", role="menu" requires role="menuitem", ' +
335
+ 'and role="tablist" requires role="tab". Missing required children break the pattern.',
336
+ whyItMatters: 'When ARIA patterns are incomplete, assistive technology cannot convey the correct structure. ' +
337
+ 'Users expect certain behaviors from ARIA roles - a list should have items, a menu should ' +
338
+ 'have menu items. Missing pieces create confusion.',
339
+ commonFixes: [
340
+ 'Add the required child roles to parent elements',
341
+ 'For role="list", ensure children have role="listitem"',
342
+ 'For role="menu", children need role="menuitem", "menuitemcheckbox", or "menuitemradio"',
343
+ 'Consider using native HTML elements instead of ARIA when possible',
344
+ ],
345
+ codeExample: {
346
+ bad: '<div role="list">\n <div>Item 1</div>\n <div>Item 2</div>\n</div>',
347
+ good: '<div role="list">\n <div role="listitem">Item 1</div>\n <div role="listitem">Item 2</div>\n</div>',
348
+ },
349
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
350
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/aria-required-children',
351
+ },
352
+ 'aria-required-parent': {
353
+ id: 'aria-required-parent',
354
+ name: 'ARIA Required Parent',
355
+ wcagCriterion: '1.3.1',
356
+ wcagName: 'Info and Relationships',
357
+ level: 'A',
358
+ explanation: 'Certain ARIA roles must be contained within specific parent roles. For example, ' +
359
+ 'role="listitem" must be inside role="list", role="tab" must be inside role="tablist", ' +
360
+ 'and role="menuitem" must be inside role="menu" or role="menubar".',
361
+ whyItMatters: 'Child roles without proper parents are orphaned and lose their semantic meaning. ' +
362
+ 'A listitem outside a list isn\'t announced as a list item. Screen reader users miss ' +
363
+ 'the relationship between elements.',
364
+ commonFixes: [
365
+ 'Wrap elements in the required parent role container',
366
+ 'For role="listitem", parent needs role="list" or role="listbox"',
367
+ 'For role="tab", parent needs role="tablist"',
368
+ 'Consider using native HTML elements that have built-in relationships',
369
+ ],
370
+ codeExample: {
371
+ bad: '<div role="listitem">Orphaned item</div>',
372
+ good: '<div role="list">\n <div role="listitem">Nested item</div>\n</div>',
373
+ },
374
+ wcagUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html',
375
+ dequeUrl: 'https://dequeuniversity.com/rules/axe/4.8/aria-required-parent',
376
+ },
377
+ };
378
+ /**
379
+ * Check if GitHub Copilot CLI is available
380
+ */
381
+ async function isCopilotAvailable() {
382
+ try {
383
+ const { exec } = await import('child_process');
384
+ const { promisify } = await import('util');
385
+ const execAsync = promisify(exec);
386
+ await execAsync('which gh');
387
+ // Check if copilot extension is available
388
+ const result = await execAsync('gh copilot --help 2>&1').catch(() => null);
389
+ return result !== null;
390
+ }
391
+ catch {
392
+ return false;
393
+ }
394
+ }
395
+ export async function learnCommand(violationId, options = {}) {
396
+ printBanner();
397
+ // Handle --list flag
398
+ if (options.list) {
399
+ printViolationList();
400
+ return;
401
+ }
402
+ // No violation ID provided
403
+ if (!violationId) {
404
+ printInfo('Usage: ally learn <violation-id>');
405
+ console.log();
406
+ console.log(chalk.dim('Learn about a specific accessibility violation in depth.'));
407
+ console.log();
408
+ console.log(chalk.bold('Examples:'));
409
+ console.log(chalk.cyan(' ally learn image-alt'));
410
+ console.log(chalk.cyan(' ally learn color-contrast'));
411
+ console.log(chalk.cyan(' ally learn --list'));
412
+ console.log();
413
+ printInfo('Run ally learn --list to see all available violations.');
414
+ return;
415
+ }
416
+ // Normalize the violation ID (lowercase, handle common aliases)
417
+ const normalizedId = normalizeViolationId(violationId);
418
+ // Look up the violation
419
+ const violation = VIOLATIONS_DB[normalizedId];
420
+ if (!violation) {
421
+ printError(`Unknown violation: ${violationId}`);
422
+ console.log();
423
+ printInfo('Did you mean one of these?');
424
+ const suggestions = findSimilarViolations(violationId);
425
+ for (const suggestion of suggestions) {
426
+ console.log(chalk.cyan(` ally learn ${suggestion}`));
427
+ }
428
+ console.log();
429
+ printInfo('Run ally learn --list to see all available violations.');
430
+ return;
431
+ }
432
+ // Display violation information
433
+ await displayViolationInfo(violation);
434
+ }
435
+ function normalizeViolationId(id) {
436
+ // Common aliases
437
+ const aliases = {
438
+ 'alt': 'image-alt',
439
+ 'img-alt': 'image-alt',
440
+ 'alt-text': 'image-alt',
441
+ 'contrast': 'color-contrast',
442
+ 'lang': 'html-has-lang',
443
+ 'language': 'html-has-lang',
444
+ 'title': 'document-title',
445
+ 'page-title': 'document-title',
446
+ 'input-label': 'label',
447
+ 'form-label': 'label',
448
+ 'labels': 'label',
449
+ 'iframe-title': 'frame-title',
450
+ 'viewport': 'meta-viewport',
451
+ 'zoom': 'meta-viewport',
452
+ 'landmarks': 'region',
453
+ };
454
+ const lower = id.toLowerCase();
455
+ return aliases[lower] || lower;
456
+ }
457
+ function findSimilarViolations(id) {
458
+ const violations = Object.keys(VIOLATIONS_DB);
459
+ const lower = id.toLowerCase();
460
+ // Find violations that contain the search term
461
+ const matches = violations.filter(v => v.includes(lower) || lower.includes(v.split('-')[0]));
462
+ if (matches.length > 0) {
463
+ return matches.slice(0, 3);
464
+ }
465
+ // Return some popular ones as fallback
466
+ return ['image-alt', 'color-contrast', 'button-name'];
467
+ }
468
+ function printViolationList() {
469
+ console.log(chalk.bold.cyan('Available Violation Types'));
470
+ console.log(chalk.dim('='.repeat(60)));
471
+ console.log();
472
+ // Group by WCAG level
473
+ const byLevel = { A: [], AA: [], AAA: [] };
474
+ for (const violation of Object.values(VIOLATIONS_DB)) {
475
+ byLevel[violation.level].push(violation);
476
+ }
477
+ for (const level of ['A', 'AA', 'AAA']) {
478
+ const violations = byLevel[level];
479
+ if (violations.length === 0)
480
+ continue;
481
+ const levelColor = level === 'A' ? chalk.green :
482
+ level === 'AA' ? chalk.yellow :
483
+ chalk.red;
484
+ console.log(levelColor.bold(`WCAG Level ${level}`));
485
+ console.log(chalk.dim('-'.repeat(60)));
486
+ for (const v of violations) {
487
+ const idPadded = v.id.padEnd(25);
488
+ console.log(chalk.cyan(idPadded) +
489
+ chalk.dim(`${v.wcagCriterion} `) +
490
+ v.name);
491
+ }
492
+ console.log();
493
+ }
494
+ console.log(chalk.dim('─'.repeat(60)));
495
+ console.log();
496
+ printInfo('Learn about a violation:');
497
+ console.log(chalk.cyan(' ally learn <violation-id>'));
498
+ console.log();
499
+ console.log(chalk.dim('Example: ally learn image-alt'));
500
+ }
501
+ async function displayViolationInfo(violation) {
502
+ // Header box with violation name and WCAG criterion
503
+ const levelColor = violation.level === 'A' ? chalk.green :
504
+ violation.level === 'AA' ? chalk.yellow :
505
+ chalk.red;
506
+ const header = `${violation.name}
507
+
508
+ ${chalk.bold('WCAG Criterion:')} ${violation.wcagCriterion} ${violation.wcagName}
509
+ ${chalk.bold('Level:')} ${levelColor.bold(violation.level)}`;
510
+ console.log(boxen(header, {
511
+ padding: 1,
512
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
513
+ borderStyle: 'round',
514
+ borderColor: 'cyan',
515
+ title: violation.id,
516
+ titleAlignment: 'center',
517
+ }));
518
+ // What is this?
519
+ console.log(chalk.bold.white('What is this?'));
520
+ console.log(chalk.dim('─'.repeat(60)));
521
+ console.log(wrapText(violation.explanation, 60));
522
+ console.log();
523
+ // Why it matters
524
+ console.log(chalk.bold.white('Why it matters'));
525
+ console.log(chalk.dim('─'.repeat(60)));
526
+ console.log(wrapText(violation.whyItMatters, 60));
527
+ console.log();
528
+ // Common fixes
529
+ console.log(chalk.bold.white('How to fix'));
530
+ console.log(chalk.dim('─'.repeat(60)));
531
+ for (const fix of violation.commonFixes) {
532
+ console.log(chalk.green(' • ') + fix);
533
+ }
534
+ console.log();
535
+ // Code example
536
+ if (violation.codeExample) {
537
+ console.log(chalk.bold.white('Code example'));
538
+ console.log(chalk.dim('─'.repeat(60)));
539
+ console.log();
540
+ console.log(chalk.red.bold(' ✗ Bad:'));
541
+ for (const line of violation.codeExample.bad.split('\n')) {
542
+ console.log(chalk.red(` ${line}`));
543
+ }
544
+ console.log();
545
+ console.log(chalk.green.bold(' ✓ Good:'));
546
+ for (const line of violation.codeExample.good.split('\n')) {
547
+ console.log(chalk.green(` ${line}`));
548
+ }
549
+ console.log();
550
+ }
551
+ // Resources
552
+ console.log(chalk.bold.white('Learn more'));
553
+ console.log(chalk.dim('─'.repeat(60)));
554
+ console.log(chalk.blue(' WCAG: ') + chalk.underline(violation.wcagUrl));
555
+ console.log(chalk.blue(' Deque: ') + chalk.underline(violation.dequeUrl));
556
+ console.log();
557
+ // Copilot integration hint
558
+ const hasCopilot = await isCopilotAvailable();
559
+ if (hasCopilot) {
560
+ console.log(boxen(chalk.cyan('Tip: ') + 'Get context-specific guidance with Copilot:\n\n' +
561
+ chalk.dim(` gh copilot explain "How do I fix ${violation.id} violations in my React app?"`) + '\n' +
562
+ chalk.dim(` gh copilot explain "Best practices for ${violation.name.toLowerCase()}"`), {
563
+ padding: { top: 0, bottom: 0, left: 1, right: 1 },
564
+ borderStyle: 'round',
565
+ borderColor: 'blue',
566
+ dimBorder: true,
567
+ }));
568
+ }
569
+ else {
570
+ printInfo('Install GitHub Copilot CLI for context-specific guidance: ' +
571
+ chalk.cyan('gh extension install github/gh-copilot'));
572
+ }
573
+ }
574
+ function wrapText(text, width) {
575
+ const words = text.split(' ');
576
+ const lines = [];
577
+ let currentLine = '';
578
+ for (const word of words) {
579
+ if ((currentLine + ' ' + word).trim().length <= width) {
580
+ currentLine = (currentLine + ' ' + word).trim();
581
+ }
582
+ else {
583
+ if (currentLine)
584
+ lines.push(currentLine);
585
+ currentLine = word;
586
+ }
587
+ }
588
+ if (currentLine)
589
+ lines.push(currentLine);
590
+ return lines.join('\n');
591
+ }
592
+ export default learnCommand;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ally pr-check command - Post accessibility results to GitHub PR
3
+ */
4
+ interface PrCheckOptions {
5
+ input?: string;
6
+ pr?: number;
7
+ comment?: boolean;
8
+ failOn?: string;
9
+ token?: string;
10
+ }
11
+ export declare function prCheckCommand(options?: PrCheckOptions): Promise<void>;
12
+ export default prCheckCommand;