@reddoorla/maintenance 0.46.0 → 0.49.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 +407 -222
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +7 -3
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.js +295 -183
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -161,6 +161,9 @@ function parseNotifyRouting(raw) {
|
|
|
161
161
|
function isDashboardVisible(site) {
|
|
162
162
|
return site.status !== null && ACTIVE_STATUSES.has(site.status);
|
|
163
163
|
}
|
|
164
|
+
function toFrequency(raw) {
|
|
165
|
+
return typeof raw === "string" && FREQUENCIES.includes(raw) ? raw : "None";
|
|
166
|
+
}
|
|
164
167
|
function mapRow(rec) {
|
|
165
168
|
const f = rec.fields;
|
|
166
169
|
const attachments = f["Header image"] ?? [];
|
|
@@ -171,8 +174,8 @@ function mapRow(rec) {
|
|
|
171
174
|
url: String(f["url"] ?? ""),
|
|
172
175
|
status: f["Status"] ?? null,
|
|
173
176
|
pointOfContact: f["point of contact"] ?? null,
|
|
174
|
-
maintenanceFreq: f["maintenence freq"]
|
|
175
|
-
testingFreq: f["testing freq"]
|
|
177
|
+
maintenanceFreq: toFrequency(f["maintenence freq"]),
|
|
178
|
+
testingFreq: toFrequency(f["testing freq"]),
|
|
176
179
|
maintenanceDay: f["maintenance day"] ?? null,
|
|
177
180
|
testingDay: f["testing day"] ?? null,
|
|
178
181
|
ga4PropertyId: f["GA4 property ID"] ?? null,
|
|
@@ -295,7 +298,7 @@ async function updateLaunched(base, recordId, at) {
|
|
|
295
298
|
const fields = { Status: "maintenance", "Launched at": at };
|
|
296
299
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
297
300
|
}
|
|
298
|
-
var WEBSITES_TABLE, ACTIVE_STATUSES;
|
|
301
|
+
var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES;
|
|
299
302
|
var init_websites = __esm({
|
|
300
303
|
"src/reports/airtable/websites.ts"() {
|
|
301
304
|
"use strict";
|
|
@@ -304,6 +307,7 @@ var init_websites = __esm({
|
|
|
304
307
|
"maintenance",
|
|
305
308
|
"launch period"
|
|
306
309
|
]);
|
|
310
|
+
FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
|
|
307
311
|
}
|
|
308
312
|
});
|
|
309
313
|
|
|
@@ -607,7 +611,10 @@ var init_checklist = __esm({
|
|
|
607
611
|
label: "Interactions & Animations",
|
|
608
612
|
field: "Test: Interactions & Animations"
|
|
609
613
|
},
|
|
610
|
-
|
|
614
|
+
// `field` keeps its original Airtable column name ("…Verified After Updates") even though
|
|
615
|
+
// the client-facing label is now "Tested After Updates" — the column holds operator data,
|
|
616
|
+
// so renaming the label is display-only and avoids a live-base column migration.
|
|
617
|
+
{ key: "updates", label: "Tested After Updates", field: "Test: Verified After Updates" }
|
|
611
618
|
];
|
|
612
619
|
ALL_CHECKLIST_FIELDS = [...MAINTENANCE_CHECKLIST, ...TESTING_CHECKLIST].map(
|
|
613
620
|
(i) => i.field
|
|
@@ -701,7 +708,7 @@ async function createDraft(base, input) {
|
|
|
701
708
|
async function setDraftReady(base, recordId, ready) {
|
|
702
709
|
await base(REPORTS_TABLE).update([{ id: recordId, fields: { "Draft ready": ready } }]);
|
|
703
710
|
}
|
|
704
|
-
async function updateReportScores(base, recordId, scores, completedOn) {
|
|
711
|
+
async function updateReportScores(base, recordId, scores, completedOn, enrichment) {
|
|
705
712
|
const fields = {
|
|
706
713
|
"Lighthouse \u2014 Performance": scores.performance,
|
|
707
714
|
"Lighthouse \u2014 Accessibility": scores.accessibility,
|
|
@@ -709,6 +716,14 @@ async function updateReportScores(base, recordId, scores, completedOn) {
|
|
|
709
716
|
"Lighthouse \u2014 SEO": scores.seo
|
|
710
717
|
};
|
|
711
718
|
if (completedOn) fields["Completed on"] = ymd(completedOn);
|
|
719
|
+
if (enrichment?.gaUsersCurrent !== void 0)
|
|
720
|
+
fields["GA users (period)"] = enrichment.gaUsersCurrent;
|
|
721
|
+
if (enrichment?.gaUsersPrevious !== void 0)
|
|
722
|
+
fields["GA users (prev period)"] = enrichment.gaUsersPrevious;
|
|
723
|
+
if (enrichment?.searchFoundPage1 !== void 0)
|
|
724
|
+
fields["Search found page 1"] = enrichment.searchFoundPage1;
|
|
725
|
+
if (enrichment?.searchPosition !== void 0)
|
|
726
|
+
fields["Search position"] = enrichment.searchPosition;
|
|
712
727
|
await base(REPORTS_TABLE).update([{ id: recordId, fields }]);
|
|
713
728
|
}
|
|
714
729
|
async function listSendableReports(base) {
|
|
@@ -808,7 +823,7 @@ var init_copy = __esm({
|
|
|
808
823
|
"Links & Navigation",
|
|
809
824
|
"Form Functionality",
|
|
810
825
|
"Interactions & Animations",
|
|
811
|
-
"
|
|
826
|
+
"Tested After Updates"
|
|
812
827
|
],
|
|
813
828
|
notesHeader: "NOTES",
|
|
814
829
|
seoCta: "Contact us if you are interested in more in-depth data or have questions about SEO.",
|
|
@@ -824,14 +839,10 @@ var init_copy = __esm({
|
|
|
824
839
|
],
|
|
825
840
|
announceHeading: "YOUR ONGOING SITE CARE",
|
|
826
841
|
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:",
|
|
827
|
-
announceCadenceHeading: "WHAT TO EXPECT",
|
|
828
|
-
announceTestingLabel: "Full site testing",
|
|
829
|
-
announceMaintenanceLabel: "Routine maintenance",
|
|
830
|
-
announcePreviewLabel: "From your latest full site test:",
|
|
831
842
|
announceImprovementResend: "Your contact forms now deliver straight to your inbox through reliable infrastructure, so no inquiry slips through the cracks.",
|
|
832
843
|
announceImprovementSvelte5: "We've modernized your site to the latest framework \u2014 it's faster, more secure, and built to last.",
|
|
833
844
|
announceCadence: "After each one we'll send you a short report like this \u2014 there's nothing you need to do.",
|
|
834
|
-
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just
|
|
845
|
+
announceOpenDoor: "And if you'd ever like to expand the scope, add features, or freshen anything up, just let us know."
|
|
835
846
|
};
|
|
836
847
|
}
|
|
837
848
|
});
|
|
@@ -905,65 +916,124 @@ var init_html = __esm({
|
|
|
905
916
|
}
|
|
906
917
|
});
|
|
907
918
|
|
|
908
|
-
// src/reports/
|
|
909
|
-
function fmtDate(d) {
|
|
910
|
-
if (!d || Number.isNaN(d.getTime())) return "";
|
|
911
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
912
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
913
|
-
const yyyy = d.getUTCFullYear();
|
|
914
|
-
return `${mm}.${dd}.${yyyy}`;
|
|
915
|
-
}
|
|
919
|
+
// src/reports/email-sections.ts
|
|
916
920
|
function fmtUsers(n) {
|
|
917
921
|
return n.toLocaleString("en-US");
|
|
918
922
|
}
|
|
919
|
-
function
|
|
920
|
-
return
|
|
923
|
+
function checklistRowsSection(rows, opts) {
|
|
924
|
+
return rows.map((label, i) => {
|
|
925
|
+
const isLast = i === rows.length - 1;
|
|
926
|
+
const border = isLast ? "" : ` border-bottom="solid ${BORDER} 1px"`;
|
|
927
|
+
const lastPad = isLast ? ` padding-bottom="${opts.lastPaddingBottom}"` : "";
|
|
928
|
+
return `
|
|
929
|
+
<mj-section background-color="${opts.background}" padding="0px"${lastPad}>
|
|
930
|
+
<mj-group>
|
|
931
|
+
<mj-column padding-left="0px" width="90%"${border}>
|
|
932
|
+
<mj-text height="25px" padding-left="0px" color="${GREY}" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
933
|
+
</mj-column>
|
|
934
|
+
<mj-column width="10%"${border} padding-top="15px">
|
|
935
|
+
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
936
|
+
</mj-column>
|
|
937
|
+
</mj-group>
|
|
938
|
+
</mj-section>`;
|
|
939
|
+
}).join("");
|
|
940
|
+
}
|
|
941
|
+
function lighthouseScoresSection(lighthouse2, opts = {}) {
|
|
942
|
+
const background = opts.background ?? "#F4F4F4";
|
|
943
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
944
|
+
const labelTop = opts.pad ?? "55px";
|
|
945
|
+
const footnoteBottom = opts.pad ? "0px" : "36px";
|
|
946
|
+
const rows = LIGHTHOUSE_ROWS.map(
|
|
947
|
+
({ label, key, range }, i) => `
|
|
948
|
+
<mj-text color="${RED}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
949
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400" padding-top="0px">${lighthouse2[key]}</mj-text>
|
|
950
|
+
<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">${range}</mj-text>${i < LIGHTHOUSE_ROWS.length - 1 ? `
|
|
951
|
+
<mj-divider border-width="1px" border-style="solid" border-color="${BORDER}" padding="0" />` : ""}`
|
|
952
|
+
).join("");
|
|
953
|
+
return `
|
|
954
|
+
<mj-section background-color="${background}"${sectionPad}>
|
|
955
|
+
<mj-column>
|
|
956
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">LIGHTHOUSE SCORES*</mj-text>${rows}
|
|
957
|
+
<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="${footnoteBottom}" line-height="20px">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>
|
|
958
|
+
</mj-column>
|
|
959
|
+
</mj-section>`;
|
|
921
960
|
}
|
|
922
961
|
function analyticsTrendLine(cur, prev) {
|
|
923
962
|
if (cur === void 0 || prev === void 0) {
|
|
924
|
-
return
|
|
963
|
+
return trendLine(TREND_NEUTRAL, `Last Period: ${prev !== void 0 ? fmtUsers(prev) : "\u2014"}`);
|
|
925
964
|
}
|
|
926
965
|
if (prev === 0) {
|
|
927
|
-
return cur > 0 ?
|
|
966
|
+
return cur > 0 ? trendLine(TREND_UP, "\u25B2 New this period (0 last period)") : trendLine(TREND_NEUTRAL, "Last Period: 0");
|
|
928
967
|
}
|
|
929
968
|
const pct = Math.round((cur - prev) / prev * 100);
|
|
930
969
|
const range = `(${fmtUsers(prev)} \u2192 ${fmtUsers(cur)})`;
|
|
931
|
-
if (pct > 0) return
|
|
932
|
-
if (pct < 0) return
|
|
933
|
-
return
|
|
970
|
+
if (pct > 0) return trendLine(TREND_UP, `\u25B2 ${pct}% vs last period ${range}`);
|
|
971
|
+
if (pct < 0) return trendLine(TREND_NEUTRAL, `\u25BC ${Math.abs(pct)}% vs last period ${range}`);
|
|
972
|
+
return trendLine(TREND_NEUTRAL, `No change vs last period (${fmtUsers(prev)})`);
|
|
973
|
+
}
|
|
974
|
+
function trendLine(color, text) {
|
|
975
|
+
return `<mj-text color="${color}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${text}</mj-text>`;
|
|
976
|
+
}
|
|
977
|
+
function footnoteLine(text) {
|
|
978
|
+
return `<mj-text color="${GREY}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">${text}</mj-text>`;
|
|
979
|
+
}
|
|
980
|
+
function analyticsSection(opts) {
|
|
981
|
+
const users = opts.current !== void 0 ? fmtUsers(opts.current) : "\u2014";
|
|
982
|
+
const body = (opts.bodyLines ?? []).map((l) => trendLine(TREND_NEUTRAL, l)).join("\n ");
|
|
983
|
+
const footnotes = (opts.footnoteLines ?? []).map(footnoteLine).join("\n ");
|
|
984
|
+
const sectionPad = opts.pad ? ` padding-top="${opts.pad}" padding-bottom="${opts.pad}"` : "";
|
|
985
|
+
const labelTop = opts.pad ?? "75px";
|
|
986
|
+
return `
|
|
987
|
+
<mj-section background-color="${opts.background}"${sectionPad}>
|
|
988
|
+
<mj-column>
|
|
989
|
+
<mj-text color="${RED}" font-size="20px" font-weight="700" padding-top="${labelTop}">ANALYTICS</mj-text>
|
|
990
|
+
<mj-text color="${RED}" font-size="44px" font-weight="400">${users} Users</mj-text>
|
|
991
|
+
${analyticsTrendLine(opts.current, opts.previous)}
|
|
992
|
+
${body}
|
|
993
|
+
${footnotes}
|
|
994
|
+
</mj-column>
|
|
995
|
+
</mj-section>`;
|
|
996
|
+
}
|
|
997
|
+
var escapeXml, RED, GREY, BORDER, TREND_UP, TREND_NEUTRAL, CHECK_PNG, LIGHTHOUSE_ROWS;
|
|
998
|
+
var init_email_sections = __esm({
|
|
999
|
+
"src/reports/email-sections.ts"() {
|
|
1000
|
+
"use strict";
|
|
1001
|
+
init_html();
|
|
1002
|
+
init_assets();
|
|
1003
|
+
escapeXml = escapeHtml;
|
|
1004
|
+
RED = "#C00";
|
|
1005
|
+
GREY = "#757575";
|
|
1006
|
+
BORDER = "#CCCCCC";
|
|
1007
|
+
TREND_UP = "#2E7D32";
|
|
1008
|
+
TREND_NEUTRAL = GREY;
|
|
1009
|
+
CHECK_PNG = `cid:${CHECK_CID}`;
|
|
1010
|
+
LIGHTHOUSE_ROWS = [
|
|
1011
|
+
{ label: "Performance", key: "performance", range: "Acceptable 50\u201389 // Ideal 90\u2013100" },
|
|
1012
|
+
{ label: "Readability (A11y)", key: "accessibility", range: "Acceptable 80\u201399 // Ideal 100" },
|
|
1013
|
+
{ label: "Best Practices", key: "bestPractices", range: "Acceptable 60\u201379 // Ideal 80\u2013100" },
|
|
1014
|
+
{ label: "Site Structure", key: "seo", range: "Acceptable 50\u201389 // Ideal 90\u2013100" }
|
|
1015
|
+
];
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
// src/reports/maintenance-email/template.ts
|
|
1020
|
+
function fmtDate(d) {
|
|
1021
|
+
if (!d || Number.isNaN(d.getTime())) return "";
|
|
1022
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1023
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
1024
|
+
const yyyy = d.getUTCFullYear();
|
|
1025
|
+
return `${mm}.${dd}.${yyyy}`;
|
|
934
1026
|
}
|
|
935
1027
|
function maintenanceChecksSection(copy, searchPosition) {
|
|
936
1028
|
const googleLabel = searchPosition !== void 0 ? `Page 1 Google Result (#${searchPosition})` : copy.maintenanceChecks[3] ?? "";
|
|
937
1029
|
const rows = copy.maintenanceChecks.map((label, i) => i === 3 ? googleLabel : label);
|
|
938
|
-
return rows
|
|
939
|
-
(label, i) => `
|
|
940
|
-
<mj-section background-color="white" padding="0px"${i === rows.length - 1 ? ' padding-bottom="36px"' : ""}>
|
|
941
|
-
<mj-group>
|
|
942
|
-
<mj-column padding-left="0px" width="90%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
943
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
944
|
-
</mj-column>
|
|
945
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
946
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
947
|
-
</mj-column>
|
|
948
|
-
</mj-group>
|
|
949
|
-
</mj-section>`
|
|
950
|
-
).join("");
|
|
1030
|
+
return checklistRowsSection(rows, { background: "white", lastPaddingBottom: "36px" });
|
|
951
1031
|
}
|
|
952
1032
|
function testingChecklistSection(copy) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
<mj-group>
|
|
958
|
-
<mj-column width="90%" padding-left="0px"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""}>
|
|
959
|
-
<mj-text height="25px" padding-left="0px" color="#757575" padding-top="20px" padding-bottom="7.5px" font-size="16px">${escapeXml(label)}</mj-text>
|
|
960
|
-
</mj-column>
|
|
961
|
-
<mj-column width="10%"${i < rows.length - 1 ? ' border-bottom="solid #CCCCCC 1px"' : ""} padding-top="15px">
|
|
962
|
-
<mj-image align="right" padding-right="0px" width="20px" height="20px" padding-top="2.5px" padding-bottom="15px" src="${CHECK_PNG}" />
|
|
963
|
-
</mj-column>
|
|
964
|
-
</mj-group>
|
|
965
|
-
</mj-section>`
|
|
966
|
-
).join("");
|
|
1033
|
+
return checklistRowsSection(copy.testingChecklist, {
|
|
1034
|
+
background: "#F4F4F4",
|
|
1035
|
+
lastPaddingBottom: "60px"
|
|
1036
|
+
});
|
|
967
1037
|
}
|
|
968
1038
|
function maintenanceTestingPlaceholder(lastTested) {
|
|
969
1039
|
return `
|
|
@@ -983,7 +1053,7 @@ function testingIntroSection(copy) {
|
|
|
983
1053
|
<mj-section background-color="#F4F4F4">
|
|
984
1054
|
<mj-column>
|
|
985
1055
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">TESTING</mj-text>
|
|
986
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1056
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.testingIntro)}</mj-text>
|
|
987
1057
|
</mj-column>
|
|
988
1058
|
</mj-section>`;
|
|
989
1059
|
}
|
|
@@ -991,8 +1061,8 @@ function commentarySection(text, copy) {
|
|
|
991
1061
|
return `
|
|
992
1062
|
<mj-section background-color="white">
|
|
993
1063
|
<mj-column>
|
|
994
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${
|
|
995
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1064
|
+
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="55px">${escapeXml2(copy.notesHeader)}</mj-text>
|
|
1065
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(text).replace(/\r\n?|\n/g, "<br/>")}</mj-text>
|
|
996
1066
|
</mj-column>
|
|
997
1067
|
</mj-section>`;
|
|
998
1068
|
}
|
|
@@ -1001,8 +1071,8 @@ function hasHeaderDims(data) {
|
|
|
1001
1071
|
}
|
|
1002
1072
|
function headerImageTag(data) {
|
|
1003
1073
|
const src = `cid:${data.headerImageCid}`;
|
|
1004
|
-
const alt = `${
|
|
1005
|
-
const href = isHttpUrl(data.siteUrl) ?
|
|
1074
|
+
const alt = `${escapeXml2(data.siteName)} maintenance report`;
|
|
1075
|
+
const href = isHttpUrl(data.siteUrl) ? escapeXml2(data.siteUrl) : "#";
|
|
1006
1076
|
if (hasHeaderDims(data)) {
|
|
1007
1077
|
return `<mj-image href="${href}" src="${src}" alt="${alt}" width="${data.headerWidth}px" css-class="rd-header" container-background-color="${data.headerBgColor}" />`;
|
|
1008
1078
|
}
|
|
@@ -1015,7 +1085,7 @@ function headerStyleBlock(data) {
|
|
|
1015
1085
|
function buildMjml(data) {
|
|
1016
1086
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1017
1087
|
const isTesting = data.reportType === "Testing";
|
|
1018
|
-
const previewText = `Checked up on ${
|
|
1088
|
+
const previewText = `Checked up on ${escapeXml2(data.siteName)}`;
|
|
1019
1089
|
return `<mjml>
|
|
1020
1090
|
<mj-head>
|
|
1021
1091
|
<mj-attributes>
|
|
@@ -1037,89 +1107,65 @@ function buildMjml(data) {
|
|
|
1037
1107
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">COMPLETED ON</mj-text>
|
|
1038
1108
|
<mj-text color="#C00" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
1039
1109
|
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">MAINTENANCE CHECKS</mj-text>
|
|
1040
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${
|
|
1110
|
+
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px">${escapeXml2(copy.maintenanceIntro)}</mj-text>
|
|
1041
1111
|
</mj-column>
|
|
1042
1112
|
</mj-section>
|
|
1043
1113
|
${maintenanceChecksSection(copy, data.searchPosition)}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Readability</mj-text>
|
|
1052
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.accessibility}</mj-text>
|
|
1053
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 80\u201399 // Ideal 100</mj-text>
|
|
1054
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1055
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Best Practices</mj-text>
|
|
1056
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.bestPractices}</mj-text>
|
|
1057
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 60\u201379 // Ideal 80\u201392</mj-text>
|
|
1058
|
-
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1059
|
-
<mj-text color="#C00" font-size="20px" font-weight="300" padding-top="25px">Site Structure</mj-text>
|
|
1060
|
-
<mj-text color="#C00" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse.seo}</mj-text>
|
|
1061
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="0px" padding-bottom="36px">Acceptable 50\u201389 // Ideal 90\u2013100</mj-text>
|
|
1062
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">*A Lighthouse score is a numerical measure provided by Google's Lighthouse tool, which evaluates various aspects of a web page's quality.</mj-text>
|
|
1063
|
-
</mj-column>
|
|
1064
|
-
</mj-section>
|
|
1065
|
-
<mj-section background-color="white">
|
|
1066
|
-
<mj-column>
|
|
1067
|
-
<mj-text color="#C00" font-size="20px" font-weight="700" padding-top="75px">ANALYTICS</mj-text>
|
|
1068
|
-
<mj-text color="#C00" font-size="44px" font-weight="400">${data.gaUsersCurrent !== void 0 ? fmtUsers(data.gaUsersCurrent) : "\u2014"} Users</mj-text>
|
|
1069
|
-
${analyticsTrendLine(data.gaUsersCurrent, data.gaUsersPrevious)}
|
|
1070
|
-
<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" padding-top="24px" padding-bottom="36px" line-height="20px">${escapeXml(copy.seoCta)}</mj-text>
|
|
1071
|
-
</mj-column>
|
|
1072
|
-
</mj-section>
|
|
1114
|
+
${lighthouseScoresSection(data.lighthouse)}
|
|
1115
|
+
${analyticsSection({
|
|
1116
|
+
current: data.gaUsersCurrent,
|
|
1117
|
+
previous: data.gaUsersPrevious,
|
|
1118
|
+
background: "white",
|
|
1119
|
+
footnoteLines: [escapeXml2(copy.seoCta)]
|
|
1120
|
+
})}
|
|
1073
1121
|
${isTesting ? testingIntroSection(copy) + testingChecklistSection(copy) : maintenanceTestingPlaceholder(data.lastTestedDate)}
|
|
1074
1122
|
${data.commentary ? commentarySection(data.commentary, copy) : ""}
|
|
1075
1123
|
<mj-section background-color="white">
|
|
1076
1124
|
<mj-column padding-top="36px">
|
|
1077
1125
|
<mj-text color="#C00" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="36px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1078
1126
|
${copy.contact.map(
|
|
1079
|
-
(line, i) => i === copy.contact.length - 1 ? `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" padding-top="0px" line-height="30px" padding-bottom="36px">${
|
|
1127
|
+
(line, i) => i === copy.contact.length - 1 ? `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" padding-top="0px" line-height="30px" padding-bottom="36px">${escapeXml2(line)}</mj-text>` : `<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1080
1128
|
).join("\n ")}
|
|
1081
1129
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1082
|
-
<mj-text color="#757575" 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()} ${
|
|
1130
|
+
<mj-text color="#757575" 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()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1083
1131
|
<mj-text color="#757575" 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>
|
|
1084
1132
|
${[copy.footerOrg, ...copy.footerAddress].map(
|
|
1085
|
-
(line) => `<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${
|
|
1133
|
+
(line) => `<mj-text color="#757575" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1086
1134
|
).join("\n ")}
|
|
1087
1135
|
</mj-column>
|
|
1088
1136
|
</mj-section>
|
|
1089
1137
|
</mj-body>
|
|
1090
1138
|
</mjml>`;
|
|
1091
1139
|
}
|
|
1092
|
-
var
|
|
1140
|
+
var escapeXml2, BLURRED_TESTS;
|
|
1093
1141
|
var init_template = __esm({
|
|
1094
1142
|
"src/reports/maintenance-email/template.ts"() {
|
|
1095
1143
|
"use strict";
|
|
1096
1144
|
init_copy();
|
|
1097
1145
|
init_assets();
|
|
1146
|
+
init_email_sections();
|
|
1098
1147
|
init_html();
|
|
1099
1148
|
init_url();
|
|
1100
|
-
|
|
1101
|
-
CHECK_PNG = `cid:${CHECK_CID}`;
|
|
1149
|
+
escapeXml2 = escapeHtml;
|
|
1102
1150
|
BLURRED_TESTS = `cid:${BLURRED_CID}`;
|
|
1103
|
-
TREND_UP = "#2E7D32";
|
|
1104
|
-
TREND_NEUTRAL = "#757575";
|
|
1105
1151
|
}
|
|
1106
1152
|
});
|
|
1107
1153
|
|
|
1108
1154
|
// src/reports/launch-email/template.ts
|
|
1109
1155
|
function buildLaunchMjml(data) {
|
|
1110
1156
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1111
|
-
const previewText = `${
|
|
1157
|
+
const previewText = `${escapeXml2(data.siteName)} is live`;
|
|
1112
1158
|
const setupRows = copy.launchSetupItems.map(
|
|
1113
1159
|
(item) => `
|
|
1114
|
-
<mj-text color="${
|
|
1160
|
+
<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 ${escapeXml2(item)}</mj-text>`
|
|
1115
1161
|
).join("");
|
|
1116
1162
|
const contactRows = copy.contact.map(
|
|
1117
1163
|
(line) => `
|
|
1118
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
1164
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1119
1165
|
).join("");
|
|
1120
1166
|
const footerAddressRows = copy.footerAddress.map(
|
|
1121
1167
|
(line) => `
|
|
1122
|
-
<mj-text color="${
|
|
1168
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1123
1169
|
).join("");
|
|
1124
1170
|
return `<mjml>
|
|
1125
1171
|
<mj-head>
|
|
@@ -1137,90 +1183,112 @@ function buildLaunchMjml(data) {
|
|
|
1137
1183
|
</mj-section>
|
|
1138
1184
|
<mj-section background-color="white">
|
|
1139
1185
|
<mj-column>
|
|
1140
|
-
<mj-text color="${
|
|
1141
|
-
<mj-text color="${
|
|
1142
|
-
<mj-text color="${
|
|
1186
|
+
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="75px">${escapeXml2(copy.launchHeading)}</mj-text>
|
|
1187
|
+
<mj-text color="${RED2}" font-size="44px" font-weight="400">${fmtDate(data.completedOn)}</mj-text>
|
|
1188
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="20px">${escapeXml2(copy.launchBody)}</mj-text>
|
|
1143
1189
|
${setupRows}
|
|
1144
1190
|
</mj-column>
|
|
1145
1191
|
</mj-section>
|
|
1146
1192
|
<mj-section background-color="white">
|
|
1147
1193
|
<mj-column padding-top="36px">
|
|
1148
|
-
<mj-text color="${
|
|
1194
|
+
<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>
|
|
1149
1195
|
${contactRows}
|
|
1150
1196
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1151
|
-
<mj-text color="${
|
|
1152
|
-
<mj-text color="${
|
|
1153
|
-
<mj-text color="${
|
|
1197
|
+
<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()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1198
|
+
<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>
|
|
1199
|
+
<mj-text color="${GREY2}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(copy.footerOrg)}</mj-text>
|
|
1154
1200
|
${footerAddressRows}
|
|
1155
1201
|
</mj-column>
|
|
1156
1202
|
</mj-section>
|
|
1157
1203
|
</mj-body>
|
|
1158
1204
|
</mjml>`;
|
|
1159
1205
|
}
|
|
1160
|
-
var
|
|
1206
|
+
var RED2, GREY2;
|
|
1161
1207
|
var init_template2 = __esm({
|
|
1162
1208
|
"src/reports/launch-email/template.ts"() {
|
|
1163
1209
|
"use strict";
|
|
1164
1210
|
init_copy();
|
|
1165
1211
|
init_template();
|
|
1166
|
-
|
|
1167
|
-
|
|
1212
|
+
RED2 = "#C00";
|
|
1213
|
+
GREY2 = "#757575";
|
|
1168
1214
|
}
|
|
1169
1215
|
});
|
|
1170
1216
|
|
|
1171
1217
|
// src/reports/announcement-email/template.ts
|
|
1218
|
+
function sectionLabel(text) {
|
|
1219
|
+
return `<mj-text color="${RED3}" font-size="20px" font-weight="700" padding-top="0px">${escapeXml2(text)}</mj-text>`;
|
|
1220
|
+
}
|
|
1221
|
+
function bodyLine(text, paddingTop = "8px") {
|
|
1222
|
+
return `<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="${paddingTop}">${escapeXml2(text)}</mj-text>`;
|
|
1223
|
+
}
|
|
1224
|
+
function announcementSiteExtras(site) {
|
|
1225
|
+
return {
|
|
1226
|
+
cadence: { maintenance: site.maintenanceFreq, testing: site.testingFreq },
|
|
1227
|
+
improvements: { resendForms: true, svelte5: true }
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1172
1230
|
function buildAnnouncementMjml(data) {
|
|
1173
1231
|
const copy = data.copy ?? DEFAULT_COPY;
|
|
1174
1232
|
const previewText = "Your monthly report from Reddoor";
|
|
1233
|
+
const cad = data.cadence;
|
|
1234
|
+
const hasMaint = Boolean(cad && cad.maintenance !== "None");
|
|
1235
|
+
const hasTesting = Boolean(cad && cad.testing !== "None");
|
|
1175
1236
|
const improvementItems = [];
|
|
1176
1237
|
if (data.improvements?.resendForms) improvementItems.push(copy.announceImprovementResend);
|
|
1177
1238
|
if (data.improvements?.svelte5) improvementItems.push(copy.announceImprovementSvelte5);
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1239
|
+
const hasImpr = improvementItems.length > 0;
|
|
1240
|
+
const BANDS = ["white", "#F4F4F4"];
|
|
1241
|
+
let bandN = 0;
|
|
1242
|
+
const nextBg = () => BANDS[bandN++ % 2];
|
|
1243
|
+
const introBg = nextBg();
|
|
1244
|
+
const maintBg = hasMaint ? nextBg() : "";
|
|
1245
|
+
const testBg = hasTesting ? nextBg() : "";
|
|
1246
|
+
const lighthouseBg = nextBg();
|
|
1247
|
+
const analyticsBg = nextBg();
|
|
1248
|
+
const improvementsBg = hasImpr ? nextBg() : "";
|
|
1249
|
+
const contactBg = nextBg();
|
|
1250
|
+
const maintenanceSection = cad && cad.maintenance !== "None" ? `
|
|
1251
|
+
<mj-section background-color="${maintBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
1180
1252
|
<mj-column>
|
|
1181
|
-
|
|
1182
|
-
${
|
|
1183
|
-
(item) => `
|
|
1184
|
-
<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>`
|
|
1185
|
-
).join("")}
|
|
1253
|
+
${sectionLabel("MAINTENANCE CHECKS")}
|
|
1254
|
+
${bodyLine(`${copy.maintenanceIntro} We do this ${FREQ_PHRASE[cad.maintenance]}.${cad.testing === "None" ? ` ${copy.announceCadence}` : ""}`)}
|
|
1186
1255
|
</mj-column>
|
|
1187
|
-
</mj-section
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
checks: copy.testingChecklist
|
|
1194
|
-
});
|
|
1195
|
-
if (cad && cad.maintenance !== "None")
|
|
1196
|
-
cadenceBlocks.push({
|
|
1197
|
-
line: `${copy.announceMaintenanceLabel} \u2014 ${FREQ_PHRASE[cad.maintenance]}`,
|
|
1198
|
-
checks: copy.maintenanceChecks
|
|
1199
|
-
});
|
|
1200
|
-
const cadenceSection = cadenceBlocks.length > 0 ? `
|
|
1201
|
-
<mj-section background-color="white">
|
|
1256
|
+
</mj-section>${checklistRowsSection(copy.maintenanceChecks, {
|
|
1257
|
+
background: maintBg,
|
|
1258
|
+
lastPaddingBottom: SECTION_PAD
|
|
1259
|
+
})}` : "";
|
|
1260
|
+
const testingSection = cad && cad.testing !== "None" ? `
|
|
1261
|
+
<mj-section background-color="${testBg}" padding-top="${SECTION_PAD}" padding-bottom="0px">
|
|
1202
1262
|
<mj-column>
|
|
1203
|
-
|
|
1204
|
-
${
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1263
|
+
${sectionLabel("TESTING")}
|
|
1264
|
+
${bodyLine(`${copy.testingIntro} We run a full test ${FREQ_PHRASE[cad.testing]}. ${copy.announceCadence}`)}
|
|
1265
|
+
</mj-column>
|
|
1266
|
+
</mj-section>${checklistRowsSection(copy.testingChecklist, {
|
|
1267
|
+
background: testBg,
|
|
1268
|
+
lastPaddingBottom: SECTION_PAD
|
|
1269
|
+
})}` : "";
|
|
1270
|
+
const analytics = analyticsSection({
|
|
1271
|
+
current: data.gaUsersCurrent,
|
|
1272
|
+
previous: data.gaUsersPrevious,
|
|
1273
|
+
background: analyticsBg,
|
|
1274
|
+
pad: SECTION_PAD,
|
|
1275
|
+
bodyLines: data.searchPosition !== void 0 ? [`Page 1 Google result (#${data.searchPosition}) for your brand search`] : []
|
|
1276
|
+
});
|
|
1277
|
+
const improvementsSection = hasImpr ? `
|
|
1278
|
+
<mj-section background-color="${improvementsBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1279
|
+
<mj-column>
|
|
1280
|
+
${sectionLabel("RECENT IMPROVEMENTS")}
|
|
1281
|
+
${improvementItems.map((item) => bodyLine(item)).join("\n ")}
|
|
1282
|
+
${bodyLine(copy.announceOpenDoor, "16px")}
|
|
1210
1283
|
</mj-column>
|
|
1211
1284
|
</mj-section>` : "";
|
|
1212
|
-
const scoreRows = SCORE_PREVIEW.map(
|
|
1213
|
-
({ label, key }) => `
|
|
1214
|
-
<mj-text color="${RED2}" font-size="20px" font-weight="300" padding-top="25px">${label}</mj-text>
|
|
1215
|
-
<mj-text color="${RED2}" font-size="44px" font-weight="400" padding-top="0px">${data.lighthouse[key]}</mj-text>`
|
|
1216
|
-
).join("");
|
|
1217
1285
|
const contactRows = copy.contact.map(
|
|
1218
1286
|
(line) => `
|
|
1219
|
-
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${
|
|
1287
|
+
<mj-text font-family="helvetica, sans-serif" font-size="24px" font-weight="300" line-height="30px">${escapeXml2(line)}</mj-text>`
|
|
1220
1288
|
).join("");
|
|
1221
1289
|
const footerAddressRows = copy.footerAddress.map(
|
|
1222
1290
|
(line) => `
|
|
1223
|
-
<mj-text color="${
|
|
1291
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(line)}</mj-text>`
|
|
1224
1292
|
).join("");
|
|
1225
1293
|
return `<mjml>
|
|
1226
1294
|
<mj-head>
|
|
@@ -1229,66 +1297,54 @@ function buildAnnouncementMjml(data) {
|
|
|
1229
1297
|
<mj-section padding-left="11%" padding-right="11%"/>
|
|
1230
1298
|
<mj-image padding="0px" />
|
|
1231
1299
|
</mj-attributes>
|
|
1232
|
-
<mj-preview>${
|
|
1300
|
+
<mj-preview>${escapeXml2(previewText)}</mj-preview>
|
|
1233
1301
|
${headerStyleBlock(data)}
|
|
1234
1302
|
</mj-head>
|
|
1235
1303
|
<mj-body background-color="white">
|
|
1236
1304
|
<mj-section background-color="#F4F4F4" padding-top="0px" padding-bottom="0px" padding-left="0px" padding-right="0px">
|
|
1237
1305
|
<mj-column>${headerImageTag(data)}</mj-column>
|
|
1238
1306
|
</mj-section>
|
|
1239
|
-
<mj-section background-color="
|
|
1307
|
+
<mj-section background-color="${introBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1240
1308
|
<mj-column>
|
|
1241
|
-
|
|
1242
|
-
<mj-text color="${
|
|
1243
|
-
|
|
1309
|
+
${sectionLabel(copy.announceHeading)}
|
|
1310
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="16px" font-weight="300" line-height="24px" padding-top="20px">Prepared for ${escapeXml2(data.siteName)}</mj-text>
|
|
1311
|
+
${bodyLine(copy.announceBody)}
|
|
1244
1312
|
</mj-column>
|
|
1245
1313
|
</mj-section>
|
|
1246
|
-
${
|
|
1314
|
+
${maintenanceSection}
|
|
1315
|
+
${testingSection}
|
|
1316
|
+
${lighthouseScoresSection(data.lighthouse, { background: lighthouseBg, pad: SECTION_PAD })}
|
|
1317
|
+
${analytics}
|
|
1247
1318
|
${improvementsSection}
|
|
1248
|
-
<mj-section background-color="
|
|
1249
|
-
<mj-column>
|
|
1250
|
-
<mj-text color="${RED2}" font-size="20px" font-weight="700" padding-top="55px">${escapeXml(copy.announcePreviewLabel)}</mj-text>
|
|
1251
|
-
${scoreRows}
|
|
1252
|
-
</mj-column>
|
|
1253
|
-
</mj-section>
|
|
1254
|
-
<mj-section background-color="white">
|
|
1319
|
+
<mj-section background-color="${contactBg}" padding-top="${SECTION_PAD}" padding-bottom="${SECTION_PAD}">
|
|
1255
1320
|
<mj-column>
|
|
1256
|
-
<mj-text color="${
|
|
1257
|
-
</mj-column>
|
|
1258
|
-
</mj-section>
|
|
1259
|
-
<mj-section background-color="white">
|
|
1260
|
-
<mj-column padding-top="36px">
|
|
1261
|
-
<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>
|
|
1321
|
+
<mj-text color="${RED3}" font-family="helvetica, sans-serif" font-size="24px" font-weight="700" padding-top="0px" line-height="36px">Any questions, concerns or requests?</mj-text>
|
|
1262
1322
|
${contactRows}
|
|
1263
1323
|
<mj-divider border-width="1px" border-style="solid" border-color="#CCCCCC" padding="0" />
|
|
1264
|
-
<mj-text color="${
|
|
1265
|
-
<mj-text color="${
|
|
1266
|
-
<mj-text color="${
|
|
1324
|
+
<mj-text color="${GREY3}" 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()} ${escapeXml2(copy.footerOrg)}. All rights reserved.</mj-text>
|
|
1325
|
+
<mj-text color="${GREY3}" 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>
|
|
1326
|
+
<mj-text color="${GREY3}" font-family="helvetica, sans-serif" font-size="12px" font-weight="300" line-height="16px" padding-top="0" padding-bottom="0px">${escapeXml2(copy.footerOrg)}</mj-text>
|
|
1267
1327
|
${footerAddressRows}
|
|
1268
1328
|
</mj-column>
|
|
1269
1329
|
</mj-section>
|
|
1270
1330
|
</mj-body>
|
|
1271
1331
|
</mjml>`;
|
|
1272
1332
|
}
|
|
1273
|
-
var FREQ_PHRASE,
|
|
1333
|
+
var FREQ_PHRASE, RED3, GREY3, SECTION_PAD;
|
|
1274
1334
|
var init_template3 = __esm({
|
|
1275
1335
|
"src/reports/announcement-email/template.ts"() {
|
|
1276
1336
|
"use strict";
|
|
1277
1337
|
init_copy();
|
|
1278
1338
|
init_template();
|
|
1339
|
+
init_email_sections();
|
|
1279
1340
|
FREQ_PHRASE = {
|
|
1280
1341
|
Monthly: "every month",
|
|
1281
1342
|
Quarterly: "every quarter",
|
|
1282
1343
|
Yearly: "every year"
|
|
1283
1344
|
};
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
{ label: "Performance", key: "performance" },
|
|
1288
|
-
{ label: "Readability", key: "accessibility" },
|
|
1289
|
-
{ label: "Best Practices", key: "bestPractices" },
|
|
1290
|
-
{ label: "Site Structure", key: "seo" }
|
|
1291
|
-
];
|
|
1345
|
+
RED3 = "#C00";
|
|
1346
|
+
GREY3 = "#757575";
|
|
1347
|
+
SECTION_PAD = "40px";
|
|
1292
1348
|
}
|
|
1293
1349
|
});
|
|
1294
1350
|
|
|
@@ -1598,16 +1654,16 @@ __export(digest_exports, {
|
|
|
1598
1654
|
runDigest: () => runDigest
|
|
1599
1655
|
});
|
|
1600
1656
|
function readySection(items) {
|
|
1601
|
-
const heading = `<h2 style="color:${
|
|
1657
|
+
const heading = `<h2 style="color:${RED4};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Ready for your yes</h2>`;
|
|
1602
1658
|
if (items.length === 0) {
|
|
1603
|
-
return `${heading}<p style="color:${
|
|
1659
|
+
return `${heading}<p style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;margin:0">Nothing waiting on you.</p>`;
|
|
1604
1660
|
}
|
|
1605
1661
|
const rows = items.map((it) => {
|
|
1606
1662
|
const safeUrl = it.dashboardUrl.startsWith("https://") ? it.dashboardUrl : void 0;
|
|
1607
1663
|
const link = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">review & approve</a>` : `review & approve`;
|
|
1608
1664
|
return `
|
|
1609
1665
|
<tr>
|
|
1610
|
-
<td style="color:${
|
|
1666
|
+
<td style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">
|
|
1611
1667
|
<strong style="color:#222">${escapeHtml(it.siteName)}</strong> \u2014 ${escapeHtml(it.reportType)} (${escapeHtml(it.period)})
|
|
1612
1668
|
\u2014 ${link}
|
|
1613
1669
|
</td>
|
|
@@ -1617,15 +1673,15 @@ function readySection(items) {
|
|
|
1617
1673
|
}
|
|
1618
1674
|
function attentionBadge(status) {
|
|
1619
1675
|
if (status === "new")
|
|
1620
|
-
return `<strong style="color:${
|
|
1676
|
+
return `<strong style="color:${RED4};font-family:helvetica,sans-serif">NEW</strong> `;
|
|
1621
1677
|
if (status === "worse")
|
|
1622
|
-
return `<strong style="color:${
|
|
1678
|
+
return `<strong style="color:${RED4};font-family:helvetica,sans-serif">WORSE</strong> `;
|
|
1623
1679
|
return "";
|
|
1624
1680
|
}
|
|
1625
1681
|
function attentionSection(items) {
|
|
1626
|
-
const heading = `<h2 style="color:${
|
|
1682
|
+
const heading = `<h2 style="color:${RED4};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Needs attention</h2>`;
|
|
1627
1683
|
if (items.length === 0) {
|
|
1628
|
-
return `${heading}<p style="color:${
|
|
1684
|
+
return `${heading}<p style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;margin:0">All clear \u2014 nothing needs attention.</p>`;
|
|
1629
1685
|
}
|
|
1630
1686
|
const bySite = /* @__PURE__ */ new Map();
|
|
1631
1687
|
for (const it of items) {
|
|
@@ -1642,7 +1698,7 @@ function attentionSection(items) {
|
|
|
1642
1698
|
const titleHtml = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">${escapeHtml(it.title)}</a>` : escapeHtml(it.title);
|
|
1643
1699
|
return `
|
|
1644
1700
|
<tr>
|
|
1645
|
-
<td style="color:${
|
|
1701
|
+
<td style="color:${GREY4};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">${attentionBadge(it.status)}${titleHtml}</td>
|
|
1646
1702
|
</tr>`;
|
|
1647
1703
|
}).join("");
|
|
1648
1704
|
return `
|
|
@@ -1767,7 +1823,7 @@ function renderDigestHtml(sections) {
|
|
|
1767
1823
|
<table width="600" style="border-collapse:collapse">
|
|
1768
1824
|
<tr>
|
|
1769
1825
|
<td>
|
|
1770
|
-
<h1 style="color:${
|
|
1826
|
+
<h1 style="color:${RED4};font-family:helvetica,sans-serif;font-size:24px;font-weight:700;margin:0 0 8px">Your fleet today</h1>
|
|
1771
1827
|
${readySection(sections.readyForYourYes)}
|
|
1772
1828
|
${attentionSection(sections.needsAttention)}
|
|
1773
1829
|
</td>
|
|
@@ -1779,7 +1835,7 @@ function renderDigestHtml(sections) {
|
|
|
1779
1835
|
</body>
|
|
1780
1836
|
</html>`;
|
|
1781
1837
|
}
|
|
1782
|
-
var
|
|
1838
|
+
var GREY4, RED4, ANCHOR_STYLE, SEVERITY_ORDER, FROM_ADDRESS, DIGEST_OPERATOR_FALLBACK;
|
|
1783
1839
|
var init_digest = __esm({
|
|
1784
1840
|
"src/reports/digest.ts"() {
|
|
1785
1841
|
"use strict";
|
|
@@ -1791,9 +1847,9 @@ var init_digest = __esm({
|
|
|
1791
1847
|
init_digest_collectors();
|
|
1792
1848
|
init_digest_state();
|
|
1793
1849
|
init_html();
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
ANCHOR_STYLE = `color:${
|
|
1850
|
+
GREY4 = "#757575";
|
|
1851
|
+
RED4 = "#C00";
|
|
1852
|
+
ANCHOR_STYLE = `color:${RED4};font-family:helvetica,sans-serif`;
|
|
1797
1853
|
SEVERITY_ORDER = { critical: 0, warning: 1 };
|
|
1798
1854
|
FROM_ADDRESS = "Reddoor Reports <reports@reddoorla.com>";
|
|
1799
1855
|
DIGEST_OPERATOR_FALLBACK = "info@reddoorla.com";
|
|
@@ -1951,7 +2007,11 @@ async function sendOne(client, base, site, report) {
|
|
|
1951
2007
|
headerImageCid: cidName,
|
|
1952
2008
|
headerWidth: header.displayWidth,
|
|
1953
2009
|
headerHeight: header.displayHeight,
|
|
1954
|
-
headerBgColor: header.placeholderColor
|
|
2010
|
+
headerBgColor: header.placeholderColor,
|
|
2011
|
+
// Announcement-only: re-derive cadence + improvements from the site row so the SENT email
|
|
2012
|
+
// keeps its cadence copy + improvement callouts. Without this the send-time re-render drops
|
|
2013
|
+
// them entirely (they're not stored on the Reports row). Ignored by the other templates.
|
|
2014
|
+
...report.reportType === "Announcement" ? announcementSiteExtras(site) : {}
|
|
1955
2015
|
});
|
|
1956
2016
|
const reportDate = report.completedOn ? new Date(report.completedOn) : /* @__PURE__ */ new Date();
|
|
1957
2017
|
const subject = report.subjectOverride ?? `${site.name} \u2014 ${monthYear(reportDate)} ${report.reportType} Report`;
|
|
@@ -2037,6 +2097,7 @@ var init_orchestrate = __esm({
|
|
|
2037
2097
|
init_attachments();
|
|
2038
2098
|
init_render();
|
|
2039
2099
|
init_copy();
|
|
2100
|
+
init_template3();
|
|
2040
2101
|
init_assets();
|
|
2041
2102
|
init_header_image();
|
|
2042
2103
|
init_resend();
|
|
@@ -5719,10 +5780,40 @@ init_render();
|
|
|
5719
5780
|
init_websites();
|
|
5720
5781
|
init_copy();
|
|
5721
5782
|
init_reports();
|
|
5722
|
-
init_attachments();
|
|
5723
5783
|
import { mkdir as mkdir4, writeFile as writeFile10 } from "fs/promises";
|
|
5724
5784
|
import { dirname as dirname6 } from "path";
|
|
5725
5785
|
|
|
5786
|
+
// src/reports/queue.ts
|
|
5787
|
+
init_reports();
|
|
5788
|
+
var REPORT_TIER = {
|
|
5789
|
+
Maintenance: 1,
|
|
5790
|
+
Testing: 2,
|
|
5791
|
+
Announcement: 3,
|
|
5792
|
+
Launch: 3
|
|
5793
|
+
};
|
|
5794
|
+
function reportTier(type) {
|
|
5795
|
+
return REPORT_TIER[type];
|
|
5796
|
+
}
|
|
5797
|
+
async function queueDraft(base, report) {
|
|
5798
|
+
const newTier = reportTier(report.reportType);
|
|
5799
|
+
const others = (await listReportsForSite(base, report.siteId)).filter(isPendingApproval).filter((r) => r.id !== report.id);
|
|
5800
|
+
const blocker = others.find((r) => reportTier(r.reportType) >= newTier);
|
|
5801
|
+
if (blocker) {
|
|
5802
|
+
await setDraftReady(base, report.id, false);
|
|
5803
|
+
return { queued: false, blockedBy: blocker.reportType, supersededIds: [] };
|
|
5804
|
+
}
|
|
5805
|
+
const supersededIds = [];
|
|
5806
|
+
for (const r of others) {
|
|
5807
|
+
await setDraftReady(base, r.id, false);
|
|
5808
|
+
supersededIds.push(r.id);
|
|
5809
|
+
}
|
|
5810
|
+
await setDraftReady(base, report.id, true);
|
|
5811
|
+
return { queued: true, supersededIds };
|
|
5812
|
+
}
|
|
5813
|
+
|
|
5814
|
+
// src/reports/draft.ts
|
|
5815
|
+
init_attachments();
|
|
5816
|
+
|
|
5726
5817
|
// src/reports/ga/config.ts
|
|
5727
5818
|
init_credentials();
|
|
5728
5819
|
import { dirname as dirname5, join as join26 } from "path";
|
|
@@ -5776,6 +5867,7 @@ import { JWT as JWT2 } from "google-auth-library";
|
|
|
5776
5867
|
var WEBMASTERS_READONLY = "https://www.googleapis.com/auth/webmasters.readonly";
|
|
5777
5868
|
var SC_BASE = "https://searchconsole.googleapis.com/webmasters/v3";
|
|
5778
5869
|
var PAGE_1_MAX_POSITION = 10;
|
|
5870
|
+
var BRAND_QUERY_ROW_LIMIT = 100;
|
|
5779
5871
|
function bareHost(s) {
|
|
5780
5872
|
return s.trim().replace(/^sc-domain:/i, "").replace(/^https?:\/\//i, "").split("/")[0].replace(/^www\./i, "").toLowerCase();
|
|
5781
5873
|
}
|
|
@@ -5789,6 +5881,25 @@ function resolvePropertyCandidates(entries, host) {
|
|
|
5789
5881
|
function ymd3(d) {
|
|
5790
5882
|
return d.toISOString().slice(0, 10);
|
|
5791
5883
|
}
|
|
5884
|
+
function pickBrandQuery(rows) {
|
|
5885
|
+
let best;
|
|
5886
|
+
for (const r of rows) {
|
|
5887
|
+
if (typeof r.position !== "number") continue;
|
|
5888
|
+
const impressions = r.impressions ?? 0;
|
|
5889
|
+
if (best === void 0 || impressions > best.impressions || impressions === best.impressions && r.position < best.position) {
|
|
5890
|
+
best = { position: r.position, impressions };
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
return best?.position;
|
|
5894
|
+
}
|
|
5895
|
+
function selectBrandPosition(rows, exactQuery) {
|
|
5896
|
+
const want = exactQuery.toLowerCase();
|
|
5897
|
+
const exact = rows.find(
|
|
5898
|
+
(r) => typeof r.position === "number" && (r.keys?.[0] ?? "").toLowerCase() === want
|
|
5899
|
+
);
|
|
5900
|
+
if (exact) return exact.position;
|
|
5901
|
+
return pickBrandQuery(rows);
|
|
5902
|
+
}
|
|
5792
5903
|
async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
5793
5904
|
const key = JSON.parse(readFileSync4(q.keyPath, "utf8"));
|
|
5794
5905
|
const jwt = new JWT2({
|
|
@@ -5820,14 +5931,14 @@ async function fetchSearchPresence(q, periodStart, periodEnd) {
|
|
|
5820
5931
|
dimensionFilterGroups: [
|
|
5821
5932
|
{
|
|
5822
5933
|
filters: [
|
|
5823
|
-
{ dimension: "query", operator: "
|
|
5934
|
+
{ dimension: "query", operator: "contains", expression: q.query.toLowerCase() }
|
|
5824
5935
|
]
|
|
5825
5936
|
}
|
|
5826
5937
|
],
|
|
5827
|
-
rowLimit:
|
|
5938
|
+
rowLimit: BRAND_QUERY_ROW_LIMIT
|
|
5828
5939
|
}
|
|
5829
5940
|
});
|
|
5830
|
-
const pos = res.data.rows
|
|
5941
|
+
const pos = selectBrandPosition(res.data.rows ?? [], q.query.toLowerCase());
|
|
5831
5942
|
if (typeof pos === "number") {
|
|
5832
5943
|
return { foundOnPage1: pos <= PAGE_1_MAX_POSITION, position: Math.max(1, Math.round(pos)) };
|
|
5833
5944
|
}
|
|
@@ -5885,12 +5996,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
5885
5996
|
const path = options.previewPath ?? `reports/${slug}/draft.html`;
|
|
5886
5997
|
await mkdir4(dirname6(path), { recursive: true });
|
|
5887
5998
|
await writeFile10(path, html, "utf-8");
|
|
5888
|
-
return { reportRow: null, htmlPath: path, html, softFailures };
|
|
5999
|
+
return { reportRow: null, htmlPath: path, html, softFailures, queued: null, supersededIds: [] };
|
|
5889
6000
|
}
|
|
5890
6001
|
if (base === null) throw new Error("base required when previewOnly=false");
|
|
5891
6002
|
if (options.completeRowId) {
|
|
5892
|
-
await
|
|
5893
|
-
|
|
6003
|
+
await uploadDraftHtml(options.completeRowId, slug, periodEnd, html);
|
|
6004
|
+
const outcome2 = await queueDraft(base, {
|
|
6005
|
+
id: options.completeRowId,
|
|
6006
|
+
siteId: siteRow.id,
|
|
6007
|
+
reportType
|
|
6008
|
+
});
|
|
6009
|
+
return {
|
|
6010
|
+
reportRow: options.existingRow ?? null,
|
|
6011
|
+
htmlPath: null,
|
|
6012
|
+
html,
|
|
6013
|
+
softFailures,
|
|
6014
|
+
queued: outcome2.queued,
|
|
6015
|
+
supersededIds: outcome2.supersededIds
|
|
6016
|
+
};
|
|
5894
6017
|
}
|
|
5895
6018
|
const reportId = `${siteRow.name} \u2014 ${reportType} \u2014 ${periodEnd.toISOString().slice(0, 10)}`;
|
|
5896
6019
|
const created = await createDraft(base, {
|
|
@@ -5907,13 +6030,24 @@ async function draftReportForSite(base, siteRow, reportType, options = {}) {
|
|
|
5907
6030
|
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
5908
6031
|
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
5909
6032
|
});
|
|
5910
|
-
await
|
|
5911
|
-
|
|
6033
|
+
await uploadDraftHtml(created.id, slug, periodEnd, html);
|
|
6034
|
+
const outcome = await queueDraft(base, {
|
|
6035
|
+
id: created.id,
|
|
6036
|
+
siteId: siteRow.id,
|
|
6037
|
+
reportType
|
|
6038
|
+
});
|
|
6039
|
+
return {
|
|
6040
|
+
reportRow: created,
|
|
6041
|
+
htmlPath: null,
|
|
6042
|
+
html,
|
|
6043
|
+
softFailures,
|
|
6044
|
+
queued: outcome.queued,
|
|
6045
|
+
supersededIds: outcome.supersededIds
|
|
6046
|
+
};
|
|
5912
6047
|
}
|
|
5913
|
-
async function
|
|
6048
|
+
async function uploadDraftHtml(rowId, slug, periodEnd, html) {
|
|
5914
6049
|
const htmlFilename = `${slug}-${periodEnd.toISOString().slice(0, 10)}.html`;
|
|
5915
6050
|
await uploadAttachment(rowId, "Rendered HTML", html, htmlFilename, "text/html");
|
|
5916
|
-
await setDraftReady(base, rowId, true);
|
|
5917
6051
|
}
|
|
5918
6052
|
var NO_ENRICHMENT = { value: null, softFailed: false };
|
|
5919
6053
|
async function fetchGaUsers(siteRow, periodStart, periodEnd) {
|
|
@@ -5963,6 +6097,14 @@ async function derivePeriodStart(base, siteRow, reportType, today) {
|
|
|
5963
6097
|
}
|
|
5964
6098
|
|
|
5965
6099
|
// src/cli/commands/report.ts
|
|
6100
|
+
function draftLine(reportId, queued, supersededIds, verb = "drafted") {
|
|
6101
|
+
const id = reportId ?? "(unknown)";
|
|
6102
|
+
if (queued === false) {
|
|
6103
|
+
return `\u2022 ${verb} but NOT queued: ${id} \u2014 a higher-or-equal-tier report is already pending approval`;
|
|
6104
|
+
}
|
|
6105
|
+
const sup = supersededIds.length > 0 ? ` (superseded ${supersededIds.length} lower-tier draft${supersededIds.length > 1 ? "s" : ""})` : "";
|
|
6106
|
+
return `\u2713 ${verb}: ${id}${sup}`;
|
|
6107
|
+
}
|
|
5966
6108
|
function parseSingleSiteReportType(raw) {
|
|
5967
6109
|
if (raw === void 0 || raw.trim() === "") return "Maintenance";
|
|
5968
6110
|
const norm = raw.trim().toLowerCase();
|
|
@@ -6025,15 +6167,30 @@ async function draftDueReports(base, today) {
|
|
|
6025
6167
|
lines.push(`\u2022 skipped (already drafted ${period}): ${item.site.name} ${item.reportType}`);
|
|
6026
6168
|
continue;
|
|
6027
6169
|
}
|
|
6170
|
+
const blockedByPending = reports.some(
|
|
6171
|
+
(r) => r.siteId === item.site.id && r.id !== existing.id && r.sentAt === null && r.draftReady && reportTier(r.reportType) >= reportTier(item.reportType)
|
|
6172
|
+
);
|
|
6173
|
+
if (blockedByPending) {
|
|
6174
|
+
skipped++;
|
|
6175
|
+
lines.push(
|
|
6176
|
+
`\u2022 skipped (superseded \u2014 a higher-or-equal-tier report is pending): ${item.site.name} ${item.reportType}`
|
|
6177
|
+
);
|
|
6178
|
+
continue;
|
|
6179
|
+
}
|
|
6028
6180
|
try {
|
|
6029
6181
|
const result = await draftReportForSite(base, item.site, item.reportType, {
|
|
6030
6182
|
period,
|
|
6031
6183
|
completeRowId: existing.id,
|
|
6032
6184
|
existingRow: existing
|
|
6033
6185
|
});
|
|
6034
|
-
existing.draftReady = true;
|
|
6186
|
+
existing.draftReady = result.queued === true;
|
|
6035
6187
|
lines.push(
|
|
6036
|
-
|
|
6188
|
+
draftLine(
|
|
6189
|
+
result.reportRow?.reportId ?? existing.reportId,
|
|
6190
|
+
result.queued,
|
|
6191
|
+
result.supersededIds,
|
|
6192
|
+
"completed half-made draft"
|
|
6193
|
+
)
|
|
6037
6194
|
);
|
|
6038
6195
|
if (result.softFailures.length > 0) softFailedSites++;
|
|
6039
6196
|
} catch (e) {
|
|
@@ -6053,7 +6210,7 @@ async function draftDueReports(base, today) {
|
|
|
6053
6210
|
}
|
|
6054
6211
|
try {
|
|
6055
6212
|
const result = await draftReportForSite(base, item.site, item.reportType, { period });
|
|
6056
|
-
lines.push(
|
|
6213
|
+
lines.push(draftLine(result.reportRow?.reportId, result.queued, result.supersededIds));
|
|
6057
6214
|
if (result.reportRow) reports.push(result.reportRow);
|
|
6058
6215
|
if (result.softFailures.length > 0) softFailedSites++;
|
|
6059
6216
|
} catch (e) {
|
|
@@ -6083,7 +6240,15 @@ async function runSingleSiteDraft(slug, opts) {
|
|
|
6083
6240
|
if (opts.previewOnly) {
|
|
6084
6241
|
return { output: `Preview written to ${result.htmlPath}`, code: 0 };
|
|
6085
6242
|
}
|
|
6086
|
-
return {
|
|
6243
|
+
return {
|
|
6244
|
+
output: draftLine(
|
|
6245
|
+
result.reportRow?.reportId,
|
|
6246
|
+
result.queued,
|
|
6247
|
+
result.supersededIds,
|
|
6248
|
+
"Draft created"
|
|
6249
|
+
),
|
|
6250
|
+
code: 0
|
|
6251
|
+
};
|
|
6087
6252
|
}
|
|
6088
6253
|
|
|
6089
6254
|
// src/cli/commands/init.ts
|
|
@@ -6352,7 +6517,7 @@ async function launch(site, deps = {}) {
|
|
|
6352
6517
|
`\u26A0 Launch preview upload skipped for ${target.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6353
6518
|
);
|
|
6354
6519
|
}
|
|
6355
|
-
await
|
|
6520
|
+
await queueDraft(base, { id: report.id, siteId: target.id, reportType: "Launch" });
|
|
6356
6521
|
} catch (err) {
|
|
6357
6522
|
steps.push({ name: "draft", result: errorOf(err) });
|
|
6358
6523
|
return stop();
|
|
@@ -6419,6 +6584,7 @@ init_reports();
|
|
|
6419
6584
|
init_attachments();
|
|
6420
6585
|
init_render();
|
|
6421
6586
|
init_copy();
|
|
6587
|
+
init_template3();
|
|
6422
6588
|
async function announce(deps) {
|
|
6423
6589
|
const base = deps?.base ?? openBase(readAirtableConfig());
|
|
6424
6590
|
const now = deps?.now ?? /* @__PURE__ */ new Date();
|
|
@@ -6437,15 +6603,24 @@ async function announce(deps) {
|
|
|
6437
6603
|
results.push({ site: w.name, status: "skipped-no-scores" });
|
|
6438
6604
|
continue;
|
|
6439
6605
|
}
|
|
6606
|
+
const periodEnd = now;
|
|
6607
|
+
const periodStart = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
6608
|
+
const gaUsers = (await fetchGaUsers(w, periodStart, periodEnd)).value;
|
|
6609
|
+
const search = (await fetchSearch(w, periodStart, periodEnd)).value;
|
|
6610
|
+
const enrichment = {
|
|
6611
|
+
...gaUsers ? { gaUsersCurrent: gaUsers.current, gaUsersPrevious: gaUsers.previous } : {},
|
|
6612
|
+
...search ? { searchFoundPage1: search.foundOnPage1 } : {},
|
|
6613
|
+
...search?.foundOnPage1 && search.position !== null ? { searchPosition: search.position } : {}
|
|
6614
|
+
};
|
|
6440
6615
|
let report;
|
|
6441
6616
|
let statusKind;
|
|
6442
6617
|
const existing = await findReportByPeriod(base, w.id, "Announcement", period);
|
|
6443
6618
|
if (existing) {
|
|
6444
|
-
await updateReportScores(base, existing.id, scores, now);
|
|
6619
|
+
await updateReportScores(base, existing.id, scores, now, enrichment);
|
|
6445
6620
|
report = existing;
|
|
6446
6621
|
statusKind = "reused";
|
|
6447
6622
|
} else {
|
|
6448
|
-
report = await createDraft(base, draftInputFor2(w, scores, now, period));
|
|
6623
|
+
report = await createDraft(base, draftInputFor2(w, scores, now, period, enrichment));
|
|
6449
6624
|
statusKind = "drafted";
|
|
6450
6625
|
}
|
|
6451
6626
|
const slug = siteSlug(w.name);
|
|
@@ -6455,18 +6630,17 @@ async function announce(deps) {
|
|
|
6455
6630
|
reportType: "Announcement",
|
|
6456
6631
|
completedOn: now,
|
|
6457
6632
|
lighthouse: scores,
|
|
6633
|
+
gaUsersCurrent: gaUsers?.current,
|
|
6634
|
+
gaUsersPrevious: gaUsers?.previous,
|
|
6635
|
+
searchPosition: search?.foundOnPage1 ? search.position ?? void 0 : void 0,
|
|
6458
6636
|
lastTestedDate: null,
|
|
6459
6637
|
commentary: null,
|
|
6460
6638
|
copy: resolveCopy(w),
|
|
6461
6639
|
headerImageCid: `${slug}-header`,
|
|
6462
|
-
//
|
|
6463
|
-
//
|
|
6464
|
-
//
|
|
6465
|
-
|
|
6466
|
-
// Default-on fleet-wide for v1: both recent-improvement callouts render for every
|
|
6467
|
-
// site. Operator review (the draft never auto-sends) is the relevance backstop;
|
|
6468
|
-
// per-site conditioning of these flags is a future lever, not a v1 requirement.
|
|
6469
|
-
improvements: { resendForms: true, svelte5: true }
|
|
6640
|
+
// cadence (the client's go-forward pace, "None" omitted) + default-on improvement
|
|
6641
|
+
// callouts. Shared with the send re-render via announcementSiteExtras so the sent
|
|
6642
|
+
// email matches this reviewed preview.
|
|
6643
|
+
...announcementSiteExtras(w)
|
|
6470
6644
|
});
|
|
6471
6645
|
try {
|
|
6472
6646
|
await uploadAttachment(
|
|
@@ -6481,9 +6655,19 @@ async function announce(deps) {
|
|
|
6481
6655
|
`\u26A0 Announcement preview upload skipped for ${w.name}: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6482
6656
|
);
|
|
6483
6657
|
}
|
|
6484
|
-
await
|
|
6658
|
+
const queue = await queueDraft(base, {
|
|
6659
|
+
id: report.id,
|
|
6660
|
+
siteId: w.id,
|
|
6661
|
+
reportType: "Announcement"
|
|
6662
|
+
});
|
|
6485
6663
|
const recipientMissing = !(w.reportRecipientsTo && w.reportRecipientsTo.trim());
|
|
6486
|
-
results.push({
|
|
6664
|
+
results.push({
|
|
6665
|
+
site: w.name,
|
|
6666
|
+
status: statusKind,
|
|
6667
|
+
reportId: report.id,
|
|
6668
|
+
recipientMissing,
|
|
6669
|
+
queued: queue.queued
|
|
6670
|
+
});
|
|
6487
6671
|
} catch (err) {
|
|
6488
6672
|
results.push({
|
|
6489
6673
|
site: w.name,
|
|
@@ -6505,7 +6689,7 @@ function scoresFromRow(w) {
|
|
|
6505
6689
|
seo: w.seoScore
|
|
6506
6690
|
};
|
|
6507
6691
|
}
|
|
6508
|
-
function draftInputFor2(w, scores, now, period) {
|
|
6692
|
+
function draftInputFor2(w, scores, now, period, enrichment) {
|
|
6509
6693
|
return {
|
|
6510
6694
|
reportId: `${w.name} \u2014 Announcement \u2014 ${now.toISOString().slice(0, 10)}`,
|
|
6511
6695
|
siteId: w.id,
|
|
@@ -6516,7 +6700,8 @@ function draftInputFor2(w, scores, now, period) {
|
|
|
6516
6700
|
completedOn: now,
|
|
6517
6701
|
lighthouse: scores,
|
|
6518
6702
|
lastTestedDate: null,
|
|
6519
|
-
subjectOverride: `Your testing & maintenance schedule for ${w.name}
|
|
6703
|
+
subjectOverride: `Your testing & maintenance schedule for ${w.name}`,
|
|
6704
|
+
...enrichment
|
|
6520
6705
|
};
|
|
6521
6706
|
}
|
|
6522
6707
|
|