@reddoorla/maintenance 0.17.0 → 0.18.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
@@ -658,9 +658,16 @@ function commentarySection(text) {
658
658
  </mj-column>
659
659
  </mj-section>`;
660
660
  }
661
+ function headerImageTag(data) {
662
+ const src = `cid:${data.headerImageCid}`;
663
+ const alt = `${data.siteName} maintenance report`;
664
+ if (data.headerWidth && data.headerHeight && data.headerBgColor) {
665
+ return `<mj-image href="${data.siteUrl}" src="${src}" alt="${alt}" width="${data.headerWidth}px" height="${data.headerHeight}px" container-background-color="${data.headerBgColor}" />`;
666
+ }
667
+ return `<mj-image href="${data.siteUrl}" src="${src}" alt="${alt}" />`;
668
+ }
661
669
  function buildMjml(data) {
662
670
  const isTesting = data.reportType === "Testing";
663
- const headerSrc = `cid:${data.headerImageCid}`;
664
671
  const previewText = `Checked up on ${data.siteName}`;
665
672
  return `<mjml>
666
673
  <mj-head>
@@ -674,7 +681,7 @@ function buildMjml(data) {
674
681
  <mj-body background-color="white">
675
682
  <mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
676
683
  <mj-column>
677
- <mj-image href="${data.siteUrl}" src="${headerSrc}" />
684
+ ${headerImageTag(data)}
678
685
  </mj-column>
679
686
  </mj-section>
680
687
  <mj-section background-color="white">
@@ -796,6 +803,44 @@ var init_attachments = __esm({
796
803
  }
797
804
  });
798
805
 
806
+ // src/reports/maintenance-email/header-image.ts
807
+ import sharp from "sharp";
808
+ function channelToHex(value) {
809
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
810
+ }
811
+ async function prepareHeaderImage(bytes, options = {}) {
812
+ const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;
813
+ const input = Buffer.from(bytes);
814
+ const meta = await sharp(input).metadata();
815
+ const origWidth = meta.width;
816
+ const origHeight = meta.height;
817
+ if (!origWidth || !origHeight) {
818
+ throw new Error("prepareHeaderImage: could not read source image dimensions");
819
+ }
820
+ const displayWidth = Math.min(requestedDisplayWidth, origWidth);
821
+ const displayHeight = Math.round(displayWidth * origHeight / origWidth);
822
+ const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);
823
+ const out = await sharp(input).resize({ width: targetSourceWidth, withoutEnlargement: true }).flatten({ background: "#ffffff" }).jpeg({ quality: JPEG_QUALITY }).toBuffer();
824
+ const { dominant } = await sharp(out).stats();
825
+ const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;
826
+ return {
827
+ bytes: new Uint8Array(out),
828
+ contentType: "image/jpeg",
829
+ displayWidth,
830
+ displayHeight,
831
+ placeholderColor
832
+ };
833
+ }
834
+ var DEFAULT_DISPLAY_WIDTH, RETINA_SCALE, JPEG_QUALITY;
835
+ var init_header_image = __esm({
836
+ "src/reports/maintenance-email/header-image.ts"() {
837
+ "use strict";
838
+ DEFAULT_DISPLAY_WIDTH = 600;
839
+ RETINA_SCALE = 2;
840
+ JPEG_QUALITY = 82;
841
+ }
842
+ });
843
+
799
844
  // src/reports/send/resend.ts
800
845
  import { Resend } from "resend";
801
846
  function defaultResendClient() {
@@ -868,7 +913,8 @@ async function sendOne(client, base, site, report) {
868
913
  if (!report.lighthouse) {
869
914
  throw new Error(`Report ${report.reportId} has no Lighthouse scores`);
870
915
  }
871
- const { bytes, contentType } = await fetchAttachmentBytes(site.headerImage.url);
916
+ const original = await fetchAttachmentBytes(site.headerImage.url);
917
+ const header = await prepareHeaderImage(original.bytes);
872
918
  const bundled = await loadBundledImages();
873
919
  const slug = siteSlug(site.name);
874
920
  const cidName = `${slug}-header`;
@@ -882,7 +928,10 @@ async function sendOne(client, base, site, report) {
882
928
  gaUsersPrevious: report.gaUsersPrevious ?? 0,
883
929
  lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,
884
930
  commentary: report.commentary,
885
- headerImageCid: cidName
931
+ headerImageCid: cidName,
932
+ headerWidth: header.displayWidth,
933
+ headerHeight: header.displayHeight,
934
+ headerBgColor: header.placeholderColor
886
935
  });
887
936
  const subject = report.subjectOverride ?? `${site.name} ${report.reportType} Report`;
888
937
  const explicitTo = parseAddresses(site.reportRecipientsTo);
@@ -918,9 +967,9 @@ async function sendOne(client, base, site, report) {
918
967
  html,
919
968
  attachments: [
920
969
  {
921
- filename: site.headerImage.filename,
922
- content: Buffer.from(bytes).toString("base64"),
923
- contentType,
970
+ filename: `${cidName}.jpg`,
971
+ content: Buffer.from(header.bytes).toString("base64"),
972
+ contentType: header.contentType,
924
973
  inlineContentId: cidName
925
974
  },
926
975
  // Bundled images referenced via cid:rd-check-png / cid:rd-blurred-tests-jpg
@@ -982,6 +1031,7 @@ var init_orchestrate = __esm({
982
1031
  init_attachments();
983
1032
  init_render();
984
1033
  init_assets();
1034
+ init_header_image();
985
1035
  init_resend();
986
1036
  FROM_ADDRESS = "Reddoor Reports <reports@reddoorla.com>";
987
1037
  REPLY_TO = "info@reddoorla.com";
@@ -2021,6 +2071,14 @@ function rendererFor(json) {
2021
2071
  async function runAuditCommand(site, opts) {
2022
2072
  const which = parseOnly(opts.only) ?? ALL_AUDIT_NAMES;
2023
2073
  const cwd = opts.cwd ? resolve2(opts.cwd) : process.cwd();
2074
+ if (opts.writeAirtable !== void 0 && opts.fleet !== void 0) {
2075
+ throw Object.assign(
2076
+ new Error(
2077
+ "--write-airtable is not supported with --fleet. Each site has its own Airtable row; run per-site instead: `cd <site>/ && reddoor-maint audit --write-airtable`."
2078
+ ),
2079
+ { exitCode: 2 }
2080
+ );
2081
+ }
2024
2082
  let sites = await resolveSites({
2025
2083
  ...site !== void 0 ? { site } : {},
2026
2084
  ...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
@@ -3654,7 +3712,7 @@ function scoresFromWebsite(siteRow) {
3654
3712
  }
3655
3713
  function daysAgo(today, n) {
3656
3714
  const out = new Date(today);
3657
- out.setDate(out.getDate() - n);
3715
+ out.setUTCDate(out.getUTCDate() - n);
3658
3716
  return out;
3659
3717
  }
3660
3718
  async function draftReportForSite(base, siteRow, reportType, options = {}) {