@reddoorla/maintenance 0.15.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/cli/bin.js +136 -77
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +29 -6
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.js +84 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2703,11 +2703,30 @@ async function derivePeriodStart(base, siteRow, reportType, today) {
|
|
|
2703
2703
|
|
|
2704
2704
|
// src/reports/airtable/client.ts
|
|
2705
2705
|
import Airtable from "airtable";
|
|
2706
|
+
|
|
2707
|
+
// src/util/credentials.ts
|
|
2708
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2709
|
+
import { homedir } from "os";
|
|
2710
|
+
import { join as join20 } from "path";
|
|
2711
|
+
function defaultCredentialsPath() {
|
|
2712
|
+
const base = process.env.XDG_CONFIG_HOME ?? join20(homedir(), ".config");
|
|
2713
|
+
return join20(base, "reddoor-maint", "credentials.env");
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
// src/reports/airtable/client.ts
|
|
2717
|
+
function missing(name) {
|
|
2718
|
+
return Object.assign(
|
|
2719
|
+
new Error(
|
|
2720
|
+
`${name} not set. Export it in your shell or put it in ${defaultCredentialsPath()} as ${name}=...`
|
|
2721
|
+
),
|
|
2722
|
+
{ exitCode: 2 }
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2706
2725
|
function readAirtableConfig() {
|
|
2707
2726
|
const apiKey = process.env.AIRTABLE_PAT;
|
|
2708
2727
|
const baseId = process.env.AIRTABLE_BASE_ID;
|
|
2709
|
-
if (!apiKey) throw
|
|
2710
|
-
if (!baseId) throw
|
|
2728
|
+
if (!apiKey) throw missing("AIRTABLE_PAT");
|
|
2729
|
+
if (!baseId) throw missing("AIRTABLE_BASE_ID");
|
|
2711
2730
|
return { apiKey, baseId };
|
|
2712
2731
|
}
|
|
2713
2732
|
function openBase(cfg) {
|
|
@@ -2938,6 +2957,25 @@ function findDueReports(websites, reports, today) {
|
|
|
2938
2957
|
return out;
|
|
2939
2958
|
}
|
|
2940
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
|
+
|
|
2941
2979
|
// src/dashboard/render.ts
|
|
2942
2980
|
function escapeHtml(s) {
|
|
2943
2981
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -2954,6 +2992,34 @@ function scoreTile(label, value) {
|
|
|
2954
2992
|
const display = value === null ? "\u2014" : String(value);
|
|
2955
2993
|
return `<div class="tile"><div class="tile-value">${escapeHtml(display)}</div><div class="tile-label">${escapeHtml(label)}</div></div>`;
|
|
2956
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
|
+
}
|
|
2957
3023
|
function reportRow(r) {
|
|
2958
3024
|
const date = r.completedOn ? escapeHtml(r.completedOn) : "\u2014";
|
|
2959
3025
|
const type = escapeHtml(r.reportType);
|
|
@@ -2968,6 +3034,7 @@ body { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 860px; ma
|
|
|
2968
3034
|
h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
2969
3035
|
.meta { color: #666; margin-bottom: 2rem; }
|
|
2970
3036
|
.meta a { color: inherit; }
|
|
3037
|
+
.audited { color: #999; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
|
2971
3038
|
.section { margin: 2rem 0; }
|
|
2972
3039
|
.section h2 { font-size: 1.1rem; margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; }
|
|
2973
3040
|
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
|
|
@@ -2975,6 +3042,7 @@ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
|
2975
3042
|
@media (prefers-color-scheme: dark) { .tile { border-color: #333; } }
|
|
2976
3043
|
.tile-value { font-size: 2rem; font-weight: 600; }
|
|
2977
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; }
|
|
2978
3046
|
table { width: 100%; border-collapse: collapse; }
|
|
2979
3047
|
th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
2980
3048
|
@media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }
|
|
@@ -2991,6 +3059,14 @@ function renderSiteDashboardHtml(site, reports) {
|
|
|
2991
3059
|
${scoreTile("Best Practices", site.bpScore)}
|
|
2992
3060
|
${scoreTile("SEO", site.seoScore)}
|
|
2993
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>` : "";
|
|
2994
3070
|
const reportsSection = reports.length === 0 ? `<div class="empty">No reports yet.</div>` : `<table>
|
|
2995
3071
|
<thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>Report</th></tr></thead>
|
|
2996
3072
|
<tbody>${reports.map(reportRow).join("")}</tbody>
|
|
@@ -3006,12 +3082,18 @@ function renderSiteDashboardHtml(site, reports) {
|
|
|
3006
3082
|
<body>
|
|
3007
3083
|
<h1>${name}</h1>
|
|
3008
3084
|
<div class="meta"><a href="${escapeHtml(urlSafe)}">${escapeHtml(site.url)}</a></div>
|
|
3085
|
+
${auditedLine}
|
|
3009
3086
|
|
|
3010
3087
|
<div class="section">
|
|
3011
3088
|
<h2>Lighthouse</h2>
|
|
3012
3089
|
${scoresSection}
|
|
3013
3090
|
</div>
|
|
3014
3091
|
|
|
3092
|
+
<div class="section">
|
|
3093
|
+
<h2>Site Health</h2>
|
|
3094
|
+
${healthSection}
|
|
3095
|
+
</div>
|
|
3096
|
+
|
|
3015
3097
|
<div class="section">
|
|
3016
3098
|
<h2>Reports</h2>
|
|
3017
3099
|
${reportsSection}
|
|
@@ -3043,25 +3125,6 @@ function onboardingStatus(row) {
|
|
|
3043
3125
|
return { score, total: 4, checks };
|
|
3044
3126
|
}
|
|
3045
3127
|
|
|
3046
|
-
// src/dashboard/relative-time.ts
|
|
3047
|
-
function relativeTimeFromNow(iso, now = /* @__PURE__ */ new Date()) {
|
|
3048
|
-
if (!iso) return "\u2014";
|
|
3049
|
-
const t = Date.parse(iso);
|
|
3050
|
-
if (Number.isNaN(t)) return "\u2014";
|
|
3051
|
-
const seconds = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
3052
|
-
if (seconds < 60) return "just now";
|
|
3053
|
-
const minutes = Math.floor(seconds / 60);
|
|
3054
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
3055
|
-
const hours = Math.floor(minutes / 60);
|
|
3056
|
-
if (hours < 24) return `${hours}h ago`;
|
|
3057
|
-
const days = Math.floor(hours / 24);
|
|
3058
|
-
if (days < 7) return `${days}d ago`;
|
|
3059
|
-
const weeks = Math.floor(days / 7);
|
|
3060
|
-
if (weeks < 4) return `${weeks}w ago`;
|
|
3061
|
-
const months = Math.floor(days / 30);
|
|
3062
|
-
return `${months}mo ago`;
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
3128
|
// src/dashboard/fleet-render.ts
|
|
3066
3129
|
function escapeHtml2(s) {
|
|
3067
3130
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|