@a11y-skills/audit 0.3.1 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
3
3
  All notable changes to `@a11y-skills/audit` are documented here. This project
4
4
  adheres to [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## 0.4.1 — 2026-06-12
7
+
8
+ ### Fixed
9
+
10
+ - CLI: `orientation-check` and `time-limit-detector` were failing with
11
+ "No target URL provided" because the registry entries did not pass `targetUrl`
12
+ to the underlying check functions. Both checks own their own navigation, so the
13
+ CLI's redundant pre-navigation step is now skipped for these two entries via the
14
+ new `ownsNavigation: true` flag on `CheckEntry`.
15
+
16
+ ## 0.4.0 — 2026-06-12
17
+
18
+ ### Added
19
+
20
+ - `bin` CLI (`a11y-audit`): run all ten WCAG checks against a URL without a
21
+ Playwright test runner — `npx -y @a11y-skills/audit --url <url>`. Flags:
22
+ `--checks <list>`, `--output-dir`, `--screenshot`, `--list-checks`,
23
+ `--version`, `--help`. Exit codes: `0` = no violations, `1` = violations
24
+ found, `2` = runtime error. JSON output uses the same envelope as the
25
+ function API regardless of exit code.
26
+ - `test/fixtures/cli-smoke.html`: network-independent smoke fixture (a page
27
+ with intentional axe violations) for the CI CLI smoke test.
28
+
6
29
  ## 0.3.1
7
30
 
8
31
  ### Fixed
package/README.ja.md CHANGED
@@ -50,6 +50,121 @@ npm install -D pixelmatch pngjs # runAutoPlayDetection を使う場合のみ
50
50
  > ESM 専用です。CommonJS ビルドは同梱しません。ESM(または ESM 出力の TypeScript)から
51
51
  > import してください。
52
52
 
53
+ ## CLI
54
+
55
+ テストランナー不要で、コマンド 1 本で 10 検査すべてを実行できます。
56
+
57
+ ```sh
58
+ npx -y @a11y-skills/audit --url https://example.com
59
+ ```
60
+
61
+ npm 7 以降では peer dependencies(`@playwright/test`、`@axe-core/playwright`)が
62
+ 自動でインストールされます。初回は Chromium のインストールが必要です:
63
+
64
+ ```sh
65
+ npx playwright install chromium
66
+ ```
67
+
68
+ ### フラグ
69
+
70
+ | フラグ | デフォルト | 説明 |
71
+ | --- | --- | --- |
72
+ | `--url <url>` | `TEST_PAGE` 環境変数 | 対象 URL。`--url` が環境変数より優先されます。どちらも未設定の場合は usage を表示して exit 2。 |
73
+ | `--checks <list>` | `all` | カンマ区切りのチェック名(下記一覧参照)。 |
74
+ | `--output-dir <dir>` | `./a11y-audit-results` | 結果 JSON の出力ディレクトリ。 |
75
+ | `--screenshot` | off | focus-indicator のスクリーンショットを有効化。 |
76
+ | `--list-checks` | — | チェック名一覧を出力して exit 0。 |
77
+ | `--version` | — | バージョンを出力して exit 0。 |
78
+ | `--help` | — | usage を出力して exit 0。 |
79
+
80
+ ### チェック名一覧
81
+
82
+ ```
83
+ axe-audit
84
+ focus-indicator-check
85
+ reflow-check
86
+ text-spacing-check
87
+ zoom-200-check
88
+ orientation-check
89
+ autocomplete-audit
90
+ time-limit-detector
91
+ auto-play-detection
92
+ target-size-check
93
+ ```
94
+
95
+ ### 終了コード
96
+
97
+ | コード | 意味 |
98
+ | --- | --- |
99
+ | `0` | すべてのチェックが完了し、違反なし。 |
100
+ | `1` | すべてのチェックが完了し、1 件以上の違反あり。 |
101
+ | `2` | 実行時エラー — 引数不正・peer 欠落・ナビゲーション失敗・チェックのクラッシュ。 |
102
+
103
+ exit コードが非 0 でも、完了したチェックの JSON は必ず書き出されます。
104
+
105
+ ### peer 要件
106
+
107
+ `@playwright/test >=1.50.0` および `@axe-core/playwright >=4.10.0` が必要です。
108
+ 見つからない場合は exit 2 でインストール手順を案内します。
109
+ `--list-checks`、`--help`、`--version` は peer なしでも動作します。
110
+
111
+ ### `auto-play-detection` と optional deps
112
+
113
+ `auto-play-detection` は `pixelmatch` と `pngjs` が必要です。
114
+ 未インストールの場合はそのチェックだけ `SKIPPED` となり、終了コードには影響しません。
115
+ 他の 9 検査は通常通り実行されます。
116
+
117
+ ### GitHub Actions
118
+
119
+ 手動トリガーで監査を実行するコピー&ペースト用ワークフロー。violations があるとジョブは fail する(終了コード 1)。結果を情報提供のみにしたい場合は監査ステップに `continue-on-error: true` を付ける。
120
+
121
+ ```yaml
122
+ # .github/workflows/a11y-audit.yml
123
+ name: A11y Audit
124
+
125
+ on:
126
+ workflow_dispatch:
127
+ inputs:
128
+ target_url:
129
+ description: 'Audit target URL'
130
+ required: true
131
+ default: 'https://example.com'
132
+
133
+ jobs:
134
+ a11y-audit:
135
+ runs-on: ubuntu-latest
136
+ steps:
137
+ - name: Setup Node.js
138
+ uses: actions/setup-node@v6
139
+ with:
140
+ node-version: '24'
141
+
142
+ - name: Install audit package
143
+ run: npm install --no-save @a11y-skills/audit @playwright/test @axe-core/playwright
144
+
145
+ - name: Install Playwright browsers
146
+ run: npx playwright install --with-deps chromium
147
+
148
+ - name: Run a11y audit
149
+ run: npx a11y-audit --url "$TARGET_URL" --output-dir a11y-audit-results
150
+ env:
151
+ TARGET_URL: ${{ inputs.target_url }}
152
+
153
+ - name: Upload audit results
154
+ uses: actions/upload-artifact@v7
155
+ if: always()
156
+ with:
157
+ name: a11y-audit-results
158
+ path: a11y-audit-results/
159
+ retention-days: 30
160
+ ```
161
+
162
+ Notes:
163
+
164
+ - `push` / `schedule` トリガー(URL はハードコード)を追加すると、定期的なゲートとして実行できる。
165
+ - 結果 JSON ファイル(`--screenshot` 指定時はフォーカスインジケーターのスクリーンショットも含む)はビルドアーティファクトとしてアップロードされる。
166
+ - 繰り返し実行を高速化するには、インストール済みの `@playwright/test` バージョンをキーに `~/.cache/ms-playwright` をキャッシュする — パターンはこのリポジトリの `ci.yml` を参照。
167
+
53
168
  ## 使い方 — 関数 API(推奨)
54
169
 
55
170
  ページの遷移は呼び出し側で行い、関数は検査の実行・結果 JSON の書き出し・結果オブジェクトの
package/README.md CHANGED
@@ -52,6 +52,127 @@ npm install -D pixelmatch pngjs # only if you use runAutoPlayDetection
52
52
  > ESM only. This package does not ship a CommonJS build; import it from ESM
53
53
  > (or a TypeScript project compiled to ESM).
54
54
 
55
+ ## CLI
56
+
57
+ Run all ten checks against a URL with a single command — no test runner needed.
58
+
59
+ ```sh
60
+ npx -y @a11y-skills/audit --url https://example.com
61
+ ```
62
+
63
+ Peer dependencies (`@playwright/test`, `@axe-core/playwright`) are pulled in
64
+ automatically by npm 7+. Install Chromium on first use:
65
+
66
+ ```sh
67
+ npx playwright install chromium
68
+ ```
69
+
70
+ ### Flags
71
+
72
+ | Flag | Default | Description |
73
+ | --- | --- | --- |
74
+ | `--url <url>` | `TEST_PAGE` env | Target URL. `--url` takes priority over the env var. If neither is set, usage is printed and the process exits 2. |
75
+ | `--checks <list>` | `all` | Comma-separated check names (see list below). |
76
+ | `--output-dir <dir>` | `./a11y-audit-results` | Directory for result JSON files. |
77
+ | `--screenshot` | off | Enable focus-indicator screenshot. |
78
+ | `--list-checks` | — | Print check names and exit 0. |
79
+ | `--version` | — | Print version and exit 0. |
80
+ | `--help` | — | Print usage and exit 0. |
81
+
82
+ ### Available check names
83
+
84
+ ```
85
+ axe-audit
86
+ focus-indicator-check
87
+ reflow-check
88
+ text-spacing-check
89
+ zoom-200-check
90
+ orientation-check
91
+ autocomplete-audit
92
+ time-limit-detector
93
+ auto-play-detection
94
+ target-size-check
95
+ ```
96
+
97
+ ### Exit codes
98
+
99
+ | Code | Meaning |
100
+ | --- | --- |
101
+ | `0` | All checks completed, no violations found. |
102
+ | `1` | All checks completed, at least one violation found. |
103
+ | `2` | Runtime error — bad arguments, missing peers, navigation failure, or check crash. |
104
+
105
+ Completed-check JSON is always written even when the exit code is non-zero.
106
+
107
+ ### Peer requirements
108
+
109
+ `@playwright/test >=1.50.0` and `@axe-core/playwright >=4.10.0` must be
110
+ resolvable. If they are not found, the CLI exits 2 with an install hint.
111
+ `--list-checks`, `--help`, and `--version` work without peers.
112
+
113
+ ### `auto-play-detection` and optional deps
114
+
115
+ `auto-play-detection` needs `pixelmatch` and `pngjs`. If they are absent, that
116
+ check is skipped with a `SKIPPED` message and does not affect the exit code.
117
+ The other nine checks continue normally.
118
+
119
+ ### GitHub Actions
120
+
121
+ Copy-paste workflow for a manually triggered audit. Violations fail the job
122
+ (exit code 1); add `continue-on-error: true` to the audit step if you want the
123
+ result to be informational only.
124
+
125
+ ```yaml
126
+ # .github/workflows/a11y-audit.yml
127
+ name: A11y Audit
128
+
129
+ on:
130
+ workflow_dispatch:
131
+ inputs:
132
+ target_url:
133
+ description: 'Audit target URL'
134
+ required: true
135
+ default: 'https://example.com'
136
+
137
+ jobs:
138
+ a11y-audit:
139
+ runs-on: ubuntu-latest
140
+ steps:
141
+ - name: Setup Node.js
142
+ uses: actions/setup-node@v6
143
+ with:
144
+ node-version: '24'
145
+
146
+ - name: Install audit package
147
+ run: npm install --no-save @a11y-skills/audit @playwright/test @axe-core/playwright
148
+
149
+ - name: Install Playwright browsers
150
+ run: npx playwright install --with-deps chromium
151
+
152
+ - name: Run a11y audit
153
+ run: npx a11y-audit --url "$TARGET_URL" --output-dir a11y-audit-results
154
+ env:
155
+ TARGET_URL: ${{ inputs.target_url }}
156
+
157
+ - name: Upload audit results
158
+ uses: actions/upload-artifact@v7
159
+ if: always()
160
+ with:
161
+ name: a11y-audit-results
162
+ path: a11y-audit-results/
163
+ retention-days: 30
164
+ ```
165
+
166
+ Notes:
167
+
168
+ - Add `push` / `schedule` triggers (with a hardcoded URL) to run the audit as
169
+ a recurring gate.
170
+ - Result JSON files (and the focus-indicator screenshot when `--screenshot`
171
+ is set) are uploaded as a build artifact.
172
+ - To speed up repeat runs, cache `~/.cache/ms-playwright` keyed on the
173
+ installed `@playwright/test` version — see this repository's `ci.yml` for
174
+ the pattern.
175
+
55
176
  ## Usage — function API (recommended)
56
177
 
57
178
  You navigate the page; the function runs the check, writes a result JSON, and
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @a11y-skills/audit CLI
4
+ *
5
+ * Runs WCAG 2.2 accessibility checks against a URL without needing a
6
+ * Playwright test runner. All 10 checks are available via --checks.
7
+ *
8
+ * Exit codes:
9
+ * 0 — completed, no violations found across all checks
10
+ * 1 — completed, at least one violation found
11
+ * 2 — runtime error (bad args, missing peers, navigation failure, check crash)
12
+ */
13
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @a11y-skills/audit CLI
4
+ *
5
+ * Runs WCAG 2.2 accessibility checks against a URL without needing a
6
+ * Playwright test runner. All 10 checks are available via --checks.
7
+ *
8
+ * Exit codes:
9
+ * 0 — completed, no violations found across all checks
10
+ * 1 — completed, at least one violation found
11
+ * 2 — runtime error (bad args, missing peers, navigation failure, check crash)
12
+ */
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import { parseArgs } from 'node:util';
16
+ // ---------------------------------------------------------------------------
17
+ // Check registry
18
+ // ---------------------------------------------------------------------------
19
+ // Checks are registered with lazy imports to avoid top-level peer resolution.
20
+ const CHECK_REGISTRY = [
21
+ {
22
+ name: 'axe-audit',
23
+ kind: 'page',
24
+ async run({ page, outputDir }) {
25
+ const { runAxeAudit } = await import('./playwright/runAxeAudit.js');
26
+ return (await runAxeAudit({ page, outputDir }));
27
+ },
28
+ },
29
+ {
30
+ name: 'focus-indicator-check',
31
+ kind: 'browser',
32
+ async run({ browser, outputDir, screenshot, url }) {
33
+ const { runFocusIndicatorCheck } = await import('./playwright/runFocusIndicatorCheck.js');
34
+ return (await runFocusIndicatorCheck({
35
+ browser,
36
+ targetUrl: url,
37
+ outputDir,
38
+ screenshot,
39
+ }));
40
+ },
41
+ },
42
+ {
43
+ name: 'reflow-check',
44
+ kind: 'page',
45
+ async run({ page, outputDir }) {
46
+ const { runReflowCheck } = await import('./playwright/runReflowCheck.js');
47
+ return (await runReflowCheck({ page, outputDir }));
48
+ },
49
+ },
50
+ {
51
+ name: 'text-spacing-check',
52
+ kind: 'page',
53
+ async run({ page, outputDir }) {
54
+ const { runTextSpacingCheck } = await import('./playwright/runTextSpacingCheck.js');
55
+ return (await runTextSpacingCheck({
56
+ page,
57
+ outputDir,
58
+ }));
59
+ },
60
+ },
61
+ {
62
+ name: 'zoom-200-check',
63
+ kind: 'page',
64
+ async run({ page, outputDir }) {
65
+ const { runZoomCheck } = await import('./playwright/runZoomCheck.js');
66
+ return (await runZoomCheck({ page, outputDir }));
67
+ },
68
+ },
69
+ {
70
+ name: 'orientation-check',
71
+ kind: 'page',
72
+ ownsNavigation: true,
73
+ async run({ page, outputDir, url }) {
74
+ const { runOrientationCheck } = await import('./playwright/runOrientationCheck.js');
75
+ return (await runOrientationCheck({
76
+ page,
77
+ targetUrl: url,
78
+ outputDir,
79
+ }));
80
+ },
81
+ },
82
+ {
83
+ name: 'autocomplete-audit',
84
+ kind: 'page',
85
+ async run({ page, outputDir }) {
86
+ const { runAutocompleteAudit } = await import('./playwright/runAutocompleteAudit.js');
87
+ return (await runAutocompleteAudit({
88
+ page,
89
+ outputDir,
90
+ }));
91
+ },
92
+ },
93
+ {
94
+ name: 'time-limit-detector',
95
+ kind: 'page',
96
+ ownsNavigation: true,
97
+ async run({ page, outputDir, url }) {
98
+ const { runTimeLimitDetector } = await import('./playwright/runTimeLimitDetector.js');
99
+ return (await runTimeLimitDetector({
100
+ page,
101
+ targetUrl: url,
102
+ outputDir,
103
+ }));
104
+ },
105
+ },
106
+ {
107
+ name: 'auto-play-detection',
108
+ kind: 'page',
109
+ async run({ page, outputDir }) {
110
+ const { runAutoPlayDetection } = await import('./playwright/runAutoPlayDetection.js');
111
+ return (await runAutoPlayDetection({
112
+ page,
113
+ outputDir: path.join(outputDir, 'auto-play-screenshots'),
114
+ }));
115
+ },
116
+ },
117
+ {
118
+ name: 'target-size-check',
119
+ kind: 'page',
120
+ async run({ page, outputDir }) {
121
+ const { runTargetSizeCheck } = await import('./playwright/runTargetSizeCheck.js');
122
+ return (await runTargetSizeCheck({
123
+ page,
124
+ outputDir,
125
+ }));
126
+ },
127
+ },
128
+ ];
129
+ // ---------------------------------------------------------------------------
130
+ // Helpers
131
+ // ---------------------------------------------------------------------------
132
+ function printUsage() {
133
+ process.stdout.write(`
134
+ Usage: a11y-audit --url <url> [options]
135
+
136
+ Options:
137
+ --url <url> Target URL to audit. Overrides TEST_PAGE env var.
138
+ --checks <list> Comma-separated check names (default: all).
139
+ Use --list-checks to see available names.
140
+ --output-dir <dir> Directory for result JSON files (default: ./a11y-audit-results).
141
+ --screenshot Enable focus-indicator screenshot (default: off).
142
+ --list-checks Print available check names and exit.
143
+ --version Print version and exit.
144
+ --help Print this message and exit.
145
+
146
+ Exit codes:
147
+ 0 No violations found.
148
+ 1 One or more violations found.
149
+ 2 Runtime error (bad arguments, missing peers, navigation failure).
150
+
151
+ Peer requirements:
152
+ @playwright/test >=1.50.0
153
+ @axe-core/playwright >=4.10.0
154
+
155
+ Install if missing: npm i -D @playwright/test @axe-core/playwright
156
+ Install browsers: npx playwright install chromium
157
+
158
+ auto-play-detection requires optional deps (pixelmatch, pngjs). If they are
159
+ missing, that check is skipped with a SKIPPED notice.
160
+ `);
161
+ }
162
+ /** Verify required peers are resolvable. Returns an error string or null. */
163
+ async function checkPeers() {
164
+ const missing = [];
165
+ for (const pkg of ['@playwright/test', '@axe-core/playwright']) {
166
+ try {
167
+ await import(pkg);
168
+ }
169
+ catch {
170
+ missing.push(pkg);
171
+ }
172
+ }
173
+ if (missing.length > 0) {
174
+ return (`Missing required peer dependencies: ${missing.join(', ')}\n` +
175
+ `Install them with: npm i -D @playwright/test @axe-core/playwright`);
176
+ }
177
+ return null;
178
+ }
179
+ function formatDuration(ms) {
180
+ return `${(ms / 1000).toFixed(1)}s`;
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // Main
184
+ // ---------------------------------------------------------------------------
185
+ async function main() {
186
+ // Read package version from package.json
187
+ let pkgVersion = 'unknown';
188
+ try {
189
+ const pkgPath = new URL('../package.json', import.meta.url);
190
+ const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
191
+ pkgVersion = pkgJson.version;
192
+ }
193
+ catch {
194
+ // ignore
195
+ }
196
+ let values;
197
+ try {
198
+ const result = parseArgs({
199
+ args: process.argv.slice(2),
200
+ options: {
201
+ url: { type: 'string' },
202
+ checks: { type: 'string' },
203
+ 'output-dir': { type: 'string' },
204
+ screenshot: { type: 'boolean', default: false },
205
+ 'list-checks': { type: 'boolean', default: false },
206
+ version: { type: 'boolean', default: false },
207
+ help: { type: 'boolean', default: false },
208
+ },
209
+ strict: true,
210
+ });
211
+ values = result.values;
212
+ }
213
+ catch (err) {
214
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
215
+ printUsage();
216
+ process.exit(2);
217
+ }
218
+ if (values.help) {
219
+ printUsage();
220
+ process.exit(0);
221
+ }
222
+ if (values.version) {
223
+ process.stdout.write(`${pkgVersion}\n`);
224
+ process.exit(0);
225
+ }
226
+ if (values['list-checks']) {
227
+ process.stdout.write(CHECK_REGISTRY.map((c) => c.name).join('\n') + '\n');
228
+ process.exit(0);
229
+ }
230
+ // --- Resolve URL ---
231
+ const url = values.url ?? process.env.TEST_PAGE;
232
+ if (!url) {
233
+ process.stderr.write('Error: --url is required (or set the TEST_PAGE environment variable).\n');
234
+ printUsage();
235
+ process.exit(2);
236
+ }
237
+ // --- Resolve checks ---
238
+ const checksArg = values.checks;
239
+ let selectedChecks;
240
+ if (!checksArg || checksArg === 'all') {
241
+ selectedChecks = CHECK_REGISTRY;
242
+ }
243
+ else {
244
+ const names = checksArg.split(',').map((n) => n.trim());
245
+ const unknown = names.filter((n) => !CHECK_REGISTRY.some((c) => c.name === n));
246
+ if (unknown.length > 0) {
247
+ process.stderr.write(`Error: unknown check(s): ${unknown.join(', ')}\n` +
248
+ `Run --list-checks to see available names.\n`);
249
+ process.exit(2);
250
+ }
251
+ selectedChecks = names
252
+ .map((n) => CHECK_REGISTRY.find((c) => c.name === n))
253
+ .filter((c) => c !== undefined);
254
+ }
255
+ const outputDir = path.resolve(values['output-dir'] ?? './a11y-audit-results');
256
+ const screenshot = values.screenshot === true;
257
+ // --- Peer check (only before running checks) ---
258
+ const peerError = await checkPeers();
259
+ if (peerError) {
260
+ process.stderr.write(`Error: ${peerError}\n`);
261
+ process.exit(2);
262
+ }
263
+ // --- Launch browser ---
264
+ let browser;
265
+ try {
266
+ const { chromium } = await import('@playwright/test');
267
+ browser = await chromium.launch();
268
+ }
269
+ catch (err) {
270
+ process.stderr.write(`Error launching browser: ${err instanceof Error ? err.message : String(err)}\n`);
271
+ process.exit(2);
272
+ }
273
+ // Ensure output directory exists
274
+ fs.mkdirSync(outputDir, { recursive: true });
275
+ process.stdout.write(`a11y-audit v${pkgVersion}\n`);
276
+ process.stdout.write(`URL: ${url}\n`);
277
+ process.stdout.write(`Checks: ${selectedChecks.map((c) => c.name).join(', ')}\n`);
278
+ process.stdout.write(`Output dir: ${outputDir}\n`);
279
+ process.stdout.write(`\n`);
280
+ const summaries = [];
281
+ let hasViolations = false;
282
+ let hasErrors = false;
283
+ for (const check of selectedChecks) {
284
+ const start = Date.now();
285
+ // page-based checks get a fresh context + navigated page
286
+ let context = null;
287
+ let page = null;
288
+ try {
289
+ if (check.kind === 'page') {
290
+ context = await browser.newContext();
291
+ page = await context.newPage();
292
+ if (!check.ownsNavigation) {
293
+ // Use 'load' for file: URLs to avoid networkidle hanging
294
+ const waitUntil = url.startsWith('file:') ? 'load' : 'networkidle';
295
+ try {
296
+ await page.goto(url, { waitUntil });
297
+ }
298
+ catch (navErr) {
299
+ const msg = navErr instanceof Error ? navErr.message : String(navErr);
300
+ process.stderr.write(`[${check.name}] Navigation failed: ${msg}\n`);
301
+ summaries.push({
302
+ name: check.name,
303
+ status: 'ERROR',
304
+ durationMs: Date.now() - start,
305
+ error: msg,
306
+ });
307
+ hasErrors = true;
308
+ continue;
309
+ }
310
+ }
311
+ }
312
+ const result = await check.run({
313
+ // page is null only for browser-kind checks, which do not use it
314
+ page: page,
315
+ browser,
316
+ outputDir,
317
+ screenshot,
318
+ url,
319
+ });
320
+ const durationMs = Date.now() - start;
321
+ const summary = result.summary;
322
+ const violationCount = summary?.violationCount ?? 0;
323
+ const incompleteCount = summary?.incompleteCount ?? 0;
324
+ const passCount = summary?.passCount ?? 0;
325
+ if (violationCount > 0) {
326
+ hasViolations = true;
327
+ }
328
+ process.stdout.write(`[${check.name}] DONE in ${formatDuration(durationMs)} — ` +
329
+ `violations: ${violationCount}, passes: ${passCount}, incomplete: ${incompleteCount}\n`);
330
+ summaries.push({
331
+ name: check.name,
332
+ status: 'DONE',
333
+ durationMs,
334
+ violationCount,
335
+ incompleteCount,
336
+ passCount,
337
+ });
338
+ }
339
+ catch (err) {
340
+ const durationMs = Date.now() - start;
341
+ const msg = err instanceof Error ? err.message : String(err);
342
+ // auto-play-detection: optional dep missing → SKIPPED, not ERROR
343
+ const isOptionalDepMissing = check.name === 'auto-play-detection' &&
344
+ (msg.includes('pixelmatch') ||
345
+ msg.includes('pngjs') ||
346
+ msg.includes('Cannot find module') ||
347
+ msg.includes('ERR_MODULE_NOT_FOUND'));
348
+ if (isOptionalDepMissing) {
349
+ process.stdout.write(`[${check.name}] SKIPPED — optional deps missing (pixelmatch, pngjs). ` +
350
+ `Install with: npm i pixelmatch pngjs\n`);
351
+ summaries.push({
352
+ name: check.name,
353
+ status: 'SKIPPED',
354
+ durationMs,
355
+ skipReason: 'optional deps missing (pixelmatch, pngjs)',
356
+ });
357
+ }
358
+ else {
359
+ process.stderr.write(`[${check.name}] ERROR: ${msg}\n`);
360
+ summaries.push({
361
+ name: check.name,
362
+ status: 'ERROR',
363
+ durationMs,
364
+ error: msg,
365
+ });
366
+ hasErrors = true;
367
+ }
368
+ }
369
+ finally {
370
+ if (context !== null) {
371
+ try {
372
+ await context.close();
373
+ }
374
+ catch {
375
+ // ignore cleanup errors
376
+ }
377
+ }
378
+ }
379
+ }
380
+ // --- Close browser ---
381
+ try {
382
+ await browser.close();
383
+ }
384
+ catch {
385
+ // ignore
386
+ }
387
+ // --- Summary ---
388
+ const totalMs = summaries.reduce((acc, s) => acc + s.durationMs, 0);
389
+ const doneCount = summaries.filter((s) => s.status === 'DONE').length;
390
+ const skippedCount = summaries.filter((s) => s.status === 'SKIPPED').length;
391
+ const errorCount = summaries.filter((s) => s.status === 'ERROR').length;
392
+ const totalViolations = summaries.reduce((acc, s) => acc + (s.violationCount ?? 0), 0);
393
+ process.stdout.write(`\n`);
394
+ process.stdout.write(`--- Summary (${formatDuration(totalMs)}) ---\n` +
395
+ ` Checks run: ${doneCount}\n` +
396
+ ` Skipped: ${skippedCount}\n` +
397
+ ` Errors: ${errorCount}\n` +
398
+ ` Total violations: ${totalViolations}\n` +
399
+ ` Results written to: ${outputDir}\n`);
400
+ if (hasErrors) {
401
+ process.exit(2);
402
+ }
403
+ else if (hasViolations) {
404
+ process.exit(1);
405
+ }
406
+ else {
407
+ process.exit(0);
408
+ }
409
+ }
410
+ main().catch((err) => {
411
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
412
+ process.exit(2);
413
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a11y-skills/audit",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Playwright + axe-core based WCAG 2.2 accessibility audit functions (axe, focus indicator, reflow, target size, text spacing, zoom, orientation, autocomplete, time limit, auto-play).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/masuP9/a11y-specialist-skills",
13
+ "url": "git+https://github.com/masuP9/a11y-specialist-skills.git",
14
14
  "directory": "packages/a11y-audit"
15
15
  },
16
16
  "homepage": "https://github.com/masuP9/a11y-specialist-skills/tree/main/packages/a11y-audit",
@@ -29,6 +29,9 @@
29
29
  "reflow",
30
30
  "target-size"
31
31
  ],
32
+ "bin": {
33
+ "a11y-audit": "dist/cli.js"
34
+ },
32
35
  "exports": {
33
36
  ".": {
34
37
  "types": "./dist/index.d.ts",