apex-auditor 0.1.5 → 0.1.6
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/dist/cli.js +85 -2
- package/dist/lighthouse-runner.js +23 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,10 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
import { runAuditsForConfig } from "./lighthouse-runner.js";
|
|
5
|
+
const ANSI_RESET = "\u001B[0m";
|
|
6
|
+
const ANSI_RED = "\u001B[31m";
|
|
7
|
+
const ANSI_YELLOW = "\u001B[33m";
|
|
8
|
+
const ANSI_GREEN = "\u001B[32m";
|
|
5
9
|
function parseArgs(argv) {
|
|
6
10
|
let configPath;
|
|
7
11
|
for (let i = 2; i < argv.length; i += 1) {
|
|
@@ -28,9 +32,11 @@ export async function runAuditCli(argv) {
|
|
|
28
32
|
await writeFile(resolve(outputDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
|
|
29
33
|
const markdown = buildMarkdown(summary.results);
|
|
30
34
|
await writeFile(resolve(outputDir, "summary.md"), markdown, "utf8");
|
|
31
|
-
// Also echo a compact table to stdout for quick viewing.
|
|
35
|
+
// Also echo a compact, colourised table to stdout for quick viewing.
|
|
36
|
+
const consoleTable = buildConsoleTable(summary.results);
|
|
32
37
|
// eslint-disable-next-line no-console
|
|
33
|
-
console.log(
|
|
38
|
+
console.log(consoleTable);
|
|
39
|
+
printRedIssues(summary.results);
|
|
34
40
|
}
|
|
35
41
|
function buildMarkdown(results) {
|
|
36
42
|
const header = [
|
|
@@ -40,6 +46,14 @@ function buildMarkdown(results) {
|
|
|
40
46
|
const lines = results.map((result) => buildRow(result));
|
|
41
47
|
return `${header}\n${lines.join("\n")}`;
|
|
42
48
|
}
|
|
49
|
+
function buildConsoleTable(results) {
|
|
50
|
+
const header = [
|
|
51
|
+
"| Label | Path | Device | P | A | BP | SEO | LCP (s) | FCP (s) | TBT (ms) | CLS | Error | Top issues |",
|
|
52
|
+
"|-------|------|--------|---|---|----|-----|---------|---------|----------|-----|-------|-----------|",
|
|
53
|
+
].join("\n");
|
|
54
|
+
const lines = results.map((result) => buildConsoleRow(result));
|
|
55
|
+
return `${header}\n${lines.join("\n")}`;
|
|
56
|
+
}
|
|
43
57
|
function buildRow(result) {
|
|
44
58
|
const scores = result.scores;
|
|
45
59
|
const metrics = result.metrics;
|
|
@@ -51,6 +65,21 @@ function buildRow(result) {
|
|
|
51
65
|
const error = result.runtimeErrorCode ?? (result.runtimeErrorMessage !== undefined ? result.runtimeErrorMessage : "");
|
|
52
66
|
return `| ${result.label} | ${result.path} | ${result.device} | ${scores.performance ?? "-"} | ${scores.accessibility ?? "-"} | ${scores.bestPractices ?? "-"} | ${scores.seo ?? "-"} | ${lcpSeconds} | ${fcpSeconds} | ${tbtMs} | ${cls} | ${error} | ${issues} |`;
|
|
53
67
|
}
|
|
68
|
+
function buildConsoleRow(result) {
|
|
69
|
+
const scores = result.scores;
|
|
70
|
+
const metrics = result.metrics;
|
|
71
|
+
const lcpSeconds = metrics.lcpMs !== undefined ? (metrics.lcpMs / 1000).toFixed(1) : "-";
|
|
72
|
+
const fcpSeconds = metrics.fcpMs !== undefined ? (metrics.fcpMs / 1000).toFixed(1) : "-";
|
|
73
|
+
const tbtMs = metrics.tbtMs !== undefined ? Math.round(metrics.tbtMs).toString() : "-";
|
|
74
|
+
const cls = metrics.cls !== undefined ? metrics.cls.toFixed(3) : "-";
|
|
75
|
+
const issues = formatTopIssues(result.opportunities);
|
|
76
|
+
const error = result.runtimeErrorCode ?? (result.runtimeErrorMessage !== undefined ? result.runtimeErrorMessage : "");
|
|
77
|
+
const performanceText = colourScore(scores.performance);
|
|
78
|
+
const accessibilityText = colourScore(scores.accessibility);
|
|
79
|
+
const bestPracticesText = colourScore(scores.bestPractices);
|
|
80
|
+
const seoText = colourScore(scores.seo);
|
|
81
|
+
return `| ${result.label} | ${result.path} | ${result.device} | ${performanceText} | ${accessibilityText} | ${bestPracticesText} | ${seoText} | ${lcpSeconds} | ${fcpSeconds} | ${tbtMs} | ${cls} | ${error} | ${issues} |`;
|
|
82
|
+
}
|
|
54
83
|
function formatTopIssues(opportunities) {
|
|
55
84
|
if (opportunities.length === 0) {
|
|
56
85
|
return "";
|
|
@@ -64,3 +93,57 @@ function formatTopIssues(opportunities) {
|
|
|
64
93
|
});
|
|
65
94
|
return items.join("; ");
|
|
66
95
|
}
|
|
96
|
+
function colourScore(score) {
|
|
97
|
+
if (score === undefined) {
|
|
98
|
+
return "-";
|
|
99
|
+
}
|
|
100
|
+
const value = score;
|
|
101
|
+
const text = value.toString();
|
|
102
|
+
let colour;
|
|
103
|
+
if (value < 50) {
|
|
104
|
+
colour = ANSI_RED;
|
|
105
|
+
}
|
|
106
|
+
else if (value < 90) {
|
|
107
|
+
colour = ANSI_YELLOW;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
colour = ANSI_GREEN;
|
|
111
|
+
}
|
|
112
|
+
return `${colour}${text}${ANSI_RESET}`;
|
|
113
|
+
}
|
|
114
|
+
function isRedScore(score) {
|
|
115
|
+
return typeof score === "number" && score < 50;
|
|
116
|
+
}
|
|
117
|
+
function printRedIssues(results) {
|
|
118
|
+
const redResults = results.filter((result) => {
|
|
119
|
+
const scores = result.scores;
|
|
120
|
+
return (isRedScore(scores.performance) ||
|
|
121
|
+
isRedScore(scores.accessibility) ||
|
|
122
|
+
isRedScore(scores.bestPractices) ||
|
|
123
|
+
isRedScore(scores.seo));
|
|
124
|
+
});
|
|
125
|
+
if (redResults.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.log("\nRed issues (scores below 50):");
|
|
130
|
+
for (const result of redResults) {
|
|
131
|
+
const scores = result.scores;
|
|
132
|
+
const badParts = [];
|
|
133
|
+
if (isRedScore(scores.performance)) {
|
|
134
|
+
badParts.push(`P:${scores.performance}`);
|
|
135
|
+
}
|
|
136
|
+
if (isRedScore(scores.accessibility)) {
|
|
137
|
+
badParts.push(`A:${scores.accessibility}`);
|
|
138
|
+
}
|
|
139
|
+
if (isRedScore(scores.bestPractices)) {
|
|
140
|
+
badParts.push(`BP:${scores.bestPractices}`);
|
|
141
|
+
}
|
|
142
|
+
if (isRedScore(scores.seo)) {
|
|
143
|
+
badParts.push(`SEO:${scores.seo}`);
|
|
144
|
+
}
|
|
145
|
+
const issues = formatTopIssues(result.opportunities);
|
|
146
|
+
// eslint-disable-next-line no-console
|
|
147
|
+
console.log(`- ${result.label} ${result.path} [${result.device}] – ${badParts.join(", ")} – ${issues}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -68,6 +68,8 @@ export async function runAuditsForConfig({ config, configPath, }) {
|
|
|
68
68
|
const firstPage = config.pages[0];
|
|
69
69
|
const healthCheckUrl = buildUrl({ baseUrl: config.baseUrl, path: firstPage.path, query: config.query });
|
|
70
70
|
await ensureUrlReachable(healthCheckUrl);
|
|
71
|
+
const totalSteps = config.pages.reduce((sum, page) => sum + page.devices.length * runs, 0);
|
|
72
|
+
let completedSteps = 0;
|
|
71
73
|
const session = await createChromeSession(config.chromePort);
|
|
72
74
|
try {
|
|
73
75
|
for (const page of config.pages) {
|
|
@@ -84,6 +86,13 @@ export async function runAuditsForConfig({ config, configPath, }) {
|
|
|
84
86
|
logLevel: config.logLevel ?? "error",
|
|
85
87
|
});
|
|
86
88
|
summaries.push(summary);
|
|
89
|
+
completedSteps += 1;
|
|
90
|
+
logProgress({
|
|
91
|
+
completed: completedSteps,
|
|
92
|
+
total: totalSteps,
|
|
93
|
+
path: page.path,
|
|
94
|
+
device,
|
|
95
|
+
});
|
|
87
96
|
}
|
|
88
97
|
results.push(aggregateSummaries(summaries));
|
|
89
98
|
}
|
|
@@ -102,6 +111,20 @@ function buildUrl({ baseUrl, path, query }) {
|
|
|
102
111
|
const queryPart = query && query.length > 0 ? query : "";
|
|
103
112
|
return `${cleanBase}${cleanPath}${queryPart}`;
|
|
104
113
|
}
|
|
114
|
+
function logProgress({ completed, total, path, device, }) {
|
|
115
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
116
|
+
const message = `Running audits ${completed}/${total} (${percentage}%) – ${path} [${device}]`;
|
|
117
|
+
if (typeof process !== "undefined" && process.stdout && typeof process.stdout.write === "function" && process.stdout.isTTY) {
|
|
118
|
+
const padded = message.padEnd(80, " ");
|
|
119
|
+
process.stdout.write(`\r${padded}`);
|
|
120
|
+
if (completed === total) {
|
|
121
|
+
process.stdout.write("\n");
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.log(message);
|
|
127
|
+
}
|
|
105
128
|
async function runSingleAudit(params) {
|
|
106
129
|
const options = {
|
|
107
130
|
port: params.port,
|