@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.
- package/dist/cli/bin.js +510 -2
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +98 -2
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/forms/index.js +42 -2
- package/dist/forms/index.js.map +1 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +179 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
|
@@ -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
|
-
|
|
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");
|