@reddoorla/maintenance 0.41.0 → 0.42.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 +258 -16
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/forms/index.d.ts +11 -2
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +110 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -651,6 +651,7 @@ async function createDraft(base, input) {
|
|
|
651
651
|
if (input.searchFoundPage1 !== void 0) fields["Search found page 1"] = input.searchFoundPage1;
|
|
652
652
|
if (input.searchPosition !== void 0) fields["Search position"] = input.searchPosition;
|
|
653
653
|
if (input.period !== void 0) fields["Period"] = input.period;
|
|
654
|
+
if (input.subjectOverride !== void 0) fields["Subject override"] = input.subjectOverride;
|
|
654
655
|
const created = await base(REPORTS_TABLE).create([{ fields }]);
|
|
655
656
|
const rec = created[0];
|
|
656
657
|
if (!rec) throw new Error("Airtable create returned no records");
|
|
@@ -717,7 +718,7 @@ var init_reports = __esm({
|
|
|
717
718
|
"src/reports/airtable/reports.ts"() {
|
|
718
719
|
"use strict";
|
|
719
720
|
REPORTS_TABLE = "Reports";
|
|
720
|
-
REPORT_TYPES = ["Maintenance", "Testing", "Launch"];
|
|
721
|
+
REPORT_TYPES = ["Maintenance", "Testing", "Launch", "Announcement"];
|
|
721
722
|
}
|
|
722
723
|
});
|
|
723
724
|
|
|
@@ -777,7 +778,15 @@ var init_copy = __esm({
|
|
|
777
778
|
"Hosting, DNS, and SSL configured",
|
|
778
779
|
"Continuous integration + automatic dependency updates",
|
|
779
780
|
"Analytics and uptime monitoring"
|
|
780
|
-
]
|
|
781
|
+
],
|
|
782
|
+
announceHeading: "YOUR MONTHLY REPORT",
|
|
783
|
+
announceBody: "We've set up ongoing monitoring and maintenance for your site. Each month we quietly check that everything's healthy and up to date \u2014 and now you'll get a short report so you can see it at a glance.",
|
|
784
|
+
announceMonitorItems: ["Performance", "Accessibility", "Security", "Uptime"],
|
|
785
|
+
announcePreviewLabel: "A snapshot of your latest scores:",
|
|
786
|
+
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
787
|
+
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
788
|
+
announceCadence: "You'll receive this every month. There's nothing you need to do.",
|
|
789
|
+
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just reply \u2014 we'd love to help."
|
|
781
790
|
};
|
|
782
791
|
}
|
|
783
792
|
});
|
|
@@ -1114,10 +1123,115 @@ var init_template2 = __esm({
|
|
|
1114
1123
|
}
|
|
1115
1124
|
});
|
|
1116
1125
|
|
|
1126
|
+
// src/reports/announcement-email/template.ts
|
|
1127
|
+
function buildAnnouncementMjml(data) {
|
|
1128
|
+
const copy = data.copy ?? DEFAULT_COPY;
|
|
1129
|
+
const previewText = "Your monthly report from Reddoor";
|
|
1130
|
+
const improvementItems = [];
|
|
1131
|
+
if (data.improvements?.resendForms) improvementItems.push(copy.announceImprovementResend);
|
|
1132
|
+
if (data.improvements?.svelte5) improvementItems.push(copy.announceImprovementSvelte5);
|
|
1133
|
+
const improvementsSection = improvementItems.length > 0 ? `
|
|
1134
|
+
<mj-section background-color="white">
|
|
1135
|
+
<mj-column>
|
|
1136
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="36px">RECENT IMPROVEMENTS</mj-text>
|
|
1137
|
+
${improvementItems.map(
|
|
1138
|
+
(item) => `
|
|
1139
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px" padding-bottom="4px">\u2022 ${escapeXml(item)}</mj-text>`
|
|
1140
|
+
).join("")}
|
|
1141
|
+
</mj-column>
|
|
1142
|
+
</mj-section>` : "";
|
|
1143
|
+
const monitorRows = copy.announceMonitorItems.map(
|
|
1144
|
+
(item) => `
|
|
1145
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="4px" padding-bottom="4px">\u2022 ${escapeXml(item)}</mj-text>`
|
|
1146
|
+
).join("");
|
|
1147
|
+
const scoreRows = SCORE_PREVIEW.map(
|
|
1148
|
+
({ label, key }) => `
|
|
1149
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
1150
|
+
<mj-text color="${RED2}" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse[key]}</mj-text>`
|
|
1151
|
+
).join("");
|
|
1152
|
+
const contactRows = copy.contact.map(
|
|
1153
|
+
(line) => `
|
|
1154
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml(line)}</mj-text>`
|
|
1155
|
+
).join("");
|
|
1156
|
+
const footerAddressRows = copy.footerAddress.map(
|
|
1157
|
+
(line) => `
|
|
1158
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml(line)}</mj-text>`
|
|
1159
|
+
).join("");
|
|
1160
|
+
return `<mjml>
|
|
1161
|
+
<mj-head>
|
|
1162
|
+
<mj-attributes>
|
|
1163
|
+
<mj-text font-family="helvetica, sans-serif" padding-left="5px" padding-right="5px" />
|
|
1164
|
+
<mj-section padding-left="11%" padding-right="11%"/>
|
|
1165
|
+
<mj-image padding="0px" />
|
|
1166
|
+
</mj-attributes>
|
|
1167
|
+
<mj-preview>${escapeXml(previewText)}</mj-preview>
|
|
1168
|
+
${headerStyleBlock(data)}
|
|
1169
|
+
</mj-head>
|
|
1170
|
+
<mj-body background-color="white">
|
|
1171
|
+
<mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
|
|
1172
|
+
<mj-column>${headerImageTag(data)}</mj-column>
|
|
1173
|
+
</mj-section>
|
|
1174
|
+
<mj-section background-color="white">
|
|
1175
|
+
<mj-column>
|
|
1176
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="75px">${escapeXml(copy.announceHeading)}</mj-text>
|
|
1177
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="20px">Prepared for ${escapeXml(data.siteName)}</mj-text>
|
|
1178
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="8px">${escapeXml(copy.announceBody)}</mj-text>
|
|
1179
|
+
</mj-column>
|
|
1180
|
+
</mj-section>
|
|
1181
|
+
${improvementsSection}
|
|
1182
|
+
<mj-section background-color="white">
|
|
1183
|
+
<mj-column>
|
|
1184
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="36px">WHAT WE MONITOR</mj-text>
|
|
1185
|
+
${monitorRows}
|
|
1186
|
+
</mj-column>
|
|
1187
|
+
</mj-section>
|
|
1188
|
+
<mj-section background-color="#F4F4F4">
|
|
1189
|
+
<mj-column>
|
|
1190
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="55px">${escapeXml(copy.announcePreviewLabel)}</mj-text>
|
|
1191
|
+
${scoreRows}
|
|
1192
|
+
</mj-column>
|
|
1193
|
+
</mj-section>
|
|
1194
|
+
<mj-section background-color="white">
|
|
1195
|
+
<mj-column>
|
|
1196
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="36px">${escapeXml(copy.announceCadence)}</mj-text>
|
|
1197
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="8px">${escapeXml(copy.announceOpenDoor)}</mj-text>
|
|
1198
|
+
</mj-column>
|
|
1199
|
+
</mj-section>
|
|
1200
|
+
<mj-section background-color="white">
|
|
1201
|
+
<mj-column padding-top="36px">
|
|
1202
|
+
<mj-text color="${RED2}" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="36px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1203
|
+
${contactRows}
|
|
1204
|
+
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1205
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" line-height="20px" font-style="italic">Copyright ${(/* @__PURE__ */ new Date()).getUTCFullYear()} ${escapeXml(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1206
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="700" line-height="16px" padding-top="0" padding-bottom="0px">Our mailing address is:</mj-text>
|
|
1207
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml(copy.footerOrg)}</mj-text>
|
|
1208
|
+
${footerAddressRows}
|
|
1209
|
+
</mj-column>
|
|
1210
|
+
</mj-section>
|
|
1211
|
+
</mj-body>
|
|
1212
|
+
</mjml>`;
|
|
1213
|
+
}
|
|
1214
|
+
var RED2, GREY2, SCORE_PREVIEW;
|
|
1215
|
+
var init_template3 = __esm({
|
|
1216
|
+
"src/reports/announcement-email/template.ts"() {
|
|
1217
|
+
"use strict";
|
|
1218
|
+
init_copy();
|
|
1219
|
+
init_template();
|
|
1220
|
+
RED2 = "#C00";
|
|
1221
|
+
GREY2 = "#757575";
|
|
1222
|
+
SCORE_PREVIEW = [
|
|
1223
|
+
{ label: "Performance", key: "performance" },
|
|
1224
|
+
{ label: "Readability", key: "accessibility" },
|
|
1225
|
+
{ label: "Best Practices", key: "bestPractices" },
|
|
1226
|
+
{ label: "Site Structure", key: "seo" }
|
|
1227
|
+
];
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1117
1231
|
// src/reports/render.ts
|
|
1118
1232
|
import mjml2html from "mjml";
|
|
1119
1233
|
async function renderReportHtml(data) {
|
|
1120
|
-
const mjml = data.reportType === "Launch" ? buildLaunchMjml(data) : buildMjml(data);
|
|
1234
|
+
const mjml = data.reportType === "Launch" ? buildLaunchMjml(data) : data.reportType === "Announcement" ? buildAnnouncementMjml(data) : buildMjml(data);
|
|
1121
1235
|
const out = await mjml2html(mjml, { validationLevel: "strict" });
|
|
1122
1236
|
return { html: out.html, warnings: out.errors ?? [] };
|
|
1123
1237
|
}
|
|
@@ -1126,6 +1240,7 @@ var init_render = __esm({
|
|
|
1126
1240
|
"use strict";
|
|
1127
1241
|
init_template();
|
|
1128
1242
|
init_template2();
|
|
1243
|
+
init_template3();
|
|
1129
1244
|
}
|
|
1130
1245
|
});
|
|
1131
1246
|
|
|
@@ -1419,16 +1534,16 @@ __export(digest_exports, {
|
|
|
1419
1534
|
runDigest: () => runDigest
|
|
1420
1535
|
});
|
|
1421
1536
|
function readySection(items) {
|
|
1422
|
-
const heading = `<h2 style="color:${
|
|
1537
|
+
const heading = `<h2 style="color:${RED3};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Ready for your yes</h2>`;
|
|
1423
1538
|
if (items.length === 0) {
|
|
1424
|
-
return `${heading}<p style="color:${
|
|
1539
|
+
return `${heading}<p style="color:${GREY3};font-family:helvetica,sans-serif;font-size:16px;margin:0">Nothing waiting on you.</p>`;
|
|
1425
1540
|
}
|
|
1426
1541
|
const rows = items.map((it) => {
|
|
1427
1542
|
const safeUrl = it.dashboardUrl.startsWith("https://") ? it.dashboardUrl : void 0;
|
|
1428
1543
|
const link = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">review & approve</a>` : `review & approve`;
|
|
1429
1544
|
return `
|
|
1430
1545
|
<tr>
|
|
1431
|
-
<td style="color:${
|
|
1546
|
+
<td style="color:${GREY3};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">
|
|
1432
1547
|
<strong style="color:#222">${escapeHtml(it.siteName)}</strong> \u2014 ${escapeHtml(it.reportType)} (${escapeHtml(it.period)})
|
|
1433
1548
|
\u2014 ${link}
|
|
1434
1549
|
</td>
|
|
@@ -1438,15 +1553,15 @@ function readySection(items) {
|
|
|
1438
1553
|
}
|
|
1439
1554
|
function attentionBadge(status) {
|
|
1440
1555
|
if (status === "new")
|
|
1441
|
-
return `<strong style="color:${
|
|
1556
|
+
return `<strong style="color:${RED3};font-family:helvetica,sans-serif">NEW</strong> `;
|
|
1442
1557
|
if (status === "worse")
|
|
1443
|
-
return `<strong style="color:${
|
|
1558
|
+
return `<strong style="color:${RED3};font-family:helvetica,sans-serif">WORSE</strong> `;
|
|
1444
1559
|
return "";
|
|
1445
1560
|
}
|
|
1446
1561
|
function attentionSection(items) {
|
|
1447
|
-
const heading = `<h2 style="color:${
|
|
1562
|
+
const heading = `<h2 style="color:${RED3};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Needs attention</h2>`;
|
|
1448
1563
|
if (items.length === 0) {
|
|
1449
|
-
return `${heading}<p style="color:${
|
|
1564
|
+
return `${heading}<p style="color:${GREY3};font-family:helvetica,sans-serif;font-size:16px;margin:0">All clear \u2014 nothing needs attention.</p>`;
|
|
1450
1565
|
}
|
|
1451
1566
|
const bySite = /* @__PURE__ */ new Map();
|
|
1452
1567
|
for (const it of items) {
|
|
@@ -1463,7 +1578,7 @@ function attentionSection(items) {
|
|
|
1463
1578
|
const titleHtml = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">${escapeHtml(it.title)}</a>` : escapeHtml(it.title);
|
|
1464
1579
|
return `
|
|
1465
1580
|
<tr>
|
|
1466
|
-
<td style="color:${
|
|
1581
|
+
<td style="color:${GREY3};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">${attentionBadge(it.status)}${titleHtml}</td>
|
|
1467
1582
|
</tr>`;
|
|
1468
1583
|
}).join("");
|
|
1469
1584
|
return `
|
|
@@ -1588,7 +1703,7 @@ function renderDigestHtml(sections) {
|
|
|
1588
1703
|
<table width="600" style="border-collapse:collapse">
|
|
1589
1704
|
<tr>
|
|
1590
1705
|
<td>
|
|
1591
|
-
<h1 style="color:${
|
|
1706
|
+
<h1 style="color:${RED3};font-family:helvetica,sans-serif;font-size:24px;font-weight:700;margin:0 0 8px">Your fleet today</h1>
|
|
1592
1707
|
${readySection(sections.readyForYourYes)}
|
|
1593
1708
|
${attentionSection(sections.needsAttention)}
|
|
1594
1709
|
</td>
|
|
@@ -1600,7 +1715,7 @@ function renderDigestHtml(sections) {
|
|
|
1600
1715
|
</body>
|
|
1601
1716
|
</html>`;
|
|
1602
1717
|
}
|
|
1603
|
-
var
|
|
1718
|
+
var GREY3, RED3, ANCHOR_STYLE, SEVERITY_ORDER, FROM_ADDRESS, DIGEST_OPERATOR_FALLBACK;
|
|
1604
1719
|
var init_digest = __esm({
|
|
1605
1720
|
"src/reports/digest.ts"() {
|
|
1606
1721
|
"use strict";
|
|
@@ -1612,9 +1727,9 @@ var init_digest = __esm({
|
|
|
1612
1727
|
init_digest_collectors();
|
|
1613
1728
|
init_digest_state();
|
|
1614
1729
|
init_html();
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
ANCHOR_STYLE = `color:${
|
|
1730
|
+
GREY3 = "#757575";
|
|
1731
|
+
RED3 = "#C00";
|
|
1732
|
+
ANCHOR_STYLE = `color:${RED3};font-family:helvetica,sans-serif`;
|
|
1618
1733
|
SEVERITY_ORDER = { critical: 0, warning: 1 };
|
|
1619
1734
|
FROM_ADDRESS = "Reddoor Reports <reports@reddoorla.com>";
|
|
1620
1735
|
DIGEST_OPERATOR_FALLBACK = "info@reddoorla.com";
|
|
@@ -6211,6 +6326,127 @@ async function runLaunchCommand(site, opts) {
|
|
|
6211
6326
|
return { output: formatResult9(result), code: result.complete ? 0 : 1 };
|
|
6212
6327
|
}
|
|
6213
6328
|
|
|
6329
|
+
// src/recipes/announce.ts
|
|
6330
|
+
init_client();
|
|
6331
|
+
init_websites();
|
|
6332
|
+
init_reports();
|
|
6333
|
+
init_attachments();
|
|
6334
|
+
init_render();
|
|
6335
|
+
init_copy();
|
|
6336
|
+
async function announce(deps) {
|
|
6337
|
+
const base = deps?.base ?? openBase(readAirtableConfig());
|
|
6338
|
+
const now = deps?.now ?? /* @__PURE__ */ new Date();
|
|
6339
|
+
const websites = await listWebsites(base);
|
|
6340
|
+
let targets = websites.filter((w) => w.status === "maintenance");
|
|
6341
|
+
if (deps?.site) {
|
|
6342
|
+
const wanted = siteSlug(deps.site);
|
|
6343
|
+
targets = targets.filter((w) => siteSlug(w.name) === wanted);
|
|
6344
|
+
}
|
|
6345
|
+
const period = now.toISOString().slice(0, 7);
|
|
6346
|
+
const results = [];
|
|
6347
|
+
for (const w of targets) {
|
|
6348
|
+
try {
|
|
6349
|
+
const scores = scoresFromRow(w);
|
|
6350
|
+
if (scores === null) {
|
|
6351
|
+
results.push({ site: w.name, status: "skipped-no-scores" });
|
|
6352
|
+
continue;
|
|
6353
|
+
}
|
|
6354
|
+
let report;
|
|
6355
|
+
let statusKind;
|
|
6356
|
+
const existing = await findReportByPeriod(base, w.id, "Announcement", period);
|
|
6357
|
+
if (existing) {
|
|
6358
|
+
await updateReportScores(base, existing.id, scores, now);
|
|
6359
|
+
report = existing;
|
|
6360
|
+
statusKind = "reused";
|
|
6361
|
+
} else {
|
|
6362
|
+
report = await createDraft(base, draftInputFor2(w, scores, now, period));
|
|
6363
|
+
statusKind = "drafted";
|
|
6364
|
+
}
|
|
6365
|
+
const slug = siteSlug(w.name);
|
|
6366
|
+
const { html } = await renderReportHtml({
|
|
6367
|
+
siteName: w.name,
|
|
6368
|
+
siteUrl: w.url,
|
|
6369
|
+
reportType: "Announcement",
|
|
6370
|
+
completedOn: now,
|
|
6371
|
+
lighthouse: scores,
|
|
6372
|
+
lastTestedDate: null,
|
|
6373
|
+
commentary: null,
|
|
6374
|
+
copy: resolveCopy(w),
|
|
6375
|
+
headerImageCid: `${slug}-header`,
|
|
6376
|
+
// Default-on fleet-wide for v1: both recent-improvement callouts render for every
|
|
6377
|
+
// site. Operator review (the draft never auto-sends) is the relevance backstop;
|
|
6378
|
+
// per-site conditioning of these flags is a future lever, not a v1 requirement.
|
|
6379
|
+
improvements: { resendForms: true, svelte5: true }
|
|
6380
|
+
});
|
|
6381
|
+
try {
|
|
6382
|
+
await uploadAttachment(
|
|
6383
|
+
report.id,
|
|
6384
|
+
"Rendered HTML",
|
|
6385
|
+
html,
|
|
6386
|
+
`${slug}-${now.toISOString().slice(0, 10)}.html`,
|
|
6387
|
+
"text/html"
|
|
6388
|
+
);
|
|
6389
|
+
} catch (uploadErr) {
|
|
6390
|
+
console.warn(
|
|
6391
|
+
`\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6392
|
+
);
|
|
6393
|
+
}
|
|
6394
|
+
await setDraftReady(base, report.id, true);
|
|
6395
|
+
const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
|
|
6396
|
+
results.push({ site: w.name, status: statusKind, reportId: report.id, recipientMissing });
|
|
6397
|
+
} catch (err) {
|
|
6398
|
+
results.push({
|
|
6399
|
+
site: w.name,
|
|
6400
|
+
status: "error",
|
|
6401
|
+
message: err instanceof Error ? err.message : String(err)
|
|
6402
|
+
});
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
return { results };
|
|
6406
|
+
}
|
|
6407
|
+
function scoresFromRow(w) {
|
|
6408
|
+
if (w.pScore === null || w.rScore === null || w.bpScore === null || w.seoScore === null) {
|
|
6409
|
+
return null;
|
|
6410
|
+
}
|
|
6411
|
+
return {
|
|
6412
|
+
performance: w.pScore,
|
|
6413
|
+
accessibility: w.rScore,
|
|
6414
|
+
bestPractices: w.bpScore,
|
|
6415
|
+
seo: w.seoScore
|
|
6416
|
+
};
|
|
6417
|
+
}
|
|
6418
|
+
function draftInputFor2(w, scores, now, period) {
|
|
6419
|
+
return {
|
|
6420
|
+
reportId: `${w.name} \u2014 Announcement \u2014 ${now.toISOString().slice(0, 10)}`,
|
|
6421
|
+
siteId: w.id,
|
|
6422
|
+
reportType: "Announcement",
|
|
6423
|
+
period,
|
|
6424
|
+
periodStart: now,
|
|
6425
|
+
periodEnd: now,
|
|
6426
|
+
completedOn: now,
|
|
6427
|
+
lighthouse: scores,
|
|
6428
|
+
lastTestedDate: null,
|
|
6429
|
+
subjectOverride: `Your new monthly report for ${w.name}`
|
|
6430
|
+
};
|
|
6431
|
+
}
|
|
6432
|
+
|
|
6433
|
+
// src/cli/commands/announce.ts
|
|
6434
|
+
function formatSiteResult(r) {
|
|
6435
|
+
if (r.status === "skipped-no-scores") return `[${r.site}] skipped-no-scores`;
|
|
6436
|
+
if (r.status === "error") return `[${r.site}] error: ${r.message}`;
|
|
6437
|
+
const note = r.recipientMissing ? " \u26A0 recipient missing" : "";
|
|
6438
|
+
return `[${r.site}] ${r.status}${note}`;
|
|
6439
|
+
}
|
|
6440
|
+
function formatAnnounceResult(result) {
|
|
6441
|
+
if (result.results.length === 0) return "No maintenance sites to announce.";
|
|
6442
|
+
return result.results.map(formatSiteResult).join("\n");
|
|
6443
|
+
}
|
|
6444
|
+
async function runAnnounceCommand(site, _opts) {
|
|
6445
|
+
const result = await announce(site ? { site } : {});
|
|
6446
|
+
const hadError = result.results.some((r) => r.status === "error");
|
|
6447
|
+
return { output: formatAnnounceResult(result), code: hadError ? 1 : 0 };
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6214
6450
|
// src/cli/commands/github-signals.ts
|
|
6215
6451
|
init_client();
|
|
6216
6452
|
init_websites();
|
|
@@ -6464,6 +6700,12 @@ cli.command(
|
|
|
6464
6700
|
).action(
|
|
6465
6701
|
async (site, opts) => runOrExit(() => runLaunchCommand(site, opts), opts)
|
|
6466
6702
|
);
|
|
6703
|
+
cli.command(
|
|
6704
|
+
"announce [site]",
|
|
6705
|
+
"Draft the monthly-report announcement email for maintenance sites (all, or one) for approval."
|
|
6706
|
+
).action(
|
|
6707
|
+
async (site, opts) => runOrExit(() => runAnnounceCommand(site, opts), opts)
|
|
6708
|
+
);
|
|
6467
6709
|
cli.command("report [site]", "Draft or send maintenance/testing reports.").option("--due", "Scan all Websites and draft overdue reports.").option(
|
|
6468
6710
|
"--preview",
|
|
6469
6711
|
"Single-site dry run; writes reports/<slug>/draft.html, never touches Airtable."
|