eaa-kit 0.2.1 → 0.4.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/README.md +51 -4
- package/dist/astro/index.d.ts +7 -34
- package/dist/astro/index.js +6 -26
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-CPoZMXGM.js +779 -0
- package/dist/audit-CpXH2Mk8.js +2 -0
- package/dist/{baseline-CV_3lbER.js → baseline-22Y1NWxM.js} +1 -1
- package/dist/{baseline-CgBmzFTr.js → baseline-DB9CZGnV.js} +17 -27
- package/dist/cli/index.js +336 -142
- package/dist/collect-CFM8gEVv.js +134 -0
- package/dist/command-D8l_oYbV.js +77 -0
- package/dist/component-C3GL1Mnu.js +146 -0
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B4IBKMO8.js +521 -0
- package/dist/{crawl-CtJbMNNb.js → crawl-BiI1Lau_.js} +13 -23
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/frameworks-B4ClIJgE.js +314 -0
- package/dist/frameworks-etFg_O8K.js +2 -0
- package/dist/fs-BmPtmFke.js +40 -0
- package/dist/html-BGTO3ypW.js +543 -0
- package/dist/{impact-DvgBjupx.js → impact-DZt2oBCP.js} +15 -1
- package/dist/index.js +2 -2
- package/dist/{init-DRKIdpK1.js → init-DIWDE35F.js} +13 -10
- package/dist/jsdom-4IMzv0eE.js +3 -0
- package/dist/jsdom-DCpGSLfW.js +81 -0
- package/dist/{json-C9xS1PNC.js → json-DjEvy1nX.js} +19 -22
- package/dist/json-QQuFIw1W.js +2 -0
- package/dist/{load-sCkKsvGQ.js → load-5wRGLvub.js} +3 -9
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DSRnXmcd.js → playwright-BWniOain.js} +70 -23
- package/dist/{pool-DixLeu8L.js → pool-BMevaLWD.js} +4 -12
- package/dist/{project-CufCqIE2.js → project-CzOnkLH6.js} +14 -21
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-Dtowi2EC.js +321 -0
- package/dist/{render-BO0nVrrZ.js → render-DrvXRCEn.js} +8 -16
- package/dist/{result-2aZPfM8w.js → result-DoamKFsp.js} +115 -1
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/run-BMASMmwO.d.ts +45 -0
- package/dist/run-DB34BSOZ.js +53 -0
- package/dist/{sarif-eSCuI0eX.js → sarif-SR3_lLYd.js} +22 -36
- package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
- package/dist/text-CKKpzkYM.js +43 -0
- package/dist/vite/index.d.ts +39 -0
- package/dist/vite/index.js +40 -0
- package/dist/webpack/index.d.ts +33 -0
- package/dist/webpack/index.js +20 -0
- package/package.json +36 -9
- package/dist/audit-B2dIKpJ5.js +0 -2
- package/dist/audit-DXpKkXsC.js +0 -950
- package/dist/escape-Dm1o_RAk.js +0 -21
- package/dist/html-BLEuzep6.js +0 -337
- package/dist/jsdom-BEu6Ra_2.js +0 -163
- package/dist/jsdom-C6dIyaxN.js +0 -3
- package/dist/routes-BxbSZKXC.js +0 -123
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { c as runOptions, i as failedPage, l as shapeResults, o as pageUrl, r as blindRulesInScope, t as DEFAULT_TAGS } from "./result-DoamKFsp.js";
|
|
2
|
+
import axe from "axe-core";
|
|
3
|
+
import { Script } from "node:vm";
|
|
4
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
5
|
+
//#region src/audit/runners/jsdom.ts
|
|
6
|
+
/** Per-page ceiling; one pathological document must not stall a CI run. */
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
8
|
+
/**
|
|
9
|
+
* Audit collected pages with axe-core inside jsdom.
|
|
10
|
+
*
|
|
11
|
+
* Pages are processed sequentially: jsdom parsing and axe-core are both
|
|
12
|
+
* CPU-bound on the main thread, so concurrency buys nothing here. A page that
|
|
13
|
+
* throws or times out is recorded with an `error` and the run continues.
|
|
14
|
+
*/
|
|
15
|
+
async function runJsdomAudit(pages, options = {}) {
|
|
16
|
+
const audits = [];
|
|
17
|
+
for (const page of pages) audits.push(await auditPage(page, options));
|
|
18
|
+
return audits;
|
|
19
|
+
}
|
|
20
|
+
async function auditPage(page, options = {}) {
|
|
21
|
+
const tags = options.tags ?? DEFAULT_TAGS;
|
|
22
|
+
const url = pageUrl(page, options.baseUrl);
|
|
23
|
+
const startedAt = Date.now();
|
|
24
|
+
const identity = {
|
|
25
|
+
relativePath: page.relativePath,
|
|
26
|
+
absolutePath: page.absolutePath,
|
|
27
|
+
url,
|
|
28
|
+
engine: "jsdom"
|
|
29
|
+
};
|
|
30
|
+
let dom;
|
|
31
|
+
try {
|
|
32
|
+
dom = createDom(page.html, url);
|
|
33
|
+
injectAxe(dom);
|
|
34
|
+
const { axe: pageAxe } = dom.window;
|
|
35
|
+
const results = await withTimeout(pageAxe.run(dom.window.document, runOptions(tags)), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
36
|
+
return shapeResults(results, {
|
|
37
|
+
...identity,
|
|
38
|
+
durationMs: Date.now() - startedAt,
|
|
39
|
+
blind: blindRulesInScope(tags)
|
|
40
|
+
});
|
|
41
|
+
} catch (cause) {
|
|
42
|
+
return failedPage({
|
|
43
|
+
...identity,
|
|
44
|
+
durationMs: Date.now() - startedAt
|
|
45
|
+
}, cause instanceof Error ? cause.message : String(cause));
|
|
46
|
+
} finally {
|
|
47
|
+
dom?.window.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function createDom(html, url) {
|
|
51
|
+
const virtualConsole = new VirtualConsole();
|
|
52
|
+
virtualConsole.on("jsdomError", () => {});
|
|
53
|
+
return new JSDOM(html, {
|
|
54
|
+
url,
|
|
55
|
+
virtualConsole,
|
|
56
|
+
runScripts: "outside-only",
|
|
57
|
+
pretendToBeVisual: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* axe-core is 1.3 MB of source. Compiling it once and re-running the compiled
|
|
62
|
+
* script in each window's context avoids re-parsing it for every page.
|
|
63
|
+
*/
|
|
64
|
+
let axeScript;
|
|
65
|
+
function injectAxe(dom) {
|
|
66
|
+
axeScript ??= new Script(axe.source, { filename: "axe-core.js" });
|
|
67
|
+
axeScript.runInContext(dom.getInternalVMContext());
|
|
68
|
+
}
|
|
69
|
+
async function withTimeout(promise, ms) {
|
|
70
|
+
promise.catch(() => {});
|
|
71
|
+
let timer;
|
|
72
|
+
try {
|
|
73
|
+
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
74
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`axe-core timed out after ${ms}ms`)), ms);
|
|
75
|
+
})]);
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { runJsdomAudit as n, auditPage as t };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { i as isImpactLevel, r as countAtOrAbove } from "./impact-
|
|
1
|
+
import { i as impactLabel, o as isImpactLevel, r as countAtOrAbove } from "./impact-DZt2oBCP.js";
|
|
2
2
|
import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
|
|
3
|
+
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
4
|
+
import { s as ruleOutcomes } from "./result-DoamKFsp.js";
|
|
5
|
+
import { t as buildCoverage } from "./coverage-B4IBKMO8.js";
|
|
3
6
|
import axe from "axe-core";
|
|
4
7
|
/**
|
|
5
8
|
* Build the machine-readable report.
|
|
@@ -29,6 +32,8 @@ function buildJsonReport(audits, options) {
|
|
|
29
32
|
baseUrl: options.baseUrl ?? null
|
|
30
33
|
},
|
|
31
34
|
summary: buildSummary(audits, options.failOn),
|
|
35
|
+
completeness: options.completeness,
|
|
36
|
+
coverage: buildCoverage(audits),
|
|
32
37
|
rules: buildRuleIndex(audits),
|
|
33
38
|
pages: audits.map(toJsonPage)
|
|
34
39
|
};
|
|
@@ -36,23 +41,14 @@ function buildJsonReport(audits, options) {
|
|
|
36
41
|
/** Every rule mentioned by any page, keyed by id and sorted for stable diffs. */
|
|
37
42
|
function buildRuleIndex(audits) {
|
|
38
43
|
const index = /* @__PURE__ */ new Map();
|
|
39
|
-
for (const audit of audits) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const outcome of outcomes) {
|
|
48
|
-
if (index.has(outcome.ruleId)) continue;
|
|
49
|
-
index.set(outcome.ruleId, {
|
|
50
|
-
help: outcome.help,
|
|
51
|
-
helpUrl: outcome.helpUrl,
|
|
52
|
-
successCriteria: outcome.successCriteria,
|
|
53
|
-
en301549: outcome.enClauses
|
|
54
|
-
});
|
|
55
|
-
}
|
|
44
|
+
for (const audit of audits) for (const outcome of ruleOutcomes(audit)) {
|
|
45
|
+
if (index.has(outcome.ruleId)) continue;
|
|
46
|
+
index.set(outcome.ruleId, {
|
|
47
|
+
help: outcome.help,
|
|
48
|
+
helpUrl: outcome.helpUrl,
|
|
49
|
+
successCriteria: outcome.successCriteria,
|
|
50
|
+
en301549: outcome.enClauses
|
|
51
|
+
});
|
|
56
52
|
}
|
|
57
53
|
return Object.fromEntries([...index].sort(([a], [b]) => a.localeCompare(b)));
|
|
58
54
|
}
|
|
@@ -60,6 +56,7 @@ function buildRuleIndex(audits) {
|
|
|
60
56
|
function serialiseJsonReport(report) {
|
|
61
57
|
return `${JSON.stringify(report, null, 2)}\n`;
|
|
62
58
|
}
|
|
59
|
+
/** The run's tally, exported so the HTML report reads the same numbers. */
|
|
63
60
|
function buildSummary(audits, failOn) {
|
|
64
61
|
const byImpact = {
|
|
65
62
|
critical: 0,
|
|
@@ -80,8 +77,7 @@ function buildSummary(audits, failOn) {
|
|
|
80
77
|
for (const finding of audit.violations) {
|
|
81
78
|
violations += 1;
|
|
82
79
|
violatingElements += finding.nodes.length;
|
|
83
|
-
|
|
84
|
-
byImpact[impact && isImpactLevel(impact) ? impact : "unclassified"] += 1;
|
|
80
|
+
byImpact[impactLabel(finding.impact)] += 1;
|
|
85
81
|
}
|
|
86
82
|
for (const finding of audit.incomplete) if (finding.reason === "engine-limitation") notEvaluated += 1;
|
|
87
83
|
else needsReview += 1;
|
|
@@ -123,7 +119,8 @@ function toJsonFinding(finding) {
|
|
|
123
119
|
nodes: finding.nodes.map((node) => ({
|
|
124
120
|
html: node.html,
|
|
125
121
|
target: node.target,
|
|
126
|
-
failureSummary: node.failureSummary ?? null
|
|
122
|
+
failureSummary: node.failureSummary ?? null,
|
|
123
|
+
fingerprint: elementFingerprint(finding.ruleId, node.target.join(" "), node.html)
|
|
127
124
|
}))
|
|
128
125
|
};
|
|
129
126
|
}
|
|
@@ -138,4 +135,4 @@ function byRuleId(a, b) {
|
|
|
138
135
|
return a.ruleId.localeCompare(b.ruleId);
|
|
139
136
|
}
|
|
140
137
|
//#endregion
|
|
141
|
-
export {
|
|
138
|
+
export { buildSummary as n, serialiseJsonReport as r, buildJsonReport as t };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import {
|
|
1
|
+
import { _ as url, a as isoDate, d as pipe, g as union, h as transform, i as enumeration, l as object, m as string, p as safeParse, r as email, t as array, u as optional, v as withDefault } from "./schema-is6CGX2D.js";
|
|
2
|
+
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
//#region src/config/define.ts
|
|
@@ -181,12 +182,5 @@ async function importModule(file) {
|
|
|
181
182
|
if (module.default === void 0) throw new ConfigError(`${path.basename(file)} has no default export`, ["Expected: export default defineConfig({ … })"]);
|
|
182
183
|
return module.default;
|
|
183
184
|
}
|
|
184
|
-
async function isFile(candidate) {
|
|
185
|
-
try {
|
|
186
|
-
return (await stat(candidate)).isFile();
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
185
|
//#endregion
|
|
192
186
|
export { COMPLIANCE_STATUSES as a, ISSUE_REASONS as c, defineConfig as d, parseConfig as f, ASSESSMENT_METHODS as i, STATEMENT_LOCALES as l, findConfigFile as n, COUNTRIES as o, loadConfig as r, ConfigError as s, CONFIG_FILENAMES as t, configSchema as u };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
|
|
2
|
+
//#region src/nuxt/index.d.ts
|
|
3
|
+
/** The part of the Nitro instance this reads, handed over by `nitro:init`. */
|
|
4
|
+
interface NitroLike {
|
|
5
|
+
options: {
|
|
6
|
+
output?: {
|
|
7
|
+
publicDir?: string;
|
|
8
|
+
};
|
|
9
|
+
/** True for `nuxt generate`; absent for a server build. */
|
|
10
|
+
static?: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface NuxtOptionsLike {
|
|
14
|
+
rootDir?: string;
|
|
15
|
+
}
|
|
16
|
+
interface NuxtLike {
|
|
17
|
+
options: NuxtOptionsLike;
|
|
18
|
+
hook(name: 'nitro:init', handler: (nitro: NitroLike) => void): void;
|
|
19
|
+
hook(name: 'close', handler: () => Promise<void>): void;
|
|
20
|
+
}
|
|
21
|
+
interface EaaKitNuxtOptions extends IntegrationOptions {
|
|
22
|
+
/** Directory to audit. Defaults to the one Nitro says it wrote. */
|
|
23
|
+
directory?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Let a server build pass without auditing anything.
|
|
26
|
+
*
|
|
27
|
+
* Off by default: `nuxt build` writes no browsable HTML, and a silent pass
|
|
28
|
+
* over a directory with no pages in it is indistinguishable from a clean
|
|
29
|
+
* site.
|
|
30
|
+
*/
|
|
31
|
+
allowServerBuild?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function eaaKitModule(options?: EaaKitNuxtOptions, nuxt?: NuxtLike): void;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { BuildAuditError, EaaKitNuxtOptions, NitroLike, NuxtLike, NuxtOptionsLike, eaaKitModule as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { n as auditBuild, r as stderrLogger, t as BuildAuditError } from "../run-DB34BSOZ.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/nuxt/index.ts
|
|
4
|
+
function eaaKitModule(options = {}, nuxt) {
|
|
5
|
+
if (nuxt === void 0) return;
|
|
6
|
+
let publicDir;
|
|
7
|
+
let prerendered = false;
|
|
8
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
9
|
+
publicDir = nitro.options.output?.publicDir;
|
|
10
|
+
prerendered = nitro.options.static === true;
|
|
11
|
+
});
|
|
12
|
+
nuxt.hook("close", async () => {
|
|
13
|
+
const directory = options.directory ?? publicDir;
|
|
14
|
+
if (options.directory === void 0 && !prerendered) {
|
|
15
|
+
if (options.allowServerBuild === true) return;
|
|
16
|
+
throw new BuildAuditError("eaa-kit: this was a server build, so no pages were written to disk and there was nothing to audit. Run `nuxt generate` to prerender them, audit the running site with `eaa-kit audit --url`, or set allowServerBuild to skip this.");
|
|
17
|
+
}
|
|
18
|
+
if (directory === void 0) throw new BuildAuditError("eaa-kit: Nitro reported no public directory, so there is no path to audit. Pass `directory` to the module to name one.");
|
|
19
|
+
await auditBuild(path.resolve(nuxt.options.rootDir ?? process.cwd(), directory), options, stderrLogger());
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { BuildAuditError, eaaKitModule as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
2
|
+
import { c as runOptions, i as failedPage, l as shapeResults, o as pageUrl, t as DEFAULT_TAGS } from "./result-DoamKFsp.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { stat } from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
@@ -74,7 +75,7 @@ async function handle(root, requestUrl, response) {
|
|
|
74
75
|
}
|
|
75
76
|
try {
|
|
76
77
|
const target = (await stat(file)).isDirectory() ? path.join(file, "index.html") : file;
|
|
77
|
-
if (!await
|
|
78
|
+
if (!await isFile(target)) {
|
|
78
79
|
response.writeHead(404).end();
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
@@ -88,13 +89,6 @@ async function handle(root, requestUrl, response) {
|
|
|
88
89
|
response.writeHead(404).end();
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
async function isReadableFile(candidate) {
|
|
92
|
-
try {
|
|
93
|
-
return (await stat(candidate)).isFile();
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
92
|
/** Resolves a request path inside the root, or nothing if it escapes it. */
|
|
99
93
|
function resolveFile(root, requestUrl) {
|
|
100
94
|
const { pathname } = new URL(requestUrl, "http://127.0.0.1");
|
|
@@ -151,7 +145,7 @@ async function runBrowserAudit(directory, pages, options = {}) {
|
|
|
151
145
|
browser = await chromium.launch({ headless: true });
|
|
152
146
|
} catch (cause) {
|
|
153
147
|
await server?.close();
|
|
154
|
-
throw cause;
|
|
148
|
+
throw launchFailure(cause);
|
|
155
149
|
}
|
|
156
150
|
try {
|
|
157
151
|
const context = await browser.newContext({
|
|
@@ -176,7 +170,7 @@ async function auditOne(context, origin, page, options) {
|
|
|
176
170
|
const identity = {
|
|
177
171
|
relativePath: page.relativePath,
|
|
178
172
|
absolutePath: page.absolutePath,
|
|
179
|
-
url:
|
|
173
|
+
url: pageUrl(page, options.baseUrl),
|
|
180
174
|
engine: "browser"
|
|
181
175
|
};
|
|
182
176
|
const tab = await context.newPage();
|
|
@@ -218,9 +212,27 @@ async function auditOne(context, origin, page, options) {
|
|
|
218
212
|
function servedUrl(origin, relativePath) {
|
|
219
213
|
return `${origin}/${relativePath.split("/").map(encodeURIComponent).join("/")}`;
|
|
220
214
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
215
|
+
/**
|
|
216
|
+
* A launch that failed because the browser was never downloaded, told as setup.
|
|
217
|
+
*
|
|
218
|
+
* The third setup failure, and the only one that used to arrive as a crash.
|
|
219
|
+
* `npm i -D playwright` does not fetch Chromium, so somebody who followed the
|
|
220
|
+
* install line exactly still lands here — and what they saw was Playwright's
|
|
221
|
+
* message boxed in ASCII, wrapped in a stack trace through this package's
|
|
222
|
+
* bundled internals. That reads as eaa-kit falling over, not as a step left to
|
|
223
|
+
* run, and the two want very different things from the reader.
|
|
224
|
+
*
|
|
225
|
+
* Anything else is passed through untouched: a launch can fail for reasons
|
|
226
|
+
* that really are bugs, and dressing those up as setup advice would send
|
|
227
|
+
* somebody off installing a browser they already have.
|
|
228
|
+
*/
|
|
229
|
+
function launchFailure(cause) {
|
|
230
|
+
if (!(cause instanceof Error)) return cause;
|
|
231
|
+
const missing = /Executable doesn't exist at (.+)/.exec(cause.message);
|
|
232
|
+
if (missing === null) return cause;
|
|
233
|
+
return new BrowserUnavailableError(`Playwright is installed, but the Chromium it drives is not.
|
|
234
|
+
Download it with: npx playwright install chromium
|
|
235
|
+
It was looked for at: ${missing[1]?.trim() ?? "an unreported path"}`);
|
|
224
236
|
}
|
|
225
237
|
/**
|
|
226
238
|
* Playwright is an optional peer dependency: the browserless path must never
|
|
@@ -229,18 +241,53 @@ function reportedUrl(page, baseUrl) {
|
|
|
229
241
|
* problems with different fixes.
|
|
230
242
|
*/
|
|
231
243
|
async function loadChromium(cwd = process.cwd()) {
|
|
232
|
-
|
|
244
|
+
const tried = [];
|
|
245
|
+
const specifiers = ["playwright", "@playwright/test"];
|
|
246
|
+
const roots = [cwd, void 0];
|
|
247
|
+
for (const root of roots) for (const specifier of specifiers) {
|
|
248
|
+
const loaded = await tryLoad(specifier, root);
|
|
249
|
+
if (loaded === void 0) continue;
|
|
250
|
+
tried.push(`${specifier}${root === void 0 ? " (from eaa-kit)" : ""}`);
|
|
251
|
+
const chromium = launcherIn(loaded);
|
|
252
|
+
if (chromium !== void 0) return chromium;
|
|
253
|
+
}
|
|
254
|
+
if (tried.length === 0) throw new BrowserUnavailableError("Browser mode needs Playwright, which is an optional peer dependency.\n Install it with: npm i -D playwright\n Then the browser: npx playwright install chromium");
|
|
255
|
+
throw new BrowserUnavailableError(`Found ${tried.join(" and ")}, but no chromium launcher on it.\n This usually means an incomplete or mismatched install. Try:
|
|
256
|
+
npm i -D playwright@latest
|
|
257
|
+
npx playwright install chromium --force`);
|
|
258
|
+
}
|
|
259
|
+
/** Import a specifier, resolved from `root` when given. Undefined if absent. */
|
|
260
|
+
async function tryLoad(specifier, root) {
|
|
233
261
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (module === void 0) try {
|
|
238
|
-
module = await import("playwright");
|
|
262
|
+
if (root === void 0) return await import(specifier);
|
|
263
|
+
const require = createRequire(pathToFileURL(path.join(root, "package.json")).href);
|
|
264
|
+
return await import(pathToFileURL(require.resolve(specifier)).href);
|
|
239
265
|
} catch {
|
|
240
|
-
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The chromium launcher, whichever shape the module came back in.
|
|
271
|
+
*
|
|
272
|
+
* Playwright is CommonJS. Whether `await import()` of it exposes named exports
|
|
273
|
+
* depends on Node's static analysis of the file succeeding, which is not
|
|
274
|
+
* guaranteed and differs between versions; when it does not, everything is on
|
|
275
|
+
* `default` instead. Reading only the named export produced "installed but
|
|
276
|
+
* exports no chromium launcher" against a perfectly good install.
|
|
277
|
+
*
|
|
278
|
+
* Exported for its own tests. Driving this through a real import under vitest
|
|
279
|
+
* proves nothing: vitest hands CommonJS back through an interop proxy that
|
|
280
|
+
* answers `.chromium` whether or not Node hoisted it, so both shapes look
|
|
281
|
+
* identical from in there and the test passes with this function broken. The
|
|
282
|
+
* shapes are checked here directly, and the real interop is exercised against
|
|
283
|
+
* real Node by scripts/test-packaged.mjs.
|
|
284
|
+
*/
|
|
285
|
+
function launcherIn(module) {
|
|
286
|
+
const candidates = [module, module?.default];
|
|
287
|
+
for (const candidate of candidates) {
|
|
288
|
+
const chromium = candidate?.chromium;
|
|
289
|
+
if (chromium !== void 0 && typeof chromium.launch === "function") return chromium;
|
|
241
290
|
}
|
|
242
|
-
if (!module.chromium) throw new BrowserUnavailableError("Playwright is installed but exports no chromium launcher");
|
|
243
|
-
return module.chromium;
|
|
244
291
|
}
|
|
245
292
|
//#endregion
|
|
246
293
|
export { BrowserUnavailableError, runBrowserAudit };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
1
|
+
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
2
|
+
import { i as failedPage, o as pageUrl } from "./result-DoamKFsp.js";
|
|
4
3
|
import { Worker } from "node:worker_threads";
|
|
5
4
|
import { availableParallelism } from "node:os";
|
|
6
5
|
//#region src/audit/runners/pool.ts
|
|
@@ -97,7 +96,7 @@ async function runPooledAudit(pages, options = {}) {
|
|
|
97
96
|
return runWorkers(pages, runnerOptions, Math.min(workers, pages.length), entry);
|
|
98
97
|
}
|
|
99
98
|
async function auditHere(pages, options) {
|
|
100
|
-
const { runJsdomAudit } = await import("./jsdom-
|
|
99
|
+
const { runJsdomAudit } = await import("./jsdom-4IMzv0eE.js");
|
|
101
100
|
return runJsdomAudit(pages, options);
|
|
102
101
|
}
|
|
103
102
|
async function runWorkers(pages, options, count, entry) {
|
|
@@ -159,11 +158,6 @@ function identity(page, options) {
|
|
|
159
158
|
durationMs: 0
|
|
160
159
|
};
|
|
161
160
|
}
|
|
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
161
|
/**
|
|
168
162
|
* Locate the worker entry point.
|
|
169
163
|
*
|
|
@@ -180,9 +174,7 @@ async function findWorkerEntry() {
|
|
|
180
174
|
new URL("../audit/runners/worker.js", import.meta.url),
|
|
181
175
|
new URL("./worker.js", import.meta.url)
|
|
182
176
|
];
|
|
183
|
-
for (const candidate of candidates)
|
|
184
|
-
if ((await stat(candidate)).isFile()) return candidate;
|
|
185
|
-
} catch {}
|
|
177
|
+
for (const candidate of candidates) if (await isFile(candidate)) return candidate;
|
|
186
178
|
}
|
|
187
179
|
//#endregion
|
|
188
180
|
export { plannedWorkers, runPooledAudit };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as isDirectory, t as exists } from "./fs-BmPtmFke.js";
|
|
2
|
+
import { r as candidateOutputs } from "./frameworks-B4ClIJgE.js";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { glob } from "tinyglobby";
|
|
4
6
|
import { spawn } from "node:child_process";
|
|
@@ -17,15 +19,6 @@ import { spawn } from "node:child_process";
|
|
|
17
19
|
* project's server if the build produces nothing browsable. Naming a directory
|
|
18
20
|
* or passing --url skips all of it.
|
|
19
21
|
*/
|
|
20
|
-
/** Build output directories, in the order worth trying them. */
|
|
21
|
-
const BUILD_DIRECTORIES = [
|
|
22
|
-
"dist",
|
|
23
|
-
"out",
|
|
24
|
-
"build",
|
|
25
|
-
"_site",
|
|
26
|
-
".output/public",
|
|
27
|
-
"public"
|
|
28
|
-
];
|
|
29
22
|
/** Ports the common dev and preview servers use, tried if nothing is announced. */
|
|
30
23
|
const KNOWN_PORTS = [
|
|
31
24
|
3e3,
|
|
@@ -55,10 +48,7 @@ async function detectPackageManager(cwd) {
|
|
|
55
48
|
["pnpm-lock.yaml", "pnpm"],
|
|
56
49
|
["yarn.lock", "yarn"],
|
|
57
50
|
["bun.lockb", "bun"]
|
|
58
|
-
])
|
|
59
|
-
await stat(path.join(cwd, file));
|
|
60
|
-
return manager;
|
|
61
|
-
} catch {}
|
|
51
|
+
]) if (await exists(path.join(cwd, file))) return manager;
|
|
62
52
|
return "npm";
|
|
63
53
|
}
|
|
64
54
|
/**
|
|
@@ -69,13 +59,10 @@ async function detectPackageManager(cwd) {
|
|
|
69
59
|
* assets. What makes a directory the build output is that there is HTML in it.
|
|
70
60
|
*/
|
|
71
61
|
async function findBuildOutput(cwd) {
|
|
72
|
-
|
|
62
|
+
const candidates = await candidateOutputs(cwd, await readPackageJson(cwd));
|
|
63
|
+
for (const candidate of candidates) {
|
|
73
64
|
const directory = path.join(cwd, candidate);
|
|
74
|
-
|
|
75
|
-
if (!(await stat(directory)).isDirectory()) continue;
|
|
76
|
-
} catch {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
65
|
+
if (!await isDirectory(directory)) continue;
|
|
79
66
|
if ((await glob(["**/*.html", "**/*.htm"], {
|
|
80
67
|
cwd: directory,
|
|
81
68
|
ignore: ["**/node_modules/**"],
|
|
@@ -239,6 +226,12 @@ async function autoDetectSource(cwd, options = {}) {
|
|
|
239
226
|
};
|
|
240
227
|
}
|
|
241
228
|
const pkg = await readPackageJson(cwd);
|
|
229
|
+
const { detectFramework } = await import("./frameworks-etFg_O8K.js");
|
|
230
|
+
const detected = await detectFramework(cwd, pkg);
|
|
231
|
+
if (detected !== void 0 && detected.framework.outputs.length === 0) {
|
|
232
|
+
step(`${detected.framework.name} renders on a server and writes no HTML to disk`);
|
|
233
|
+
return { steps };
|
|
234
|
+
}
|
|
242
235
|
if (pkg === void 0) return void 0;
|
|
243
236
|
const scripts = pkg.scripts ?? {};
|
|
244
237
|
if (options.noBuild) return void 0;
|
|
@@ -278,4 +271,4 @@ async function autoDetectSource(cwd, options = {}) {
|
|
|
278
271
|
};
|
|
279
272
|
}
|
|
280
273
|
//#endregion
|
|
281
|
-
export { autoDetectSource };
|
|
274
|
+
export { runScript as a, readPackageJson as i, detectPackageManager as n, startServer as o, findBuildOutput as r, autoDetectSource as t };
|