@reddoorla/maintenance 0.67.0 → 0.68.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/README.md +16 -0
- package/dist/airtable-WGQQ3KAD.js +9 -0
- package/dist/{announce-74ZIAV26.js → announce-72R3LRNT.js} +9 -11
- package/dist/{announce-74ZIAV26.js.map → announce-72R3LRNT.js.map} +1 -1
- package/dist/{bump-deps-HYBYNPMY.js → bump-deps-QNM3H4GI.js} +2 -2
- package/dist/{chunk-JDGIO54P.js → chunk-2E2X5BCG.js} +43 -3
- package/dist/chunk-2E2X5BCG.js.map +1 -0
- package/dist/{chunk-VKAYZVMI.js → chunk-45AUGDFR.js} +2 -1
- package/dist/chunk-45AUGDFR.js.map +1 -0
- package/dist/{chunk-SYTPVYIK.js → chunk-6JFOAPW4.js} +241 -4
- package/dist/chunk-6JFOAPW4.js.map +1 -0
- package/dist/{chunk-Y4O44LZB.js → chunk-6MT5W6UF.js} +2 -2
- package/dist/{chunk-OAMJINM5.js → chunk-A5KYRGQA.js} +4 -4
- package/dist/{chunk-CL5DYUWQ.js → chunk-C3PDS623.js} +3 -1
- package/dist/chunk-C3PDS623.js.map +1 -0
- package/dist/{chunk-KN5OC5YH.js → chunk-GKSXZIMX.js} +3 -3
- package/dist/{chunk-YKOOAWVL.js → chunk-H6YZBXBQ.js} +18 -12
- package/dist/chunk-H6YZBXBQ.js.map +1 -0
- package/dist/chunk-MAYHLXCS.js +322 -0
- package/dist/chunk-MAYHLXCS.js.map +1 -0
- package/dist/{chunk-ULL4AD3Y.js → chunk-O6G4HHMF.js} +7 -9
- package/dist/{chunk-ULL4AD3Y.js.map → chunk-O6G4HHMF.js.map} +1 -1
- package/dist/{chunk-6LIWDIXU.js → chunk-RINXPU3H.js} +2 -2
- package/dist/{chunk-T34EUM5B.js → chunk-XWPZ46IN.js} +2 -2
- package/dist/{chunk-7MJJEEII.js → chunk-YQMYQAJX.js} +2 -2
- package/dist/cli/bin.js +32 -14
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +5 -5
- package/dist/{convert-to-pnpm-5MPZWFHM.js → convert-to-pnpm-XH7NYRVK.js} +2 -2
- package/dist/{digest-MECMZPFE.js → digest-J7TDG62Q.js} +14 -5
- package/dist/digest-J7TDG62Q.js.map +1 -0
- package/dist/ensure-site-2PF522VL.js +87 -0
- package/dist/ensure-site-2PF522VL.js.map +1 -0
- package/dist/{github-signals-VWBMQNLH.js → github-signals-PP7GWYIY.js} +4 -4
- package/dist/index.d.ts +7 -1
- package/dist/index.js +95 -29
- package/dist/index.js.map +1 -1
- package/dist/{init-X3BCJAUG.js → init-WLR35YGN.js} +2 -2
- package/dist/{launch-NQCL5K5H.js → launch-2HPQXWII.js} +10 -12
- package/dist/{launch-NQCL5K5H.js.map → launch-2HPQXWII.js.map} +1 -1
- package/dist/{lighthouse-airtable-QUNOSDT5.js → lighthouse-airtable-UBSXKHAQ.js} +3 -3
- package/dist/{onboard-7JOBZY35.js → onboard-X6KJ5Z2V.js} +2 -2
- package/dist/{orchestrate-JQX72RJE.js → orchestrate-PVCGBH4K.js} +4 -6
- package/dist/preflight-KWVYJ2DU.js +64 -0
- package/dist/preflight-KWVYJ2DU.js.map +1 -0
- package/dist/{renovate-dispatch-PWXFZNHN.js → renovate-dispatch-LG4RJGDY.js} +2 -2
- package/dist/{report-QUKNKUUE.js → report-ZBFMTT76.js} +8 -9
- package/dist/{report-QUKNKUUE.js.map → report-ZBFMTT76.js.map} +1 -1
- package/dist/{self-updating-OKJKU2FE.js → self-updating-JTTFOJ7Q.js} +2 -2
- package/dist/{selftest-6JVYR24F.js → selftest-LFZPZRJP.js} +7 -9
- package/dist/{selftest-6JVYR24F.js.map → selftest-LFZPZRJP.js.map} +1 -1
- package/dist/{svelte-codemods-GG6EEES5.js → svelte-codemods-KJLNH6FQ.js} +2 -2
- package/dist/{sync-configs-UFHZZPL7.js → sync-configs-6GBZLGRQ.js} +2 -2
- package/dist/{upgrade-S25N3473.js → upgrade-TPTE26FG.js} +2 -2
- package/dist/{websites-U3UZS5VG.js → websites-6YTT5ZTO.js} +2 -2
- package/dist/{write-audits-to-airtable-GAJUCSTY.js → write-audits-to-airtable-IWOO4FFH.js} +4 -4
- package/package.json +1 -1
- package/dist/airtable-33SY2UBZ.js +0 -9
- package/dist/chunk-CL5DYUWQ.js.map +0 -1
- package/dist/chunk-JDGIO54P.js.map +0 -1
- package/dist/chunk-OCS7SZUZ.js +0 -243
- package/dist/chunk-OCS7SZUZ.js.map +0 -1
- package/dist/chunk-OGGRFBL4.js +0 -14
- package/dist/chunk-OGGRFBL4.js.map +0 -1
- package/dist/chunk-SYTPVYIK.js.map +0 -1
- package/dist/chunk-VKAYZVMI.js.map +0 -1
- package/dist/chunk-YKOOAWVL.js.map +0 -1
- package/dist/digest-MECMZPFE.js.map +0 -1
- /package/dist/{airtable-33SY2UBZ.js.map → airtable-WGQQ3KAD.js.map} +0 -0
- /package/dist/{bump-deps-HYBYNPMY.js.map → bump-deps-QNM3H4GI.js.map} +0 -0
- /package/dist/{chunk-Y4O44LZB.js.map → chunk-6MT5W6UF.js.map} +0 -0
- /package/dist/{chunk-OAMJINM5.js.map → chunk-A5KYRGQA.js.map} +0 -0
- /package/dist/{chunk-KN5OC5YH.js.map → chunk-GKSXZIMX.js.map} +0 -0
- /package/dist/{chunk-6LIWDIXU.js.map → chunk-RINXPU3H.js.map} +0 -0
- /package/dist/{chunk-T34EUM5B.js.map → chunk-XWPZ46IN.js.map} +0 -0
- /package/dist/{chunk-7MJJEEII.js.map → chunk-YQMYQAJX.js.map} +0 -0
- /package/dist/{convert-to-pnpm-5MPZWFHM.js.map → convert-to-pnpm-XH7NYRVK.js.map} +0 -0
- /package/dist/{github-signals-VWBMQNLH.js.map → github-signals-PP7GWYIY.js.map} +0 -0
- /package/dist/{init-X3BCJAUG.js.map → init-WLR35YGN.js.map} +0 -0
- /package/dist/{lighthouse-airtable-QUNOSDT5.js.map → lighthouse-airtable-UBSXKHAQ.js.map} +0 -0
- /package/dist/{onboard-7JOBZY35.js.map → onboard-X6KJ5Z2V.js.map} +0 -0
- /package/dist/{orchestrate-JQX72RJE.js.map → orchestrate-PVCGBH4K.js.map} +0 -0
- /package/dist/{renovate-dispatch-PWXFZNHN.js.map → renovate-dispatch-LG4RJGDY.js.map} +0 -0
- /package/dist/{self-updating-OKJKU2FE.js.map → self-updating-JTTFOJ7Q.js.map} +0 -0
- /package/dist/{svelte-codemods-GG6EEES5.js.map → svelte-codemods-KJLNH6FQ.js.map} +0 -0
- /package/dist/{sync-configs-UFHZZPL7.js.map → sync-configs-6GBZLGRQ.js.map} +0 -0
- /package/dist/{upgrade-S25N3473.js.map → upgrade-TPTE26FG.js.map} +0 -0
- /package/dist/{websites-U3UZS5VG.js.map → websites-6YTT5ZTO.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-GAJUCSTY.js.map → write-audits-to-airtable-IWOO4FFH.js.map} +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ELIGIBLE_STATUSES,
|
|
3
|
+
reportPeriodKey
|
|
4
|
+
} from "./chunk-45AUGDFR.js";
|
|
5
|
+
import {
|
|
6
|
+
isProbablyEmail,
|
|
7
|
+
parseAddresses
|
|
8
|
+
} from "./chunk-H6YZBXBQ.js";
|
|
9
|
+
import {
|
|
10
|
+
listAllReports
|
|
11
|
+
} from "./chunk-6JFOAPW4.js";
|
|
12
|
+
import {
|
|
13
|
+
listWebsites,
|
|
14
|
+
siteSlug
|
|
15
|
+
} from "./chunk-C3PDS623.js";
|
|
16
|
+
import {
|
|
17
|
+
openBase,
|
|
18
|
+
readAirtableConfig
|
|
19
|
+
} from "./chunk-HN6I2XWI.js";
|
|
20
|
+
|
|
21
|
+
// src/reports/preflight.ts
|
|
22
|
+
var OPERATOR_DOMAINS = ["reddoorla.com", "tuckerlemos.com"];
|
|
23
|
+
var STALE_ANCHOR_DAYS = 396;
|
|
24
|
+
function domainOf(addr) {
|
|
25
|
+
const at = addr.lastIndexOf("@");
|
|
26
|
+
return at === -1 ? "" : addr.slice(at + 1).toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
function isOperatorSite(site) {
|
|
29
|
+
if (!site.url) return false;
|
|
30
|
+
try {
|
|
31
|
+
const host = new URL(
|
|
32
|
+
/^[a-z][a-z0-9+.-]*:\/\//i.test(site.url) ? site.url : `https://${site.url}`
|
|
33
|
+
).hostname.toLowerCase();
|
|
34
|
+
return OPERATOR_DOMAINS.some((d) => host === d || host.endsWith(`.${d}`));
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function lastSentForType(reports, type) {
|
|
40
|
+
const sent = reports.filter((r) => r.reportType === type && r.sentAt !== null).map((r) => r.sentAt).sort();
|
|
41
|
+
return sent[sent.length - 1] ?? null;
|
|
42
|
+
}
|
|
43
|
+
function checkFrequency(site, which, reports, findings, now) {
|
|
44
|
+
const raw = which === "maintenance" ? site.maintenanceFreqRaw : site.testingFreqRaw;
|
|
45
|
+
const coerced = which === "maintenance" ? site.maintenanceFreq : site.testingFreq;
|
|
46
|
+
if (raw !== null && raw !== "" && coerced === "None" && raw.trim() !== "None") {
|
|
47
|
+
findings.push({
|
|
48
|
+
level: "fail",
|
|
49
|
+
check: "frequency-unrecognized",
|
|
50
|
+
message: `${which} frequency cell is '${raw}' \u2014 not an exact Monthly/Quarterly/Yearly/None, so the scheduler silently treats it as None and the site drops off the calendar; fix the Airtable value`
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (coerced === "None") return;
|
|
55
|
+
const anchor = which === "maintenance" ? site.maintenanceDay : site.testingDay;
|
|
56
|
+
if (!anchor) return;
|
|
57
|
+
const type = which === "maintenance" ? "Maintenance" : "Testing";
|
|
58
|
+
const lastSent = lastSentForType(reports, type);
|
|
59
|
+
if (lastSent && new Date(lastSent).getTime() >= new Date(anchor).getTime()) return;
|
|
60
|
+
const ageDays = (now.getTime() - new Date(anchor).getTime()) / 864e5;
|
|
61
|
+
if (ageDays > STALE_ANCHOR_DAYS) {
|
|
62
|
+
findings.push({
|
|
63
|
+
level: "warn",
|
|
64
|
+
check: "anchor-stale",
|
|
65
|
+
message: `${which} day anchor is ${anchor} (>13 months old) and nothing newer has been sent \u2014 \`report --due\` will draft a back-dated overdue report; reset the anchor or clear the schedule`
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function preflightSite(site, reports, type, now) {
|
|
70
|
+
const findings = [];
|
|
71
|
+
const scheduledForType = type === "Announcement" ? true : type === "Maintenance" ? site.maintenanceFreq !== "None" : site.testingFreq !== "None";
|
|
72
|
+
if (!scheduledForType) {
|
|
73
|
+
findings.push({
|
|
74
|
+
level: "info",
|
|
75
|
+
check: "not-scheduled",
|
|
76
|
+
message: `no ${type} schedule (frequency is None/blank) \u2014 send-requirement checks skipped; schedule + pending-draft checks still apply`
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const explicitTo = parseAddresses(site.reportRecipientsTo);
|
|
80
|
+
const fallbackTo = parseAddresses(site.pointOfContact);
|
|
81
|
+
const to = scheduledForType ? explicitTo ?? fallbackTo ?? [] : [];
|
|
82
|
+
if (scheduledForType && to.length === 0) {
|
|
83
|
+
findings.push({
|
|
84
|
+
level: "fail",
|
|
85
|
+
check: "recipients-missing",
|
|
86
|
+
message: "no recipients \u2014 Report recipients (To) and point of contact are both empty; the send will throw"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (const addr of to) {
|
|
90
|
+
if (!isProbablyEmail(addr)) {
|
|
91
|
+
findings.push({
|
|
92
|
+
level: "fail",
|
|
93
|
+
check: "recipients-malformed",
|
|
94
|
+
message: `recipient '${addr}' is malformed \u2014 use bare addresses only in Report recipients (To) / point of contact`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const cc = scheduledForType ? parseAddresses(site.reportRecipientsCc) ?? [] : [];
|
|
99
|
+
for (const addr of cc) {
|
|
100
|
+
if (!isProbablyEmail(addr)) {
|
|
101
|
+
findings.push({
|
|
102
|
+
level: "fail",
|
|
103
|
+
check: "recipients-malformed",
|
|
104
|
+
message: `CC '${addr}' is malformed \u2014 fix Report recipients (CC)`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!isOperatorSite(site)) {
|
|
109
|
+
const operatorAddrs = to.filter((a) => OPERATOR_DOMAINS.includes(domainOf(a)));
|
|
110
|
+
if (operatorAddrs.length > 0) {
|
|
111
|
+
const allOperator = operatorAddrs.length === to.length;
|
|
112
|
+
findings.push({
|
|
113
|
+
level: "warn",
|
|
114
|
+
check: "recipient-operator-address",
|
|
115
|
+
message: allOperator ? `resolved To is ONLY operator address(es) ${operatorAddrs.join(", ")} \u2014 probably a test-send leftover; the client will NOT receive this report` : `resolved To includes operator address(es) ${operatorAddrs.join(", ")} alongside the client's \u2014 confirm that's intentional (the ops inbox is already CC'd on every send)`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (scheduledForType && explicitTo && fallbackTo) {
|
|
120
|
+
const a = [...explicitTo].map((x) => x.toLowerCase()).sort().join(",");
|
|
121
|
+
const b = [...fallbackTo].map((x) => x.toLowerCase()).sort().join(",");
|
|
122
|
+
if (a !== b) {
|
|
123
|
+
findings.push({
|
|
124
|
+
level: "info",
|
|
125
|
+
check: "to-override-shadows-contact",
|
|
126
|
+
message: `Report recipients (To) [${explicitTo.join(", ")}] overrides point of contact [${fallbackTo.join(", ")}] \u2014 the contact won't receive this unless also listed`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (scheduledForType && !site.headerImage) {
|
|
131
|
+
findings.push({
|
|
132
|
+
level: "fail",
|
|
133
|
+
check: "header-image-missing",
|
|
134
|
+
message: "no Header image on the Websites row \u2014 the send will throw"
|
|
135
|
+
});
|
|
136
|
+
} else if (scheduledForType && site.headerImage && !site.headerImage.type.startsWith("image/")) {
|
|
137
|
+
findings.push({
|
|
138
|
+
level: "fail",
|
|
139
|
+
check: "header-image-not-image",
|
|
140
|
+
message: `Header image attachment is '${site.headerImage.type}' (${site.headerImage.filename}) \u2014 not a decodable image; the send throws in prepareHeaderImage`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const scores = [site.pScore, site.rScore, site.bpScore, site.seoScore];
|
|
144
|
+
if (scheduledForType && scores.some((s) => s === null)) {
|
|
145
|
+
if (type === "Announcement") {
|
|
146
|
+
findings.push({
|
|
147
|
+
level: "warn",
|
|
148
|
+
check: "scores-missing",
|
|
149
|
+
message: "Websites row is missing Lighthouse scores \u2014 `announce` will skip this site; run `audit lighthouse --write-airtable` first"
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
findings.push({
|
|
153
|
+
level: "fail",
|
|
154
|
+
check: "scores-missing",
|
|
155
|
+
message: "Websites row is missing Lighthouse scores \u2014 drafting will throw (scoresFromWebsite); run `audit lighthouse --write-airtable` first"
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (type === "Announcement" && site.status !== "maintenance") {
|
|
160
|
+
findings.push({
|
|
161
|
+
level: "info",
|
|
162
|
+
check: "status-not-maintenance",
|
|
163
|
+
message: `status is '${site.status ?? "(blank)"}' \u2014 \`announce --all\` only drafts for maintenance sites`
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const pending = reports.filter((r) => r.draftReady && r.sentAt === null);
|
|
167
|
+
if (pending.length > 0) {
|
|
168
|
+
const currentPeriod = reportPeriodKey(now);
|
|
169
|
+
const isExpectedPayload = (r) => type !== "Announcement" && r.reportType === type && r.period === currentPeriod;
|
|
170
|
+
const expected = pending.filter(isExpectedPayload);
|
|
171
|
+
const stale = pending.filter((r) => !isExpectedPayload(r));
|
|
172
|
+
if (expected.length > 0) {
|
|
173
|
+
findings.push({
|
|
174
|
+
level: "info",
|
|
175
|
+
check: "pending-drafts",
|
|
176
|
+
message: `current-cycle ${type} draft is queued${expected[0].approvedToSend ? " and approved" : " (awaiting approval)"} \u2014 this is the report \`--send-ready\` will deliver`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (stale.length > 0) {
|
|
180
|
+
const summary = stale.map(
|
|
181
|
+
(r) => `${r.reportType} ${r.period ?? "(no period)"}${r.approvedToSend ? " [APPROVED]" : ""}`
|
|
182
|
+
).join(", ");
|
|
183
|
+
findings.push({
|
|
184
|
+
level: "warn",
|
|
185
|
+
check: "pending-drafts",
|
|
186
|
+
message: `${stale.length} unsent draft(s) queued: ${summary} \u2014 resolve before sending so the client doesn't get the wrong report first`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
checkFrequency(site, "maintenance", reports, findings, now);
|
|
191
|
+
checkFrequency(site, "testing", reports, findings, now);
|
|
192
|
+
if (type === "Announcement") {
|
|
193
|
+
const anySent = lastSentForType(reports, "Maintenance") ?? lastSentForType(reports, "Testing");
|
|
194
|
+
if (!site.maintenanceDay && !site.testingDay && !anySent) {
|
|
195
|
+
findings.push({
|
|
196
|
+
level: "info",
|
|
197
|
+
check: "anchor-missing",
|
|
198
|
+
message: "no maintenance/testing day anchors and nothing ever sent \u2014 set both anchors to the announcement send date (the Sonder playbook) so recurrence starts from the announcement"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { site: site.name, findings };
|
|
203
|
+
}
|
|
204
|
+
function preflightFleet(sites) {
|
|
205
|
+
if (sites.length < 3) return [];
|
|
206
|
+
const findings = [];
|
|
207
|
+
const allEmpty = (get, column) => {
|
|
208
|
+
if (sites.every((s) => get(s) === null || get(s) === "")) {
|
|
209
|
+
findings.push({
|
|
210
|
+
level: "warn",
|
|
211
|
+
check: "column-possibly-renamed",
|
|
212
|
+
message: `'${column}' is empty on all ${sites.length} selected sites \u2014 if that column was renamed in Airtable the code reads null silently; verify the column name`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
allEmpty((s) => s.pointOfContact, "point of contact");
|
|
217
|
+
allEmpty((s) => s.maintenanceFreqRaw, "maintenence freq");
|
|
218
|
+
allEmpty((s) => s.headerImage, "Header image");
|
|
219
|
+
const byContact = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const s of sites) {
|
|
221
|
+
const to = parseAddresses(s.reportRecipientsTo) ?? parseAddresses(s.pointOfContact) ?? [];
|
|
222
|
+
for (const addr of to) {
|
|
223
|
+
const key = addr.toLowerCase();
|
|
224
|
+
byContact.set(key, [...byContact.get(key) ?? [], s.name]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const [addr, names] of byContact) {
|
|
228
|
+
if (names.length > 1 && !OPERATOR_DOMAINS.includes(domainOf(addr))) {
|
|
229
|
+
findings.push({
|
|
230
|
+
level: "info",
|
|
231
|
+
check: "duplicate-contact",
|
|
232
|
+
message: `${names.join(" and ")} resolve to the same recipient (${addr}) \u2014 fine if one person owns both; otherwise a copy-paste error on one row`
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return findings;
|
|
237
|
+
}
|
|
238
|
+
async function preflight(deps) {
|
|
239
|
+
const base = deps?.base ?? openBase(readAirtableConfig());
|
|
240
|
+
const type = deps?.type ?? "Announcement";
|
|
241
|
+
const now = deps?.now ?? /* @__PURE__ */ new Date();
|
|
242
|
+
const websites = await listWebsites(base);
|
|
243
|
+
const selected = deps?.site ? websites.filter((w) => siteSlug(w.name) === siteSlug(deps.site)) : type === "Announcement" ? websites.filter((w) => w.status === "maintenance") : websites.filter((w) => w.status === null || ELIGIBLE_STATUSES.has(w.status));
|
|
244
|
+
const allReports = selected.length > 0 ? await listAllReports(base) : [];
|
|
245
|
+
const results = selected.map(
|
|
246
|
+
(site) => preflightSite(
|
|
247
|
+
site,
|
|
248
|
+
allReports.filter((r) => r.siteId === site.id),
|
|
249
|
+
type,
|
|
250
|
+
now
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
return { results, fleet: deps?.site ? [] : preflightFleet(selected) };
|
|
254
|
+
}
|
|
255
|
+
function approveBlockers(site, report) {
|
|
256
|
+
const findings = [];
|
|
257
|
+
const explicitTo = parseAddresses(site.reportRecipientsTo);
|
|
258
|
+
const fallbackTo = parseAddresses(site.pointOfContact);
|
|
259
|
+
const to = explicitTo ?? fallbackTo ?? [];
|
|
260
|
+
if (to.length === 0) {
|
|
261
|
+
findings.push({
|
|
262
|
+
level: "fail",
|
|
263
|
+
check: "recipients-missing",
|
|
264
|
+
message: "no recipients \u2014 Report recipients (To) and point of contact are both empty; the send will throw"
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
for (const addr of to) {
|
|
268
|
+
if (!isProbablyEmail(addr)) {
|
|
269
|
+
findings.push({
|
|
270
|
+
level: "fail",
|
|
271
|
+
check: "recipients-malformed",
|
|
272
|
+
message: `recipient '${addr}' is malformed \u2014 fix Report recipients (To) / point of contact in Airtable`
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const addr of parseAddresses(site.reportRecipientsCc) ?? []) {
|
|
277
|
+
if (!isProbablyEmail(addr)) {
|
|
278
|
+
findings.push({
|
|
279
|
+
level: "fail",
|
|
280
|
+
check: "recipients-malformed",
|
|
281
|
+
message: `CC '${addr}' is malformed \u2014 fix Report recipients (CC) in Airtable`
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!site.headerImage) {
|
|
286
|
+
findings.push({
|
|
287
|
+
level: "fail",
|
|
288
|
+
check: "header-image-missing",
|
|
289
|
+
message: "no Header image on the Websites row \u2014 the send will throw"
|
|
290
|
+
});
|
|
291
|
+
} else if (!site.headerImage.type.startsWith("image/")) {
|
|
292
|
+
findings.push({
|
|
293
|
+
level: "fail",
|
|
294
|
+
check: "header-image-not-image",
|
|
295
|
+
message: `Header image attachment is '${site.headerImage.type}' (${site.headerImage.filename}) \u2014 not a decodable image; the send throws in prepareHeaderImage`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (report.lighthouse === null) {
|
|
299
|
+
findings.push({
|
|
300
|
+
level: "fail",
|
|
301
|
+
check: "report-scores-missing",
|
|
302
|
+
message: "the Reports row has no Lighthouse scores (one blank/non-numeric cell nulls all four) \u2014 the send will throw"
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (!isOperatorSite(site)) {
|
|
306
|
+
const operatorAddrs = to.filter((a) => OPERATOR_DOMAINS.includes(domainOf(a)));
|
|
307
|
+
if (operatorAddrs.length > 0 && operatorAddrs.length === to.length) {
|
|
308
|
+
findings.push({
|
|
309
|
+
level: "warn",
|
|
310
|
+
check: "recipient-operator-address",
|
|
311
|
+
message: `resolved To is ONLY operator address(es) ${operatorAddrs.join(", ")} \u2014 the client will NOT receive this report`
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return findings;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
preflight,
|
|
320
|
+
approveBlockers
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=chunk-MAYHLXCS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reports/preflight.ts"],"sourcesContent":["import { openBase, readAirtableConfig } from \"./airtable/client.js\";\nimport type { AirtableBase } from \"./airtable/client.js\";\nimport { listWebsites, siteSlug } from \"./airtable/websites.js\";\nimport type { WebsiteRow } from \"./airtable/websites.js\";\nimport { listAllReports } from \"./airtable/reports.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport { parseAddresses, isProbablyEmail } from \"./send/orchestrate.js\";\nimport { ELIGIBLE_STATUSES, reportPeriodKey } from \"./due.js\";\nimport type { ReportType } from \"./types.js\";\n\nexport type PreflightLevel = \"fail\" | \"warn\" | \"info\";\n\nexport type PreflightFinding = {\n level: PreflightLevel;\n /** Stable machine-readable check id, e.g. \"recipients-missing\". */\n check: string;\n message: string;\n};\n\nexport type PreflightSiteResult = {\n site: string;\n findings: PreflightFinding[];\n};\n\n/** Domains whose addresses are the OPERATOR's, not a client's. A client site whose\n * resolved To contains one of these is almost always a test-send leftover — the\n * exact misconfiguration that would silently divert a client announcement. */\nconst OPERATOR_DOMAINS = [\"reddoorla.com\", \"tuckerlemos.com\"];\n\n/** Anchors older than this, WHEN the anchor is actually the schedule's base date\n * (no newer Sent-at — see nextDueDate's `lastSent ?? fallback`), mean the next\n * `report --due` run drafts a surprise back-dated report. */\nconst STALE_ANCHOR_DAYS = 396; // ~13 months\n\nfunction domainOf(addr: string): string {\n const at = addr.lastIndexOf(\"@\");\n return at === -1 ? \"\" : addr.slice(at + 1).toLowerCase();\n}\n\nfunction isOperatorSite(site: WebsiteRow): boolean {\n if (!site.url) return false;\n try {\n // Airtable `url` cells aren't guaranteed a scheme; retry with one before giving up.\n const host = new URL(\n /^[a-z][a-z0-9+.-]*:\\/\\//i.test(site.url) ? site.url : `https://${site.url}`,\n ).hostname.toLowerCase();\n return OPERATOR_DOMAINS.some((d) => host === d || host.endsWith(`.${d}`));\n } catch {\n return false;\n }\n}\n\nfunction lastSentForType(reports: ReportRow[], type: ReportType): string | null {\n const sent = reports\n .filter((r) => r.reportType === type && r.sentAt !== null)\n .map((r) => r.sentAt!)\n .sort();\n return sent[sent.length - 1] ?? null;\n}\n\nfunction checkFrequency(\n site: WebsiteRow,\n which: \"maintenance\" | \"testing\",\n reports: ReportRow[],\n findings: PreflightFinding[],\n now: Date,\n): void {\n // Validate the RAW Airtable cell, not the coerced Frequency: toFrequency maps any\n // unrecognized value (typo, trailing space, renamed option) to \"None\", so by the\n // time WebsiteRow is built the damage is invisible — the site just silently drops\n // off the schedule. This is the loud version.\n const raw = which === \"maintenance\" ? site.maintenanceFreqRaw : site.testingFreqRaw;\n const coerced = which === \"maintenance\" ? site.maintenanceFreq : site.testingFreq;\n if (raw !== null && raw !== \"\" && coerced === \"None\" && raw.trim() !== \"None\") {\n findings.push({\n level: \"fail\",\n check: \"frequency-unrecognized\",\n message: `${which} frequency cell is '${raw}' — not an exact Monthly/Quarterly/Yearly/None, so the scheduler silently treats it as None and the site drops off the calendar; fix the Airtable value`,\n });\n return;\n }\n if (coerced === \"None\") return;\n\n const anchor = which === \"maintenance\" ? site.maintenanceDay : site.testingDay;\n if (!anchor) return;\n // nextDueDate uses `lastSent ?? anchor`: once anything has been sent, the anchor\n // is inert and an old one is normal on every healthy mature site — only warn when\n // the anchor is genuinely the base date the scheduler would use.\n const type: ReportType = which === \"maintenance\" ? \"Maintenance\" : \"Testing\";\n const lastSent = lastSentForType(reports, type);\n if (lastSent && new Date(lastSent).getTime() >= new Date(anchor).getTime()) return;\n const ageDays = (now.getTime() - new Date(anchor).getTime()) / 86_400_000;\n if (ageDays > STALE_ANCHOR_DAYS) {\n findings.push({\n level: \"warn\",\n check: \"anchor-stale\",\n message: `${which} day anchor is ${anchor} (>13 months old) and nothing newer has been sent — \\`report --due\\` will draft a back-dated overdue report; reset the anchor or clear the schedule`,\n });\n }\n}\n\n/**\n * Pure per-site preflight: every check that, left unfixed, makes a report send fail,\n * go to the wrong inbox, or surprise the operator. Read-only over data already\n * fetched; no Airtable handle, no network — trivially testable.\n *\n * Mirrors the send/draft-time validation (recipients + header image in\n * send/orchestrate.ts, Websites-row scores in draft.ts) so problems surface BEFORE\n * draft/approve instead of exploding at `report --send-ready`, and adds the checks\n * send time cannot do: operator-address leftovers, To-override shadowing,\n * pending-draft races, schedule hygiene against the RAW Airtable values.\n */\nexport function preflightSite(\n site: WebsiteRow,\n reports: ReportRow[],\n type: ReportType,\n now: Date,\n): PreflightSiteResult {\n const findings: PreflightFinding[] = [];\n\n // A site that isn't on the checked calendar never drafts, so its send\n // requirements (recipients / header image / scores) can't hurt anyone —\n // checking them anyway would drown the fleet run in fails for legacy hosting\n // rows. Frequency hygiene and pending-draft checks still run: a typo'd cell\n // is exactly HOW a site falls off the calendar, and an already-drafted report\n // can still be approved + sent regardless of schedule.\n const scheduledForType =\n type === \"Announcement\"\n ? true\n : type === \"Maintenance\"\n ? site.maintenanceFreq !== \"None\"\n : site.testingFreq !== \"None\";\n if (!scheduledForType) {\n findings.push({\n level: \"info\",\n check: \"not-scheduled\",\n message: `no ${type} schedule (frequency is None/blank) — send-requirement checks skipped; schedule + pending-draft checks still apply`,\n });\n }\n\n // --- Recipients: same resolution order as the send path (To override, else contact).\n const explicitTo = parseAddresses(site.reportRecipientsTo);\n const fallbackTo = parseAddresses(site.pointOfContact);\n const to = scheduledForType ? (explicitTo ?? fallbackTo ?? []) : [];\n if (scheduledForType && to.length === 0) {\n findings.push({\n level: \"fail\",\n check: \"recipients-missing\",\n message:\n \"no recipients — Report recipients (To) and point of contact are both empty; the send will throw\",\n });\n }\n for (const addr of to) {\n if (!isProbablyEmail(addr)) {\n findings.push({\n level: \"fail\",\n check: \"recipients-malformed\",\n message: `recipient '${addr}' is malformed — use bare addresses only in Report recipients (To) / point of contact`,\n });\n }\n }\n const cc = scheduledForType ? (parseAddresses(site.reportRecipientsCc) ?? []) : [];\n for (const addr of cc) {\n if (!isProbablyEmail(addr)) {\n findings.push({\n level: \"fail\",\n check: \"recipients-malformed\",\n message: `CC '${addr}' is malformed — fix Report recipients (CC)`,\n });\n }\n }\n if (!isOperatorSite(site)) {\n const operatorAddrs = to.filter((a) => OPERATOR_DOMAINS.includes(domainOf(a)));\n if (operatorAddrs.length > 0) {\n const allOperator = operatorAddrs.length === to.length;\n findings.push({\n level: \"warn\",\n check: \"recipient-operator-address\",\n message: allOperator\n ? `resolved To is ONLY operator address(es) ${operatorAddrs.join(\", \")} — probably a test-send leftover; the client will NOT receive this report`\n : `resolved To includes operator address(es) ${operatorAddrs.join(\", \")} alongside the client's — confirm that's intentional (the ops inbox is already CC'd on every send)`,\n });\n }\n }\n if (scheduledForType && explicitTo && fallbackTo) {\n const a = [...explicitTo]\n .map((x) => x.toLowerCase())\n .sort()\n .join(\",\");\n const b = [...fallbackTo]\n .map((x) => x.toLowerCase())\n .sort()\n .join(\",\");\n if (a !== b) {\n findings.push({\n level: \"info\",\n check: \"to-override-shadows-contact\",\n message: `Report recipients (To) [${explicitTo.join(\", \")}] overrides point of contact [${fallbackTo.join(\", \")}] — the contact won't receive this unless also listed`,\n });\n }\n }\n\n // --- Hard requirements of the draft/send paths, surfaced early.\n if (scheduledForType && !site.headerImage) {\n findings.push({\n level: \"fail\",\n check: \"header-image-missing\",\n message: \"no Header image on the Websites row — the send will throw\",\n });\n } else if (scheduledForType && site.headerImage && !site.headerImage.type.startsWith(\"image/\")) {\n findings.push({\n level: \"fail\",\n check: \"header-image-not-image\",\n message: `Header image attachment is '${site.headerImage.type}' (${site.headerImage.filename}) — not a decodable image; the send throws in prepareHeaderImage`,\n });\n }\n const scores = [site.pScore, site.rScore, site.bpScore, site.seoScore];\n if (scheduledForType && scores.some((s) => s === null)) {\n if (type === \"Announcement\") {\n // announce() skips score-less sites (skipped-no-scores) rather than throwing.\n findings.push({\n level: \"warn\",\n check: \"scores-missing\",\n message:\n \"Websites row is missing Lighthouse scores — `announce` will skip this site; run `audit lighthouse --write-airtable` first\",\n });\n } else {\n // report <site> / report --due HARD-THROW here (scoresFromWebsite in draft.ts).\n findings.push({\n level: \"fail\",\n check: \"scores-missing\",\n message:\n \"Websites row is missing Lighthouse scores — drafting will throw (scoresFromWebsite); run `audit lighthouse --write-airtable` first\",\n });\n }\n }\n if (type === \"Announcement\" && site.status !== \"maintenance\") {\n findings.push({\n level: \"info\",\n check: \"status-not-maintenance\",\n message: `status is '${site.status ?? \"(blank)\"}' — \\`announce --all\\` only drafts for maintenance sites`,\n });\n }\n\n // --- Pending drafts. For an Announcement these always race (the queue tier rule\n // supersedes or blocks). For Maintenance/Testing, the CURRENT cycle's own draft\n // sitting ready on send day IS the payload — that's the steady state, not a\n // problem — so only genuinely stale/foreign drafts get the warn.\n const pending = reports.filter((r) => r.draftReady && r.sentAt === null);\n if (pending.length > 0) {\n const currentPeriod = reportPeriodKey(now);\n const isExpectedPayload = (r: ReportRow): boolean =>\n type !== \"Announcement\" && r.reportType === type && r.period === currentPeriod;\n const expected = pending.filter(isExpectedPayload);\n const stale = pending.filter((r) => !isExpectedPayload(r));\n if (expected.length > 0) {\n findings.push({\n level: \"info\",\n check: \"pending-drafts\",\n message: `current-cycle ${type} draft is queued${expected[0]!.approvedToSend ? \" and approved\" : \" (awaiting approval)\"} — this is the report \\`--send-ready\\` will deliver`,\n });\n }\n if (stale.length > 0) {\n const summary = stale\n .map(\n (r) =>\n `${r.reportType} ${r.period ?? \"(no period)\"}${r.approvedToSend ? \" [APPROVED]\" : \"\"}`,\n )\n .join(\", \");\n findings.push({\n level: \"warn\",\n check: \"pending-drafts\",\n message: `${stale.length} unsent draft(s) queued: ${summary} — resolve before sending so the client doesn't get the wrong report first`,\n });\n }\n }\n\n // --- Schedule hygiene (validates the RAW Airtable frequency cells).\n checkFrequency(site, \"maintenance\", reports, findings, now);\n checkFrequency(site, \"testing\", reports, findings, now);\n if (type === \"Announcement\") {\n const anySent = lastSentForType(reports, \"Maintenance\") ?? lastSentForType(reports, \"Testing\");\n if (!site.maintenanceDay && !site.testingDay && !anySent) {\n findings.push({\n level: \"info\",\n check: \"anchor-missing\",\n message:\n \"no maintenance/testing day anchors and nothing ever sent — set both anchors to the announcement send date (the Sonder playbook) so recurrence starts from the announcement\",\n });\n }\n }\n\n return { site: site.name, findings };\n}\n\n/**\n * Fleet-level heuristics that need the whole selection. Airtable column renames\n * don't error — the mapper reads null forever (its own header comment admits this).\n * A load-bearing column empty on EVERY selected site is far more likely a rename\n * than N coincidences; say so. Likewise two different sites resolving to one\n * recipient is worth one eyeball (it can be legitimate — same owner, two sites).\n */\nexport function preflightFleet(sites: WebsiteRow[]): PreflightFinding[] {\n if (sites.length < 3) return []; // too few rows to distinguish rename from data\n const findings: PreflightFinding[] = [];\n const allEmpty = (get: (s: WebsiteRow) => unknown, column: string): void => {\n if (sites.every((s) => get(s) === null || get(s) === \"\")) {\n findings.push({\n level: \"warn\",\n check: \"column-possibly-renamed\",\n message: `'${column}' is empty on all ${sites.length} selected sites — if that column was renamed in Airtable the code reads null silently; verify the column name`,\n });\n }\n };\n allEmpty((s) => s.pointOfContact, \"point of contact\");\n allEmpty((s) => s.maintenanceFreqRaw, \"maintenence freq\");\n allEmpty((s) => s.headerImage, \"Header image\");\n\n const byContact = new Map<string, string[]>();\n for (const s of sites) {\n const to = parseAddresses(s.reportRecipientsTo) ?? parseAddresses(s.pointOfContact) ?? [];\n for (const addr of to) {\n const key = addr.toLowerCase();\n byContact.set(key, [...(byContact.get(key) ?? []), s.name]);\n }\n }\n for (const [addr, names] of byContact) {\n if (names.length > 1 && !OPERATOR_DOMAINS.includes(domainOf(addr))) {\n findings.push({\n level: \"info\",\n check: \"duplicate-contact\",\n message: `${names.join(\" and \")} resolve to the same recipient (${addr}) — fine if one person owns both; otherwise a copy-paste error on one row`,\n });\n }\n }\n return findings;\n}\n\nexport type PreflightDeps = {\n /** Airtable handle. Defaults to opening the live base from credentials. */\n base?: AirtableBase;\n /** Slug of a single site (matched via siteSlug). Mutually exclusive with `all`. */\n site?: string;\n /** Fleet mode: for Announcement, maintenance-status sites (announce's own filter);\n * for Maintenance/Testing, everything `report --due` schedules — ELIGIBLE_STATUSES\n * plus null-status rows (due.ts treats null as active for legacy rows). */\n all?: boolean;\n type?: ReportType;\n now?: Date;\n};\n\nexport type PreflightResult = {\n results: PreflightSiteResult[];\n fleet: PreflightFinding[];\n};\n\n/**\n * Read-only rollout preflight over the live Airtable base. One Websites fetch, one\n * Reports fetch (the --due path does the same for the same rate-limit reason), then\n * {@link preflightSite} per selected site plus {@link preflightFleet} across the\n * selection. NEVER writes and NEVER sends.\n */\nexport async function preflight(deps?: PreflightDeps): Promise<PreflightResult> {\n const base = deps?.base ?? openBase(readAirtableConfig());\n const type: ReportType = deps?.type ?? \"Announcement\";\n const now = deps?.now ?? new Date();\n\n const websites = await listWebsites(base);\n const selected = deps?.site\n ? websites.filter((w) => siteSlug(w.name) === siteSlug(deps.site!))\n : type === \"Announcement\"\n ? websites.filter((w) => w.status === \"maintenance\")\n : websites.filter((w) => w.status === null || ELIGIBLE_STATUSES.has(w.status));\n\n const allReports = selected.length > 0 ? await listAllReports(base) : [];\n const results = selected.map((site) =>\n preflightSite(\n site,\n allReports.filter((r) => r.siteId === site.id),\n type,\n now,\n ),\n );\n return { results, fleet: deps?.site ? [] : preflightFleet(selected) };\n}\n\n/**\n * The send-blocking subset for ONE report, at approve time: exactly the\n * conditions that make `sendOne` throw (no recipients, malformed To/CC, no\n * header image, no report-level Lighthouse scores) plus the wrong-inbox warn\n * (operator address resolved as a client's To). PURE — the approve gate, the\n * dashboard's pending-row chip, and the digest collector all call this one\n * function so \"approvable\" can't drift from \"sendable\".\n *\n * Deliberately narrower than {@link preflightSite}: schedule hygiene and\n * pending-draft races are fleet/site concerns — blocking THIS report's approval\n * on them would be over-gating.\n */\nexport function approveBlockers(site: WebsiteRow, report: ReportRow): PreflightFinding[] {\n const findings: PreflightFinding[] = [];\n\n const explicitTo = parseAddresses(site.reportRecipientsTo);\n const fallbackTo = parseAddresses(site.pointOfContact);\n const to = explicitTo ?? fallbackTo ?? [];\n if (to.length === 0) {\n findings.push({\n level: \"fail\",\n check: \"recipients-missing\",\n message:\n \"no recipients — Report recipients (To) and point of contact are both empty; the send will throw\",\n });\n }\n for (const addr of to) {\n if (!isProbablyEmail(addr)) {\n findings.push({\n level: \"fail\",\n check: \"recipients-malformed\",\n message: `recipient '${addr}' is malformed — fix Report recipients (To) / point of contact in Airtable`,\n });\n }\n }\n for (const addr of parseAddresses(site.reportRecipientsCc) ?? []) {\n if (!isProbablyEmail(addr)) {\n findings.push({\n level: \"fail\",\n check: \"recipients-malformed\",\n message: `CC '${addr}' is malformed — fix Report recipients (CC) in Airtable`,\n });\n }\n }\n if (!site.headerImage) {\n findings.push({\n level: \"fail\",\n check: \"header-image-missing\",\n message: \"no Header image on the Websites row — the send will throw\",\n });\n } else if (!site.headerImage.type.startsWith(\"image/\")) {\n findings.push({\n level: \"fail\",\n check: \"header-image-not-image\",\n message: `Header image attachment is '${site.headerImage.type}' (${site.headerImage.filename}) — not a decodable image; the send throws in prepareHeaderImage`,\n });\n }\n // sendOne throws on a null report.lighthouse (one blank/non-numeric cell nulls\n // all four) — the REPORT's snapshot, not the Websites-row scores.\n if (report.lighthouse === null) {\n findings.push({\n level: \"fail\",\n check: \"report-scores-missing\",\n message:\n \"the Reports row has no Lighthouse scores (one blank/non-numeric cell nulls all four) — the send will throw\",\n });\n }\n if (!isOperatorSite(site)) {\n const operatorAddrs = to.filter((a) => OPERATOR_DOMAINS.includes(domainOf(a)));\n if (operatorAddrs.length > 0 && operatorAddrs.length === to.length) {\n findings.push({\n level: \"warn\",\n check: \"recipient-operator-address\",\n message: `resolved To is ONLY operator address(es) ${operatorAddrs.join(\", \")} — the client will NOT receive this report`,\n });\n }\n }\n return findings;\n}\n\n/** Convenience: just the fail-level blockers, formatted for gate messages. */\nexport function formatBlockers(findings: PreflightFinding[]): string[] {\n return findings.filter((f) => f.level === \"fail\").map((f) => `${f.check}: ${f.message}`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
queueDraft
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RINXPU3H.js";
|
|
4
4
|
import {
|
|
5
|
+
checklistFor,
|
|
6
|
+
createDraft,
|
|
7
|
+
listReportsForSite,
|
|
5
8
|
renderReportHtml,
|
|
6
9
|
resolveCopy,
|
|
7
10
|
uploadAttachment
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
checklistFor,
|
|
11
|
-
createDraft,
|
|
12
|
-
listReportsForSite
|
|
13
|
-
} from "./chunk-OCS7SZUZ.js";
|
|
11
|
+
} from "./chunk-6JFOAPW4.js";
|
|
14
12
|
import {
|
|
15
13
|
isNetlifyAppUrl
|
|
16
14
|
} from "./chunk-U5SZ4FOP.js";
|
|
17
15
|
import {
|
|
18
16
|
siteSlug,
|
|
19
17
|
updateAnalyticsHealth
|
|
20
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-C3PDS623.js";
|
|
21
19
|
import {
|
|
22
20
|
defaultCredentialsPath
|
|
23
21
|
} from "./chunk-6VAL7XJT.js";
|
|
@@ -441,4 +439,4 @@ export {
|
|
|
441
439
|
fetchGaUsers,
|
|
442
440
|
fetchSearch
|
|
443
441
|
};
|
|
444
|
-
//# sourceMappingURL=chunk-
|
|
442
|
+
//# sourceMappingURL=chunk-O6G4HHMF.js.map
|