eaa-kit 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 +102 -0
- package/dist/astro/index.d.ts +90 -0
- package/dist/astro/index.js +48 -0
- package/dist/audit/runners/worker.d.ts +1 -0
- package/dist/audit/runners/worker.js +27 -0
- package/dist/audit-6gbV0Zjd.js +582 -0
- package/dist/audit-VtuUOuyX.js +2 -0
- package/dist/baseline-DQTnNlc4.js +169 -0
- package/dist/baseline-Itspu3-Y.js +2 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +259 -0
- package/dist/escape-Dm1o_RAk.js +21 -0
- package/dist/fingerprint-DRoneAjj.js +20 -0
- package/dist/html-BLEuzep6.js +337 -0
- package/dist/impact-DvgBjupx.js +32 -0
- package/dist/impact-EEB9ZXmC.d.ts +7 -0
- package/dist/index.d.ts +264 -0
- package/dist/index.js +2 -0
- package/dist/jsdom-BEu6Ra_2.js +163 -0
- package/dist/jsdom-C6dIyaxN.js +3 -0
- package/dist/json-1ESNIiHY.js +139 -0
- package/dist/playwright-BfWuTG_u.js +235 -0
- package/dist/pool-DixLeu8L.js +188 -0
- package/dist/render-K9KxDDSA.js +774 -0
- package/dist/result-2aZPfM8w.js +168 -0
- package/dist/sarif-eSCuI0eX.js +192 -0
- package/dist/statement/templates/at.de.md +127 -0
- package/dist/statement/templates/at.en.md +121 -0
- package/dist/statement/templates/ch.de.md +138 -0
- package/dist/statement/templates/ch.en.md +135 -0
- package/dist/statement/templates/de.de.md +129 -0
- package/dist/statement/templates/de.en.md +125 -0
- package/dist/version-B3v4rNoG.js +15 -0
- package/package.json +95 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { n as failedPage } from "./result-2aZPfM8w.js";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { Worker } from "node:worker_threads";
|
|
5
|
+
import { availableParallelism } from "node:os";
|
|
6
|
+
//#region src/audit/runners/pool.ts
|
|
7
|
+
/**
|
|
8
|
+
* Audit pages across worker threads.
|
|
9
|
+
*
|
|
10
|
+
* The sequential runner's own comment is right that async concurrency buys
|
|
11
|
+
* nothing here: jsdom parsing and axe-core are CPU-bound, and interleaving them
|
|
12
|
+
* on one thread just interleaves them. Threads are a different claim. Measured
|
|
13
|
+
* on a 4-core box over 100 pages of a typical marketing site: 199 ms per page
|
|
14
|
+
* on one thread, 101 ms per page over three workers.
|
|
15
|
+
*
|
|
16
|
+
* This module deliberately does not import jsdom. When the work goes to
|
|
17
|
+
* workers, the process that spawned them never parses a page itself, and
|
|
18
|
+
* loading a 630 ms dependency to supervise threads that each load their own
|
|
19
|
+
* would be pure overhead. The one path that needs it — the fallback below —
|
|
20
|
+
* imports it when it gets there.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* What one page costs to audit, near enough to decide whether threads are worth
|
|
24
|
+
* starting: a fixed cost for building the document and setting axe-core up,
|
|
25
|
+
* plus a term for the markup it has to walk.
|
|
26
|
+
*
|
|
27
|
+
* Calibrated by measurement on a 4-core box, not derived: a 190-byte page takes
|
|
28
|
+
* ~30 ms and a 12 KB page ~200 ms. It is a coarse estimate and does not need to
|
|
29
|
+
* be anything better — the only decisions resting on it are how many threads to
|
|
30
|
+
* start, and whether to start any, where the measured cost of being one worker
|
|
31
|
+
* out is a few percent either way.
|
|
32
|
+
*/
|
|
33
|
+
const PAGE_FIXED_MS = 25;
|
|
34
|
+
const BYTES_PER_MS = 70;
|
|
35
|
+
/**
|
|
36
|
+
* Estimated work below which the run stays on one thread.
|
|
37
|
+
*
|
|
38
|
+
* Workers load jsdom while the process that spawned them waits, and on a busy
|
|
39
|
+
* machine two of them loading it at once take longer than one would. Under this
|
|
40
|
+
* much work that latency is not repaid: measured over 10 pages of ~190 bytes,
|
|
41
|
+
* 1.47 s on one thread against 1.51 s across two.
|
|
42
|
+
*/
|
|
43
|
+
const MIN_WORK_FOR_THREADS_MS = 500;
|
|
44
|
+
/**
|
|
45
|
+
* Estimated work one worker should be carrying before another is added.
|
|
46
|
+
*
|
|
47
|
+
* Two workers carry a run to about 32 pages of a typical marketing site on
|
|
48
|
+
* their own; past that a third starts winning. Measured at 2 and 3 workers:
|
|
49
|
+
* 12 pages 2.91 s and 3.10 s, 32 pages 4.84 s and 4.88 s, 48 pages 6.23 s and
|
|
50
|
+
* 5.90 s, 64 pages 7.91 s and 7.00 s.
|
|
51
|
+
*/
|
|
52
|
+
const WORK_PER_WORKER_MS = 1600;
|
|
53
|
+
/** Threading at all means at least two, or there is nothing to overlap with. */
|
|
54
|
+
const MIN_WORKERS = 2;
|
|
55
|
+
/**
|
|
56
|
+
* Ceiling on workers regardless of core count. Past this, the run is bounded by
|
|
57
|
+
* memory bandwidth and by each worker's own start-up rather than by cores, and
|
|
58
|
+
* every extra thread holds another jsdom document tree.
|
|
59
|
+
*/
|
|
60
|
+
const MAX_WORKERS = 8;
|
|
61
|
+
/** Roughly how long these pages would take to audit on one thread. */
|
|
62
|
+
function estimateWorkMs(pages) {
|
|
63
|
+
let total = 0;
|
|
64
|
+
for (const page of pages) total += PAGE_FIXED_MS + page.html.length / BYTES_PER_MS;
|
|
65
|
+
return total;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* How many workers this run should use, given the machine it is on.
|
|
69
|
+
*
|
|
70
|
+
* Page count alone cannot answer this: five pages of a real marketing site are
|
|
71
|
+
* worth threading and forty pages of a stub site are not, and the difference is
|
|
72
|
+
* how much markup there is to walk. So the decision is made on estimated work.
|
|
73
|
+
*
|
|
74
|
+
* Returns 1 for anything small enough that starting threads would cost more
|
|
75
|
+
* than it saves — the caller treats that as "audit in this process".
|
|
76
|
+
*/
|
|
77
|
+
function plannedWorkers(pages, parallelism = availableParallelism()) {
|
|
78
|
+
const work = estimateWorkMs(pages);
|
|
79
|
+
if (work < MIN_WORK_FOR_THREADS_MS) return 1;
|
|
80
|
+
const cores = Math.max(1, parallelism - 1);
|
|
81
|
+
if (cores < MIN_WORKERS) return 1;
|
|
82
|
+
const wanted = Math.max(MIN_WORKERS, Math.round(work / WORK_PER_WORKER_MS));
|
|
83
|
+
return Math.min(wanted, cores, MAX_WORKERS);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Audit pages in parallel, returning them in the order they were given.
|
|
87
|
+
*
|
|
88
|
+
* Falls back to auditing in this process whenever threads are not available or
|
|
89
|
+
* not worth it, so callers get results either way and never a partial answer.
|
|
90
|
+
*/
|
|
91
|
+
async function runPooledAudit(pages, options = {}) {
|
|
92
|
+
if (pages.length === 0) return [];
|
|
93
|
+
const { concurrency, workerEntry, ...runnerOptions } = options;
|
|
94
|
+
const workers = concurrency ?? plannedWorkers(pages);
|
|
95
|
+
const entry = workers > 1 ? workerEntry ?? await findWorkerEntry() : void 0;
|
|
96
|
+
if (!entry) return auditHere(pages, runnerOptions);
|
|
97
|
+
return runWorkers(pages, runnerOptions, Math.min(workers, pages.length), entry);
|
|
98
|
+
}
|
|
99
|
+
async function auditHere(pages, options) {
|
|
100
|
+
const { runJsdomAudit } = await import("./jsdom-C6dIyaxN.js");
|
|
101
|
+
return runJsdomAudit(pages, options);
|
|
102
|
+
}
|
|
103
|
+
async function runWorkers(pages, options, count, entry) {
|
|
104
|
+
const audits = Array.from({ length: pages.length });
|
|
105
|
+
let next = 0;
|
|
106
|
+
await Promise.all(Array.from({ length: count }, () => {
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
let worker;
|
|
109
|
+
try {
|
|
110
|
+
worker = new Worker(entry, { workerData: options });
|
|
111
|
+
} catch {
|
|
112
|
+
resolve();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let inFlight;
|
|
116
|
+
let completed = 0;
|
|
117
|
+
const finish = () => {
|
|
118
|
+
worker.terminate();
|
|
119
|
+
resolve();
|
|
120
|
+
};
|
|
121
|
+
const feed = () => {
|
|
122
|
+
const index = next;
|
|
123
|
+
if (index >= pages.length) {
|
|
124
|
+
inFlight = void 0;
|
|
125
|
+
finish();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
next += 1;
|
|
129
|
+
inFlight = index;
|
|
130
|
+
worker.postMessage(pages[index]);
|
|
131
|
+
};
|
|
132
|
+
worker.on("message", (audit) => {
|
|
133
|
+
if (inFlight !== void 0) {
|
|
134
|
+
audits[inFlight] = audit;
|
|
135
|
+
completed += 1;
|
|
136
|
+
}
|
|
137
|
+
feed();
|
|
138
|
+
});
|
|
139
|
+
worker.on("error", (cause) => {
|
|
140
|
+
if (inFlight !== void 0 && completed > 0) audits[inFlight] = failedPage(identity(pages[inFlight], options), `audit worker failed: ${cause.message}`);
|
|
141
|
+
finish();
|
|
142
|
+
});
|
|
143
|
+
feed();
|
|
144
|
+
});
|
|
145
|
+
}));
|
|
146
|
+
const missing = audits.flatMap((audit, index) => audit ? [] : [index]);
|
|
147
|
+
if (missing.length > 0) {
|
|
148
|
+
const swept = await auditHere(missing.map((index) => pages[index]), options);
|
|
149
|
+
for (const [position, index] of missing.entries()) audits[index] = swept[position];
|
|
150
|
+
}
|
|
151
|
+
return audits.map((audit, index) => audit ?? failedPage(identity(pages[index], options), "the audit worker holding this page stopped before it reported"));
|
|
152
|
+
}
|
|
153
|
+
function identity(page, options) {
|
|
154
|
+
return {
|
|
155
|
+
relativePath: page.relativePath,
|
|
156
|
+
absolutePath: page.absolutePath,
|
|
157
|
+
url: pageUrl(page, options.baseUrl),
|
|
158
|
+
engine: "jsdom",
|
|
159
|
+
durationMs: 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** Kept in step with the sequential runner's own URL derivation. */
|
|
163
|
+
function pageUrl(page, baseUrl) {
|
|
164
|
+
if (!baseUrl) return pathToFileURL(page.absolutePath).href;
|
|
165
|
+
return new URL(page.relativePath, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).href;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Locate the worker entry point.
|
|
169
|
+
*
|
|
170
|
+
* It is a file on disk, so it has to be found at runtime, and the layout
|
|
171
|
+
* differs between running from source and running the bundle — where this
|
|
172
|
+
* module is inlined into a chunk at the root of dist/ and the worker is a
|
|
173
|
+
* separate entry. The candidates are tried in order rather than assuming one,
|
|
174
|
+
* the same way the statement templates are found.
|
|
175
|
+
*/
|
|
176
|
+
async function findWorkerEntry() {
|
|
177
|
+
const candidates = [
|
|
178
|
+
new URL("./worker.ts", import.meta.url),
|
|
179
|
+
new URL("./audit/runners/worker.js", import.meta.url),
|
|
180
|
+
new URL("../audit/runners/worker.js", import.meta.url),
|
|
181
|
+
new URL("./worker.js", import.meta.url)
|
|
182
|
+
];
|
|
183
|
+
for (const candidate of candidates) try {
|
|
184
|
+
if ((await stat(candidate)).isFile()) return candidate;
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
export { plannedWorkers, runPooledAudit };
|