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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const BOX = {
|
|
2
|
+
topLeft: "┌",
|
|
3
|
+
topRight: "┐",
|
|
4
|
+
bottomLeft: "└",
|
|
5
|
+
bottomRight: "┘",
|
|
6
|
+
horizontal: "─",
|
|
7
|
+
vertical: "│",
|
|
8
|
+
junctionTop: "┬",
|
|
9
|
+
junctionMid: "┼",
|
|
10
|
+
junctionBottom: "┴",
|
|
11
|
+
junctionLeft: "├",
|
|
12
|
+
junctionRight: "┤",
|
|
13
|
+
};
|
|
14
|
+
function stripAnsi(value) {
|
|
15
|
+
return value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
16
|
+
}
|
|
17
|
+
function visibleLength(value) {
|
|
18
|
+
return stripAnsi(value).length;
|
|
19
|
+
}
|
|
20
|
+
function repeatChar(char, count) {
|
|
21
|
+
return new Array(Math.max(0, count)).fill(char).join("");
|
|
22
|
+
}
|
|
23
|
+
function padCell(value, width) {
|
|
24
|
+
const len = visibleLength(value);
|
|
25
|
+
if (len >= width) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return `${value}${repeatChar(" ", width - len)}`;
|
|
29
|
+
}
|
|
30
|
+
function computeWidths(params) {
|
|
31
|
+
const columnCount = Math.max(params.headers.length, ...params.rows.map((r) => r.length));
|
|
32
|
+
const widths = new Array(columnCount).fill(1);
|
|
33
|
+
for (let i = 0; i < columnCount; i += 1) {
|
|
34
|
+
const header = params.headers[i] ?? "";
|
|
35
|
+
widths[i] = Math.max(widths[i] ?? 1, visibleLength(header));
|
|
36
|
+
}
|
|
37
|
+
for (const row of params.rows) {
|
|
38
|
+
for (let i = 0; i < columnCount; i += 1) {
|
|
39
|
+
const cell = row[i] ?? "";
|
|
40
|
+
widths[i] = Math.max(widths[i] ?? 1, visibleLength(cell));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return widths;
|
|
44
|
+
}
|
|
45
|
+
function joinBorder(widths, left, mid, right) {
|
|
46
|
+
const parts = widths.map((w) => repeatChar(BOX.horizontal, w + 2));
|
|
47
|
+
return `${left}${parts.join(mid)}${right}`;
|
|
48
|
+
}
|
|
49
|
+
function renderRow(cells, widths) {
|
|
50
|
+
const padded = widths.map((w, i) => ` ${padCell(cells[i] ?? "", w)} `);
|
|
51
|
+
return `${BOX.vertical}${padded.join(BOX.vertical)}${BOX.vertical}`;
|
|
52
|
+
}
|
|
53
|
+
export function renderTable(params) {
|
|
54
|
+
const widths = computeWidths(params);
|
|
55
|
+
const top = joinBorder(widths, BOX.topLeft, BOX.junctionTop, BOX.topRight);
|
|
56
|
+
const header = renderRow(params.headers, widths);
|
|
57
|
+
const mid = joinBorder(widths, BOX.junctionLeft, BOX.junctionMid, BOX.junctionRight);
|
|
58
|
+
const body = params.rows.map((r) => renderRow(r, widths));
|
|
59
|
+
const bottom = joinBorder(widths, BOX.bottomLeft, BOX.junctionBottom, BOX.bottomRight);
|
|
60
|
+
return [top, header, mid, ...body, bottom].join("\n");
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const COLOR_MAP = {
|
|
2
|
+
reset: "\u001b[0m",
|
|
3
|
+
bold: "\u001b[1m",
|
|
4
|
+
dim: "\u001b[2m",
|
|
5
|
+
cyan: "\u001b[36m",
|
|
6
|
+
magenta: "\u001b[35m",
|
|
7
|
+
yellow: "\u001b[33m",
|
|
8
|
+
green: "\u001b[32m",
|
|
9
|
+
red: "\u001b[31m",
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Terminal theme helper that supports NO_COLOR/CI fallbacks.
|
|
13
|
+
*/
|
|
14
|
+
export class UiTheme {
|
|
15
|
+
noColor;
|
|
16
|
+
constructor(params) {
|
|
17
|
+
this.noColor = params.noColor;
|
|
18
|
+
}
|
|
19
|
+
apply(token, value) {
|
|
20
|
+
if (this.noColor) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
const open = COLOR_MAP[token];
|
|
24
|
+
return `${open}${value}${COLOR_MAP.reset}`;
|
|
25
|
+
}
|
|
26
|
+
bold(value) {
|
|
27
|
+
return this.apply("bold", value);
|
|
28
|
+
}
|
|
29
|
+
dim(value) {
|
|
30
|
+
return this.apply("dim", value);
|
|
31
|
+
}
|
|
32
|
+
cyan(value) {
|
|
33
|
+
return this.apply("cyan", value);
|
|
34
|
+
}
|
|
35
|
+
magenta(value) {
|
|
36
|
+
return this.apply("magenta", value);
|
|
37
|
+
}
|
|
38
|
+
yellow(value) {
|
|
39
|
+
return this.apply("yellow", value);
|
|
40
|
+
}
|
|
41
|
+
green(value) {
|
|
42
|
+
return this.apply("green", value);
|
|
43
|
+
}
|
|
44
|
+
red(value) {
|
|
45
|
+
return this.apply("red", value);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/url.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function buildUrl(params) {
|
|
2
|
+
const cleanBase = params.baseUrl.replace(/\/$/, "");
|
|
3
|
+
const cleanPath = params.path.startsWith("/") ? params.path : `/${params.path}`;
|
|
4
|
+
const queryPart = params.query && params.query.length > 0 ? params.query : "";
|
|
5
|
+
return `${cleanBase}${cleanPath}${queryPart}`;
|
|
6
|
+
}
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
3
|
+
export async function postJsonWebhook(params) {
|
|
4
|
+
const { url, payload, timeoutMs } = params;
|
|
5
|
+
const target = new URL(url);
|
|
6
|
+
const body = JSON.stringify(payload);
|
|
7
|
+
const isHttps = target.protocol === "https:";
|
|
8
|
+
const requestFn = isHttps ? httpsRequest : httpRequest;
|
|
9
|
+
await new Promise((resolve, reject) => {
|
|
10
|
+
const req = requestFn({
|
|
11
|
+
method: "POST",
|
|
12
|
+
hostname: target.hostname,
|
|
13
|
+
path: `${target.pathname}${target.search}`,
|
|
14
|
+
port: target.port || (isHttps ? 443 : 80),
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
"Content-Length": Buffer.byteLength(body),
|
|
18
|
+
},
|
|
19
|
+
timeout: timeoutMs ?? 5000,
|
|
20
|
+
}, (res) => {
|
|
21
|
+
// Consume response to free socket
|
|
22
|
+
res.resume();
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
req.on("error", (error) => reject(error));
|
|
26
|
+
req.write(body);
|
|
27
|
+
req.end();
|
|
28
|
+
});
|
|
29
|
+
}
|
package/dist/wizard-cli.js
CHANGED
|
@@ -13,7 +13,6 @@ const PROFILE_TO_DETECTOR = {
|
|
|
13
13
|
custom: undefined,
|
|
14
14
|
};
|
|
15
15
|
const DEFAULT_BASE_URL = "http://localhost:3000";
|
|
16
|
-
const DEFAULT_RUNS = 1;
|
|
17
16
|
const DEFAULT_PROJECT_ROOT = ".";
|
|
18
17
|
const DEFAULT_PRESELECT_COUNT = 5;
|
|
19
18
|
const DEFAULT_DEVICES = ["mobile", "desktop"];
|
|
@@ -51,13 +50,6 @@ const baseQuestions = [
|
|
|
51
50
|
message: "Query string appended to every route (optional)",
|
|
52
51
|
initial: "",
|
|
53
52
|
},
|
|
54
|
-
{
|
|
55
|
-
type: "number",
|
|
56
|
-
name: "runs",
|
|
57
|
-
message: "Number of Lighthouse runs per page/device",
|
|
58
|
-
initial: DEFAULT_RUNS,
|
|
59
|
-
min: 1,
|
|
60
|
-
},
|
|
61
53
|
];
|
|
62
54
|
const pageQuestions = [
|
|
63
55
|
{
|
|
@@ -95,12 +87,6 @@ const addMorePagesQuestion = {
|
|
|
95
87
|
message: "Add another page to audit?",
|
|
96
88
|
initial: false,
|
|
97
89
|
};
|
|
98
|
-
const detectRoutesQuestion = {
|
|
99
|
-
type: "confirm",
|
|
100
|
-
name: "value",
|
|
101
|
-
message: "Attempt to auto-detect routes from your project?",
|
|
102
|
-
initial: true,
|
|
103
|
-
};
|
|
104
90
|
const projectRootQuestion = {
|
|
105
91
|
type: "text",
|
|
106
92
|
name: "projectRoot",
|
|
@@ -128,6 +114,13 @@ async function ask(question) {
|
|
|
128
114
|
const answers = await prompts(question, PROMPT_OPTIONS);
|
|
129
115
|
return answers;
|
|
130
116
|
}
|
|
117
|
+
async function collectBaseAnswers() {
|
|
118
|
+
const answers = await ask(baseQuestions);
|
|
119
|
+
return {
|
|
120
|
+
baseUrl: answers.baseUrl,
|
|
121
|
+
query: answers.query,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
131
124
|
function parseArgs(argv) {
|
|
132
125
|
let configPath;
|
|
133
126
|
for (let index = 2; index < argv.length; index += 1) {
|
|
@@ -159,12 +152,11 @@ async function ensureWritable(path) {
|
|
|
159
152
|
console.log("Aborted. Existing config preserved.");
|
|
160
153
|
process.exit(0);
|
|
161
154
|
}
|
|
162
|
-
|
|
163
|
-
const answers = await ask(baseQuestions);
|
|
155
|
+
function buildBaseConfig(answers) {
|
|
164
156
|
return {
|
|
165
157
|
baseUrl: answers.baseUrl.trim(),
|
|
166
158
|
query: answers.query && answers.query.length > 0 ? answers.query : undefined,
|
|
167
|
-
runs:
|
|
159
|
+
runs: 1,
|
|
168
160
|
};
|
|
169
161
|
}
|
|
170
162
|
async function confirmAddPage(hasPages) {
|
|
@@ -179,6 +171,10 @@ async function collectSinglePage() {
|
|
|
179
171
|
}
|
|
180
172
|
async function collectPages(initialPages) {
|
|
181
173
|
const pages = [...initialPages];
|
|
174
|
+
// If we already detected pages, return them immediately for speed.
|
|
175
|
+
if (pages.length > 0) {
|
|
176
|
+
return pages;
|
|
177
|
+
}
|
|
182
178
|
while (true) {
|
|
183
179
|
const shouldAdd = await confirmAddPage(pages.length > 0);
|
|
184
180
|
if (!shouldAdd) {
|
|
@@ -192,10 +188,6 @@ async function collectPages(initialPages) {
|
|
|
192
188
|
}
|
|
193
189
|
}
|
|
194
190
|
async function maybeDetectPages(profile) {
|
|
195
|
-
const shouldDetect = await ask(detectRoutesQuestion);
|
|
196
|
-
if (!shouldDetect.value) {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
191
|
const preferredDetector = await selectDetector(profile);
|
|
200
192
|
const projectRootAnswer = await ask(projectRootQuestion);
|
|
201
193
|
const repoRoot = resolve(projectRootAnswer.projectRoot);
|
|
@@ -278,12 +270,13 @@ function convertRouteToPage(route) {
|
|
|
278
270
|
async function buildConfig() {
|
|
279
271
|
const profileAnswer = await ask(profileQuestion);
|
|
280
272
|
const baseAnswers = await collectBaseAnswers();
|
|
273
|
+
console.log("Tip: parallel workers auto-tune from CPU/memory. Override later with --parallel <n> or inspect with --show-parallel.");
|
|
281
274
|
const detectedPages = await maybeDetectPages(profileAnswer.profile);
|
|
282
275
|
const pages = await collectPages(detectedPages);
|
|
283
276
|
return {
|
|
284
277
|
baseUrl: baseAnswers.baseUrl,
|
|
285
278
|
query: baseAnswers.query,
|
|
286
|
-
runs:
|
|
279
|
+
runs: 1,
|
|
287
280
|
pages,
|
|
288
281
|
};
|
|
289
282
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apex-auditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI to run structured Lighthouse audits (Performance, Accessibility, Best Practices, SEO) across routes.",
|
|
6
6
|
"type": "module",
|
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"chrome-launcher": "^1.2.1",
|
|
22
22
|
"lighthouse": "^13.0.1",
|
|
23
|
-
"prompts": "^2.4.2"
|
|
23
|
+
"prompts": "^2.4.2",
|
|
24
|
+
"ws": "^8.18.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"tsx": "^4.20.6",
|
|
27
28
|
"typescript": "^5.9.3",
|
|
28
29
|
"@types/node": "^22.0.0",
|
|
29
30
|
"@types/prompts": "^2.4.9",
|
|
31
|
+
"@types/ws": "^8.5.14",
|
|
30
32
|
"vitest": "^4.0.14"
|
|
31
33
|
}
|
|
32
34
|
}
|