@reddoorla/maintenance 0.42.0 → 0.44.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 CHANGED
@@ -575,6 +575,44 @@ var init_write_audits_to_airtable = __esm({
575
575
  }
576
576
  });
577
577
 
578
+ // src/reports/checklist.ts
579
+ function checklistFor(type) {
580
+ return type === "Maintenance" ? MAINTENANCE_CHECKLIST : type === "Testing" ? TESTING_CHECKLIST : [];
581
+ }
582
+ function isChecklistComplete(report) {
583
+ return checklistFor(report.reportType).every((i) => report.checklist[i.field] === true);
584
+ }
585
+ var MAINTENANCE_CHECKLIST, TESTING_CHECKLIST, ALL_CHECKLIST_FIELDS;
586
+ var init_checklist = __esm({
587
+ "src/reports/checklist.ts"() {
588
+ "use strict";
589
+ MAINTENANCE_CHECKLIST = [
590
+ { key: "deploy", label: "Deploy & Function Health", field: "Maint: Deploy & Function Health" },
591
+ { key: "cms", label: "CMS Checked", field: "Maint: CMS Checked" },
592
+ { key: "domain", label: "Domain, DNS & SSL", field: "Maint: Domain, DNS & SSL" },
593
+ { key: "google", label: "Google Indexed", field: "Maint: Google Indexed" },
594
+ { key: "security", label: "Security Updates", field: "Maint: Security Updates" },
595
+ { key: "uptime", label: "Uptime Checked", field: "Maint: Uptime Checked" }
596
+ ];
597
+ TESTING_CHECKLIST = [
598
+ { key: "desktop", label: "Desktop Browsers", field: "Test: Desktop Browsers" },
599
+ { key: "mobile", label: "Mobile Browsers", field: "Test: Mobile Browsers" },
600
+ { key: "titles", label: "Page Titles & Meta", field: "Test: Page Titles & Meta" },
601
+ { key: "links", label: "Links & Navigation", field: "Test: Links & Navigation" },
602
+ { key: "forms", label: "Form Functionality", field: "Test: Form Functionality" },
603
+ {
604
+ key: "interactions",
605
+ label: "Interactions & Animations",
606
+ field: "Test: Interactions & Animations"
607
+ },
608
+ { key: "updates", label: "Verified After Updates", field: "Test: Verified After Updates" }
609
+ ];
610
+ ALL_CHECKLIST_FIELDS = [...MAINTENANCE_CHECKLIST, ...TESTING_CHECKLIST].map(
611
+ (i) => i.field
612
+ );
613
+ }
614
+ });
615
+
578
616
  // src/reports/airtable/reports.ts
579
617
  function toReportType(raw) {
580
618
  if (raw && REPORT_TYPES.includes(raw)) return raw;
@@ -613,7 +651,8 @@ function mapRow2(rec) {
613
651
  approvedBy: f["Approved By"] ?? null,
614
652
  deliveryStatus: f["Delivery status"] ?? "pending",
615
653
  renderedHtmlAttachment: html,
616
- resendMessageId: f["Resend message ID"] ?? null
654
+ resendMessageId: f["Resend message ID"] ?? null,
655
+ checklist: Object.fromEntries(ALL_CHECKLIST_FIELDS.map((name) => [name, Boolean(f[name])]))
617
656
  };
618
657
  }
619
658
  function lighthouseFromFields(f) {
@@ -717,6 +756,7 @@ var REPORTS_TABLE, REPORT_TYPES;
717
756
  var init_reports = __esm({
718
757
  "src/reports/airtable/reports.ts"() {
719
758
  "use strict";
759
+ init_checklist();
720
760
  REPORTS_TABLE = "Reports";
721
761
  REPORT_TYPES = ["Maintenance", "Testing", "Launch", "Announcement"];
722
762
  }
@@ -751,21 +791,22 @@ var init_copy = __esm({
751
791
  DEFAULT_COPY = {
752
792
  maintenanceIntro: "Includes checking the hosting, DNS, Content Management System (CMS, if applicable), search indexing and security of the site for major flaws and updating as necessary.",
753
793
  maintenanceChecks: [
754
- "Reviewed Logs",
794
+ "Deploy & Function Health",
755
795
  "CMS Checked",
756
- "DNS Checked",
796
+ "Domain, DNS & SSL",
757
797
  "Google Indexed",
758
- "Reviewed Certificate",
759
- "Security Updates"
798
+ "Security Updates",
799
+ "Uptime Checked"
760
800
  ],
761
801
  testingIntro: "Testing includes checks similar to those at launch: testing on common browsers and operating systems, at different screen sizes, and checking every function, and updating all packages for performance rather than just those needed for security.",
762
802
  testingChecklist: [
763
803
  "Desktop Browsers",
764
804
  "Mobile Browsers",
765
- "Package Updates",
766
- "Bottlenecks",
805
+ "Page Titles & Meta",
806
+ "Links & Navigation",
767
807
  "Form Functionality",
768
- "Animation Functionality"
808
+ "Interactions & Animations",
809
+ "Verified After Updates"
769
810
  ],
770
811
  notesHeader: "NOTES",
771
812
  seoCta: "Contact us if you are interested in more in-depth data or have questions about SEO.",
@@ -779,13 +820,16 @@ var init_copy = __esm({
779
820
  "Continuous integration + automatic dependency updates",
780
821
  "Analytics and uptime monitoring"
781
822
  ],
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.",
823
+ announceHeading: "YOUR ONGOING SITE CARE",
824
+ announceBody: "We've completed a full test of your site and set it up for ongoing care to keep it fast, secure, and healthy. Here's what you can expect from us going forward:",
825
+ announceCadenceHeading: "WHAT TO EXPECT",
826
+ announceTestingLabel: "Full site testing",
827
+ announceMaintenanceLabel: "Routine maintenance",
784
828
  announceMonitorItems: ["Performance", "Accessibility", "Security", "Uptime"],
785
- announcePreviewLabel: "A snapshot of your latest scores:",
829
+ announcePreviewLabel: "From your latest full site test:",
786
830
  announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
787
831
  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.",
832
+ announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
789
833
  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."
790
834
  };
791
835
  }
@@ -1140,6 +1184,23 @@ function buildAnnouncementMjml(data) {
1140
1184
  ).join("")}
1141
1185
  </mj-column>
1142
1186
  </mj-section>` : "";
1187
+ const cad = data.cadence;
1188
+ const cadenceLines = [];
1189
+ if (cad && cad.testing !== "None")
1190
+ cadenceLines.push(`${copy.announceTestingLabel} \u2014 ${FREQ_PHRASE[cad.testing]}`);
1191
+ if (cad && cad.maintenance !== "None")
1192
+ cadenceLines.push(`${copy.announceMaintenanceLabel} \u2014 ${FREQ_PHRASE[cad.maintenance]}`);
1193
+ const cadenceSection = cadenceLines.length > 0 ? `
1194
+ <mj-section background-color="white">
1195
+ <mj-column>
1196
+ <mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="36px">${escapeXml(copy.announceCadenceHeading)}</mj-text>
1197
+ ${cadenceLines.map(
1198
+ (line) => `
1199
+ <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(line)}</mj-text>`
1200
+ ).join("")}
1201
+ <mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="12px">${escapeXml(copy.announceCadence)}</mj-text>
1202
+ </mj-column>
1203
+ </mj-section>` : "";
1143
1204
  const monitorRows = copy.announceMonitorItems.map(
1144
1205
  (item) => `
1145
1206
  <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>`
@@ -1178,6 +1239,7 @@ function buildAnnouncementMjml(data) {
1178
1239
  <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
1240
  </mj-column>
1180
1241
  </mj-section>
1242
+ ${cadenceSection}
1181
1243
  ${improvementsSection}
1182
1244
  <mj-section background-color="white">
1183
1245
  <mj-column>
@@ -1193,8 +1255,7 @@ function buildAnnouncementMjml(data) {
1193
1255
  </mj-section>
1194
1256
  <mj-section background-color="white">
1195
1257
  <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>
1258
+ <mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="36px">${escapeXml(copy.announceOpenDoor)}</mj-text>
1198
1259
  </mj-column>
1199
1260
  </mj-section>
1200
1261
  <mj-section background-color="white">
@@ -1211,12 +1272,17 @@ function buildAnnouncementMjml(data) {
1211
1272
  </mj-body>
1212
1273
  </mjml>`;
1213
1274
  }
1214
- var RED2, GREY2, SCORE_PREVIEW;
1275
+ var FREQ_PHRASE, RED2, GREY2, SCORE_PREVIEW;
1215
1276
  var init_template3 = __esm({
1216
1277
  "src/reports/announcement-email/template.ts"() {
1217
1278
  "use strict";
1218
1279
  init_copy();
1219
1280
  init_template();
1281
+ FREQ_PHRASE = {
1282
+ Monthly: "every month",
1283
+ Quarterly: "every quarter",
1284
+ Yearly: "every year"
1285
+ };
1220
1286
  RED2 = "#C00";
1221
1287
  GREY2 = "#757575";
1222
1288
  SCORE_PREVIEW = [
@@ -1827,6 +1893,13 @@ async function sendApprovedReports(options = {}) {
1827
1893
  return { output: lines.join("\n"), code: anyFailed ? 1 : 0 };
1828
1894
  }
1829
1895
  async function sendOne(client, base, site, report) {
1896
+ if (!isChecklistComplete(report)) {
1897
+ const items = checklistFor(report.reportType);
1898
+ const done = items.filter((i) => report.checklist[i.field] === true).length;
1899
+ throw new Error(
1900
+ `Report ${report.reportId} checklist incomplete \u2014 ${done}/${items.length} items checked`
1901
+ );
1902
+ }
1830
1903
  if (!site.headerImage) {
1831
1904
  throw new Error(`Site '${site.name}' has no Header image set on the Websites row`);
1832
1905
  }
@@ -1970,6 +2043,7 @@ var init_orchestrate = __esm({
1970
2043
  init_header_image();
1971
2044
  init_resend();
1972
2045
  init_idempotency();
2046
+ init_checklist();
1973
2047
  FROM_ADDRESS2 = "Reddoor Reports <reports@reddoorla.com>";
1974
2048
  REPLY_TO = "info@reddoorla.com";
1975
2049
  MONTHS2 = [
@@ -6373,6 +6447,10 @@ async function announce(deps) {
6373
6447
  commentary: null,
6374
6448
  copy: resolveCopy(w),
6375
6449
  headerImageCid: `${slug}-header`,
6450
+ // The client's go-forward pace, read straight off the Websites row — the email
6451
+ // states each cadence ("Full site testing — every quarter", etc.); a "None" pace
6452
+ // is omitted so we never claim a cadence the site isn't on.
6453
+ cadence: { maintenance: w.maintenanceFreq, testing: w.testingFreq },
6376
6454
  // Default-on fleet-wide for v1: both recent-improvement callouts render for every
6377
6455
  // site. Operator review (the draft never auto-sends) is the relevance backstop;
6378
6456
  // per-site conditioning of these flags is a future lever, not a v1 requirement.
@@ -6426,7 +6504,7 @@ function draftInputFor2(w, scores, now, period) {
6426
6504
  completedOn: now,
6427
6505
  lighthouse: scores,
6428
6506
  lastTestedDate: null,
6429
- subjectOverride: `Your new monthly report for ${w.name}`
6507
+ subjectOverride: `Your testing & maintenance schedule for ${w.name}`
6430
6508
  };
6431
6509
  }
6432
6510