@reddoorla/maintenance 0.10.7 → 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
@@ -2216,7 +2216,13 @@ function mapRow(rec) {
2216
2216
  rScore: f["rScore"] ?? null,
2217
2217
  bpScore: f["bpScore"] ?? null,
2218
2218
  seoScore: f["seoScore"] ?? null,
2219
- 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
+ })()
2220
2226
  };
2221
2227
  }
2222
2228
  async function listWebsites(base) {
@@ -2921,6 +2927,96 @@ function findDueReports(websites, reports, today) {
2921
2927
  }
2922
2928
  return out;
2923
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
+ }
2924
3020
  export {
2925
3021
  ALL_AUDIT_NAMES,
2926
3022
  ALL_RECIPE_NAMES,
@@ -2944,6 +3040,7 @@ export {
2944
3040
  localPath,
2945
3041
  onboard,
2946
3042
  renderReportHtml,
3043
+ renderSiteDashboardHtml,
2947
3044
  runAudits,
2948
3045
  runAuditsAcross,
2949
3046
  securityAudit,
@@ -2952,6 +3049,7 @@ export {
2952
3049
  sendApprovedReports,
2953
3050
  svelteCodemods,
2954
3051
  syncConfigs,
2955
- upgradeSvelte4to5
3052
+ upgradeSvelte4to5,
3053
+ verifyDashboardToken
2956
3054
  };
2957
3055
  //# sourceMappingURL=index.js.map