@wcag-checkr/ci 1.0.0-rc.31 → 1.0.0-rc.310

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 (104) hide show
  1. package/dist/assets/ErrorBoundary-C-kswn4E.js +594 -0
  2. package/dist/assets/ai-usage-log-BX3L6bKl.js +1 -0
  3. package/dist/assets/content-script.ts-FuMy_sE5.js +217 -0
  4. package/dist/assets/{content-script.ts-loader-Dfu1UEfD.js → content-script.ts-loader-CBHeu186.js} +1 -1
  5. package/dist/assets/copy-ai-fixer-prompt-DQYkHOv3.js +19 -0
  6. package/dist/assets/{crash-reporter-Dc5lvxvY.js → crash-reporter-Bu2p8K-p.js} +1 -1
  7. package/dist/assets/design-system-audit-DpxJrxnb.js +1 -0
  8. package/dist/assets/devtools-panel-DFQvqKKj.js +1 -0
  9. package/dist/assets/diff-DA41zYPc.js +1 -0
  10. package/dist/assets/dom-criterion-analyzers-DoUaJV5C.js +8 -0
  11. package/dist/assets/fraunces-latin-400-normal-6IfK1voy.woff2 +0 -0
  12. package/dist/assets/fraunces-latin-400-normal-NUPT2cO8.woff +0 -0
  13. package/dist/assets/fraunces-latin-500-normal-BTR4KCeb.woff +0 -0
  14. package/dist/assets/fraunces-latin-500-normal-DnGCNyPD.woff2 +0 -0
  15. package/dist/assets/fraunces-latin-600-normal-BFCDtZfi.woff2 +0 -0
  16. package/dist/assets/fraunces-latin-600-normal-DL5QCzvS.woff +0 -0
  17. package/dist/assets/fraunces-latin-ext-400-normal-D8gbi3Gu.woff2 +0 -0
  18. package/dist/assets/fraunces-latin-ext-400-normal-UihxqfOe.woff +0 -0
  19. package/dist/assets/fraunces-latin-ext-500-normal-BMcFk1Xs.woff +0 -0
  20. package/dist/assets/fraunces-latin-ext-500-normal-Z5DV8IzT.woff2 +0 -0
  21. package/dist/assets/fraunces-latin-ext-600-normal-B0Dy4lqi.woff +0 -0
  22. package/dist/assets/fraunces-latin-ext-600-normal-BtzmzP0X.woff2 +0 -0
  23. package/dist/assets/fraunces-vietnamese-400-normal-B65MOf9T.woff +0 -0
  24. package/dist/assets/fraunces-vietnamese-400-normal-CvGt0Ybw.woff2 +0 -0
  25. package/dist/assets/fraunces-vietnamese-500-normal-B-KbxExq.woff +0 -0
  26. package/dist/assets/fraunces-vietnamese-500-normal-GOH_-EGq.woff2 +0 -0
  27. package/dist/assets/fraunces-vietnamese-600-normal-BjlAJixd.woff2 +0 -0
  28. package/dist/assets/fraunces-vietnamese-600-normal-DlAl5EAR.woff +0 -0
  29. package/dist/assets/geist-sans-latin-400-normal-BOaIZNA2.woff +0 -0
  30. package/dist/assets/geist-sans-latin-400-normal-gapTbOY8.woff2 +0 -0
  31. package/dist/assets/geist-sans-latin-500-normal-CN2lyvyL.woff +0 -0
  32. package/dist/assets/geist-sans-latin-500-normal-uokXdC-Q.woff2 +0 -0
  33. package/dist/assets/geist-sans-latin-600-normal-CA1yjETN.woff +0 -0
  34. package/dist/assets/geist-sans-latin-600-normal-DFOURf8L.woff2 +0 -0
  35. package/dist/assets/geist-sans-latin-700-normal-BmN9tIp5.woff2 +0 -0
  36. package/dist/assets/geist-sans-latin-700-normal-CjScfYeH.woff +0 -0
  37. package/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  38. package/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  39. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  40. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  41. package/dist/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
  42. package/dist/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
  43. package/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  44. package/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  45. package/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  46. package/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  47. package/dist/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
  48. package/dist/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
  49. package/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  50. package/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  51. package/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  52. package/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  53. package/dist/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  54. package/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  55. package/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  56. package/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  57. package/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  58. package/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  59. package/dist/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
  60. package/dist/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
  61. package/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  62. package/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  63. package/dist/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
  64. package/dist/assets/options-BPhjrbGI.js +6 -0
  65. package/dist/assets/parallel-tab-flow-Xk9RSjay.js +1 -0
  66. package/dist/assets/scheduled-audit-runner-DyKpb3zg.js +2167 -0
  67. package/dist/assets/service-worker.ts-CMkltOzu.js +2 -0
  68. package/dist/assets/side-panel-Ctm2yXeo.css +1 -0
  69. package/dist/assets/side-panel-f_X4NOJt.js +4 -0
  70. package/dist/assets/site-report-renderer-DNgytqhZ.js +189 -0
  71. package/dist/assets/{styles-C4Kq0zOO.js → styles-Cn731SYD.js} +13 -13
  72. package/dist/assets/styles-d5msFsnl.css +1 -0
  73. package/dist/assets/zip-encoder-CtULHXx_.js +1 -0
  74. package/dist/axe.min.js +12 -0
  75. package/dist/devtools/panel.html +9 -8
  76. package/dist/manifest.json +11 -8
  77. package/dist/options/options.html +5 -6
  78. package/dist/service-worker-loader.js +1 -1
  79. package/dist/side-panel/App.tsx +129 -5
  80. package/dist/side-panel/audit-launcher.ts +21 -1
  81. package/dist/side-panel/azure-devops-issue.test.ts +68 -0
  82. package/dist/side-panel/azure-devops-issue.ts +89 -0
  83. package/dist/side-panel/gitlab-issue.test.ts +53 -0
  84. package/dist/side-panel/gitlab-issue.ts +78 -0
  85. package/dist/side-panel/main.tsx +39 -2
  86. package/dist/side-panel/side-panel.html +11 -8
  87. package/dist/side-panel/store.ts +149 -13
  88. package/dist/side-panel/styles.css +39 -0
  89. package/dist/side-panel/wire-messaging.ts +146 -9
  90. package/package.json +1 -1
  91. package/wcagcheckr-ci.mjs +193 -32
  92. package/dist/assets/ErrorBoundary-BLcMSVSr.js +0 -524
  93. package/dist/assets/ai-usage-log-Dj9Ub_DT.js +0 -1
  94. package/dist/assets/content-script.ts-CwcUMq3e.js +0 -181
  95. package/dist/assets/devtools-panel-DQ3Bbomf.js +0 -1
  96. package/dist/assets/diff-D4sCAdXf.js +0 -1
  97. package/dist/assets/forensic-log-B1UCXZ23.js +0 -129
  98. package/dist/assets/options-BG2i5vFf.js +0 -6
  99. package/dist/assets/preload-helper-D7HrI6pR.js +0 -1
  100. package/dist/assets/service-worker.ts-CO86CV_p.js +0 -715
  101. package/dist/assets/side-panel-XSB07vDa.js +0 -1
  102. package/dist/assets/site-report-renderer-CyHkM6hB.js +0 -147
  103. package/dist/assets/state-PELIq3oj.js +0 -1
  104. package/dist/assets/styles-Cevp58mS.css +0 -1
package/wcagcheckr-ci.mjs CHANGED
@@ -68,6 +68,10 @@ function parseArgs(argv) {
68
68
  args.inputFile = a;
69
69
  continue;
70
70
  }
71
+ if (args.command === 'pdf' && !args.pdfTarget && !a.startsWith('--')) {
72
+ args.pdfTarget = a;
73
+ continue;
74
+ }
71
75
  if (a === '--format') args.format = argv[++i];
72
76
  else if (a === '--output') args.output = argv[++i];
73
77
  else if (a === '--threshold') args.threshold = argv[++i];
@@ -76,6 +80,9 @@ function parseArgs(argv) {
76
80
  else if (a === '--quiet') args.quiet = true;
77
81
  else if (a === '--license') args.license = argv[++i];
78
82
  else if (a === '--public-key-url') args.publicKeyUrl = argv[++i];
83
+ else if (a === '--watch') args.watch = true;
84
+ else if (a === '--watch-dir') args.watchDir = argv[++i];
85
+ else if (a === '--watch-debounce') args.watchDebounce = parseInt(argv[++i], 10);
79
86
  else if (a === '--help' || a === '-h') args.help = true;
80
87
  }
81
88
  return args;
@@ -87,6 +94,13 @@ function printUsage() {
87
94
  Usage:
88
95
  wcagcheckr-ci audit <url> [options]
89
96
  wcagcheckr-ci verify <forensic-log.json> [options]
97
+ wcagcheckr-ci pdf <path-or-url> [options]
98
+
99
+ pdf options:
100
+ --output <file> Write JSON report to file
101
+ --threshold <none|critical|serious|moderate|minor>
102
+ Severity gate for non-zero exit
103
+ --quiet Suppress progress output
90
104
 
91
105
  audit options:
92
106
  --format <json|sarif|junit> Output format (default: json)
@@ -97,6 +111,12 @@ audit options:
97
111
  --timeout <ms> Audit timeout (default: 120000)
98
112
  --license <token> Activate this license token before auditing
99
113
  (gates paid features like forensic anchoring)
114
+ --watch Stay open after the initial audit and re-run
115
+ whenever a file under --watch-dir changes
116
+ --watch-dir <path> Directory to watch when --watch is on
117
+ (default: current working directory)
118
+ --watch-debounce <ms> Debounce window after a change before
119
+ re-auditing (default: 1500)
100
120
  --quiet Suppress progress output
101
121
 
102
122
  verify options:
@@ -137,6 +157,52 @@ if (args.command === 'verify') {
137
157
  // runVerify exits the process itself.
138
158
  }
139
159
 
160
+ // ─── pdf subcommand (rc.153, G3) ────────────────────────────────────────────
161
+ //
162
+ // Metadata-level PDF accessibility audit. No page rendering — checks
163
+ // the document Catalog for tagged status, document language, title,
164
+ // structure-tree root. The #1 PDF accessibility failure (untagged) is
165
+ // catchable at this level.
166
+
167
+ if (args.command === 'pdf') {
168
+ if (!args.pdfTarget) {
169
+ console.error('✗ pdf: missing PDF path or URL.\n');
170
+ printUsage();
171
+ process.exit(2);
172
+ }
173
+ const { auditPdfFile, auditPdfUrl } = await import('./pdf-audit.mjs');
174
+ const isUrl = /^https?:\/\//.test(args.pdfTarget);
175
+ log(`auditing PDF metadata for ${args.pdfTarget}`);
176
+ const result = isUrl
177
+ ? await auditPdfUrl(args.pdfTarget)
178
+ : await auditPdfFile(args.pdfTarget);
179
+ if (!result.ok) {
180
+ console.error(`✗ ${result.fatalError ?? 'PDF audit failed.'}`);
181
+ process.exit(2);
182
+ }
183
+ if (args.output) {
184
+ const { writeFileSync } = await import('node:fs');
185
+ writeFileSync(args.output, JSON.stringify(result, null, 2), 'utf8');
186
+ log(`output written to ${args.output}`);
187
+ } else {
188
+ process.stdout.write(JSON.stringify(result, null, 2));
189
+ process.stdout.write('\n');
190
+ }
191
+ const exceeds = args.threshold === 'none'
192
+ ? false
193
+ : result.findings.some((f) => {
194
+ const rank = SEVERITY_RANK[f.impact];
195
+ const cap = SEVERITY_RANK[args.threshold];
196
+ return rank <= cap;
197
+ });
198
+ log(
199
+ `${result.findings.length} finding${result.findings.length === 1 ? '' : 's'} ` +
200
+ `(version ${result.version ?? '?'}, tagged=${result.metadata?.tagged ?? '?'}, ` +
201
+ `lang=${result.metadata?.lang ?? '?'}, title=${result.metadata?.title ? 'yes' : 'no'})`,
202
+ );
203
+ process.exit(exceeds ? 1 : 0);
204
+ }
205
+
140
206
  if (args.command !== 'audit' || !args.url) {
141
207
  console.error(`✗ Unknown command or missing URL.\n`);
142
208
  printUsage();
@@ -162,6 +228,9 @@ if (!['none', 'critical', 'serious', 'moderate', 'minor'].includes(args.threshol
162
228
 
163
229
  log(`Loading extension from ${EXT_DIR}`);
164
230
  log(`Auditing ${args.url} (threshold: ${args.threshold}, format: ${args.format})`);
231
+ if (args.watch) {
232
+ log(`watch mode ON — re-audits on file changes under ${args.watchDir ?? process.cwd()}`);
233
+ }
165
234
 
166
235
  // Chrome extensions cannot load in legacy `headless: true` mode, so we
167
236
  // pass `--headless=new` as a Chromium arg (the modern headless mode that
@@ -179,8 +248,10 @@ const context = await chromium.launchPersistentContext('', {
179
248
  });
180
249
 
181
250
  let exitCode = 0;
251
+ let targetPage;
252
+ let sidePanel;
182
253
  try {
183
- const [targetPage] = context.pages();
254
+ [targetPage] = context.pages();
184
255
  await targetPage.goto(args.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
185
256
  log('target page loaded');
186
257
 
@@ -193,7 +264,7 @@ try {
193
264
  if (!extId) throw new Error('extension SW did not register within 10s');
194
265
  log(`extension loaded (id: ${extId.slice(0, 8)}…)`);
195
266
 
196
- const sidePanel = await context.newPage();
267
+ sidePanel = await context.newPage();
197
268
  await sidePanel.goto(`chrome-extension://${extId}/side-panel/side-panel.html`);
198
269
 
199
270
  // Headless persona — pick dev mode if wizard appears.
@@ -204,27 +275,40 @@ try {
204
275
  }
205
276
  await sidePanel.waitForSelector('h1:has-text("wcagcheckr")', { timeout: 10_000 });
206
277
 
207
- // Activate license token, if provided. Gated paid features (forensic
208
- // anchoring, AI-summary, etc.) need an active license at audit time.
209
- if (args.license) {
210
- log('activating license token');
211
- const activated = await sidePanel.evaluate(
212
- async (token) =>
213
- await new Promise((resolve) => {
214
- chrome.runtime.sendMessage(
215
- { type: 'LICENSE_SET_REQUEST', token },
216
- (response) => resolve(response ?? null),
217
- );
218
- }),
219
- args.license,
278
+ // rc.124 Headless-build lockdown gate. A team license is required to
279
+ // run audits during the private build phase. If --license is omitted
280
+ // OR validates to a non-team tier, refuse before doing any audit work.
281
+ if (!args.license) {
282
+ throw new Error(
283
+ 'wcagcheckr is in private build phase. A team license is required to use the CLI. ' +
284
+ 'Pass --license <token>. Email cliff@locustware.com for beta access.',
220
285
  );
221
- if (!activated || activated.tier === 'free') {
222
- throw new Error(
223
- `license activation did not yield a paid tier — got ${JSON.stringify(activated)}`,
224
- );
225
- }
226
- log(`license active — tier: ${activated.tier}`);
227
286
  }
287
+ log('activating license token');
288
+ const activated = await sidePanel.evaluate(
289
+ async (token) =>
290
+ await new Promise((resolve) => {
291
+ chrome.runtime.sendMessage(
292
+ { type: 'LICENSE_SET_REQUEST', token },
293
+ (response) => resolve(response ?? null),
294
+ );
295
+ }),
296
+ args.license,
297
+ );
298
+ if (!activated || !activated.validated) {
299
+ throw new Error(
300
+ `license activation failed — got ${JSON.stringify(activated)}`,
301
+ );
302
+ }
303
+ // Per the headless-build strategy, only team-tier licenses can use the CLI.
304
+ // At launch this constraint relaxes per per-feature tier mapping.
305
+ if (activated.tier !== 'team') {
306
+ throw new Error(
307
+ `wcagcheckr CLI requires a team-tier license during private build (got tier='${activated.tier}'). ` +
308
+ 'Email cliff@locustware.com for beta access.',
309
+ );
310
+ }
311
+ log(`license active — tier: ${activated.tier}`);
228
312
 
229
313
  // Skip onboarding tour
230
314
  for (let i = 0; i < 5; i++) {
@@ -242,21 +326,43 @@ try {
242
326
  break;
243
327
  }
244
328
 
245
- await sidePanel.getByLabel('Audit mode').selectOption('full-page');
246
- await targetPage.bringToFront();
247
- await targetPage.waitForTimeout(500);
248
- await sidePanel.bringToFront();
249
- await sidePanel.waitForTimeout(300);
250
- await sidePanel.getByRole('button', { name: /Scan page|Auditing/ }).click();
251
- log('audit started');
329
+ // The per-audit work: navigate target (re-load on subsequent runs to
330
+ // pick up code changes), kick off the scan, wait for completion, export,
331
+ // print, threshold-check. Returns the exit code that THIS audit pass
332
+ // should produce — the watch loop overall stays at 0 until Ctrl+C.
333
+ async function runOneAudit() {
334
+ // On re-runs, navigate the target page again so it picks up source
335
+ // changes (assumes the dev server hot-reloads or the URL serves fresh
336
+ // content).
337
+ await targetPage.bringToFront();
338
+ await targetPage.reload({ waitUntil: 'domcontentloaded', timeout: 30_000 });
339
+ await targetPage.waitForTimeout(500);
340
+ await sidePanel.bringToFront();
341
+ await sidePanel.waitForTimeout(300);
342
+
343
+ await sidePanel.getByLabel('Audit mode').selectOption('full-page');
344
+ await sidePanel.getByRole('button', { name: /Scan page|Auditing/ }).click();
345
+ log('audit started');
346
+
347
+ await sidePanel
348
+ .getByText(/\d+\s+states?\s+·\s+\d+\s+unique\s+violations?/, { exact: false })
349
+ .waitFor({ timeout: args.timeout });
350
+ log('audit completed');
351
+
352
+ return await collectResults();
353
+ }
354
+
355
+ // Result-export + threshold-check, extracted so runOneAudit returns the
356
+ // exit code suggestion. Closure over `args`, `sidePanel`, etc.
357
+ async function collectResults() {
358
+ return await doExportAndThresholdCheck();
359
+ }
252
360
 
253
- await sidePanel
254
- .getByText(/\d+\s+states?\s+·\s+\d+\s+unique\s+violations?/, { exact: false })
255
- .waitFor({ timeout: args.timeout });
256
- log('audit completed');
361
+ await runOneAudit();
257
362
 
258
363
  // Trigger the existing export pipeline. The SW handles EXPORT_REQUEST and
259
364
  // returns the formatted content for the requested format.
365
+ async function doExportAndThresholdCheck() {
260
366
  const formatMap = { json: 'json', sarif: 'sarif', junit: 'junit' };
261
367
  const exportFormat = formatMap[args.format];
262
368
 
@@ -340,6 +446,61 @@ try {
340
446
  log(`✓ threshold not exceeded (--threshold=${args.threshold})`);
341
447
  }
342
448
  }
449
+ } // end doExportAndThresholdCheck
450
+
451
+ // Watch mode: keep the browser open, watch the directory tree, re-audit
452
+ // on change (debounced). Ctrl+C exits.
453
+ if (args.watch) {
454
+ const watchDir = args.watchDir ?? process.cwd();
455
+ const debounceMs = args.watchDebounce ?? 1500;
456
+ const { watch } = await import('node:fs');
457
+ log(`watching ${watchDir} (debounce ${debounceMs}ms) — Ctrl+C to exit`);
458
+ let pending = false;
459
+ let inFlight = false;
460
+ let runCount = 1;
461
+ function trigger() {
462
+ if (inFlight) {
463
+ pending = true;
464
+ return;
465
+ }
466
+ inFlight = true;
467
+ setTimeout(async () => {
468
+ try {
469
+ runCount++;
470
+ log(`\n── Re-audit #${runCount} ──`);
471
+ await runOneAudit();
472
+ if (pending) {
473
+ pending = false;
474
+ inFlight = false;
475
+ trigger();
476
+ } else {
477
+ inFlight = false;
478
+ }
479
+ } catch (err) {
480
+ log(`re-audit failed: ${err.message}`);
481
+ inFlight = false;
482
+ }
483
+ }, debounceMs);
484
+ }
485
+ try {
486
+ watch(watchDir, { recursive: true }, (eventType, filename) => {
487
+ // Skip noise: node_modules, dist artifacts, .git, dotfiles other
488
+ // than the typical source files.
489
+ if (!filename) return;
490
+ const f = String(filename);
491
+ if (f.includes('node_modules')) return;
492
+ if (f.includes('.git')) return;
493
+ if (f.includes('/dist/') || f.startsWith('dist/') || f.includes('\\dist\\') || f.startsWith('dist\\')) return;
494
+ log(`change: ${f}`);
495
+ trigger();
496
+ });
497
+ } catch (err) {
498
+ log(`fs.watch failed (recursive may not be supported on this platform): ${err.message}`);
499
+ log('watch mode disabled; exiting after this audit.');
500
+ }
501
+ // Block forever until the user kills the process.
502
+ await new Promise(() => {});
503
+ }
343
504
  } catch (err) {
344
505
  console.error(`✗ ${err.message}`);
345
506
  if (err.stack && !args.quiet) console.error(err.stack);