conformly 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 +21 -0
- package/README.md +68 -0
- package/bin/conformly.mjs +119 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Conformly
|
|
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,68 @@
|
|
|
1
|
+
# conformly
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/conformly)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
Free **RGAA / WCAG** web accessibility scanner for your **CLI & CI** — check any URL in one
|
|
8
|
+
command, no account required. Powered by the public [Conformly](https://conformly.online)
|
|
9
|
+
free-scan API (real-browser render, so React/Vue/Angular apps are analysed properly).
|
|
10
|
+
|
|
11
|
+
> Scanner d'accessibilité **RGAA / WCAG** en ligne de commande & CI, sans compte.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx conformly scan https://your-site.com
|
|
17
|
+
# → Score RGAA : 96 % · 1 anomalie(s)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Fail the build (exit 1) below a score threshold
|
|
22
|
+
npx conformly scan https://your-site.com --threshold 90
|
|
23
|
+
|
|
24
|
+
# Raw JSON output
|
|
25
|
+
npx conformly scan https://your-site.com --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### In CI (GitHub Actions)
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- name: Accessibility check
|
|
32
|
+
run: npx conformly scan https://your-site.com --threshold 90
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Exit code `1` when the score is below the threshold → your pipeline fails, just like a
|
|
36
|
+
broken test. Works the same in GitLab CI, CircleCI, etc.
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
| Option | Effet |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `--threshold N` | Sort en erreur (exit 1) si le score est sous `N` (0–100). |
|
|
43
|
+
| `--json` | Sortie JSON brute (`{ url, score, anomalies, threshold }`). |
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Variable | Défaut | Rôle |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `CONFORMLY_URL` | `https://conformly.online` | Base de l'API. |
|
|
50
|
+
|
|
51
|
+
## What it checks — and what it doesn't
|
|
52
|
+
|
|
53
|
+
The scan combines **axe-core** with **expert tests** (page language, title, skip link,
|
|
54
|
+
visible focus, 200% zoom…) and maps findings to RGAA criteria.
|
|
55
|
+
|
|
56
|
+
**Honest scope:** automated testing only covers **part** of WCAG/RGAA. A full accessibility
|
|
57
|
+
declaration still requires human review of the non-automatable criteria. Treat the score as
|
|
58
|
+
a fast, reliable signal and a between-audits safety net — **not** a certification.
|
|
59
|
+
|
|
60
|
+
## Continuous monitoring
|
|
61
|
+
|
|
62
|
+
A CI check is a floor. For drift over time — regression alerts on every deploy, per-page
|
|
63
|
+
history, timestamped evidence, per-framework fix guides — create a free account at
|
|
64
|
+
**[conformly.online](https://conformly.online)**.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
[MIT](./LICENSE) — © Conformly · [conformly.online](https://conformly.online)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Epic 15.4 — CLI de scan d'accessibilité RGAA. Utilise l'API publique du scan gratuit
|
|
3
|
+
// de Conformly (aucun token requis). Idéal en CI : `--threshold` fait échouer le build
|
|
4
|
+
// sous le score visé.
|
|
5
|
+
//
|
|
6
|
+
// Usage :
|
|
7
|
+
// npx conformly scan <url> [--threshold <0-100>] [--json]
|
|
8
|
+
//
|
|
9
|
+
// Variables : CONFORMLY_URL (défaut https://conformly.online).
|
|
10
|
+
|
|
11
|
+
const API = (process.env.CONFORMLY_URL || "https://conformly.online").replace(/\/$/, "");
|
|
12
|
+
const POLL_MS = 2500;
|
|
13
|
+
const MAX_POLLS = 60;
|
|
14
|
+
|
|
15
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
16
|
+
|
|
17
|
+
function usage(code) {
|
|
18
|
+
console.error(
|
|
19
|
+
[
|
|
20
|
+
"conformly — scan d'accessibilité RGAA",
|
|
21
|
+
"",
|
|
22
|
+
"Usage :",
|
|
23
|
+
" conformly scan <url> [--threshold <0-100>] [--json]",
|
|
24
|
+
"",
|
|
25
|
+
"Options :",
|
|
26
|
+
" --threshold N Échoue (exit 1) si le score est sous N.",
|
|
27
|
+
" --json Sortie JSON brute.",
|
|
28
|
+
].join("\n"),
|
|
29
|
+
);
|
|
30
|
+
process.exit(code);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
if (argv[0] !== "scan" || !argv[1] || argv[1].startsWith("-")) usage(2);
|
|
35
|
+
const url = argv[1];
|
|
36
|
+
let threshold = null;
|
|
37
|
+
let json = false;
|
|
38
|
+
for (let i = 2; i < argv.length; i++) {
|
|
39
|
+
if (argv[i] === "--threshold") threshold = Number(argv[++i]);
|
|
40
|
+
else if (argv[i] === "--json") json = true;
|
|
41
|
+
else usage(2);
|
|
42
|
+
}
|
|
43
|
+
if (threshold !== null && (Number.isNaN(threshold) || threshold < 0 || threshold > 100)) usage(2);
|
|
44
|
+
return { url, threshold, json };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function anomalyCount(violations) {
|
|
48
|
+
if (!Array.isArray(violations)) return 0;
|
|
49
|
+
let n = 0;
|
|
50
|
+
for (const v of violations) {
|
|
51
|
+
if (!v || typeof v.ruleId !== "string") continue;
|
|
52
|
+
n += Array.isArray(v.nodes) ? v.nodes.length : 1;
|
|
53
|
+
}
|
|
54
|
+
return n;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const { url, threshold, json } = parseArgs(process.argv.slice(2));
|
|
59
|
+
|
|
60
|
+
let started;
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(`${API}/api/free-scan`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "content-type": "application/json" },
|
|
65
|
+
body: JSON.stringify({ url }),
|
|
66
|
+
});
|
|
67
|
+
started = await res.json();
|
|
68
|
+
if (!res.ok || !started.id) {
|
|
69
|
+
console.error(`Erreur : ${started.error || res.status}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`Erreur réseau : ${err.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!json) process.stderr.write(`Scan de ${url} en cours…\n`);
|
|
78
|
+
|
|
79
|
+
let result = null;
|
|
80
|
+
for (let i = 0; i < MAX_POLLS; i++) {
|
|
81
|
+
await sleep(POLL_MS);
|
|
82
|
+
try {
|
|
83
|
+
const r = await fetch(`${API}/api/free-scan/${started.id}`);
|
|
84
|
+
const data = await r.json();
|
|
85
|
+
if (data.status === "done" || data.status === "error") {
|
|
86
|
+
result = data;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
/* on retente */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!result) {
|
|
95
|
+
console.error("Délai dépassé.");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
if (result.status === "error") {
|
|
99
|
+
console.error(`Analyse impossible : ${result.errorReason || "erreur inconnue"}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const score = result.score != null ? Math.round(result.score) : null;
|
|
104
|
+
const anomalies = anomalyCount(result.violations);
|
|
105
|
+
|
|
106
|
+
if (json) {
|
|
107
|
+
console.log(JSON.stringify({ url, score, anomalies, threshold }, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`Score RGAA : ${score != null ? score + " %" : "—"} · ${anomalies} anomalie(s)`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (threshold !== null && score != null && score < threshold) {
|
|
113
|
+
console.error(`Score ${score}% sous le seuil ${threshold}%.`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "conformly",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI de scan d'accessibilité RGAA/WCAG — vérifiez une URL en ligne de commande, idéal en CI. Sans compte.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"conformly": "bin/conformly.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"accessibility",
|
|
19
|
+
"accessibilité",
|
|
20
|
+
"rgaa",
|
|
21
|
+
"wcag",
|
|
22
|
+
"a11y",
|
|
23
|
+
"audit",
|
|
24
|
+
"ci",
|
|
25
|
+
"cli",
|
|
26
|
+
"conformly"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://conformly.online",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://conformly.online"
|
|
31
|
+
},
|
|
32
|
+
"author": "Conformly (https://conformly.online)",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/SeifBouarada/conformly-cli.git"
|
|
37
|
+
}
|
|
38
|
+
}
|