@aria-a11y/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vanessa Martin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @aria-a11y/cli
2
+
3
+ The **zero-config** command-line runner for [Aria](https://github.com/vsm1996/aria)'s
4
+ accessibility rules. No ESLint config file, no host setup — point it at your
5
+ code and it works, parsing `.jsx`/`.tsx` (and plain JS) out of the box.
6
+
7
+ ```sh
8
+ npx @aria-a11y/cli check src # report a11y diagnostics (both tiers);
9
+ # exits nonzero on any format-tier issue — the CI teeth
10
+ npx @aria-a11y/cli fix src # apply format-tier (safe, meaning-preserving) fixes only
11
+ ```
12
+
13
+ `aria fix` applies only auto-safe format-tier fixes; lint-tier suggestions are
14
+ never applied. It honors an optional `aria.config.{ts,js,json}` (how a design
15
+ system declares component semantics) but requires none.
16
+
17
+ ## How it works
18
+
19
+ Internally the CLI wraps ESLint's `Linter` with a Babel→ESTree parser, so
20
+ `eslint` is a real dependency — an implementation detail, not something you
21
+ configure. The rules are the *exact same modules* the ESLint plugin runs, so
22
+ output is identical to ESLint by construction. "Standalone" means no ESLint
23
+ config and no host, not zero ESLint code inside.
24
+
25
+ MIT
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/runner.ts
4
+ import { createRequire } from "module";
5
+ import { parse, resolve } from "path";
6
+ import { Linter } from "eslint";
7
+ import plugin from "eslint-plugin-aria-a11y";
8
+ import babelParser from "@babel/eslint-parser";
9
+ var linter = new Linter({ cwd: parse(process.cwd()).root });
10
+ var recommendedRules = plugin.configs["recommended"]?.rules;
11
+ if (recommendedRules === void 0) {
12
+ throw new Error("eslint-plugin-aria-a11y is missing its recommended config");
13
+ }
14
+ var requireFromHere = createRequire(import.meta.url);
15
+ var presetReact = requireFromHere.resolve("@babel/preset-react");
16
+ var presetTypescript = requireFromHere.resolve("@babel/preset-typescript");
17
+ var baseConfig = {
18
+ // Flat config only opts non-.js extensions into linting via an explicit
19
+ // `files` glob — without this, .tsx/.jsx get "no matching configuration".
20
+ files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}"],
21
+ plugins: { "aria-a11y": plugin },
22
+ languageOptions: {
23
+ parser: babelParser,
24
+ parserOptions: {
25
+ requireConfigFile: false,
26
+ ecmaFeatures: { jsx: true },
27
+ babelOptions: {
28
+ // Absolute preset paths so Babel needn't resolve them from the target's cwd.
29
+ presets: [[presetReact, { runtime: "automatic" }], presetTypescript]
30
+ }
31
+ }
32
+ },
33
+ // The recommended rule set — all eight rules at their tier severities.
34
+ rules: recommendedRules
35
+ };
36
+ function toDiagnostics(messages) {
37
+ return messages.filter((m) => m.ruleId !== null || m.fatal).map((m) => ({
38
+ ruleId: m.ruleId ?? null,
39
+ severity: m.severity === 2 ? "error" : "warning",
40
+ message: m.message,
41
+ line: m.line ?? 1,
42
+ column: m.column ?? 1,
43
+ formatTier: m.fix !== void 0
44
+ }));
45
+ }
46
+ function lintText(code, filename) {
47
+ return toDiagnostics(linter.verify(code, baseConfig, resolve(filename)));
48
+ }
49
+ function fixText(code, filename) {
50
+ const result = linter.verifyAndFix(code, baseConfig, resolve(filename));
51
+ return { output: result.output, remaining: toDiagnostics(result.messages) };
52
+ }
53
+
54
+ export {
55
+ lintText,
56
+ fixText
57
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ fixText,
4
+ lintText
5
+ } from "./chunk-AX3Y6N3T.js";
6
+
7
+ // src/cli.ts
8
+ import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
9
+ import { join, relative } from "path";
10
+ var SOURCE_EXT = /\.(?:js|jsx|ts|tsx|mjs|cjs|mts|cts)$/;
11
+ var SKIP_DIR = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".turbo"]);
12
+ var useColor = process.stdout.isTTY && process.env["NO_COLOR"] === void 0;
13
+ var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
14
+ var red = (s) => paint("31", s);
15
+ var yellow = (s) => paint("33", s);
16
+ var dim = (s) => paint("2", s);
17
+ var bold = (s) => paint("1", s);
18
+ function collectFiles(paths) {
19
+ const out = [];
20
+ const walk = (p) => {
21
+ let st;
22
+ try {
23
+ st = statSync(p);
24
+ } catch {
25
+ process.stderr.write(red(`aria: no such path: ${p}
26
+ `));
27
+ return;
28
+ }
29
+ if (st.isDirectory()) {
30
+ for (const entry of readdirSync(p)) {
31
+ if (SKIP_DIR.has(entry)) continue;
32
+ walk(join(p, entry));
33
+ }
34
+ } else if (SOURCE_EXT.test(p)) {
35
+ out.push(p);
36
+ }
37
+ };
38
+ for (const p of paths) walk(p);
39
+ return out;
40
+ }
41
+ function printDiagnostics(file, diags) {
42
+ if (diags.length === 0) return;
43
+ process.stdout.write(bold(relative(process.cwd(), file)) + "\n");
44
+ for (const d of diags) {
45
+ const sev = d.severity === "error" ? red("error") : yellow("warn ");
46
+ const loc = dim(`${d.line}:${d.column}`);
47
+ const rule = dim(d.ruleId ?? "");
48
+ process.stdout.write(` ${loc} ${sev} ${d.message} ${rule}
49
+ `);
50
+ }
51
+ process.stdout.write("\n");
52
+ }
53
+ function summarize(files, diags) {
54
+ const errors = diags.filter((d) => d.severity === "error").length;
55
+ const warnings = diags.length - errors;
56
+ const format = diags.filter((d) => d.formatTier).length;
57
+ process.stdout.write(
58
+ dim(
59
+ `${files} file(s) scanned \u2014 ${diags.length} finding(s): ${errors} error, ${warnings} warning (${format} format-tier).
60
+ `
61
+ )
62
+ );
63
+ }
64
+ function runCheck(paths) {
65
+ const files = collectFiles(paths);
66
+ const all = [];
67
+ for (const file of files) {
68
+ const diags = lintText(readFileSync(file, "utf8"), file);
69
+ printDiagnostics(file, diags);
70
+ all.push(...diags);
71
+ }
72
+ summarize(files.length, all);
73
+ return all.some((d) => d.formatTier) ? 1 : 0;
74
+ }
75
+ function runFix(paths) {
76
+ const files = collectFiles(paths);
77
+ let fixedCount = 0;
78
+ const remaining = [];
79
+ for (const file of files) {
80
+ const before = readFileSync(file, "utf8");
81
+ const { output, remaining: left } = fixText(before, file);
82
+ if (output !== before) {
83
+ writeFileSync(file, output);
84
+ fixedCount += 1;
85
+ process.stdout.write(dim(`fixed ${relative(process.cwd(), file)}
86
+ `));
87
+ }
88
+ printDiagnostics(file, left);
89
+ remaining.push(...left);
90
+ }
91
+ process.stdout.write(dim(`
92
+ ${fixedCount} file(s) fixed.
93
+ `));
94
+ summarize(files.length, remaining);
95
+ return remaining.some((d) => d.formatTier) ? 1 : 0;
96
+ }
97
+ var HELP = `aria \u2014 zero-config accessibility linter (Aria rules, no ESLint config required)
98
+
99
+ Usage:
100
+ aria check [paths...] Report accessibility diagnostics (both tiers).
101
+ Exits nonzero if any format-tier issue is present.
102
+ aria fix [paths...] Apply format-tier (safe, meaning-preserving) fixes.
103
+ Never applies lint-tier suggestions.
104
+
105
+ paths default to the current directory. Scans .js/.jsx/.ts/.tsx (and .mjs/.cjs).
106
+ `;
107
+ function main(argv) {
108
+ const [command, ...rest] = argv;
109
+ const paths = rest.filter((a) => !a.startsWith("-"));
110
+ const targets = paths.length > 0 ? paths : ["."];
111
+ switch (command) {
112
+ case "check":
113
+ return runCheck(targets);
114
+ case "fix":
115
+ return runFix(targets);
116
+ case "help":
117
+ case "--help":
118
+ case "-h":
119
+ case void 0:
120
+ process.stdout.write(HELP);
121
+ return command === void 0 ? 1 : 0;
122
+ default:
123
+ process.stderr.write(red(`aria: unknown command "${command}"
124
+
125
+ `) + HELP);
126
+ return 1;
127
+ }
128
+ }
129
+ process.exit(main(process.argv.slice(2)));
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A diagnostic as the CLI surfaces it. Mirrors ESLint's message plus the one
3
+ * derived fact the CLI's exit gate needs: whether the finding is format-tier.
4
+ */
5
+ interface AriaDiagnostic {
6
+ ruleId: string | null;
7
+ severity: 'error' | 'warning';
8
+ message: string;
9
+ line: number;
10
+ column: number;
11
+ /**
12
+ * True when ESLint attached an auto-applicable `fix` to this message. Per the
13
+ * gate (already enforced inside the rules via `emit`), only native/declared
14
+ * basis — i.e. format-tier — diagnostics carry a `fix`; lint-tier ones carry
15
+ * a `suggest` or nothing. So this flag IS the format-tier signal, reused, not
16
+ * recomputed.
17
+ */
18
+ formatTier: boolean;
19
+ }
20
+ /** Lint one source string. `filename` drives TS/TSX detection and config discovery. */
21
+ declare function lintText(code: string, filename: string): AriaDiagnostic[];
22
+ /**
23
+ * Fix one source string. Applies ONLY ESLint-native `fix` edits — which, by the
24
+ * gate, are exactly the format-tier (native/declared) fixes. Lint-tier
25
+ * suggestions are never applied (ESLint's `verifyAndFix` never applies
26
+ * `suggest`). No separate "is this safe" check exists or is needed.
27
+ */
28
+ declare function fixText(code: string, filename: string): {
29
+ output: string;
30
+ remaining: AriaDiagnostic[];
31
+ };
32
+
33
+ export { type AriaDiagnostic, fixText, lintText };
package/dist/runner.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ fixText,
4
+ lintText
5
+ } from "./chunk-AX3Y6N3T.js";
6
+ export {
7
+ fixText,
8
+ lintText
9
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@aria-a11y/cli",
3
+ "version": "0.1.0",
4
+ "description": "Zero-config standalone CLI for Aria's accessibility rules — aria check / aria fix, no ESLint config required.",
5
+ "keywords": [
6
+ "accessibility",
7
+ "a11y",
8
+ "aria",
9
+ "wcag",
10
+ "jsx",
11
+ "react",
12
+ "cli",
13
+ "linter",
14
+ "formatter"
15
+ ],
16
+ "homepage": "https://github.com/vsm1996/aria#readme",
17
+ "bugs": "https://github.com/vsm1996/aria/issues",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/vsm1996/aria.git",
21
+ "directory": "packages/cli"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Vanessa Martin",
25
+ "type": "module",
26
+ "bin": {
27
+ "aria": "./dist/cli.js"
28
+ },
29
+ "main": "./dist/runner.js",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/runner.d.ts",
33
+ "import": "./dist/runner.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "@babel/core": "^7.29.0",
41
+ "@babel/eslint-parser": "^7.29.0",
42
+ "@babel/preset-react": "^7.27.0",
43
+ "@babel/preset-typescript": "^7.27.0",
44
+ "eslint": "^9.13.0",
45
+ "eslint-plugin-aria-a11y": "^0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "tsup": "^8.3.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "typecheck": "tsc --noEmit"
53
+ }
54
+ }