open-local-audit 0.81.0 → 0.83.0

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,238 @@
1
+ import { compareReports } from "./compare.js";
2
+ import { escapeHtml, overallScore } from "./reporters.js";
3
+ function evidence(report, id) {
4
+ const finding = report.findings.find((item) => item.id === id);
5
+ return finding ? finding.evidence[0]?.value || finding.title : undefined;
6
+ }
7
+ // Values read back from older report files are coerced, so the page only ever prints numbers.
8
+ function wholeNumber(value) {
9
+ return typeof value === "number" && Number.isFinite(value) ? Math.round(value) : undefined;
10
+ }
11
+ function monthKey(date) {
12
+ return `${date.getFullYear()}-${date.getMonth()}`;
13
+ }
14
+ function historyRows(runs) {
15
+ const dates = runs.map(({ report }) => new Date(report.scannedAt));
16
+ const sharedMonth = new Set(dates.map(monthKey)).size < dates.length;
17
+ const manyYears = new Set(dates.map((date) => date.getFullYear())).size > 1;
18
+ return runs.map(({ report }, index) => {
19
+ const previous = runs[index - 1]?.report;
20
+ // The same rules as the monitoring comparison: a changed host, profile, or order starts a new baseline.
21
+ let comparison;
22
+ try {
23
+ comparison = previous ? compareReports(previous, report) : undefined;
24
+ }
25
+ catch {
26
+ comparison = undefined;
27
+ }
28
+ const severity = (level) => report.findings.filter((finding) => finding.severity === level).length;
29
+ const date = dates[index];
30
+ return {
31
+ date: date.toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" }),
32
+ // Label by month unless two runs share a month; add the year to day labels when the runs span years.
33
+ shortDate: date.toLocaleDateString("en-GB", sharedMonth ? { day: "numeric", month: "short", ...(manyYears ? { year: "2-digit" } : {}) } : { month: "short", year: "numeric" }),
34
+ score: overallScore(report),
35
+ ...(wholeNumber(report.lighthouse?.categories?.performance) === undefined
36
+ ? {}
37
+ : { performance: wholeNumber(report.lighthouse?.categories?.performance) }),
38
+ ...(wholeNumber(report.responseTimeMs) === undefined ? {} : { responseTimeMs: wholeNumber(report.responseTimeMs) }),
39
+ high: severity("high"),
40
+ medium: severity("medium"),
41
+ low: severity("low"),
42
+ fixed: comparison ? comparison.fixed.map((finding) => finding.title) : [],
43
+ added: comparison ? comparison.introduced.map((finding) => finding.title) : [],
44
+ ...(comparison ? {} : { baseline: previous ? "New baseline" : "First check" }),
45
+ ...(evidence(report, "tls-certificate-valid") ? { certificate: evidence(report, "tls-certificate-valid") } : {}),
46
+ ...(evidence(report, "domain-expiry") ? { domain: evidence(report, "domain-expiry") } : {})
47
+ };
48
+ });
49
+ }
50
+ const width = 640;
51
+ const height = 240;
52
+ const plot = { left: 40, right: 96, top: 16, bottom: 32 };
53
+ function chart(rows) {
54
+ const showSpeed = rows.some((row) => row.performance !== undefined);
55
+ const plotWidth = width - plot.left - plot.right;
56
+ const plotHeight = height - plot.top - plot.bottom;
57
+ const x = (index) => plot.left + (rows.length === 1 ? plotWidth / 2 : (index * plotWidth) / (rows.length - 1));
58
+ const y = (value) => plot.top + plotHeight - (value / 100) * plotHeight;
59
+ const grid = [0, 25, 50, 75, 100]
60
+ .map((value) => `<line class="grid" x1="${plot.left}" x2="${width - plot.right}" y1="${y(value)}" y2="${y(value)}"/><text class="axis" x="${plot.left - 8}" y="${y(value) + 4}" text-anchor="end">${value}</text>`)
61
+ .join("");
62
+ // Count labels back from the newest check so it is always labelled and the spacing stays even.
63
+ const labelEvery = Math.max(1, Math.ceil(rows.length / 8));
64
+ const xLabels = rows
65
+ .map((row, index) => (rows.length - 1 - index) % labelEvery === 0
66
+ ? `<text class="axis" x="${x(index)}" y="${height - 10}" text-anchor="middle">${escapeHtml(row.shortDate)}</text>`
67
+ : "")
68
+ .join("");
69
+ const pointsFor = (key) => rows.flatMap((row, index) => (row[key] === undefined ? [] : [{ index, value: row[key] }]));
70
+ // A run without a value breaks the line, so no line implies data that does not exist.
71
+ const segments = (points) => points.reduce((all, point) => {
72
+ const current = all[all.length - 1];
73
+ if (current && current[current.length - 1].index === point.index - 1) {
74
+ current.push(point);
75
+ }
76
+ else {
77
+ all.push([point]);
78
+ }
79
+ return all;
80
+ }, []);
81
+ const scorePoints = pointsFor("score");
82
+ const speedPoints = showSpeed ? pointsFor("performance") : [];
83
+ // End labels sit at the newest check; when both are close, they are pushed apart so they never overlap.
84
+ const labels = [
85
+ { label: "Score", points: scorePoints },
86
+ { label: "Speed", points: speedPoints }
87
+ ].flatMap(({ label, points }) => {
88
+ const last = points[points.length - 1];
89
+ return last && last.index === rows.length - 1 ? [{ text: `${label} ${last.value}`, y: y(last.value) + 4 }] : [];
90
+ });
91
+ if (labels.length === 2 && Math.abs(labels[0].y - labels[1].y) < 14) {
92
+ const [upper, lower] = labels[0].y <= labels[1].y ? [labels[0], labels[1]] : [labels[1], labels[0]];
93
+ const middle = (upper.y + lower.y) / 2;
94
+ upper.y = middle - 7;
95
+ lower.y = middle + 7;
96
+ }
97
+ const series = (points, css) => [
98
+ ...segments(points)
99
+ .filter((segment) => segment.length > 1)
100
+ .map((segment) => `<polyline class="line ${css}" points="${segment.map((point) => `${x(point.index)},${y(point.value)}`).join(" ")}"/>`),
101
+ ...points.map((point) => `<circle class="dot ${css}" cx="${x(point.index)}" cy="${y(point.value)}" r="5"/>`)
102
+ ].join("");
103
+ const endLabels = labels
104
+ .map((label) => `<text class="end-label" x="${x(rows.length - 1) + 10}" y="${label.y}">${escapeHtml(label.text)}</text>`)
105
+ .join("");
106
+ return `<figure class="chart">
107
+ <figcaption>${showSpeed ? "Overall score and page speed" : "Overall score"} out of 100, by check</figcaption>
108
+ ${showSpeed
109
+ ? '<div class="legend"><span><i class="key score"></i>Overall score</span><span><i class="key speed"></i>Page speed (Lighthouse performance)</span></div>'
110
+ : ""}
111
+ <div class="plot">
112
+ <svg viewBox="0 0 ${width} ${height}" role="img" aria-label="${showSpeed ? "Overall score and page speed" : "Overall score"} for each check; the table below lists every value">
113
+ ${grid}${xLabels}
114
+ <line class="crosshair" x1="0" x2="0" y1="${plot.top}" y2="${plot.top + plotHeight}" visibility="hidden"/>
115
+ ${series(scorePoints, "score")}${series(speedPoints, "speed")}${endLabels}
116
+ <rect class="hit" x="${plot.left}" y="${plot.top}" width="${plotWidth}" height="${plotHeight}"/>
117
+ </svg>
118
+ <div class="tooltip" role="status" hidden></div>
119
+ </div>
120
+ </figure>`;
121
+ }
122
+ function list(items) {
123
+ return items.length === 0 ? "" : `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
124
+ }
125
+ // A self-contained page that shows every monitoring run of one site, newest last in the chart and first in the table.
126
+ export function renderSiteHistory(runs, options) {
127
+ const rows = historyRows(runs);
128
+ const showSpeed = rows.some((row) => row.performance !== undefined);
129
+ const showResponse = rows.some((row) => row.responseTimeMs !== undefined);
130
+ const brandName = options.brand?.name ?? "Open Local Audit";
131
+ const accent = options.brand?.primaryColor ?? "#145a73";
132
+ const data = rows.map((row) => ({ date: row.date, score: row.score, performance: row.performance ?? null }));
133
+ const tableRows = [...rows]
134
+ .reverse()
135
+ .map((row) => `<tr>
136
+ <th scope="row">${escapeHtml(row.date)}</th>
137
+ <td>${row.score}/100</td>
138
+ ${showSpeed ? `<td>${row.performance === undefined ? "–" : `${row.performance}/100`}</td>` : ""}
139
+ ${showResponse ? `<td>${row.responseTimeMs === undefined ? "–" : `${row.responseTimeMs} ms`}</td>` : ""}
140
+ <td>${row.high} high, ${row.medium} medium, ${row.low} low</td>
141
+ <td>${row.baseline ? escapeHtml(row.baseline) : `${list(row.fixed.map((title) => `Fixed: ${title}`))}${list(row.added.map((title) => `New: ${title}`))}` || "No change"}${row.certificate ? `<p class="flag">HTTPS certificate: ${escapeHtml(row.certificate)}</p>` : ""}${row.domain ? `<p class="flag">Domain registration: ${escapeHtml(row.domain)}</p>` : ""}</td>
142
+ </tr>`)
143
+ .join("");
144
+ const footer = [options.brand?.footerText, options.brand?.contact].filter(Boolean).join(" | ");
145
+ return `<!doctype html>
146
+ <html lang="en">
147
+ <head>
148
+ <meta charset="utf-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1">
150
+ <title>${escapeHtml(options.name)} website history</title>
151
+ <style>
152
+ :root { color-scheme: light; --surface: #fcfcfb; --plane: #f9f9f7; --ink: #0b0b0b; --ink-2: #52514e; --muted: #6b6a65; --grid: #e1e0d9; --border: rgba(11,11,11,0.10); --score: #2a78d6; --speed: #eb6834; --accent: ${escapeHtml(accent)}; --eyebrow: var(--accent); }
153
+ @media (prefers-color-scheme: dark) {
154
+ :root:where(:not([data-theme="light"])) { color-scheme: dark; --surface: #1a1a19; --plane: #0d0d0d; --ink: #f0efec; --ink-2: #c3c2b7; --muted: #898781; --grid: #2c2c2a; --border: rgba(255,255,255,0.10); --score: #3987e5; --speed: #d95926; --eyebrow: var(--ink-2); }
155
+ }
156
+ :root[data-theme="dark"] { color-scheme: dark; --surface: #1a1a19; --plane: #0d0d0d; --ink: #f0efec; --ink-2: #c3c2b7; --muted: #898781; --grid: #2c2c2a; --border: rgba(255,255,255,0.10); --score: #3987e5; --speed: #d95926; --eyebrow: var(--ink-2); }
157
+ body { margin: 0; background: var(--plane); color: var(--ink); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
158
+ main { max-width: 860px; margin: 0 auto; padding: 32px 16px; }
159
+ .eyebrow { color: var(--eyebrow); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; margin: 0; }
160
+ h1 { margin: 4px 0 4px; font-size: 1.6rem; overflow-wrap: anywhere; }
161
+ .meta, figcaption, .axis, .legend { color: var(--ink-2); }
162
+ .chart { margin: 24px 0; padding: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
163
+ figcaption { font-size: 0.9rem; margin-bottom: 8px; }
164
+ .legend { display: flex; gap: 16px; font-size: 0.85rem; margin-bottom: 8px; }
165
+ .key { display: inline-block; width: 14px; height: 2px; vertical-align: middle; margin-right: 6px; }
166
+ .key.score { background: var(--score); } .key.speed { background: var(--speed); }
167
+ /* Below 640px the chart scrolls sideways instead of shrinking its lines and labels. */
168
+ .plot { position: relative; overflow-x: auto; }
169
+ svg { width: 100%; min-width: 640px; height: auto; display: block; overflow: visible; }
170
+ .grid { stroke: var(--grid); stroke-width: 1; vector-effect: non-scaling-stroke; }
171
+ .axis { font-size: 11px; fill: var(--muted); }
172
+ .line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
173
+ .line.score { stroke: var(--score); } .line.speed { stroke: var(--speed); }
174
+ .dot { stroke: var(--surface); stroke-width: 2; }
175
+ .dot.score { fill: var(--score); } .dot.speed { fill: var(--speed); }
176
+ .end-label { font-size: 12px; fill: var(--ink-2); }
177
+ .crosshair { stroke: var(--muted); stroke-width: 1; }
178
+ .hit { fill: transparent; }
179
+ .tooltip { position: absolute; top: 0; pointer-events: none; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 6px 10px; font-size: 0.85rem; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.12); }
180
+ .table-wrap { overflow-x: auto; }
181
+ table { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
182
+ th, td { text-align: left; vertical-align: top; padding: 10px 12px; border-bottom: 1px solid var(--grid); font-size: 0.9rem; }
183
+ thead th { color: var(--ink-2); font-weight: 600; }
184
+ ul { margin: 0; padding-left: 18px; }
185
+ .flag { margin: 6px 0 0; }
186
+ footer { margin-top: 24px; color: var(--muted); font-size: 0.85rem; }
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <main>
191
+ <p class="eyebrow">${escapeHtml(brandName)} website history</p>
192
+ <h1>${escapeHtml(options.name)}</h1>
193
+ <p class="meta">${rows.length} check${rows.length === 1 ? "" : "s"}${rows.length > 0 ? `, ${escapeHtml(rows[0].date)} to ${escapeHtml(rows[rows.length - 1].date)}` : ""}.</p>
194
+ ${rows.length > 0 ? chart(rows) : ""}
195
+ <div class="table-wrap">
196
+ <table>
197
+ <thead><tr><th scope="col">Check</th><th scope="col">Score</th>${showSpeed ? '<th scope="col">Page speed</th>' : ""}${showResponse ? '<th scope="col">Response time</th>' : ""}<th scope="col">Open issues</th><th scope="col">Changes</th></tr></thead>
198
+ <tbody>${tableRows}</tbody>
199
+ </table>
200
+ </div>
201
+ ${footer ? `<footer>${escapeHtml(footer)}</footer>` : ""}
202
+ </main>
203
+ <script>
204
+ (() => {
205
+ const data = ${JSON.stringify(data).replace(/</g, "\\u003c")};
206
+ const svg = document.querySelector("svg");
207
+ const tip = document.querySelector(".tooltip");
208
+ if (!svg || !tip || data.length === 0) return;
209
+ const hair = svg.querySelector(".crosshair");
210
+ const left = ${plot.left}, plotWidth = ${width - plot.left - plot.right};
211
+ const xFor = (i) => left + (data.length === 1 ? plotWidth / 2 : (i * plotWidth) / (data.length - 1));
212
+ const show = (event) => {
213
+ const box = svg.getBoundingClientRect();
214
+ const scale = box.width / ${width};
215
+ const px = (event.clientX - box.left) / scale;
216
+ let index = 0;
217
+ data.forEach((_, i) => { if (Math.abs(xFor(i) - px) < Math.abs(xFor(index) - px)) index = i; });
218
+ const row = data[index];
219
+ hair.setAttribute("x1", xFor(index)); hair.setAttribute("x2", xFor(index)); hair.setAttribute("visibility", "visible");
220
+ tip.replaceChildren();
221
+ const lines = [row.date, "Overall score: " + row.score + "/100"];
222
+ if (row.performance !== null) lines.push("Page speed: " + row.performance + "/100");
223
+ for (const text of lines) { const line = document.createElement("div"); line.textContent = text; tip.append(line); }
224
+ tip.hidden = false;
225
+ const tipX = xFor(index) * scale + 12;
226
+ tip.style.left = Math.min(tipX, box.width - tip.offsetWidth) + "px";
227
+ };
228
+ const hide = () => { tip.hidden = true; hair.setAttribute("visibility", "hidden"); };
229
+ svg.addEventListener("pointermove", show);
230
+ svg.addEventListener("pointerdown", show);
231
+ svg.addEventListener("pointerleave", (event) => { if (event.pointerType !== "touch") hide(); });
232
+ })();
233
+ </script>
234
+ </body>
235
+ </html>
236
+ `;
237
+ }
238
+ //# sourceMappingURL=monitor-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-history.js","sourceRoot":"","sources":["../src/monitor-history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface MonitorScheduleOptions {
2
+ platform: NodeJS.Platform;
3
+ cwd: string;
4
+ outDir: string;
5
+ args: string[];
6
+ version: string;
7
+ }
8
+ export interface MonitorScheduleFile {
9
+ path: string;
10
+ content: string;
11
+ }
12
+ export interface MonitorSchedule {
13
+ scriptPath: string;
14
+ files: MonitorScheduleFile[];
15
+ instructions: string;
16
+ }
17
+ export declare function buildMonitorSchedule(options: MonitorScheduleOptions): MonitorSchedule;
18
+ export declare function writeMonitorSchedule(schedule: MonitorSchedule, outDir: string): Promise<void>;
@@ -0,0 +1,91 @@
1
+ import { chmod, mkdir, writeFile } from "node:fs/promises";
2
+ import { posix, win32 } from "node:path";
3
+ // PowerShell also ends single-quoted strings at typographic apostrophes, so those are doubled too.
4
+ function psEscape(value) {
5
+ return value.replace(/['‘-‛]/g, "$&$&");
6
+ }
7
+ // Task Scheduler rejects a task command longer than this.
8
+ const taskCommandLimit = 261;
9
+ function psQuote(value) {
10
+ return `'${psEscape(value)}'`;
11
+ }
12
+ function shQuote(value) {
13
+ return /^[A-Za-z0-9_.:@/=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
14
+ }
15
+ // Builds run scripts for the operating system's scheduler. On Windows the task starts PowerShell on a UTF-8 .ps1
16
+ // directly: cmd misreads batch files that contain non-ASCII paths and cannot start a .cmd whose path contains "&",
17
+ // and the .ps1 passes every path to npx as a separate argument.
18
+ export function buildMonitorSchedule(options) {
19
+ const packageSpec = `open-local-audit@${options.version}`;
20
+ if (options.platform === "win32") {
21
+ const scriptPath = win32.join(options.outDir, "run-monitor.ps1");
22
+ const log = win32.join(options.outDir, "monitor.log");
23
+ const ps1 = [
24
+ "# Written by open-local-audit monitor --print-schedule. Regenerate it after changing options or upgrading.",
25
+ "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
26
+ `Set-Location -LiteralPath ${psQuote(options.cwd)}`,
27
+ // Task Scheduler starts without a login shell, so say so in the log instead of exiting 0 when npx is missing.
28
+ "if (-not (Get-Command npx -ErrorAction SilentlyContinue)) {",
29
+ ` "npx was not found on PATH; install Node.js for all users or add its folder to the system PATH." | Out-File -LiteralPath ${psQuote(log)} -Append -Encoding utf8`,
30
+ " exit 1",
31
+ "}",
32
+ // Converting each line to text keeps Windows PowerShell from logging stderr lines as errors. npx's exit code,
33
+ // including --fail-on-alerts, becomes the task's result.
34
+ `& npx --yes ${packageSpec} monitor ${options.args.map(psQuote).join(" ")} 2>&1 | ForEach-Object { "$_" } | Out-File -LiteralPath ${psQuote(log)} -Append -Encoding utf8`,
35
+ "exit $LASTEXITCODE",
36
+ ""
37
+ ].join("\r\n");
38
+ const run = `powershell -NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"`;
39
+ // schtasks needs the script path quoted inside the /TR value. In `"... \"path\""` the path sits outside cmd's own
40
+ // quotes, so cmd's special characters in it are escaped with ^. PowerShell 7 rewrites the inner quotes, even
41
+ // after --%, so PowerShell users hand the whole line to cmd as one single-quoted string.
42
+ const task = `schtasks /Create /SC MONTHLY /D 1 /ST 09:00 /TN "Open Local Audit monitor" /TR "${run.replace(/"/g, '\\"').replace(/[&|<>^]/g, "^$&")}"`;
43
+ return {
44
+ scriptPath,
45
+ // Windows PowerShell 5.1 reads a script without a byte order mark in the ANSI code page.
46
+ files: [{ path: scriptPath, content: `\uFEFF${ps1}` }],
47
+ instructions: [
48
+ `Wrote ${scriptPath}`,
49
+ ...(run.length > taskCommandLimit
50
+ ? [`Warning: Task Scheduler accepts at most ${taskCommandLimit} characters for the task command, and this one has ${run.length}. Use a shorter --out-dir path.`]
51
+ : []),
52
+ "Schedule it on the 1st of every month at 09:00 by running one of these once.",
53
+ "In Command Prompt:",
54
+ task,
55
+ "In PowerShell:",
56
+ `cmd /c '${psEscape(task)}'`,
57
+ `Each run appends its output to ${log}. The task runs while you are signed in to Windows and opens a console window; if the computer is off or you are signed out at that time, the run waits until the next month, or start one by hand with: schtasks /Run /TN "Open Local Audit monitor"`,
58
+ `Remove the task with: schtasks /Delete /TN "Open Local Audit monitor"`
59
+ ].join("\n")
60
+ };
61
+ }
62
+ const scriptPath = posix.join(options.outDir, "run-monitor.sh");
63
+ const log = posix.join(options.outDir, "monitor.log");
64
+ const command = ["npx", "--yes", packageSpec, "monitor", ...options.args];
65
+ return {
66
+ scriptPath,
67
+ files: [
68
+ {
69
+ path: scriptPath,
70
+ content: ["#!/bin/sh", `cd ${shQuote(options.cwd)} || exit 1`, `${command.map(shQuote).join(" ")} >> ${shQuote(log)} 2>&1`, ""].join("\n")
71
+ }
72
+ ],
73
+ instructions: [
74
+ `Wrote ${scriptPath}`,
75
+ "Schedule it on the 1st of every month at 09:00 by adding this line with crontab -e:",
76
+ // cron reads an unescaped % as a newline.
77
+ `0 9 1 * * ${shQuote(scriptPath).replace(/%/g, "\\%")}`,
78
+ `cron starts with a short PATH; if npx is not found there, add your Node.js directory to PATH at the top of the script. Each run appends its output to ${log}.`
79
+ ].join("\n")
80
+ };
81
+ }
82
+ export async function writeMonitorSchedule(schedule, outDir) {
83
+ await mkdir(outDir, { recursive: true });
84
+ for (const file of schedule.files) {
85
+ await writeFile(file.path, file.content);
86
+ if (file.path.endsWith(".sh")) {
87
+ await chmod(file.path, 0o755);
88
+ }
89
+ }
90
+ }
91
+ //# sourceMappingURL=monitor-schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-schedule.js","sourceRoot":"","sources":["../src/monitor-schedule.ts"],"names":[],"mappings":"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"}
package/dist/monitor.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import type { BatchInputEntry } from "./batch.js";
2
- import type { AuditProfile, AuditReport, ReportBrandConfig } from "./types.js";
2
+ import type { AuditComparison } from "./compare.js";
3
+ import type { AuditProfile, AuditReport, LighthouseSummary, ReportBrandConfig } from "./types.js";
3
4
  export interface MonitorOptions {
4
5
  outDir: string;
5
6
  audit: (url: string, profile: AuditProfile) => Promise<AuditReport>;
6
7
  profile?: AuditProfile;
7
8
  brand?: ReportBrandConfig;
8
9
  now?: Date;
10
+ lighthouse?: (url: string) => Promise<LighthouseSummary>;
9
11
  }
10
12
  export interface MonitorAlert {
11
13
  kind: "new-high" | "certificate" | "domain" | "failed";
@@ -19,10 +21,16 @@ export interface MonitorSiteResult {
19
21
  reportDir?: string;
20
22
  comparisonPath?: string;
21
23
  pdfPath?: string;
24
+ emailPath?: string;
25
+ historyPath?: string;
22
26
  previousRunId?: string;
23
27
  score?: number;
24
28
  previousScore?: number;
25
29
  scoreChange?: number;
30
+ performance?: number;
31
+ previousPerformance?: number;
32
+ responseTimeMs?: number;
33
+ previousResponseTimeMs?: number;
26
34
  newFindings: number;
27
35
  fixedFindings: number;
28
36
  alerts: MonitorAlert[];
@@ -42,6 +50,17 @@ export declare function monitorSiteFolder(rawUrl: string): {
42
50
  folder: string;
43
51
  };
44
52
  export declare function monitorRunId(date: Date): string;
53
+ export interface ClientEmailInput {
54
+ entry: BatchInputEntry;
55
+ report: AuditReport;
56
+ runDate: Date;
57
+ pdfFile: string;
58
+ brand?: ReportBrandConfig;
59
+ comparison?: AuditComparison;
60
+ previousPerformance?: number;
61
+ newBaseline?: boolean;
62
+ }
63
+ export declare function renderClientEmail(input: ClientEmailInput): string;
45
64
  export declare function runMonitor(entries: BatchInputEntry[], options: MonitorOptions): Promise<MonitorSummary>;
46
65
  export declare function renderMonitorJson(summary: MonitorSummary): string;
47
66
  export declare function renderMonitorMarkdown(summary: MonitorSummary): string;