@reddoorla/maintenance 0.50.0 → 0.52.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.
@@ -38,6 +38,38 @@ var init_credentials = __esm({
38
38
  }
39
39
  });
40
40
 
41
+ // src/reports/airtable/throttle.ts
42
+ function createMinIntervalThrottle(opts) {
43
+ const { minIntervalMs, now, delay } = opts;
44
+ return function wrap(fn) {
45
+ let chain = Promise.resolve();
46
+ let last = Number.NEGATIVE_INFINITY;
47
+ return (...args) => {
48
+ chain = chain.then(async () => {
49
+ const wait = minIntervalMs - (now() - last);
50
+ if (wait > 0) await delay(wait);
51
+ last = now();
52
+ fn(...args);
53
+ }).catch(() => {
54
+ });
55
+ };
56
+ };
57
+ }
58
+ function applyThrottle(base, opts) {
59
+ const real = base._base?.runAction;
60
+ if (typeof real !== "function") return base;
61
+ const wrap = createMinIntervalThrottle(opts);
62
+ const throttled = wrap(real.bind(base._base));
63
+ base._base.runAction = throttled;
64
+ base.runAction = throttled;
65
+ return base;
66
+ }
67
+ var init_throttle = __esm({
68
+ "src/reports/airtable/throttle.ts"() {
69
+ "use strict";
70
+ }
71
+ });
72
+
41
73
  // src/reports/airtable/client.ts
42
74
  var client_exports = {};
43
75
  __export(client_exports, {
@@ -61,12 +93,20 @@ function readAirtableConfig() {
61
93
  return { apiKey, baseId };
62
94
  }
63
95
  function openBase(cfg) {
64
- return new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);
96
+ const base = new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);
97
+ return applyThrottle(base, {
98
+ minIntervalMs: MIN_REQUEST_INTERVAL_MS,
99
+ now: () => Date.now(),
100
+ delay: (ms) => new Promise((resolve3) => setTimeout(resolve3, ms))
101
+ });
65
102
  }
103
+ var MIN_REQUEST_INTERVAL_MS;
66
104
  var init_client = __esm({
67
105
  "src/reports/airtable/client.ts"() {
68
106
  "use strict";
69
107
  init_credentials();
108
+ init_throttle();
109
+ MIN_REQUEST_INTERVAL_MS = 220;
70
110
  }
71
111
  });
72
112
 
@@ -74,12 +114,15 @@ var init_client = __esm({
74
114
  var websites_exports = {};
75
115
  __export(websites_exports, {
76
116
  ACTIVE_STATUSES: () => ACTIVE_STATUSES,
117
+ SEVERITY_RANK: () => SEVERITY_RANK,
77
118
  WEBSITES_TABLE: () => WEBSITES_TABLE,
78
119
  getWebsiteBySlug: () => getWebsiteBySlug,
79
120
  isDashboardVisible: () => isDashboardVisible,
80
121
  listWebsites: () => listWebsites,
81
122
  mapRow: () => mapRow,
123
+ normalizeSecurityAdvisory: () => normalizeSecurityAdvisory,
82
124
  parseNotifyRouting: () => parseNotifyRouting,
125
+ parseSecurityAdvisories: () => parseSecurityAdvisories,
83
126
  siteSlug: () => siteSlug,
84
127
  updateA11yCounts: () => updateA11yCounts,
85
128
  updateAuditFields: () => updateAuditFields,
@@ -160,6 +203,7 @@ function mapRow(rec) {
160
203
  securityVulnsModerate: f["Security Vulns Moderate"] ?? null,
161
204
  securityVulnsLow: f["Security Vulns Low"] ?? null,
162
205
  lastSecurityAuditAt: f["Last security audit at"] ?? null,
206
+ securityAdvisories: parseSecurityAdvisories(f["Security advisories"]),
163
207
  certDaysRemaining: f["Cert days remaining"] ?? null,
164
208
  domainCheckedAt: f["Domain checked at"] ?? null,
165
209
  crossbrowserOk: typeof f["Crossbrowser OK"] === "boolean" ? f["Crossbrowser OK"] : null,
@@ -223,6 +267,38 @@ function depsFields(counts) {
223
267
  }
224
268
  return fields;
225
269
  }
270
+ function normalizeSecurityAdvisory(raw) {
271
+ if (!raw || typeof raw !== "object") return null;
272
+ const e = raw;
273
+ const module = typeof e["module"] === "string" ? e["module"] : null;
274
+ const severity = e["severity"];
275
+ if (module === null) return null;
276
+ if (severity !== "low" && severity !== "moderate" && severity !== "high" && severity !== "critical")
277
+ return null;
278
+ const cves = Array.isArray(e["cves"]) ? e["cves"].filter((c) => typeof c === "string") : [];
279
+ return {
280
+ module,
281
+ severity,
282
+ title: typeof e["title"] === "string" ? e["title"] : "",
283
+ cves,
284
+ url: typeof e["url"] === "string" ? e["url"] : null
285
+ };
286
+ }
287
+ function parseSecurityAdvisories(raw) {
288
+ if (typeof raw !== "string" || raw.trim() === "") return null;
289
+ let parsed;
290
+ try {
291
+ parsed = JSON.parse(raw);
292
+ } catch {
293
+ return null;
294
+ }
295
+ if (!Array.isArray(parsed)) return null;
296
+ return parsed.map(normalizeSecurityAdvisory).filter((a) => a !== null);
297
+ }
298
+ function securityAdvisoryFields(advisories) {
299
+ const capped = [...advisories].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]).slice(0, MAX_PERSISTED_ADVISORIES);
300
+ return { "Security advisories": JSON.stringify(capped) };
301
+ }
226
302
  function securityFields(counts) {
227
303
  return {
228
304
  "Security Vulns Critical": counts.critical,
@@ -266,6 +342,8 @@ async function updateAuditFields(base, recordId, audits) {
266
342
  if (audits.a11y) Object.assign(fields, a11yFields(audits.a11y));
267
343
  if (audits.deps) Object.assign(fields, depsFields(audits.deps));
268
344
  if (audits.security) Object.assign(fields, securityFields(audits.security));
345
+ if (audits.securityAdvisories)
346
+ Object.assign(fields, securityAdvisoryFields(audits.securityAdvisories));
269
347
  if (audits.domain) Object.assign(fields, domainFields(audits.domain));
270
348
  if (audits.browser) Object.assign(fields, browserFields(audits.browser));
271
349
  await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
@@ -286,7 +364,7 @@ async function updateLaunched(base, recordId, at) {
286
364
  const fields = { Status: "maintenance", "Launched at": at };
287
365
  await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
288
366
  }
289
- var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES;
367
+ var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES, SEVERITY_RANK, MAX_PERSISTED_ADVISORIES;
290
368
  var init_websites = __esm({
291
369
  "src/reports/airtable/websites.ts"() {
292
370
  "use strict";
@@ -296,6 +374,13 @@ var init_websites = __esm({
296
374
  "launch period"
297
375
  ]);
298
376
  FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
377
+ SEVERITY_RANK = {
378
+ critical: 0,
379
+ high: 1,
380
+ moderate: 2,
381
+ low: 3
382
+ };
383
+ MAX_PERSISTED_ADVISORIES = 25;
299
384
  }
300
385
  });
301
386
 
@@ -456,9 +541,19 @@ function securityCountsFromResult(result) {
456
541
  const c = details?.counts ?? { low: 0, moderate: 0, high: 0, critical: 0 };
457
542
  return { critical: c.critical, high: c.high, moderate: c.moderate, low: c.low };
458
543
  }
544
+ function advisoriesFromResult(result) {
545
+ if (result.audit !== "security") {
546
+ throw new Error(`Expected a 'security' AuditResult, got '${result.audit}'`);
547
+ }
548
+ const details = result.details;
549
+ const raw = details?.advisories;
550
+ if (!Array.isArray(raw)) return [];
551
+ return raw.map(normalizeSecurityAdvisory).filter((a) => a !== null);
552
+ }
459
553
  var init_security_airtable = __esm({
460
554
  "src/audits/security-airtable.ts"() {
461
555
  "use strict";
556
+ init_websites();
462
557
  }
463
558
  });
464
559
 
@@ -547,6 +642,7 @@ async function writeAuditsToAirtable(args) {
547
642
  if (sec && hasSecurityCounts(sec)) {
548
643
  const counts = securityCountsFromResult(sec);
549
644
  audits.security = counts;
645
+ audits.securityAdvisories = advisoriesFromResult(sec);
550
646
  writes.push({ audit: "security", counts });
551
647
  }
552
648
  const dom = results.find((r) => r.audit === "domain");