@octaviaflow/accessibility-checker 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/LICENSE ADDED
File without changes
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Accessibility Checker
2
+
3
+ ## Overview
4
+
5
+ Accessibility Checker is a JavaScript project designed to help developers ensure that their web content meets accessibility standards. This tool provides functionalities to check and validate the accessibility of web pages.
6
+
7
+ ## Features
8
+
9
+ - Core functionality for accessibility checking.
10
+ - Utility functions to support main functionalities.
11
+ - Unit tests to ensure reliability and correctness.
12
+
13
+ ## Installation
14
+
15
+ To install the project, clone the repository and run the following command in the project directory:
16
+
17
+ ```
18
+ npm install
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ To use the accessibility checker, you can run the main entry point:
24
+
25
+ ```
26
+ node src/index.js
27
+ ```
28
+
29
+ ## Running Tests
30
+
31
+ To run the unit tests, use the following command:
32
+
33
+ ```
34
+ npm test
35
+ ```
36
+
37
+ ## Contributing
38
+
39
+ Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes.
40
+
41
+ ## License
42
+
43
+ This project is licensed under the MIT License. See the LICENSE file for more details.
44
+
45
+ ### octaviaflow - Local Accessibility Checker
46
+
47
+ Usage:
48
+
49
+ - node src/index.js --input path/to/file.html
50
+ - node src/index.js --sample
51
+ - Optional: --output path/to/results.json (defaults to ./data/results.json)
52
+
53
+ Behavior:
54
+
55
+ - Performs lightweight accessibility heuristics (images missing alt, empty links, empty headings).
56
+ - Stores results as an array in the output JSON file (suitable for future import to MongoDB).
57
+ - No IBM telemetry or IBM/carbon dependencies included.
58
+
59
+ ### octaviaflow - Local Accessibility Checker (TypeScript)
60
+
61
+ Quick start:
62
+
63
+ 1. Install deps:
64
+
65
+ cd accessibility-checker
66
+ npm install
67
+
68
+ 2. Build:
69
+
70
+ npm run build
71
+
72
+ 3. Run:
73
+
74
+ node ./cjs/index.js --sample
75
+ or
76
+ octaviaflow --input path/to/file.html --output ./data/results.json
77
+
78
+ What changed:
79
+
80
+ - Converted to TypeScript.
81
+ - Uses axe-core + heuristics for more complete checks.
82
+ - Stores results locally as an array in JSON (ready for importing to MongoDB).
83
+ - No IBM telemetry or Carbon packages included.
84
+
85
+ Testing:
86
+
87
+ - npm test (uses vitest)
88
+
89
+ Build outputs:
90
+
91
+ - cjs/ CommonJS build (cjs/index.js)
92
+ - mjs/ ES module build (mjs/index.js)
93
+ - types/ TypeScript declaration files
94
+ - bin/ CLI shim (bin/octaviaflow.js)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // simple shim that delegates to the CommonJS build
3
+ try {
4
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
5
+ const mod = require('../cjs/index.js');
6
+ if (mod && typeof mod.main === 'function') {
7
+ mod.main(process.argv.slice(2)).catch((e) => {
8
+ console.error('Error:', e);
9
+ process.exit(2);
10
+ });
11
+ } else {
12
+ console.error('octaviaflow entrypoint missing');
13
+ process.exit(1);
14
+ }
15
+ } catch (e) {
16
+ console.error('Failed to start octaviaflow:', e);
17
+ process.exit(2);
18
+ }
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyze = analyze;
37
+ const jsdom_1 = require("jsdom");
38
+ const axe = __importStar(require("axe-core"));
39
+ function heuristicAnalyze(html) {
40
+ const issues = [];
41
+ // images without alt
42
+ const imgRegex = /<img\b[^>]*>/gi;
43
+ let m;
44
+ while ((m = imgRegex.exec(html))) {
45
+ const tag = m[0];
46
+ if (!/alt\s*=\s*["'][^"']*["']/i.test(tag)) {
47
+ issues.push({
48
+ type: 'image-missing-alt',
49
+ message: 'Image tag missing alt attribute',
50
+ snippet: tag,
51
+ });
52
+ }
53
+ }
54
+ // links without descriptive text (naive)
55
+ const aRegex = /<a\b[^>]*>([\s\S]*?)<\/a>/gi;
56
+ while ((m = aRegex.exec(html))) {
57
+ const inner = (m[1] || '').trim();
58
+ if (!inner || /^<img\b/i.test(inner) || inner.length === 0) {
59
+ issues.push({
60
+ type: 'link-empty',
61
+ message: 'Link has no accessible text',
62
+ snippet: m[0],
63
+ });
64
+ }
65
+ }
66
+ // empty headings
67
+ const hRegex = /<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi;
68
+ while ((m = hRegex.exec(html))) {
69
+ const content = (m[1] || '').replace(/<[^>]*>/g, '').trim();
70
+ if (!content) {
71
+ issues.push({
72
+ type: 'heading-empty',
73
+ message: 'Heading tag is empty',
74
+ snippet: m[0],
75
+ });
76
+ }
77
+ }
78
+ return issues;
79
+ }
80
+ async function runAxe(html) {
81
+ const dom = new jsdom_1.JSDOM(html, {
82
+ runScripts: 'dangerously',
83
+ resources: 'usable',
84
+ });
85
+ const { window } = dom;
86
+ // axe.run expects document; pass document
87
+ const result = await axe.run(window.document, {
88
+ // default rules; you can customize here
89
+ });
90
+ return result;
91
+ }
92
+ async function analyze(html) {
93
+ const heuristics = heuristicAnalyze(html);
94
+ let axeRes = null;
95
+ try {
96
+ axeRes = await runAxe(html);
97
+ }
98
+ catch (e) {
99
+ // If axe fails for some HTML, continue with heuristics
100
+ axeRes = { error: String(e) };
101
+ }
102
+ const axeIssues = [];
103
+ if (axeRes && Array.isArray(axeRes.violations)) {
104
+ for (const v of axeRes.violations) {
105
+ for (const node of v.nodes || []) {
106
+ axeIssues.push({
107
+ type: `axe:${v.id}`,
108
+ message: v.description || v.help,
109
+ snippet: node.html,
110
+ helpUrl: v.helpUrl,
111
+ source: 'axe',
112
+ });
113
+ }
114
+ }
115
+ }
116
+ const combined = heuristics.concat(axeIssues);
117
+ const byType = {};
118
+ for (const it of combined) {
119
+ byType[it.type] = (byType[it.type] || 0) + 1;
120
+ }
121
+ return {
122
+ summary: {
123
+ totalIssues: combined.length,
124
+ byType,
125
+ axeViolations: axeIssues.length,
126
+ },
127
+ issues: combined,
128
+ axe: axeRes,
129
+ };
130
+ }
@@ -0,0 +1,94 @@
1
+ import { JSDOM } from 'jsdom';
2
+ import * as axe from 'axe-core';
3
+ function heuristicAnalyze(html) {
4
+ const issues = [];
5
+ // images without alt
6
+ const imgRegex = /<img\b[^>]*>/gi;
7
+ let m;
8
+ while ((m = imgRegex.exec(html))) {
9
+ const tag = m[0];
10
+ if (!/alt\s*=\s*["'][^"']*["']/i.test(tag)) {
11
+ issues.push({
12
+ type: 'image-missing-alt',
13
+ message: 'Image tag missing alt attribute',
14
+ snippet: tag,
15
+ });
16
+ }
17
+ }
18
+ // links without descriptive text (naive)
19
+ const aRegex = /<a\b[^>]*>([\s\S]*?)<\/a>/gi;
20
+ while ((m = aRegex.exec(html))) {
21
+ const inner = (m[1] || '').trim();
22
+ if (!inner || /^<img\b/i.test(inner) || inner.length === 0) {
23
+ issues.push({
24
+ type: 'link-empty',
25
+ message: 'Link has no accessible text',
26
+ snippet: m[0],
27
+ });
28
+ }
29
+ }
30
+ // empty headings
31
+ const hRegex = /<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi;
32
+ while ((m = hRegex.exec(html))) {
33
+ const content = (m[1] || '').replace(/<[^>]*>/g, '').trim();
34
+ if (!content) {
35
+ issues.push({
36
+ type: 'heading-empty',
37
+ message: 'Heading tag is empty',
38
+ snippet: m[0],
39
+ });
40
+ }
41
+ }
42
+ return issues;
43
+ }
44
+ async function runAxe(html) {
45
+ const dom = new JSDOM(html, {
46
+ runScripts: 'dangerously',
47
+ resources: 'usable',
48
+ });
49
+ const { window } = dom;
50
+ // axe.run expects document; pass document
51
+ const result = await axe.run(window.document, {
52
+ // default rules; you can customize here
53
+ });
54
+ return result;
55
+ }
56
+ export async function analyze(html) {
57
+ const heuristics = heuristicAnalyze(html);
58
+ let axeRes = null;
59
+ try {
60
+ axeRes = await runAxe(html);
61
+ }
62
+ catch (e) {
63
+ // If axe fails for some HTML, continue with heuristics
64
+ axeRes = { error: String(e) };
65
+ }
66
+ const axeIssues = [];
67
+ if (axeRes && Array.isArray(axeRes.violations)) {
68
+ for (const v of axeRes.violations) {
69
+ for (const node of v.nodes || []) {
70
+ axeIssues.push({
71
+ type: `axe:${v.id}`,
72
+ message: v.description || v.help,
73
+ snippet: node.html,
74
+ helpUrl: v.helpUrl,
75
+ source: 'axe',
76
+ });
77
+ }
78
+ }
79
+ }
80
+ const combined = heuristics.concat(axeIssues);
81
+ const byType = {};
82
+ for (const it of combined) {
83
+ byType[it.type] = (byType[it.type] || 0) + 1;
84
+ }
85
+ return {
86
+ summary: {
87
+ totalIssues: combined.length,
88
+ byType,
89
+ axeViolations: axeIssues.length,
90
+ },
91
+ issues: combined,
92
+ axe: axeRes,
93
+ };
94
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@octaviaflow/accessibility-checker",
3
+ "version": "1.0.0",
4
+ "license": "Apache-2.0",
5
+ "description": "Accessibility checker for OctaviaFlow Design System - A custom implementation",
6
+ "main": "cjs/index.js",
7
+ "module": "mjs/index.js",
8
+ "types": "types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./mjs/index.js",
12
+ "require": "./cjs/index.js",
13
+ "types": "./types/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "cjs",
18
+ "mjs",
19
+ "types",
20
+ "bin"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/OctaviaFlow/OctaviaFlow-Design-System.git",
25
+ "directory": "packages/accessibility-checker"
26
+ },
27
+ "bugs": "https://github.com/OctaviaFlow/OctaviaFlow-Design-System/issues",
28
+ "scripts": {
29
+ "clean": "rimraf cjs mjs types",
30
+ "build:cjs": "tsc -p tsconfig.cjs.json",
31
+ "build:mjs": "tsc -p tsconfig.mjs.json",
32
+ "build:types": "tsc -p tsconfig.types.json",
33
+ "build": "yarn clean && yarn build:cjs && yarn build:mjs && yarn build:types",
34
+ "test": "vitest",
35
+ "ci-check": "yarn build && yarn test",
36
+ "start": "node ./cjs/index.js --sample"
37
+ },
38
+ "keywords": [
39
+ "octaviaflow",
40
+ "octaviaflow-design-system",
41
+ "accessibility",
42
+ "checker",
43
+ "web",
44
+ "a11y"
45
+ ],
46
+ "author": "OctaviaFlow",
47
+ "dependencies": {
48
+ "axe-core": "^4.11.0",
49
+ "chromedriver": "*",
50
+ "deep-diff": "^1.0.2",
51
+ "exceljs": "^4.3.0",
52
+ "js-yaml": "^4.1.0",
53
+ "jsdom": "^27.1.0",
54
+ "puppeteer": "^24.29.1",
55
+ "string-hash": "^1.1.3"
56
+ },
57
+ "devDependencies": {
58
+ "@types/jsdom": "^27.0.0",
59
+ "@types/node": "^24.10.0",
60
+ "eslint": "^9.31.1",
61
+ "jest": "^30.2.0",
62
+ "rimraf": "^6.0.1",
63
+ "ts-node": "^10.9.2",
64
+ "typescript": "^5.9.3",
65
+ "vitest": "^4.0.8"
66
+ },
67
+ "bin": {
68
+ "octaviaflow-achecker": "bin/achecker.js"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ }
73
+ }
@@ -0,0 +1,18 @@
1
+ export type Issue = {
2
+ type: string;
3
+ message: string;
4
+ snippet?: string;
5
+ helpUrl?: string;
6
+ source?: string;
7
+ };
8
+ export type Result = {
9
+ summary: {
10
+ totalIssues: number;
11
+ byType: Record<string, number>;
12
+ axeViolations?: number;
13
+ };
14
+ issues: Issue[];
15
+ axe?: any;
16
+ meta?: any;
17
+ };
18
+ export declare function analyze(html: string): Promise<Result>;