@reddoorla/maintenance 0.11.2 → 0.12.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.d.ts +31 -1
- package/dist/index.js +115 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -420,4 +420,34 @@ declare function renderSiteDashboardHtml(site: WebsiteRow, reports: ReportRow[])
|
|
|
420
420
|
*/
|
|
421
421
|
declare function verifyDashboardToken(provided: string | null | undefined, expected: string | null): boolean;
|
|
422
422
|
|
|
423
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Render the fleet homepage as a single HTML document. Pure function:
|
|
425
|
+
* no Airtable access, no env reads, no I/O. The Netlify function handler
|
|
426
|
+
* fetches and gates, then hands here. Same style vocabulary as
|
|
427
|
+
* renderSiteDashboardHtml so the two pages feel like one product.
|
|
428
|
+
*
|
|
429
|
+
* Sites without a dashboardToken render as plain text plus a "no token"
|
|
430
|
+
* badge — visible-but-inactive, so the homepage doubles as a per-site
|
|
431
|
+
* setup-progress view.
|
|
432
|
+
*/
|
|
433
|
+
declare function renderFleetHomeHtml(sites: WebsiteRow[]): string;
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Verify an `Authorization: Basic <base64>` header against the configured
|
|
437
|
+
* dashboard password. Username is intentionally ignored — operators may
|
|
438
|
+
* type anything when the browser prompts; only the password gates entry.
|
|
439
|
+
*
|
|
440
|
+
* Returns false for any of:
|
|
441
|
+
* - missing/empty Authorization header
|
|
442
|
+
* - non-Basic auth scheme
|
|
443
|
+
* - malformed base64 or payload (no colon to split user:password)
|
|
444
|
+
* - wrong password
|
|
445
|
+
* - expected password missing (DASHBOARD_PASSWORD not configured)
|
|
446
|
+
*
|
|
447
|
+
* Wrong-password compare is constant-time; lengths are checked first
|
|
448
|
+
* (timingSafeEqual throws on mismatch, and the length itself doesn't
|
|
449
|
+
* leak — operator's password length is fixed per deploy).
|
|
450
|
+
*/
|
|
451
|
+
declare function verifyBasicAuth(authHeader: string | null | undefined, expectedPassword: string | null): boolean;
|
|
452
|
+
|
|
453
|
+
export { ALL_AUDIT_NAMES, ALL_RECIPE_NAMES, type AirtableInventoryOptions, AuditName, AuditResult, BLURRED_CID, type BumpDepsOptions, type BundledImage, CHECK_CID, type ConvertToPnpmOptions, DEFAULT_INIT_STEPS, type DraftOptions, type DraftResult, type DueItem, type HeaderImage, type InitOptions, type InitResult, type InitStep, type InitStepResult, InventoryProvider, type LighthouseScores, type LocalPathOptions, type OnboardAudit, type OnboardOptions, type OrchestrateOptions, RecipeName, RecipeResult, type RenderResult, type ReportData, type ReportType, Site, type UpgradeSvelte4to5Options, a11yAudit, a11yFixturesPage, bumpDeps, convertToPnpm, depsAudit, draftReportForSite, findDueReports, fromAirtableBase, fromJsonFile, init, isRecipeName, lighthouseAudit, lintAudit, loadBundledImages, localPath, onboard, renderFleetHomeHtml, renderReportHtml, renderSiteDashboardHtml, runAudits, runAuditsAcross, securityAudit, selfCaretRange, selfPackageVersion, sendApprovedReports, svelteCodemods, upgradeSvelte4to5, verifyBasicAuth, verifyDashboardToken };
|
package/dist/index.js
CHANGED
|
@@ -2626,11 +2626,11 @@ async function uploadAttachment(recordId, fieldName, body, filename, contentType
|
|
|
2626
2626
|
}
|
|
2627
2627
|
|
|
2628
2628
|
// src/reports/draft.ts
|
|
2629
|
-
function scoresFromWebsite(
|
|
2630
|
-
const { pScore, rScore, bpScore, seoScore } =
|
|
2629
|
+
function scoresFromWebsite(siteRow2) {
|
|
2630
|
+
const { pScore, rScore, bpScore, seoScore } = siteRow2;
|
|
2631
2631
|
if (pScore === null || rScore === null || bpScore === null || seoScore === null) {
|
|
2632
2632
|
throw new Error(
|
|
2633
|
-
`Site '${
|
|
2633
|
+
`Site '${siteRow2.name}' is missing one or more Lighthouse scores on the Websites row (pScore, rScore, bpScore, seoScore). Run 'reddoor-maint audit lighthouse' from the site's checkout and paste the four numbers into Airtable, then retry.`
|
|
2634
2634
|
);
|
|
2635
2635
|
}
|
|
2636
2636
|
return { performance: pScore, accessibility: rScore, bestPractices: bpScore, seo: seoScore };
|
|
@@ -2640,18 +2640,18 @@ function daysAgo(today, n) {
|
|
|
2640
2640
|
out.setDate(out.getDate() - n);
|
|
2641
2641
|
return out;
|
|
2642
2642
|
}
|
|
2643
|
-
async function draftReportForSite(base,
|
|
2644
|
-
const scores = scoresFromWebsite(
|
|
2643
|
+
async function draftReportForSite(base, siteRow2, reportType, options = {}) {
|
|
2644
|
+
const scores = scoresFromWebsite(siteRow2);
|
|
2645
2645
|
const today = /* @__PURE__ */ new Date();
|
|
2646
|
-
const slug = siteSlug(
|
|
2647
|
-
const periodStart = base !== null ? await derivePeriodStart(base,
|
|
2646
|
+
const slug = siteSlug(siteRow2.name);
|
|
2647
|
+
const periodStart = base !== null ? await derivePeriodStart(base, siteRow2, reportType, today) : daysAgo(today, 30);
|
|
2648
2648
|
const periodEnd = today;
|
|
2649
2649
|
const completedOn = today;
|
|
2650
|
-
const lastTestedDate = reportType === "Maintenance" &&
|
|
2650
|
+
const lastTestedDate = reportType === "Maintenance" && siteRow2.testingDay ? new Date(siteRow2.testingDay) : null;
|
|
2651
2651
|
const cidName = `${slug}-header`;
|
|
2652
2652
|
const { html } = await renderReportHtml({
|
|
2653
|
-
siteName:
|
|
2654
|
-
siteUrl:
|
|
2653
|
+
siteName: siteRow2.name,
|
|
2654
|
+
siteUrl: siteRow2.url,
|
|
2655
2655
|
reportType,
|
|
2656
2656
|
completedOn,
|
|
2657
2657
|
lighthouse: scores,
|
|
@@ -2668,10 +2668,10 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
2668
2668
|
return { reportRow: null, htmlPath: path, html };
|
|
2669
2669
|
}
|
|
2670
2670
|
if (base === null) throw new Error("base required when previewOnly=false");
|
|
2671
|
-
const reportId = `${
|
|
2671
|
+
const reportId = `${siteRow2.name} \u2014 ${reportType} \u2014 ${periodEnd.toISOString().slice(0, 10)}`;
|
|
2672
2672
|
const created = await createDraft(base, {
|
|
2673
2673
|
reportId,
|
|
2674
|
-
siteId:
|
|
2674
|
+
siteId: siteRow2.id,
|
|
2675
2675
|
reportType,
|
|
2676
2676
|
periodStart,
|
|
2677
2677
|
periodEnd,
|
|
@@ -2684,8 +2684,8 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
2684
2684
|
await setDraftReady(base, created.id, true);
|
|
2685
2685
|
return { reportRow: created, htmlPath: null, html };
|
|
2686
2686
|
}
|
|
2687
|
-
async function derivePeriodStart(base,
|
|
2688
|
-
const prior = await listReportsForSite(base,
|
|
2687
|
+
async function derivePeriodStart(base, siteRow2, reportType, today) {
|
|
2688
|
+
const prior = await listReportsForSite(base, siteRow2.id);
|
|
2689
2689
|
const sameType = prior.filter((r) => r.reportType === reportType && r.periodEnd).map((r) => r.periodEnd).sort();
|
|
2690
2690
|
const latest = sameType[sameType.length - 1];
|
|
2691
2691
|
return latest ? new Date(latest) : daysAgo(today, 30);
|
|
@@ -3017,6 +3017,105 @@ function verifyDashboardToken(provided, expected) {
|
|
|
3017
3017
|
if (provided.length !== expected.length) return false;
|
|
3018
3018
|
return timingSafeEqual(Buffer.from(provided, "utf-8"), Buffer.from(expected, "utf-8"));
|
|
3019
3019
|
}
|
|
3020
|
+
|
|
3021
|
+
// src/dashboard/fleet-render.ts
|
|
3022
|
+
function escapeHtml2(s) {
|
|
3023
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3024
|
+
}
|
|
3025
|
+
function safeUrl2(raw) {
|
|
3026
|
+
try {
|
|
3027
|
+
const u = new URL(raw);
|
|
3028
|
+
if (u.protocol === "http:" || u.protocol === "https:") return raw;
|
|
3029
|
+
} catch {
|
|
3030
|
+
}
|
|
3031
|
+
return "#";
|
|
3032
|
+
}
|
|
3033
|
+
function scoreCell(value) {
|
|
3034
|
+
const display = value === null ? "\u2014" : String(value);
|
|
3035
|
+
return `<td class="score">${escapeHtml2(display)}</td>`;
|
|
3036
|
+
}
|
|
3037
|
+
function siteHrefCell(site) {
|
|
3038
|
+
const name = escapeHtml2(site.name);
|
|
3039
|
+
if (!site.dashboardToken) {
|
|
3040
|
+
return `<td class="site"><span class="name">${name}</span> <span class="badge">no token</span></td>`;
|
|
3041
|
+
}
|
|
3042
|
+
const href = `/s/${escapeHtml2(siteSlug(site.name))}?t=${escapeHtml2(site.dashboardToken)}`;
|
|
3043
|
+
return `<td class="site"><a href="${href}">${name}</a></td>`;
|
|
3044
|
+
}
|
|
3045
|
+
function siteRow(site) {
|
|
3046
|
+
return `<tr>
|
|
3047
|
+
${siteHrefCell(site)}
|
|
3048
|
+
<td><a href="${escapeHtml2(safeUrl2(site.url))}" class="url" target="_blank" rel="noopener">${escapeHtml2(site.url)}</a></td>
|
|
3049
|
+
${scoreCell(site.pScore)}
|
|
3050
|
+
${scoreCell(site.rScore)}
|
|
3051
|
+
${scoreCell(site.bpScore)}
|
|
3052
|
+
${scoreCell(site.seoScore)}
|
|
3053
|
+
</tr>`;
|
|
3054
|
+
}
|
|
3055
|
+
var STYLES2 = `
|
|
3056
|
+
:root { color-scheme: light dark; }
|
|
3057
|
+
body { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
|
|
3058
|
+
@media (prefers-color-scheme: dark) { body { color: #e8e8e8; background: #111; } a { color: #6cb6ff; } }
|
|
3059
|
+
h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
3060
|
+
.meta { color: #666; margin-bottom: 2rem; }
|
|
3061
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
|
|
3062
|
+
th { text-align: left; padding: 0.5rem; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: #666; border-bottom: 2px solid #ddd; }
|
|
3063
|
+
@media (prefers-color-scheme: dark) { th { border-color: #333; } }
|
|
3064
|
+
td { padding: 0.65rem 0.5rem; border-bottom: 1px solid #eee; }
|
|
3065
|
+
@media (prefers-color-scheme: dark) { td { border-color: #2a2a2a; } }
|
|
3066
|
+
td.site a { font-weight: 500; }
|
|
3067
|
+
td.url { color: #666; font-size: 0.85rem; }
|
|
3068
|
+
td.score { text-align: right; font-variant-numeric: tabular-nums; min-width: 3.5rem; }
|
|
3069
|
+
.badge { display: inline-block; margin-left: 0.5rem; padding: 0.1rem 0.5rem; font-size: 0.75rem; border-radius: 3px; background: #f0f0f0; color: #999; }
|
|
3070
|
+
@media (prefers-color-scheme: dark) { .badge { background: #2a2a2a; color: #777; } }
|
|
3071
|
+
.empty { color: #999; padding: 2rem; text-align: center; border: 1px dashed #ccc; border-radius: 6px; }
|
|
3072
|
+
`;
|
|
3073
|
+
function renderFleetHomeHtml(sites) {
|
|
3074
|
+
const body = sites.length === 0 ? `<div class="empty">No sites in the Websites table yet.</div>` : `<table>
|
|
3075
|
+
<thead><tr>
|
|
3076
|
+
<th>Site</th>
|
|
3077
|
+
<th>URL</th>
|
|
3078
|
+
<th>Perf</th>
|
|
3079
|
+
<th>A11y</th>
|
|
3080
|
+
<th>BP</th>
|
|
3081
|
+
<th>SEO</th>
|
|
3082
|
+
</tr></thead>
|
|
3083
|
+
<tbody>${sites.map(siteRow).join("")}</tbody>
|
|
3084
|
+
</table>`;
|
|
3085
|
+
return `<!doctype html>
|
|
3086
|
+
<html lang="en">
|
|
3087
|
+
<head>
|
|
3088
|
+
<meta charset="utf-8" />
|
|
3089
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3090
|
+
<title>Reddoor maintenance \u2014 fleet</title>
|
|
3091
|
+
<style>${STYLES2}</style>
|
|
3092
|
+
</head>
|
|
3093
|
+
<body>
|
|
3094
|
+
<h1>Reddoor fleet</h1>
|
|
3095
|
+
<div class="meta">${sites.length} site${sites.length === 1 ? "" : "s"} in the Websites table.</div>
|
|
3096
|
+
${body}
|
|
3097
|
+
</body>
|
|
3098
|
+
</html>`;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// src/dashboard/basic-auth.ts
|
|
3102
|
+
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3103
|
+
function verifyBasicAuth(authHeader, expectedPassword) {
|
|
3104
|
+
if (!authHeader || !expectedPassword) return false;
|
|
3105
|
+
const match = /^basic\s+(.+)$/i.exec(authHeader.trim());
|
|
3106
|
+
if (!match) return false;
|
|
3107
|
+
let decoded;
|
|
3108
|
+
try {
|
|
3109
|
+
decoded = Buffer.from(match[1], "base64").toString("utf-8");
|
|
3110
|
+
} catch {
|
|
3111
|
+
return false;
|
|
3112
|
+
}
|
|
3113
|
+
const colonIdx = decoded.indexOf(":");
|
|
3114
|
+
if (colonIdx === -1) return false;
|
|
3115
|
+
const provided = decoded.slice(colonIdx + 1);
|
|
3116
|
+
if (provided.length !== expectedPassword.length) return false;
|
|
3117
|
+
return timingSafeEqual2(Buffer.from(provided, "utf-8"), Buffer.from(expectedPassword, "utf-8"));
|
|
3118
|
+
}
|
|
3020
3119
|
export {
|
|
3021
3120
|
ALL_AUDIT_NAMES,
|
|
3022
3121
|
ALL_RECIPE_NAMES,
|
|
@@ -3039,6 +3138,7 @@ export {
|
|
|
3039
3138
|
loadBundledImages,
|
|
3040
3139
|
localPath,
|
|
3041
3140
|
onboard,
|
|
3141
|
+
renderFleetHomeHtml,
|
|
3042
3142
|
renderReportHtml,
|
|
3043
3143
|
renderSiteDashboardHtml,
|
|
3044
3144
|
runAudits,
|
|
@@ -3050,6 +3150,7 @@ export {
|
|
|
3050
3150
|
svelteCodemods,
|
|
3051
3151
|
syncConfigs,
|
|
3052
3152
|
upgradeSvelte4to5,
|
|
3153
|
+
verifyBasicAuth,
|
|
3053
3154
|
verifyDashboardToken
|
|
3054
3155
|
};
|
|
3055
3156
|
//# sourceMappingURL=index.js.map
|