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.
@@ -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
+ }
@@ -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
+ }
@@ -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
- async function collectBaseAnswers() {
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: answers.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: baseAnswers.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.2.9",
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
  }