@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/index.js CHANGED
@@ -2417,9 +2417,16 @@ function commentarySection(text) {
2417
2417
  </mj-column>
2418
2418
  </mj-section>`;
2419
2419
  }
2420
+ function headerImageTag(data) {
2421
+ const src = `cid:${data.headerImageCid}`;
2422
+ const alt = `${data.siteName} maintenance report`;
2423
+ if (data.headerWidth && data.headerHeight && data.headerBgColor) {
2424
+ return `<mj-image href="${data.siteUrl}" src="${src}" alt="${alt}" width="${data.headerWidth}px" height="${data.headerHeight}px" container-background-color="${data.headerBgColor}" />`;
2425
+ }
2426
+ return `<mj-image href="${data.siteUrl}" src="${src}" alt="${alt}" />`;
2427
+ }
2420
2428
  function buildMjml(data) {
2421
2429
  const isTesting = data.reportType === "Testing";
2422
- const headerSrc = `cid:${data.headerImageCid}`;
2423
2430
  const previewText = `Checked up on ${data.siteName}`;
2424
2431
  return `<mjml>
2425
2432
  <mj-head>
@@ -2433,7 +2440,7 @@ function buildMjml(data) {
2433
2440
  <mj-body background-color="white">
2434
2441
  <mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
2435
2442
  <mj-column>
2436
- <mj-image href="${data.siteUrl}" src="${headerSrc}" />
2443
+ ${headerImageTag(data)}
2437
2444
  </mj-column>
2438
2445
  </mj-section>
2439
2446
  <mj-section background-color="white">
@@ -2647,7 +2654,7 @@ function scoresFromWebsite(siteRow) {
2647
2654
  }
2648
2655
  function daysAgo(today, n) {
2649
2656
  const out = new Date(today);
2650
- out.setDate(out.getDate() - n);
2657
+ out.setUTCDate(out.getUTCDate() - n);
2651
2658
  return out;
2652
2659
  }
2653
2660
  async function draftReportForSite(base, siteRow, reportType, options = {}) {
@@ -2733,6 +2740,38 @@ function openBase(cfg) {
2733
2740
  return new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);
2734
2741
  }
2735
2742
 
2743
+ // src/reports/maintenance-email/header-image.ts
2744
+ import sharp from "sharp";
2745
+ var DEFAULT_DISPLAY_WIDTH = 600;
2746
+ var RETINA_SCALE = 2;
2747
+ var JPEG_QUALITY = 82;
2748
+ function channelToHex(value) {
2749
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
2750
+ }
2751
+ async function prepareHeaderImage(bytes, options = {}) {
2752
+ const requestedDisplayWidth = options.displayWidth ?? DEFAULT_DISPLAY_WIDTH;
2753
+ const input = Buffer.from(bytes);
2754
+ const meta = await sharp(input).metadata();
2755
+ const origWidth = meta.width;
2756
+ const origHeight = meta.height;
2757
+ if (!origWidth || !origHeight) {
2758
+ throw new Error("prepareHeaderImage: could not read source image dimensions");
2759
+ }
2760
+ const displayWidth = Math.min(requestedDisplayWidth, origWidth);
2761
+ const displayHeight = Math.round(displayWidth * origHeight / origWidth);
2762
+ const targetSourceWidth = Math.min(origWidth, displayWidth * RETINA_SCALE);
2763
+ const out = await sharp(input).resize({ width: targetSourceWidth, withoutEnlargement: true }).flatten({ background: "#ffffff" }).jpeg({ quality: JPEG_QUALITY }).toBuffer();
2764
+ const { dominant } = await sharp(out).stats();
2765
+ const placeholderColor = `#${channelToHex(dominant.r)}${channelToHex(dominant.g)}${channelToHex(dominant.b)}`;
2766
+ return {
2767
+ bytes: new Uint8Array(out),
2768
+ contentType: "image/jpeg",
2769
+ displayWidth,
2770
+ displayHeight,
2771
+ placeholderColor
2772
+ };
2773
+ }
2774
+
2736
2775
  // src/reports/send/resend.ts
2737
2776
  import { Resend } from "resend";
2738
2777
  function defaultResendClient() {
@@ -2796,7 +2835,8 @@ async function sendOne(client, base, site, report) {
2796
2835
  if (!report.lighthouse) {
2797
2836
  throw new Error(`Report ${report.reportId} has no Lighthouse scores`);
2798
2837
  }
2799
- const { bytes, contentType } = await fetchAttachmentBytes(site.headerImage.url);
2838
+ const original = await fetchAttachmentBytes(site.headerImage.url);
2839
+ const header = await prepareHeaderImage(original.bytes);
2800
2840
  const bundled = await loadBundledImages();
2801
2841
  const slug = siteSlug(site.name);
2802
2842
  const cidName = `${slug}-header`;
@@ -2810,7 +2850,10 @@ async function sendOne(client, base, site, report) {
2810
2850
  gaUsersPrevious: report.gaUsersPrevious ?? 0,
2811
2851
  lastTestedDate: report.lastTestedDate ? new Date(report.lastTestedDate) : null,
2812
2852
  commentary: report.commentary,
2813
- headerImageCid: cidName
2853
+ headerImageCid: cidName,
2854
+ headerWidth: header.displayWidth,
2855
+ headerHeight: header.displayHeight,
2856
+ headerBgColor: header.placeholderColor
2814
2857
  });
2815
2858
  const subject = report.subjectOverride ?? `${site.name} ${report.reportType} Report`;
2816
2859
  const explicitTo = parseAddresses(site.reportRecipientsTo);
@@ -2846,9 +2889,9 @@ async function sendOne(client, base, site, report) {
2846
2889
  html,
2847
2890
  attachments: [
2848
2891
  {
2849
- filename: site.headerImage.filename,
2850
- content: Buffer.from(bytes).toString("base64"),
2851
- contentType,
2892
+ filename: `${cidName}.jpg`,
2893
+ content: Buffer.from(header.bytes).toString("base64"),
2894
+ contentType: header.contentType,
2852
2895
  inlineContentId: cidName
2853
2896
  },
2854
2897
  // Bundled images referenced via cid:rd-check-png / cid:rd-blurred-tests-jpg