apex-auditor 0.2.3 → 0.2.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/bin.js +2 -0
- package/dist/cli.js +89 -14
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -33,6 +33,8 @@ function printHelp() {
|
|
|
33
33
|
" --no-color Disable ANSI colours in console output (default in CI mode)",
|
|
34
34
|
" --color Force ANSI colours in console output",
|
|
35
35
|
" --log-level <lvl> Override Lighthouse log level: silent|error|info|verbose",
|
|
36
|
+
" --mobile-only Run audits only for 'mobile' devices defined in the config",
|
|
37
|
+
" --desktop-only Run audits only for 'desktop' devices defined in the config",
|
|
36
38
|
].join("\n"));
|
|
37
39
|
}
|
|
38
40
|
export async function runBin(argv) {
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ function parseArgs(argv) {
|
|
|
21
21
|
let ci = false;
|
|
22
22
|
let colorMode = "auto";
|
|
23
23
|
let logLevelOverride;
|
|
24
|
+
let deviceFilter;
|
|
24
25
|
for (let i = 2; i < argv.length; i += 1) {
|
|
25
26
|
const arg = argv[i];
|
|
26
27
|
if ((arg === "--config" || arg === "-c") && i + 1 < argv.length) {
|
|
@@ -46,9 +47,21 @@ function parseArgs(argv) {
|
|
|
46
47
|
}
|
|
47
48
|
i += 1;
|
|
48
49
|
}
|
|
50
|
+
else if (arg === "--mobile-only") {
|
|
51
|
+
if (deviceFilter !== undefined && deviceFilter !== "mobile") {
|
|
52
|
+
throw new Error("Cannot combine --mobile-only and --desktop-only");
|
|
53
|
+
}
|
|
54
|
+
deviceFilter = "mobile";
|
|
55
|
+
}
|
|
56
|
+
else if (arg === "--desktop-only") {
|
|
57
|
+
if (deviceFilter !== undefined && deviceFilter !== "desktop") {
|
|
58
|
+
throw new Error("Cannot combine --mobile-only and --desktop-only");
|
|
59
|
+
}
|
|
60
|
+
deviceFilter = "desktop";
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
const finalConfigPath = configPath ?? "apex.config.json";
|
|
51
|
-
return { configPath: finalConfigPath, ci, colorMode, logLevelOverride };
|
|
64
|
+
return { configPath: finalConfigPath, ci, colorMode, logLevelOverride, deviceFilter };
|
|
52
65
|
}
|
|
53
66
|
/**
|
|
54
67
|
* Runs the ApexAuditor audit CLI.
|
|
@@ -64,7 +77,14 @@ export async function runAuditCli(argv) {
|
|
|
64
77
|
...config,
|
|
65
78
|
logLevel: effectiveLogLevel,
|
|
66
79
|
};
|
|
67
|
-
const
|
|
80
|
+
const filteredConfig = filterConfigDevices(effectiveConfig, args.deviceFilter);
|
|
81
|
+
if (filteredConfig.pages.length === 0) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.error("No pages remain after applying device filter. Check your config and device flags.");
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const summary = await runAuditsForConfig({ config: filteredConfig, configPath });
|
|
68
88
|
const outputDir = resolve(".apex-auditor");
|
|
69
89
|
await mkdir(outputDir, { recursive: true });
|
|
70
90
|
await writeFile(resolve(outputDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
|
|
@@ -87,6 +107,28 @@ export async function runAuditCli(argv) {
|
|
|
87
107
|
// eslint-disable-next-line no-console
|
|
88
108
|
console.log(`\nCompleted in ${elapsedDisplay} (${comboCount} page/device combinations x ${runsPerTarget} runs = ${totalRuns} Lighthouse runs).`);
|
|
89
109
|
}
|
|
110
|
+
function filterConfigDevices(config, deviceFilter) {
|
|
111
|
+
if (deviceFilter === undefined) {
|
|
112
|
+
return config;
|
|
113
|
+
}
|
|
114
|
+
const filteredPages = config.pages
|
|
115
|
+
.map((page) => filterPageDevices(page, deviceFilter))
|
|
116
|
+
.filter((page) => page !== undefined);
|
|
117
|
+
return {
|
|
118
|
+
...config,
|
|
119
|
+
pages: filteredPages,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function filterPageDevices(page, deviceFilter) {
|
|
123
|
+
const devices = page.devices.filter((device) => device === deviceFilter);
|
|
124
|
+
if (devices.length === 0) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
...page,
|
|
129
|
+
devices,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
90
132
|
function buildMarkdown(results) {
|
|
91
133
|
const header = [
|
|
92
134
|
"| Label | Path | Device | P | A | BP | SEO | LCP (s) | FCP (s) | TBT (ms) | CLS | Error | Top issues |",
|
|
@@ -97,10 +139,19 @@ function buildMarkdown(results) {
|
|
|
97
139
|
}
|
|
98
140
|
function buildConsoleTable(results, useColor) {
|
|
99
141
|
const header = [
|
|
100
|
-
"| Label | Path | Device | P | A | BP | SEO |
|
|
101
|
-
"
|
|
142
|
+
"| Label | Path | Device | P | A | BP | SEO |",
|
|
143
|
+
"|-------|------|--------|---|---|----|-----|",
|
|
102
144
|
].join("\n");
|
|
103
|
-
const rows =
|
|
145
|
+
const rows = [];
|
|
146
|
+
let previousKey;
|
|
147
|
+
for (const result of results) {
|
|
148
|
+
const key = `${result.label}:::${result.path}`;
|
|
149
|
+
if (previousKey !== undefined && key !== previousKey) {
|
|
150
|
+
rows.push("");
|
|
151
|
+
}
|
|
152
|
+
rows.push(buildConsoleRow(result, useColor));
|
|
153
|
+
previousKey = key;
|
|
154
|
+
}
|
|
104
155
|
return `${header}\n${rows.join("\n")}`;
|
|
105
156
|
}
|
|
106
157
|
function buildRow(result) {
|
|
@@ -135,7 +186,7 @@ function buildConsoleScoreLine(result, useColor) {
|
|
|
135
186
|
const bestPracticesText = colourScore(scores.bestPractices, useColor);
|
|
136
187
|
const seoText = colourScore(scores.seo, useColor);
|
|
137
188
|
const deviceText = formatDeviceLabel(result.device, useColor);
|
|
138
|
-
return `| ${result.label} | ${result.path} | ${deviceText} | ${performanceText} | ${accessibilityText} | ${bestPracticesText} | ${seoText}
|
|
189
|
+
return `| ${result.label} | ${result.path} | ${deviceText} | ${performanceText} | ${accessibilityText} | ${bestPracticesText} | ${seoText} |`;
|
|
139
190
|
}
|
|
140
191
|
function buildConsoleMetricsLine(result, useColor) {
|
|
141
192
|
const metrics = result.metrics;
|
|
@@ -143,7 +194,8 @@ function buildConsoleMetricsLine(result, useColor) {
|
|
|
143
194
|
const fcpText = formatMetricSeconds(metrics.fcpMs, FCP_GOOD_MS, FCP_WARN_MS, useColor);
|
|
144
195
|
const tbtText = formatMetricMilliseconds(metrics.tbtMs, TBT_GOOD_MS, TBT_WARN_MS, useColor);
|
|
145
196
|
const clsText = formatMetricRatio(metrics.cls, CLS_GOOD, CLS_WARN, useColor);
|
|
146
|
-
|
|
197
|
+
const parts = [`LCP ${lcpText}`, `FCP ${fcpText}`, `TBT ${tbtText}`, `CLS ${clsText}`];
|
|
198
|
+
return ` ↳ Metrics: ${parts.join(" | ")}`;
|
|
147
199
|
}
|
|
148
200
|
function buildConsoleErrorLine(result, useColor) {
|
|
149
201
|
const errorCode = result.runtimeErrorCode;
|
|
@@ -166,15 +218,38 @@ function formatTopIssues(opportunities) {
|
|
|
166
218
|
if (opportunities.length === 0) {
|
|
167
219
|
return "";
|
|
168
220
|
}
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
221
|
+
const meaningful = opportunities.filter((opp) => hasMeaningfulSavings(opp));
|
|
222
|
+
const source = meaningful.length > 0 ? meaningful : opportunities;
|
|
223
|
+
const sorted = [...source].sort(compareOpportunitiesByImpact);
|
|
224
|
+
const limit = 2;
|
|
225
|
+
const top = sorted.slice(0, limit);
|
|
226
|
+
const items = top.map((opp) => formatOpportunityLabel(opp));
|
|
176
227
|
return items.join("; ");
|
|
177
228
|
}
|
|
229
|
+
function hasMeaningfulSavings(opportunity) {
|
|
230
|
+
const savingsMs = opportunity.estimatedSavingsMs ?? 0;
|
|
231
|
+
const savingsBytes = opportunity.estimatedSavingsBytes ?? 0;
|
|
232
|
+
return savingsMs > 0 || savingsBytes > 0;
|
|
233
|
+
}
|
|
234
|
+
function compareOpportunitiesByImpact(a, b) {
|
|
235
|
+
const aMs = a.estimatedSavingsMs ?? 0;
|
|
236
|
+
const bMs = b.estimatedSavingsMs ?? 0;
|
|
237
|
+
if (aMs !== bMs) {
|
|
238
|
+
return bMs - aMs;
|
|
239
|
+
}
|
|
240
|
+
const aBytes = a.estimatedSavingsBytes ?? 0;
|
|
241
|
+
const bBytes = b.estimatedSavingsBytes ?? 0;
|
|
242
|
+
return bBytes - aBytes;
|
|
243
|
+
}
|
|
244
|
+
function formatOpportunityLabel(opportunity) {
|
|
245
|
+
const savingsMs = opportunity.estimatedSavingsMs !== undefined ? `${Math.round(opportunity.estimatedSavingsMs)}ms` : "";
|
|
246
|
+
const savingsBytes = opportunity.estimatedSavingsBytes !== undefined
|
|
247
|
+
? `${Math.round(opportunity.estimatedSavingsBytes / 1024)}KB`
|
|
248
|
+
: "";
|
|
249
|
+
const parts = [savingsMs, savingsBytes].filter((part) => part.length > 0);
|
|
250
|
+
const suffix = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
251
|
+
return `${opportunity.id}${suffix}`;
|
|
252
|
+
}
|
|
178
253
|
function formatMetricSeconds(valueMs, goodThresholdMs, warnThresholdMs, useColor) {
|
|
179
254
|
if (valueMs === undefined) {
|
|
180
255
|
return "-";
|