apex-auditor 0.1.3 → 0.1.5
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 +4 -3
- package/dist/config.js +5 -0
- package/dist/lighthouse-runner.js +46 -2
- package/dist/wizard-cli.js +2 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -34,8 +34,8 @@ export async function runAuditCli(argv) {
|
|
|
34
34
|
}
|
|
35
35
|
function buildMarkdown(results) {
|
|
36
36
|
const header = [
|
|
37
|
-
"| Label | Path | Device | P | A | BP | SEO | LCP (s) | FCP (s) | TBT (ms) | CLS | Top issues |",
|
|
38
|
-
"
|
|
37
|
+
"| Label | Path | Device | P | A | BP | SEO | LCP (s) | FCP (s) | TBT (ms) | CLS | Error | Top issues |",
|
|
38
|
+
"|-------|------|--------|---|---|----|-----|---------|---------|----------|-----|-------|-----------|",
|
|
39
39
|
].join("\n");
|
|
40
40
|
const lines = results.map((result) => buildRow(result));
|
|
41
41
|
return `${header}\n${lines.join("\n")}`;
|
|
@@ -48,7 +48,8 @@ function buildRow(result) {
|
|
|
48
48
|
const tbtMs = metrics.tbtMs !== undefined ? Math.round(metrics.tbtMs).toString() : "-";
|
|
49
49
|
const cls = metrics.cls !== undefined ? metrics.cls.toFixed(3) : "-";
|
|
50
50
|
const issues = formatTopIssues(result.opportunities);
|
|
51
|
-
|
|
51
|
+
const error = result.runtimeErrorCode ?? (result.runtimeErrorMessage !== undefined ? result.runtimeErrorMessage : "");
|
|
52
|
+
return `| ${result.label} | ${result.path} | ${result.device} | ${scores.performance ?? "-"} | ${scores.accessibility ?? "-"} | ${scores.bestPractices ?? "-"} | ${scores.seo ?? "-"} | ${lcpSeconds} | ${fcpSeconds} | ${tbtMs} | ${cls} | ${error} | ${issues} |`;
|
|
52
53
|
}
|
|
53
54
|
function formatTopIssues(opportunities) {
|
|
54
55
|
if (opportunities.length === 0) {
|
package/dist/config.js
CHANGED
|
@@ -27,11 +27,16 @@ function normaliseConfig(input, absolutePath) {
|
|
|
27
27
|
const query = typeof maybeConfig.query === "string" ? maybeConfig.query : undefined;
|
|
28
28
|
const chromePort = typeof maybeConfig.chromePort === "number" ? maybeConfig.chromePort : undefined;
|
|
29
29
|
const runs = typeof maybeConfig.runs === "number" && maybeConfig.runs > 0 ? maybeConfig.runs : undefined;
|
|
30
|
+
const rawLogLevel = maybeConfig.logLevel;
|
|
31
|
+
const logLevel = rawLogLevel === "silent" || rawLogLevel === "error" || rawLogLevel === "info" || rawLogLevel === "verbose"
|
|
32
|
+
? rawLogLevel
|
|
33
|
+
: undefined;
|
|
30
34
|
return {
|
|
31
35
|
baseUrl,
|
|
32
36
|
query,
|
|
33
37
|
chromePort,
|
|
34
38
|
runs,
|
|
39
|
+
logLevel,
|
|
35
40
|
pages,
|
|
36
41
|
};
|
|
37
42
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
1
3
|
import lighthouse from "lighthouse";
|
|
2
4
|
import { launch as launchChrome } from "chrome-launcher";
|
|
3
5
|
async function createChromeSession(chromePort) {
|
|
@@ -19,16 +21,53 @@ async function createChromeSession(chromePort) {
|
|
|
19
21
|
return {
|
|
20
22
|
port: chrome.port,
|
|
21
23
|
close: async () => {
|
|
22
|
-
|
|
24
|
+
try {
|
|
25
|
+
await chrome.kill();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
23
30
|
},
|
|
24
31
|
};
|
|
25
32
|
}
|
|
33
|
+
async function ensureUrlReachable(url) {
|
|
34
|
+
const parsed = new URL(url);
|
|
35
|
+
const client = parsed.protocol === "https:" ? httpsRequest : httpRequest;
|
|
36
|
+
await new Promise((resolve, reject) => {
|
|
37
|
+
const request = client({
|
|
38
|
+
hostname: parsed.hostname,
|
|
39
|
+
port: parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80,
|
|
40
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
41
|
+
method: "GET",
|
|
42
|
+
}, (response) => {
|
|
43
|
+
const statusCode = response.statusCode ?? 0;
|
|
44
|
+
response.resume();
|
|
45
|
+
if (statusCode >= 200 && statusCode < 400) {
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
reject(new Error(`HTTP ${statusCode}`));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
request.on("error", (error) => {
|
|
53
|
+
reject(error);
|
|
54
|
+
});
|
|
55
|
+
request.end();
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.error(`Could not reach ${url}. Is your dev server running?`, error);
|
|
59
|
+
throw error instanceof Error ? error : new Error(`URL not reachable: ${url}`);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
26
62
|
/**
|
|
27
63
|
* Run audits for all pages defined in the config and return a structured summary.
|
|
28
64
|
*/
|
|
29
65
|
export async function runAuditsForConfig({ config, configPath, }) {
|
|
30
66
|
const runs = config.runs ?? 1;
|
|
31
67
|
const results = [];
|
|
68
|
+
const firstPage = config.pages[0];
|
|
69
|
+
const healthCheckUrl = buildUrl({ baseUrl: config.baseUrl, path: firstPage.path, query: config.query });
|
|
70
|
+
await ensureUrlReachable(healthCheckUrl);
|
|
32
71
|
const session = await createChromeSession(config.chromePort);
|
|
33
72
|
try {
|
|
34
73
|
for (const page of config.pages) {
|
|
@@ -42,6 +81,7 @@ export async function runAuditsForConfig({ config, configPath, }) {
|
|
|
42
81
|
label: page.label,
|
|
43
82
|
device,
|
|
44
83
|
port: session.port,
|
|
84
|
+
logLevel: config.logLevel ?? "error",
|
|
45
85
|
});
|
|
46
86
|
summaries.push(summary);
|
|
47
87
|
}
|
|
@@ -66,7 +106,7 @@ async function runSingleAudit(params) {
|
|
|
66
106
|
const options = {
|
|
67
107
|
port: params.port,
|
|
68
108
|
output: "json",
|
|
69
|
-
logLevel:
|
|
109
|
+
logLevel: params.logLevel,
|
|
70
110
|
onlyCategories: ["performance", "accessibility", "best-practices", "seo"],
|
|
71
111
|
emulatedFormFactor: params.device,
|
|
72
112
|
};
|
|
@@ -87,6 +127,8 @@ async function runSingleAudit(params) {
|
|
|
87
127
|
scores,
|
|
88
128
|
metrics,
|
|
89
129
|
opportunities,
|
|
130
|
+
runtimeErrorCode: typeof lhr.runtimeError?.code === "string" ? lhr.runtimeError.code : undefined,
|
|
131
|
+
runtimeErrorMessage: typeof lhr.runtimeError?.message === "string" ? lhr.runtimeError.message : undefined,
|
|
90
132
|
};
|
|
91
133
|
}
|
|
92
134
|
function extractScores(lhr) {
|
|
@@ -168,6 +210,8 @@ function aggregateSummaries(summaries) {
|
|
|
168
210
|
scores: aggregateScores,
|
|
169
211
|
metrics: aggregateMetrics,
|
|
170
212
|
opportunities,
|
|
213
|
+
runtimeErrorCode: base.runtimeErrorCode,
|
|
214
|
+
runtimeErrorMessage: base.runtimeErrorMessage,
|
|
171
215
|
};
|
|
172
216
|
}
|
|
173
217
|
function averageOf(values) {
|
package/dist/wizard-cli.js
CHANGED
|
@@ -246,7 +246,8 @@ async function selectDetectedRoutes(routes) {
|
|
|
246
246
|
type: "multiselect",
|
|
247
247
|
name: "indexes",
|
|
248
248
|
message: "Select routes to include",
|
|
249
|
-
instructions:
|
|
249
|
+
instructions: true,
|
|
250
|
+
hint: "Use Space to toggle, ↑/↓ to move, and Enter to confirm.",
|
|
250
251
|
min: 1,
|
|
251
252
|
choices: buildRouteChoices(routes),
|
|
252
253
|
});
|