@reddoorla/maintenance 0.10.6 → 0.11.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.
package/dist/index.js CHANGED
@@ -556,7 +556,6 @@ async function lighthouseAudit(ctx) {
556
556
 
557
557
  // src/audits/a11y.ts
558
558
  import { readFile as readFile5, writeFile as writeFile2, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
559
- import { tmpdir as tmpdir2 } from "os";
560
559
  import { join as join5 } from "path";
561
560
 
562
561
  // src/configs/playwright-a11y.ts
@@ -682,7 +681,7 @@ async function a11yAudit(ctx) {
682
681
  const spawn2 = ctx.spawn ?? defaultSpawn;
683
682
  const site = ctx.site;
684
683
  const label = siteLabel(site);
685
- const specDir = await mkdtemp2(join5(tmpdir2(), "reddoor-a11y-spec-"));
684
+ const specDir = await mkdtemp2(join5(site.path, ".reddoor-a11y-spec-"));
686
685
  const specPath = join5(specDir, "a11y.spec.ts");
687
686
  await writeFile2(specPath, buildSpec(), "utf-8");
688
687
  const port = await findFreePort();
@@ -2217,7 +2216,13 @@ function mapRow(rec) {
2217
2216
  rScore: f["rScore"] ?? null,
2218
2217
  bpScore: f["bpScore"] ?? null,
2219
2218
  seoScore: f["seoScore"] ?? null,
2220
- lastLighthouseAuditAt: f["Last lighthouse audit at"] ?? null
2219
+ lastLighthouseAuditAt: f["Last lighthouse audit at"] ?? null,
2220
+ dashboardToken: (() => {
2221
+ const raw = f["Dashboard Token"];
2222
+ if (typeof raw !== "string") return null;
2223
+ const trimmed = raw.trim();
2224
+ return trimmed.length > 0 ? trimmed : null;
2225
+ })()
2221
2226
  };
2222
2227
  }
2223
2228
  async function listWebsites(base) {
@@ -2922,6 +2927,96 @@ function findDueReports(websites, reports, today) {
2922
2927
  }
2923
2928
  return out;
2924
2929
  }
2930
+
2931
+ // src/dashboard/render.ts
2932
+ function escapeHtml(s) {
2933
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2934
+ }
2935
+ function safeUrl(raw) {
2936
+ try {
2937
+ const u = new URL(raw);
2938
+ if (u.protocol === "http:" || u.protocol === "https:") return raw;
2939
+ } catch {
2940
+ }
2941
+ return "#";
2942
+ }
2943
+ function scoreTile(label, value) {
2944
+ const display = value === null ? "\u2014" : String(value);
2945
+ return `<div class="tile"><div class="tile-value">${escapeHtml(display)}</div><div class="tile-label">${escapeHtml(label)}</div></div>`;
2946
+ }
2947
+ function reportRow(r) {
2948
+ const date = r.completedOn ? escapeHtml(r.completedOn) : "\u2014";
2949
+ const type = escapeHtml(r.reportType);
2950
+ const id = escapeHtml(r.reportId);
2951
+ const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}">view</a>` : `<span class="muted">no attachment</span>`;
2952
+ return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${link}</td></tr>`;
2953
+ }
2954
+ var STYLES = `
2955
+ :root { color-scheme: light dark; }
2956
+ body { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 860px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
2957
+ @media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }
2958
+ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
2959
+ .meta { color: #666; margin-bottom: 2rem; }
2960
+ .meta a { color: inherit; }
2961
+ .section { margin: 2rem 0; }
2962
+ .section h2 { font-size: 1.1rem; margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; }
2963
+ .tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
2964
+ .tile { padding: 1rem; border: 1px solid #ddd; border-radius: 6px; text-align: center; }
2965
+ @media (prefers-color-scheme: dark) { .tile { border-color: #333; } }
2966
+ .tile-value { font-size: 2rem; font-weight: 600; }
2967
+ .tile-label { font-size: 0.85rem; color: #666; margin-top: 0.25rem; }
2968
+ table { width: 100%; border-collapse: collapse; }
2969
+ th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
2970
+ @media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }
2971
+ .muted { color: #999; }
2972
+ .empty { color: #999; padding: 1rem; border: 1px dashed #ccc; border-radius: 6px; text-align: center; }
2973
+ `;
2974
+ function renderSiteDashboardHtml(site, reports) {
2975
+ const name = escapeHtml(site.name);
2976
+ const urlSafe = safeUrl(site.url);
2977
+ const allScoresNull = site.pScore === null && site.rScore === null && site.bpScore === null && site.seoScore === null;
2978
+ const scoresSection = allScoresNull ? `<div class="empty">No lighthouse data yet \u2014 run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>` : `<div class="tiles">
2979
+ ${scoreTile("Performance", site.pScore)}
2980
+ ${scoreTile("Accessibility", site.rScore)}
2981
+ ${scoreTile("Best Practices", site.bpScore)}
2982
+ ${scoreTile("SEO", site.seoScore)}
2983
+ </div>`;
2984
+ const reportsSection = reports.length === 0 ? `<div class="empty">No reports yet.</div>` : `<table>
2985
+ <thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>Report</th></tr></thead>
2986
+ <tbody>${reports.map(reportRow).join("")}</tbody>
2987
+ </table>`;
2988
+ return `<!doctype html>
2989
+ <html lang="en">
2990
+ <head>
2991
+ <meta charset="utf-8" />
2992
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2993
+ <title>${name} \u2014 Reddoor maintenance</title>
2994
+ <style>${STYLES}</style>
2995
+ </head>
2996
+ <body>
2997
+ <h1>${name}</h1>
2998
+ <div class="meta"><a href="${escapeHtml(urlSafe)}">${escapeHtml(site.url)}</a></div>
2999
+
3000
+ <div class="section">
3001
+ <h2>Lighthouse</h2>
3002
+ ${scoresSection}
3003
+ </div>
3004
+
3005
+ <div class="section">
3006
+ <h2>Reports</h2>
3007
+ ${reportsSection}
3008
+ </div>
3009
+ </body>
3010
+ </html>`;
3011
+ }
3012
+
3013
+ // src/dashboard/auth.ts
3014
+ import { timingSafeEqual } from "crypto";
3015
+ function verifyDashboardToken(provided, expected) {
3016
+ if (!provided || !expected) return false;
3017
+ if (provided.length !== expected.length) return false;
3018
+ return timingSafeEqual(Buffer.from(provided, "utf-8"), Buffer.from(expected, "utf-8"));
3019
+ }
2925
3020
  export {
2926
3021
  ALL_AUDIT_NAMES,
2927
3022
  ALL_RECIPE_NAMES,
@@ -2945,6 +3040,7 @@ export {
2945
3040
  localPath,
2946
3041
  onboard,
2947
3042
  renderReportHtml,
3043
+ renderSiteDashboardHtml,
2948
3044
  runAudits,
2949
3045
  runAuditsAcross,
2950
3046
  securityAudit,
@@ -2953,6 +3049,7 @@ export {
2953
3049
  sendApprovedReports,
2954
3050
  svelteCodemods,
2955
3051
  syncConfigs,
2956
- upgradeSvelte4to5
3052
+ upgradeSvelte4to5,
3053
+ verifyDashboardToken
2957
3054
  };
2958
3055
  //# sourceMappingURL=index.js.map