@reddoorla/maintenance 0.6.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { S as Site, b as AuditResult, a as AuditName, c as RecipeResult, R as RecipeName, I as InventoryProvider } from './sync-configs-pMPW4wTq.js';
2
2
  export { C as ConfigName, d as SyncConfigsOptions, s as syncConfigs } from './sync-configs-pMPW4wTq.js';
3
+ import * as airtable_lib_airtable_base_js from 'airtable/lib/airtable_base.js';
3
4
 
4
5
  type SpawnResult = {
5
6
  code: number;
@@ -91,4 +92,206 @@ declare function localPath(path: string, opts?: LocalPathOptions): InventoryProv
91
92
 
92
93
  declare function fromJsonFile(path: string): InventoryProvider;
93
94
 
94
- export { ALL_AUDIT_NAMES, ALL_RECIPE_NAMES, AuditName, AuditResult, type BumpDepsOptions, type ConvertToPnpmOptions, InventoryProvider, type LocalPathOptions, type OnboardAudit, type OnboardOptions, RecipeName, RecipeResult, Site, type UpgradeSvelte4to5Options, a11yAudit, bumpDeps, convertToPnpm, depsAudit, fromJsonFile, isRecipeName, lighthouseAudit, lintAudit, localPath, onboard, runAudits, runAuditsAcross, securityAudit, svelteCodemods, upgradeSvelte4to5 };
95
+ type AirtableConfig = {
96
+ apiKey: string;
97
+ baseId: string;
98
+ };
99
+ type AirtableBase = ReturnType<typeof openBase>;
100
+ declare function openBase(cfg: AirtableConfig): airtable_lib_airtable_base_js.AirtableBase;
101
+
102
+ type AirtableInventoryOptions = {
103
+ /**
104
+ * Local workdir to compute each site's path as `{workdir}/{slug}`.
105
+ * Defaults to REDDOOR_FLEET_WORKDIR env var if not provided.
106
+ * Airtable doesn't store local checkout paths, so this is required.
107
+ */
108
+ workdir?: string;
109
+ };
110
+ /**
111
+ * Read sites from the Airtable Websites table as an InventoryProvider.
112
+ * Each row becomes one Site; `path` is computed as `{workdir}/{slug}`.
113
+ * Sites where BOTH maintenance freq AND testing freq are "None" are excluded
114
+ * (they're inactive — no scheduled audits or reports).
115
+ *
116
+ * Note: `repoUrl` is set to the production URL (Websites.url). For sites
117
+ * cloned via `--workdir` semantics this is wrong — the convention should be
118
+ * tightened (e.g. add a `repo` field to Websites) when fleet-clone-from-
119
+ * airtable becomes a real flow. For local audits where the site is already
120
+ * checked out at `path`, the `repoUrl` is unused.
121
+ */
122
+ declare function fromAirtableBase(base: AirtableBase, opts?: AirtableInventoryOptions): InventoryProvider;
123
+
124
+ type ReportType = "Maintenance" | "Testing";
125
+ type LighthouseScores = {
126
+ performance: number;
127
+ accessibility: number;
128
+ bestPractices: number;
129
+ seo: number;
130
+ };
131
+ type HeaderImage = {
132
+ /** Stable filename, used as the CID inside the email and as the attachment name in Resend. */
133
+ filename: string;
134
+ /** Bytes of the image, fetched once from Airtable before render+send. */
135
+ bytes: Uint8Array;
136
+ /** MIME, e.g. "image/jpeg". */
137
+ contentType: string;
138
+ };
139
+ /** Everything the template needs to render one report email. */
140
+ type ReportData = {
141
+ siteName: string;
142
+ siteUrl: string;
143
+ reportType: ReportType;
144
+ completedOn: Date;
145
+ lighthouse: LighthouseScores;
146
+ gaUsersCurrent: number;
147
+ gaUsersPrevious: number;
148
+ /** Only used when reportType === "Maintenance"; the date shown next to the blurred-testing image. */
149
+ lastTestedDate: Date | null;
150
+ /** Optional free-text rendered as a section above the footer. */
151
+ commentary: string | null;
152
+ /** Used in the header `mj-image src`; the email attaches the bytes with this CID. */
153
+ headerImageCid: string;
154
+ };
155
+
156
+ type Frequency = "None" | "Monthly" | "Quarterly" | "Yearly";
157
+ type Status = "in development" | "launch period" | "maintenance" | "hosting" | "probably not our problem" | "deprecated";
158
+ type WebsiteRow = {
159
+ id: string;
160
+ name: string;
161
+ url: string;
162
+ status: Status | null;
163
+ pointOfContact: string | null;
164
+ maintenanceFreq: Frequency;
165
+ testingFreq: Frequency;
166
+ /** Last manually-recorded maintenance day (used as fallback when no Reports row exists). */
167
+ maintenanceDay: string | null;
168
+ testingDay: string | null;
169
+ ga4PropertyId: string | null;
170
+ reportRecipientsTo: string | null;
171
+ reportRecipientsCc: string | null;
172
+ /** First attachment in the Header image field (Airtable's signed URL — fetch before expiry). */
173
+ headerImage: {
174
+ url: string;
175
+ filename: string;
176
+ type: string;
177
+ } | null;
178
+ /** Lighthouse "current state" snapshot, kept fresh by `audit lighthouse --write-airtable`. */
179
+ pScore: number | null;
180
+ rScore: number | null;
181
+ bpScore: number | null;
182
+ seoScore: number | null;
183
+ /** ISO timestamp set by `audit lighthouse --write-airtable` when scores were last refreshed. */
184
+ lastLighthouseAuditAt: string | null;
185
+ };
186
+
187
+ type DeliveryStatus = "pending" | "delivered" | "bounced" | "complained";
188
+ type ReportRow = {
189
+ id: string;
190
+ reportId: string;
191
+ siteId: string;
192
+ reportType: ReportType;
193
+ periodStart: string | null;
194
+ periodEnd: string | null;
195
+ completedOn: string | null;
196
+ lighthouse: LighthouseScores | null;
197
+ gaUsersCurrent: number | null;
198
+ gaUsersPrevious: number | null;
199
+ lastTestedDate: string | null;
200
+ commentary: string | null;
201
+ subjectOverride: string | null;
202
+ draftReady: boolean;
203
+ approvedToSend: boolean;
204
+ sentAt: string | null;
205
+ deliveryStatus: DeliveryStatus;
206
+ renderedHtmlAttachment: {
207
+ url: string;
208
+ filename: string;
209
+ } | null;
210
+ /** Read out of the Resend response and stored in a hidden field; needed for webhook reconciliation. */
211
+ resendMessageId: string | null;
212
+ };
213
+
214
+ type DraftOptions = {
215
+ /** Where to write the local preview HTML when `previewOnly`. Defaults to `reports/<slug>/draft.html`. */
216
+ previewPath?: string;
217
+ /** If true: render locally only, never touch Airtable. */
218
+ previewOnly?: boolean;
219
+ };
220
+ type DraftResult = {
221
+ /** null when previewOnly. */
222
+ reportRow: ReportRow | null;
223
+ /** Path to the local preview file (only set when previewOnly). */
224
+ htmlPath: string | null;
225
+ /** Always present — the rendered HTML string. */
226
+ html: string;
227
+ };
228
+ declare function draftReportForSite(base: AirtableBase | null, siteRow: WebsiteRow, reportType: ReportType, options?: DraftOptions): Promise<DraftResult>;
229
+
230
+ type ResendSendInput = {
231
+ from: string;
232
+ to: string[];
233
+ cc?: string[];
234
+ replyTo?: string;
235
+ subject: string;
236
+ html: string;
237
+ attachments?: Array<{
238
+ filename: string;
239
+ content: string;
240
+ contentType?: string;
241
+ /** Setting this attaches the file as inline; reference it from HTML as `src="cid:<id>"`. */
242
+ inlineContentId?: string;
243
+ }>;
244
+ /**
245
+ * Stable key forwarded as the `Idempotency-Key` header. Resend dedupes calls
246
+ * with the same key for 24 hours, returning the original message id. Use a
247
+ * key that's stable across retries of the same logical send (e.g. the
248
+ * Reports row id), so a network blip during stamping doesn't cause a
249
+ * duplicate email to the client.
250
+ */
251
+ idempotencyKey?: string;
252
+ };
253
+ type ResendSendResult = {
254
+ messageId: string;
255
+ };
256
+ type ResendClient = {
257
+ send: (input: ResendSendInput) => Promise<ResendSendResult>;
258
+ };
259
+
260
+ type OrchestrateOptions = {
261
+ resend?: ResendClient;
262
+ };
263
+ declare function sendApprovedReports(options?: OrchestrateOptions): Promise<{
264
+ output: string;
265
+ code: number;
266
+ }>;
267
+
268
+ type RenderResult = {
269
+ html: string;
270
+ warnings: Array<{
271
+ line: number;
272
+ message: string;
273
+ }>;
274
+ };
275
+ declare function renderReportHtml(data: ReportData): Promise<RenderResult>;
276
+
277
+ type DueItem = {
278
+ site: WebsiteRow;
279
+ reportType: ReportType;
280
+ /** Inclusive: the day the next report became due. */
281
+ dueDate: Date;
282
+ /** ISO date of the last `Sent at` for this (site, type), or null if there's never been one. */
283
+ lastSent: string | null;
284
+ };
285
+ /**
286
+ * Computes which (site, type) pairs are due as of `today`.
287
+ *
288
+ * Algorithm per (site, type):
289
+ * 1. If freq === "None", skip.
290
+ * 2. baseDate = max(last Sent at for this type, site's `maintenance/testing day` fallback).
291
+ * 3. If no baseDate exists at all, the site is due now.
292
+ * 4. dueDate = baseDate + frequency months.
293
+ * 5. Due iff startOfDay(today) >= startOfDay(dueDate).
294
+ */
295
+ declare function findDueReports(websites: WebsiteRow[], reports: ReportRow[], today: Date): DueItem[];
296
+
297
+ export { ALL_AUDIT_NAMES, ALL_RECIPE_NAMES, type AirtableInventoryOptions, AuditName, AuditResult, type BumpDepsOptions, type ConvertToPnpmOptions, type DraftOptions, type DraftResult, type DueItem, type HeaderImage, InventoryProvider, type LighthouseScores, type LocalPathOptions, type OnboardAudit, type OnboardOptions, type OrchestrateOptions, RecipeName, RecipeResult, type RenderResult, type ReportData, type ReportType, Site, type UpgradeSvelte4to5Options, a11yAudit, bumpDeps, convertToPnpm, depsAudit, draftReportForSite, findDueReports, fromAirtableBase, fromJsonFile, isRecipeName, lighthouseAudit, lintAudit, localPath, onboard, renderReportHtml, runAudits, runAuditsAcross, securityAudit, sendApprovedReports, svelteCodemods, upgradeSvelte4to5 };