apex-auditor 0.2.9 → 0.3.3
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 +49 -99
- package/dist/accessibility-types.js +1 -0
- package/dist/accessibility.js +152 -0
- package/dist/axe-script.js +26 -0
- package/dist/bin.js +185 -9
- package/dist/cdp-client.js +264 -0
- package/dist/cli.js +1608 -75
- package/dist/config.js +11 -0
- package/dist/lighthouse-runner.js +580 -54
- package/dist/lighthouse-worker.js +248 -0
- package/dist/measure-cli.js +139 -0
- package/dist/measure-runner.js +447 -0
- package/dist/measure-types.js +1 -0
- package/dist/shell-cli.js +566 -0
- package/dist/spinner.js +37 -0
- package/dist/ui/render-panel.js +46 -0
- package/dist/ui/render-table.js +61 -0
- package/dist/ui/ui-theme.js +47 -0
- package/dist/url.js +6 -0
- package/dist/webhooks.js +29 -0
- package/dist/wizard-cli.js +15 -22
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,151 +1,101 @@
|
|
|
1
1
|
# ApexAuditor
|
|
2
2
|
|
|
3
|
-
ApexAuditor is a
|
|
3
|
+
ApexAuditor is a **measure-first** performance + metrics assistant.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use it to:
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Developer-friendly reports**: readable console output, Markdown tables, HTML reports, and JSON summaries for CI.
|
|
11
|
-
- **Configurable throttling**: tune CPU/network throttling for accuracy vs speed trade-offs.
|
|
12
|
-
- **Parallel execution**: speed up batch testing with multiple Chrome instances.
|
|
7
|
+
- **Measure fast** (CDP-based): LCP/CLS/INP + screenshot + console errors.
|
|
8
|
+
- **Audit deep** (Lighthouse): Performance + Accessibility + Best Practices + SEO.
|
|
9
|
+
- **Review results** in a clean, structured console output and an HTML report.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
## Most common commands
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Terminal summary:
|
|
19
|
-
|
|
20
|
-

|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
Wizard route selection:
|
|
25
|
-
|
|
26
|
-

|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
|
|
32
|
-
Install as a dev dependency (recommended):
|
|
13
|
+
From your web project root:
|
|
33
14
|
|
|
34
15
|
```bash
|
|
35
|
-
pnpm
|
|
36
|
-
# or
|
|
37
|
-
npm install --save-dev apex-auditor
|
|
16
|
+
pnpm dlx apex-auditor@latest
|
|
38
17
|
```
|
|
39
18
|
|
|
40
|
-
|
|
19
|
+
Inside the interactive shell:
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
21
|
+
- **measure**
|
|
22
|
+
- **audit**
|
|
23
|
+
- **open** (open the latest HTML report)
|
|
24
|
+
- **init** (launch config wizard)
|
|
25
|
+
- **config <path>** (switch config file)
|
|
45
26
|
|
|
46
|
-
|
|
27
|
+
Cancel long-running commands:
|
|
47
28
|
|
|
48
|
-
|
|
29
|
+
- **Esc** (returns you to the shell prompt)
|
|
49
30
|
|
|
50
|
-
|
|
31
|
+
## Install
|
|
51
32
|
|
|
52
|
-
|
|
33
|
+
Install as a dev dependency (recommended):
|
|
53
34
|
|
|
54
35
|
```bash
|
|
55
|
-
|
|
36
|
+
pnpm add -D apex-auditor
|
|
56
37
|
```
|
|
57
38
|
|
|
58
|
-
|
|
39
|
+
Or run without installing:
|
|
59
40
|
|
|
60
41
|
```bash
|
|
61
|
-
apex-auditor
|
|
42
|
+
pnpm dlx apex-auditor@latest
|
|
62
43
|
```
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- Next.js (App Router / Pages Router)
|
|
67
|
-
- Remix
|
|
68
|
-
- SvelteKit
|
|
69
|
-
- Single Page Apps (Vite/CRA/etc., via HTML crawl)
|
|
70
|
-
|
|
71
|
-
### Audit (run using an existing config)
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
apex-auditor audit --config apex.config.json
|
|
75
|
-
```
|
|
45
|
+
## Outputs
|
|
76
46
|
|
|
77
|
-
|
|
47
|
+
All outputs are written under `.apex-auditor/` in your project.
|
|
78
48
|
|
|
79
|
-
|
|
80
|
-
|------|-------------|
|
|
81
|
-
| `--ci` | Enable CI mode with budgets and non-zero exit codes |
|
|
82
|
-
| `--no-color` / `--color` | Control ANSI colours in console output |
|
|
83
|
-
| `--log-level <level>` | Override Lighthouse log level (`silent`, `error`, `info`, `verbose`) |
|
|
84
|
-
| `--throttling <method>` | Throttling method: `simulate` (fast) or `devtools` (accurate) |
|
|
85
|
-
| `--cpu-slowdown <n>` | CPU slowdown multiplier (1-20, default: 4) |
|
|
86
|
-
| `--parallel <n>` | Number of pages to audit in parallel (1-10) |
|
|
87
|
-
| `--warm-up` | Perform warm-up requests before auditing |
|
|
88
|
-
| `--open` | Auto-open HTML report in browser after audit |
|
|
89
|
-
| `--json` | Output JSON to stdout (for piping) |
|
|
90
|
-
| `--mobile-only` | Only audit mobile device configurations |
|
|
91
|
-
| `--desktop-only` | Only audit desktop device configurations |
|
|
49
|
+
### `audit` outputs
|
|
92
50
|
|
|
93
|
-
|
|
51
|
+
- `summary.json`
|
|
52
|
+
- `summary.md`
|
|
53
|
+
- `report.html`
|
|
54
|
+
- `accessibility-summary.json`
|
|
55
|
+
- `accessibility/` (axe-core artifacts per page/device)
|
|
94
56
|
|
|
95
|
-
|
|
57
|
+
Notes:
|
|
96
58
|
|
|
97
|
-
|
|
59
|
+
- **Runs-per-combo is always 1**. Re-run the same command to compare results.
|
|
60
|
+
- During an audit you will see a runtime progress line like `page X/Y — /path [device] | ETA ...`.
|
|
61
|
+
- After `audit` completes, type `open` to open the latest HTML report.
|
|
98
62
|
|
|
99
|
-
|
|
100
|
-
- `summary.md` – Markdown table
|
|
101
|
-
- `report.html` – visual HTML report with score circles and metrics
|
|
63
|
+
### `measure` outputs
|
|
102
64
|
|
|
103
|
-
|
|
65
|
+
- `measure-summary.json`
|
|
66
|
+
- `measure/` (screenshots and artifacts)
|
|
104
67
|
|
|
105
68
|
## Configuration
|
|
106
69
|
|
|
107
|
-
|
|
70
|
+
ApexAuditor reads `apex.config.json` by default.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
108
73
|
|
|
109
74
|
```json
|
|
110
75
|
{
|
|
111
76
|
"baseUrl": "http://localhost:3000",
|
|
112
|
-
"
|
|
113
|
-
"throttlingMethod": "devtools",
|
|
77
|
+
"throttlingMethod": "simulate",
|
|
114
78
|
"cpuSlowdownMultiplier": 4,
|
|
115
|
-
"parallel":
|
|
79
|
+
"parallel": 4,
|
|
116
80
|
"warmUp": true,
|
|
117
81
|
"pages": [
|
|
118
82
|
{ "path": "/", "label": "home", "devices": ["mobile", "desktop"] },
|
|
119
|
-
{ "path": "/docs", "label": "docs", "devices": ["
|
|
83
|
+
{ "path": "/docs", "label": "docs", "devices": ["desktop"] }
|
|
120
84
|
],
|
|
121
85
|
"budgets": {
|
|
122
|
-
"categories": { "performance": 80, "accessibility": 90 },
|
|
123
|
-
"metrics": { "lcpMs": 2500, "inpMs": 200 }
|
|
86
|
+
"categories": { "performance": 80, "accessibility": 90, "bestPractices": 90, "seo": 90 },
|
|
87
|
+
"metrics": { "lcpMs": 2500, "inpMs": 200, "cls": 0.1 }
|
|
124
88
|
}
|
|
125
89
|
}
|
|
126
90
|
```
|
|
127
91
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
## Metrics tracked
|
|
131
|
-
|
|
132
|
-
- **LCP** (Largest Contentful Paint)
|
|
133
|
-
- **FCP** (First Contentful Paint)
|
|
134
|
-
- **TBT** (Total Blocking Time)
|
|
135
|
-
- **CLS** (Cumulative Layout Shift)
|
|
136
|
-
- **INP** (Interaction to Next Paint) - Core Web Vital
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Further documentation
|
|
141
|
-
|
|
142
|
-
For detailed guides, configuration options, and CI examples, see the `docs/` directory:
|
|
92
|
+
## Documentation
|
|
143
93
|
|
|
144
|
-
|
|
145
|
-
- `docs/configuration-and-routes.md` – `apex.config.json` schema and route detection details.
|
|
146
|
-
- `docs/cli-and-ci.md` – CLI flags, CI mode, budgets, and example workflows.
|
|
94
|
+
The docs in `docs/` reflect the current shell-based workflow:
|
|
147
95
|
|
|
148
|
-
|
|
96
|
+
- `docs/getting-started.md`
|
|
97
|
+
- `docs/configuration-and-routes.md`
|
|
98
|
+
- `docs/cli-and-ci.md`
|
|
149
99
|
|
|
150
100
|
## License
|
|
151
101
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { launch as launchChrome } from "chrome-launcher";
|
|
5
|
+
import { createAxeScript } from "./axe-script.js";
|
|
6
|
+
import { CdpClient } from "./cdp-client.js";
|
|
7
|
+
import { buildUrl } from "./url.js";
|
|
8
|
+
const DEFAULT_PARALLEL = 2;
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
10
|
+
const CHROME_FLAGS = [
|
|
11
|
+
"--headless=new",
|
|
12
|
+
"--disable-gpu",
|
|
13
|
+
"--no-sandbox",
|
|
14
|
+
"--disable-dev-shm-usage",
|
|
15
|
+
"--disable-extensions",
|
|
16
|
+
"--disable-default-apps",
|
|
17
|
+
"--no-first-run",
|
|
18
|
+
"--no-default-browser-check",
|
|
19
|
+
];
|
|
20
|
+
async function launchChromeSession() {
|
|
21
|
+
const chrome = await launchChrome({
|
|
22
|
+
chromeFlags: [...CHROME_FLAGS],
|
|
23
|
+
logLevel: "silent",
|
|
24
|
+
});
|
|
25
|
+
const close = async () => {
|
|
26
|
+
try {
|
|
27
|
+
await chrome.kill();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* noop */
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return { port: chrome.port, close };
|
|
34
|
+
}
|
|
35
|
+
async function fetchJsonVersion(port) {
|
|
36
|
+
return await new Promise((resolveVersion, rejectVersion) => {
|
|
37
|
+
const req = httpRequest({ host: "localhost", port, path: "/json/version", method: "GET" }, (res) => {
|
|
38
|
+
let data = "";
|
|
39
|
+
res.on("data", (chunk) => {
|
|
40
|
+
data += chunk.toString();
|
|
41
|
+
});
|
|
42
|
+
res.on("end", () => {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(data);
|
|
45
|
+
resolveVersion(parsed);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
rejectVersion(error instanceof Error ? error : new Error("Failed to parse json/version"));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
req.on("error", (error) => rejectVersion(error));
|
|
53
|
+
req.end();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function createTargetSession(client) {
|
|
57
|
+
const created = await client.send("Target.createTarget", { url: "about:blank" });
|
|
58
|
+
const attached = await client.send("Target.attachToTarget", { targetId: created.targetId, flatten: true });
|
|
59
|
+
return { targetId: created.targetId, sessionId: attached.sessionId };
|
|
60
|
+
}
|
|
61
|
+
async function applyDevice(client, sessionId, device) {
|
|
62
|
+
if (device === "mobile") {
|
|
63
|
+
await client.send("Emulation.setDeviceMetricsOverride", { width: 375, height: 667, deviceScaleFactor: 2, mobile: true }, sessionId);
|
|
64
|
+
await client.send("Emulation.setUserAgentOverride", {
|
|
65
|
+
userAgent: "Mozilla/5.0 (Linux; Android 12; Pixel 6 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36",
|
|
66
|
+
}, sessionId);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await client.send("Emulation.setDeviceMetricsOverride", { width: 1440, height: 900, deviceScaleFactor: 1, mobile: false }, sessionId);
|
|
70
|
+
}
|
|
71
|
+
async function runAxeForPage(params) {
|
|
72
|
+
const { baseUrl, path, label, device, query, timeoutMs, artifactsDir, client } = params;
|
|
73
|
+
const url = buildUrl({ baseUrl, path, query });
|
|
74
|
+
const axeScript = createAxeScript();
|
|
75
|
+
let runtimeErrorMessage;
|
|
76
|
+
const session = await createTargetSession(client);
|
|
77
|
+
try {
|
|
78
|
+
await applyDevice(client, session.sessionId, device);
|
|
79
|
+
await client.send("Page.enable", {}, session.sessionId);
|
|
80
|
+
await client.send("Runtime.enable", {}, session.sessionId);
|
|
81
|
+
const navResponse = await client.send("Page.navigate", { url }, session.sessionId);
|
|
82
|
+
if (navResponse.errorText) {
|
|
83
|
+
throw new Error(navResponse.errorText);
|
|
84
|
+
}
|
|
85
|
+
await client.waitForEventForSession("Page.loadEventFired", session.sessionId, timeoutMs);
|
|
86
|
+
await client.send("Runtime.evaluate", { expression: axeScript, awaitPromise: false }, session.sessionId);
|
|
87
|
+
const evaluation = await client.send("Runtime.evaluate", {
|
|
88
|
+
expression: "(() => { return (globalThis.__axeCore && globalThis.__axeCore.run) ? globalThis.__axeCore.run() : { violations: [] }; })()",
|
|
89
|
+
awaitPromise: true,
|
|
90
|
+
returnByValue: true,
|
|
91
|
+
}, session.sessionId);
|
|
92
|
+
const value = evaluation.result?.value;
|
|
93
|
+
const violations = value && typeof value === "object" && Array.isArray(value.violations)
|
|
94
|
+
? value.violations
|
|
95
|
+
: [];
|
|
96
|
+
const baseName = path.replace(/\//g, "_").replace(/^_/, "") || "page";
|
|
97
|
+
const artifactPath = resolve(artifactsDir, `${baseName}_${device}_axe.json`);
|
|
98
|
+
await writeFile(artifactPath, JSON.stringify({ url, violations }, null, 2), "utf8");
|
|
99
|
+
return { url, path, label, device, violations };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
runtimeErrorMessage = error instanceof Error ? error.message : String(error);
|
|
103
|
+
return { url, path, label, device, violations: [], runtimeErrorMessage };
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
try {
|
|
107
|
+
await client.send("Target.closeTarget", { targetId: session.targetId });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* noop */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function runAccessibilityAudit(params) {
|
|
115
|
+
const { config, configPath, parallelOverride, timeoutMs, artifactsDir } = params;
|
|
116
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
117
|
+
const tasks = config.pages.flatMap((page) => page.devices.map((device) => ({ ...page, device })));
|
|
118
|
+
const startedAt = new Date().toISOString();
|
|
119
|
+
const comboCount = tasks.length;
|
|
120
|
+
const resolvedParallel = Math.max(1, Math.min(parallelOverride ?? DEFAULT_PARALLEL, 4));
|
|
121
|
+
const timeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
122
|
+
const chrome = await launchChromeSession();
|
|
123
|
+
try {
|
|
124
|
+
const version = await fetchJsonVersion(chrome.port);
|
|
125
|
+
const client = new CdpClient(version.webSocketDebuggerUrl);
|
|
126
|
+
await client.connect();
|
|
127
|
+
const results = [];
|
|
128
|
+
for (let i = 0; i < tasks.length; i += resolvedParallel) {
|
|
129
|
+
const slice = tasks.slice(i, i + resolvedParallel);
|
|
130
|
+
const batchResults = await Promise.all(slice.map((task) => runAxeForPage({
|
|
131
|
+
baseUrl: config.baseUrl,
|
|
132
|
+
path: task.path,
|
|
133
|
+
label: task.label,
|
|
134
|
+
device: task.device,
|
|
135
|
+
query: config.query,
|
|
136
|
+
timeoutMs: timeout,
|
|
137
|
+
artifactsDir,
|
|
138
|
+
client,
|
|
139
|
+
})));
|
|
140
|
+
results.push(...batchResults);
|
|
141
|
+
}
|
|
142
|
+
const completedAt = new Date().toISOString();
|
|
143
|
+
const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
|
|
144
|
+
return {
|
|
145
|
+
meta: { configPath, comboCount, startedAt, completedAt, elapsedMs },
|
|
146
|
+
results,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await chrome.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const AXE_CDN_URL = "https://cdn.jsdelivr.net/npm/axe-core@4.8.4/axe.min.js";
|
|
2
|
+
export function createAxeScript() {
|
|
3
|
+
return `(function() {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const runAxe = () => {
|
|
6
|
+
const axe = (globalThis).axe;
|
|
7
|
+
if (axe && axe.run) {
|
|
8
|
+
(globalThis).__axeCore = axe;
|
|
9
|
+
axe.run().then(resolve).catch(() => resolve({ violations: [] }));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
resolve({ violations: [] });
|
|
13
|
+
};
|
|
14
|
+
if ((globalThis).axe) {
|
|
15
|
+
runAxe();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const script = document.createElement('script');
|
|
19
|
+
script.src = '${AXE_CDN_URL}';
|
|
20
|
+
script.async = true;
|
|
21
|
+
script.onload = runAxe;
|
|
22
|
+
script.onerror = () => resolve({ violations: [] });
|
|
23
|
+
document.head.appendChild(script);
|
|
24
|
+
});
|
|
25
|
+
})();`;
|
|
26
|
+
}
|
package/dist/bin.js
CHANGED
|
@@ -2,45 +2,172 @@
|
|
|
2
2
|
import { runAuditCli } from "./cli.js";
|
|
3
3
|
import { runWizardCli } from "./wizard-cli.js";
|
|
4
4
|
import { runQuickstartCli } from "./quickstart-cli.js";
|
|
5
|
+
import { runShellCli } from "./shell-cli.js";
|
|
6
|
+
import { runMeasureCli } from "./measure-cli.js";
|
|
5
7
|
function parseBinArgs(argv) {
|
|
6
8
|
const rawCommand = argv[2];
|
|
7
|
-
if (rawCommand === undefined
|
|
9
|
+
if (rawCommand === undefined) {
|
|
10
|
+
return { command: "shell", argv };
|
|
11
|
+
}
|
|
12
|
+
if (rawCommand === "help" || rawCommand === "--help" || rawCommand === "-h") {
|
|
8
13
|
return { command: "help", argv };
|
|
9
14
|
}
|
|
10
|
-
if (rawCommand === "
|
|
15
|
+
if (rawCommand === "shell") {
|
|
16
|
+
const commandArgv = ["node", "apex-auditor", ...argv.slice(3)];
|
|
17
|
+
return { command: "shell", argv: commandArgv };
|
|
18
|
+
}
|
|
19
|
+
if (rawCommand === "audit" ||
|
|
20
|
+
rawCommand === "measure" ||
|
|
21
|
+
rawCommand === "wizard" ||
|
|
22
|
+
rawCommand === "quickstart" ||
|
|
23
|
+
rawCommand === "guide" ||
|
|
24
|
+
rawCommand === "init") {
|
|
11
25
|
const commandArgv = ["node", "apex-auditor", ...argv.slice(3)];
|
|
12
26
|
return { command: rawCommand, argv: commandArgv };
|
|
13
27
|
}
|
|
14
28
|
return { command: "help", argv };
|
|
15
29
|
}
|
|
16
|
-
function printHelp() {
|
|
30
|
+
function printHelp(topic) {
|
|
31
|
+
if (topic === "topics") {
|
|
32
|
+
console.log([
|
|
33
|
+
"Help topics:",
|
|
34
|
+
" budgets Budget schema and CI behaviour",
|
|
35
|
+
" configs apex.config.json fields and defaults",
|
|
36
|
+
" ci CI mode, exit codes, budgets",
|
|
37
|
+
" topics This list",
|
|
38
|
+
"Examples:",
|
|
39
|
+
" apex-auditor help budgets",
|
|
40
|
+
" apex-auditor help configs",
|
|
41
|
+
" apex-auditor help ci",
|
|
42
|
+
].join("\n"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (topic === "budgets") {
|
|
46
|
+
console.log([
|
|
47
|
+
"Budgets:",
|
|
48
|
+
" - categories: performance, accessibility, bestPractices, seo (0-100 scores)",
|
|
49
|
+
" - metrics: lcpMs, fcpMs, tbtMs, cls, inpMs (numeric thresholds)",
|
|
50
|
+
"Example apex.config.json budget:",
|
|
51
|
+
' "budgets": {',
|
|
52
|
+
' "categories": { "performance": 80, "accessibility": 90 },',
|
|
53
|
+
' "metrics": { "lcpMs": 2500, "inpMs": 200 }',
|
|
54
|
+
" }",
|
|
55
|
+
"",
|
|
56
|
+
"CI behaviour:",
|
|
57
|
+
" - --ci exits non-zero if any budget is under/over its limit",
|
|
58
|
+
" - Summary prints violations; JSON includes full results",
|
|
59
|
+
].join("\n"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (topic === "configs") {
|
|
63
|
+
console.log([
|
|
64
|
+
"Config (apex.config.json):",
|
|
65
|
+
" baseUrl (required) Base URL of your running site",
|
|
66
|
+
" query Query string appended to every path (e.g., ?lhci=1)",
|
|
67
|
+
" buildId Build identifier used for incremental cache keys",
|
|
68
|
+
" runs Runs per page/device (must be 1; rerun command to compare)",
|
|
69
|
+
" auditTimeoutMs Per-audit timeout in milliseconds (prevents hung runs from stalling)",
|
|
70
|
+
" throttlingMethod simulate | devtools (default simulate)",
|
|
71
|
+
" cpuSlowdownMultiplier CPU slowdown (default 4)",
|
|
72
|
+
" parallel Workers (default auto up to 4, respects CPU/memory)",
|
|
73
|
+
" warmUp true/false to warm cache before auditing (bounded concurrency)",
|
|
74
|
+
" incremental (deprecated default) Set in config but only active when --incremental is passed",
|
|
75
|
+
" pages Array of { path, label, devices: [mobile|desktop] }",
|
|
76
|
+
" budgets Optional { categories, metrics } thresholds",
|
|
77
|
+
"",
|
|
78
|
+
"Tip:",
|
|
79
|
+
" For best accuracy and stable throughput, run audits against a production server (e.g., Next.js: next build && next start)",
|
|
80
|
+
].join("\n"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (topic === "ci") {
|
|
84
|
+
console.log([
|
|
85
|
+
"CI mode:",
|
|
86
|
+
" - Use --ci to enable budgets and non-zero exit codes on failures",
|
|
87
|
+
" - Exit code 1 if any budget fails or a runtime error occurs",
|
|
88
|
+
" - Use --json to pipe results to other tools",
|
|
89
|
+
" - Combine with --parallel and --throttling for speed/accuracy trade-offs",
|
|
90
|
+
].join("\n"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
17
93
|
console.log([
|
|
18
94
|
"ApexAuditor CLI",
|
|
19
95
|
"",
|
|
20
96
|
"Usage:",
|
|
97
|
+
" apex-auditor # interactive shell (default)",
|
|
21
98
|
" apex-auditor quickstart --base-url <url> [--project-root <path>]",
|
|
22
99
|
" apex-auditor wizard [--config <path>]",
|
|
23
100
|
" apex-auditor audit [--config <path>] [--ci] [--no-color|--color] [--log-level <level>]",
|
|
101
|
+
" apex-auditor guide (alias of wizard) interactive flow with tips for non-technical users",
|
|
102
|
+
" apex-auditor shell # same as default entrypoint",
|
|
24
103
|
"",
|
|
25
104
|
"Commands:",
|
|
105
|
+
" shell Start interactive shell (audit/open/diff/preset/help/exit)",
|
|
26
106
|
" quickstart Detect routes and run a one-off audit with sensible defaults",
|
|
27
|
-
" wizard
|
|
28
|
-
"
|
|
29
|
-
"
|
|
107
|
+
" wizard Run interactive config wizard",
|
|
108
|
+
" guide Same as wizard, with inline tips for non-technical users",
|
|
109
|
+
" audit Run Lighthouse audits using apex.config.json",
|
|
110
|
+
" measure Fast batch metrics (CDP-based, non-Lighthouse)",
|
|
111
|
+
" help Show this help message",
|
|
30
112
|
"",
|
|
31
113
|
"Options (audit):",
|
|
32
114
|
" --ci Enable CI mode with budgets and non-zero exit code on failure",
|
|
115
|
+
" --fail-on-budget Exit non-zero if budgets fail even outside CI",
|
|
33
116
|
" --no-color Disable ANSI colours in console output (default in CI mode)",
|
|
34
117
|
" --color Force ANSI colours in console output",
|
|
35
118
|
" --log-level <lvl> Override Lighthouse log level: silent|error|info|verbose",
|
|
119
|
+
" --stable Flake-resistant mode: forces parallel=1, good for big suites or flaky runners",
|
|
36
120
|
" --mobile-only Run audits only for 'mobile' devices defined in the config",
|
|
37
121
|
" --desktop-only Run audits only for 'desktop' devices defined in the config",
|
|
122
|
+
" --parallel <n> Override parallel workers (1-10). Default auto-tunes from CPU/memory.",
|
|
123
|
+
" --audit-timeout-ms <ms> Per-audit timeout in milliseconds (prevents hung runs from stalling)",
|
|
124
|
+
" --plan Print resolved settings + run size estimate and exit without auditing",
|
|
125
|
+
" --max-steps <n> Safety limit: refuse/prompt if planned Lighthouse runs exceed this (default 120)",
|
|
126
|
+
" --max-combos <n> Safety limit: refuse/prompt if planned page/device combos exceed this (default 60)",
|
|
127
|
+
" --yes, -y Auto-confirm large runs (bypass safety prompt)",
|
|
128
|
+
" --changed-only Run only pages whose paths match files in git diff --name-only (working tree diff)",
|
|
129
|
+
" --rerun-failing Re-run only combos that failed in the previous summary (runtime errors or perf<90)",
|
|
130
|
+
" --accessibility-pass Run a fast axe-core accessibility sweep after audits (lightweight, CDP-based)",
|
|
131
|
+
" --webhook-url <url> Send a JSON webhook with regressions/budgets/accessibility (regressions-only summary)",
|
|
132
|
+
" --show-parallel Print the resolved parallel workers before running.",
|
|
133
|
+
" --incremental Reuse cached results for unchanged combos (requires --build-id). Opt-in; off by default.",
|
|
134
|
+
" --build-id <id> Build identifier used as the cache key boundary for --incremental",
|
|
135
|
+
" --overview Preset: quick overview (runs=1) and samples a small set of combos unless --yes.",
|
|
136
|
+
" --overview-combos <n> Overview sampling size (default 10).",
|
|
137
|
+
" --quick Preset: fast feedback (runs=1) without changing throttling defaults",
|
|
138
|
+
" --accurate Preset: devtools throttling + warm-up + stability-first (parallel=1 unless overridden)",
|
|
139
|
+
" --open Open the HTML report after the run.",
|
|
140
|
+
"",
|
|
141
|
+
"Outputs:",
|
|
142
|
+
" - Writes .apex-auditor/summary.json, summary.md, report.html",
|
|
143
|
+
" - Prints a file:// link to the HTML report after completion",
|
|
144
|
+
"",
|
|
145
|
+
"Quick start:",
|
|
146
|
+
" pnpm dlx apex-auditor@latest wizard # guided setup",
|
|
147
|
+
" pnpm dlx apex-auditor@latest audit # run with apex.config.json",
|
|
148
|
+
"",
|
|
149
|
+
"Defaults:",
|
|
150
|
+
" - Parallel auto-tunes from CPU/memory (up to 4 by default)",
|
|
151
|
+
" - Throttling: simulate, CPU slowdown: 4, Runs: 1",
|
|
152
|
+
" - Stable: use --stable to force serial runs when parallel flakes (e.g., TargetClose/Lantern errors)",
|
|
153
|
+
" - Accuracy tip: run against a production server (e.g., Next.js: next build && next start)",
|
|
154
|
+
" - Incremental: use --incremental --build-id <id> to skip unchanged audits between runs",
|
|
155
|
+
" - Presets: choose only one of --fast, --quick, --accurate",
|
|
156
|
+
"",
|
|
157
|
+
"More help:",
|
|
158
|
+
" apex-auditor help topics",
|
|
159
|
+
" apex-auditor help budgets",
|
|
38
160
|
].join("\n"));
|
|
39
161
|
}
|
|
40
162
|
export async function runBin(argv) {
|
|
41
163
|
const parsed = parseBinArgs(argv);
|
|
42
164
|
if (parsed.command === "help") {
|
|
43
|
-
|
|
165
|
+
const topic = argv[3];
|
|
166
|
+
printHelp(topic);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (parsed.command === "shell") {
|
|
170
|
+
await runShellCli(parsed.argv);
|
|
44
171
|
return;
|
|
45
172
|
}
|
|
46
173
|
if (parsed.command === "quickstart") {
|
|
@@ -48,11 +175,60 @@ export async function runBin(argv) {
|
|
|
48
175
|
return;
|
|
49
176
|
}
|
|
50
177
|
if (parsed.command === "audit") {
|
|
51
|
-
|
|
178
|
+
try {
|
|
179
|
+
await runAuditCli(parsed.argv);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
if (message.includes("ENOENT")) {
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.error("Config file not found. Run `apex-auditor init` to create a config or set one with `config <path>`.");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.log("\nAudit completed. Press Ctrl+C to exit, or enter another command (type help for options).");
|
|
193
|
+
await runShellCli(["node", "apex-auditor"]);
|
|
194
|
+
}
|
|
52
195
|
return;
|
|
53
196
|
}
|
|
54
|
-
if (parsed.command === "
|
|
197
|
+
if (parsed.command === "measure") {
|
|
198
|
+
try {
|
|
199
|
+
await runMeasureCli(parsed.argv);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
203
|
+
if (message.includes("ENOENT")) {
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
205
|
+
console.error("Config file not found. Run `apex-auditor init` to create a config or set one with `config <path>`.");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
211
|
+
// eslint-disable-next-line no-console
|
|
212
|
+
console.log("\nMeasure run completed. Press Ctrl+C to exit, or enter another command (type help for options).");
|
|
213
|
+
await runShellCli(["node", "apex-auditor"]);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (parsed.command === "init") {
|
|
55
218
|
await runWizardCli(parsed.argv);
|
|
219
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
220
|
+
// eslint-disable-next-line no-console
|
|
221
|
+
console.log("\nConfig initialized. Press Ctrl+C to exit, or enter another command (type help for options).");
|
|
222
|
+
await runShellCli(["node", "apex-auditor"]);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (parsed.command === "wizard" || parsed.command === "guide") {
|
|
227
|
+
await runWizardCli(parsed.argv);
|
|
228
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
229
|
+
await runShellCli(["node", "apex-auditor"]);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
56
232
|
}
|
|
57
233
|
}
|
|
58
234
|
void runBin(process.argv).catch((error) => {
|