@reddoorla/maintenance 0.16.0 → 0.17.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 +63 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2957,6 +2957,25 @@ function findDueReports(websites, reports, today) {
|
|
|
2957
2957
|
return out;
|
|
2958
2958
|
}
|
|
2959
2959
|
|
|
2960
|
+
// src/dashboard/relative-time.ts
|
|
2961
|
+
function relativeTimeFromNow(iso, now = /* @__PURE__ */ new Date()) {
|
|
2962
|
+
if (!iso) return "\u2014";
|
|
2963
|
+
const t = Date.parse(iso);
|
|
2964
|
+
if (Number.isNaN(t)) return "\u2014";
|
|
2965
|
+
const seconds = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
2966
|
+
if (seconds < 60) return "just now";
|
|
2967
|
+
const minutes = Math.floor(seconds / 60);
|
|
2968
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
2969
|
+
const hours = Math.floor(minutes / 60);
|
|
2970
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2971
|
+
const days = Math.floor(hours / 24);
|
|
2972
|
+
if (days < 7) return `${days}d ago`;
|
|
2973
|
+
const weeks = Math.floor(days / 7);
|
|
2974
|
+
if (weeks < 4) return `${weeks}w ago`;
|
|
2975
|
+
const months = Math.floor(days / 30);
|
|
2976
|
+
return `${months}mo ago`;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2960
2979
|
// src/dashboard/render.ts
|
|
2961
2980
|
function escapeHtml(s) {
|
|
2962
2981
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -2973,6 +2992,34 @@ function scoreTile(label, value) {
|
|
|
2973
2992
|
const display = value === null ? "\u2014" : String(value);
|
|
2974
2993
|
return `<div class="tile"><div class="tile-value">${escapeHtml(display)}</div><div class="tile-label">${escapeHtml(label)}</div></div>`;
|
|
2975
2994
|
}
|
|
2995
|
+
function healthTile(label, value, sub) {
|
|
2996
|
+
const display = value === null ? "\u2014" : String(value);
|
|
2997
|
+
const subLine = sub ? `<div class="tile-sub">${escapeHtml(sub)}</div>` : "";
|
|
2998
|
+
return `<div class="tile"><div class="tile-value">${escapeHtml(display)}</div><div class="tile-label">${escapeHtml(label)}</div>${subLine}</div>`;
|
|
2999
|
+
}
|
|
3000
|
+
function depsSub(majorBehind) {
|
|
3001
|
+
if (majorBehind === null || majorBehind === 0) return null;
|
|
3002
|
+
return `${majorBehind} major behind`;
|
|
3003
|
+
}
|
|
3004
|
+
function securityTotal(site) {
|
|
3005
|
+
const parts = [
|
|
3006
|
+
site.securityVulnsCritical,
|
|
3007
|
+
site.securityVulnsHigh,
|
|
3008
|
+
site.securityVulnsModerate,
|
|
3009
|
+
site.securityVulnsLow
|
|
3010
|
+
];
|
|
3011
|
+
if (parts.every((p) => p === null)) return null;
|
|
3012
|
+
return parts.reduce((sum, p) => sum + (p ?? 0), 0);
|
|
3013
|
+
}
|
|
3014
|
+
function securitySub(site) {
|
|
3015
|
+
const total = securityTotal(site);
|
|
3016
|
+
if (total === null || total === 0) return null;
|
|
3017
|
+
const c = site.securityVulnsCritical ?? 0;
|
|
3018
|
+
const h = site.securityVulnsHigh ?? 0;
|
|
3019
|
+
const m = site.securityVulnsModerate ?? 0;
|
|
3020
|
+
const l = site.securityVulnsLow ?? 0;
|
|
3021
|
+
return `${c}C / ${h}H / ${m}M / ${l}L`;
|
|
3022
|
+
}
|
|
2976
3023
|
function reportRow(r) {
|
|
2977
3024
|
const date = r.completedOn ? escapeHtml(r.completedOn) : "\u2014";
|
|
2978
3025
|
const type = escapeHtml(r.reportType);
|
|
@@ -2987,6 +3034,7 @@ body { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 860px; ma
|
|
|
2987
3034
|
h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
2988
3035
|
.meta { color: #666; margin-bottom: 2rem; }
|
|
2989
3036
|
.meta a { color: inherit; }
|
|
3037
|
+
.audited { color: #999; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
|
2990
3038
|
.section { margin: 2rem 0; }
|
|
2991
3039
|
.section h2 { font-size: 1.1rem; margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; }
|
|
2992
3040
|
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
|
|
@@ -2994,6 +3042,7 @@ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
|
2994
3042
|
@media (prefers-color-scheme: dark) { .tile { border-color: #333; } }
|
|
2995
3043
|
.tile-value { font-size: 2rem; font-weight: 600; }
|
|
2996
3044
|
.tile-label { font-size: 0.85rem; color: #666; margin-top: 0.25rem; }
|
|
3045
|
+
.tile-sub { font-size: 0.75rem; color: #999; margin-top: 0.15rem; }
|
|
2997
3046
|
table { width: 100%; border-collapse: collapse; }
|
|
2998
3047
|
th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
2999
3048
|
@media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }
|
|
@@ -3010,6 +3059,14 @@ function renderSiteDashboardHtml(site, reports) {
|
|
|
3010
3059
|
${scoreTile("Best Practices", site.bpScore)}
|
|
3011
3060
|
${scoreTile("SEO", site.seoScore)}
|
|
3012
3061
|
</div>`;
|
|
3062
|
+
const secTotal = securityTotal(site);
|
|
3063
|
+
const allHealthNull = site.a11yViolations === null && site.depsDrifted === null && secTotal === null;
|
|
3064
|
+
const healthSection = allHealthNull ? `<div class="empty">No health data yet \u2014 run <code>reddoor-maint audit --write-airtable</code> from the site checkout.</div>` : `<div class="tiles">
|
|
3065
|
+
${healthTile("Accessibility issues", site.a11yViolations, null)}
|
|
3066
|
+
${healthTile("Dependency updates", site.depsDrifted, depsSub(site.depsMajorBehind))}
|
|
3067
|
+
${healthTile("Security alerts", secTotal, securitySub(site))}
|
|
3068
|
+
</div>`;
|
|
3069
|
+
const auditedLine = site.lastLighthouseAuditAt ? `<div class="audited">Last audited ${escapeHtml(relativeTimeFromNow(site.lastLighthouseAuditAt))}</div>` : "";
|
|
3013
3070
|
const reportsSection = reports.length === 0 ? `<div class="empty">No reports yet.</div>` : `<table>
|
|
3014
3071
|
<thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>Report</th></tr></thead>
|
|
3015
3072
|
<tbody>${reports.map(reportRow).join("")}</tbody>
|
|
@@ -3025,12 +3082,18 @@ function renderSiteDashboardHtml(site, reports) {
|
|
|
3025
3082
|
<body>
|
|
3026
3083
|
<h1>${name}</h1>
|
|
3027
3084
|
<div class="meta"><a href="${escapeHtml(urlSafe)}">${escapeHtml(site.url)}</a></div>
|
|
3085
|
+
${auditedLine}
|
|
3028
3086
|
|
|
3029
3087
|
<div class="section">
|
|
3030
3088
|
<h2>Lighthouse</h2>
|
|
3031
3089
|
${scoresSection}
|
|
3032
3090
|
</div>
|
|
3033
3091
|
|
|
3092
|
+
<div class="section">
|
|
3093
|
+
<h2>Site Health</h2>
|
|
3094
|
+
${healthSection}
|
|
3095
|
+
</div>
|
|
3096
|
+
|
|
3034
3097
|
<div class="section">
|
|
3035
3098
|
<h2>Reports</h2>
|
|
3036
3099
|
${reportsSection}
|
|
@@ -3062,25 +3125,6 @@ function onboardingStatus(row) {
|
|
|
3062
3125
|
return { score, total: 4, checks };
|
|
3063
3126
|
}
|
|
3064
3127
|
|
|
3065
|
-
// src/dashboard/relative-time.ts
|
|
3066
|
-
function relativeTimeFromNow(iso, now = /* @__PURE__ */ new Date()) {
|
|
3067
|
-
if (!iso) return "\u2014";
|
|
3068
|
-
const t = Date.parse(iso);
|
|
3069
|
-
if (Number.isNaN(t)) return "\u2014";
|
|
3070
|
-
const seconds = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
3071
|
-
if (seconds < 60) return "just now";
|
|
3072
|
-
const minutes = Math.floor(seconds / 60);
|
|
3073
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
3074
|
-
const hours = Math.floor(minutes / 60);
|
|
3075
|
-
if (hours < 24) return `${hours}h ago`;
|
|
3076
|
-
const days = Math.floor(hours / 24);
|
|
3077
|
-
if (days < 7) return `${days}d ago`;
|
|
3078
|
-
const weeks = Math.floor(days / 7);
|
|
3079
|
-
if (weeks < 4) return `${weeks}w ago`;
|
|
3080
|
-
const months = Math.floor(days / 30);
|
|
3081
|
-
return `${months}mo ago`;
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
3128
|
// src/dashboard/fleet-render.ts
|
|
3085
3129
|
function escapeHtml2(s) {
|
|
3086
3130
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|