@reddoorla/maintenance 0.50.0 → 0.51.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 +98 -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 +1 -1
package/dist/cli/bin.js
CHANGED
|
@@ -87,6 +87,38 @@ var init_url = __esm({
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
// src/reports/airtable/throttle.ts
|
|
91
|
+
function createMinIntervalThrottle(opts) {
|
|
92
|
+
const { minIntervalMs, now, delay } = opts;
|
|
93
|
+
return function wrap(fn) {
|
|
94
|
+
let chain = Promise.resolve();
|
|
95
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
96
|
+
return (...args) => {
|
|
97
|
+
chain = chain.then(async () => {
|
|
98
|
+
const wait = minIntervalMs - (now() - last);
|
|
99
|
+
if (wait > 0) await delay(wait);
|
|
100
|
+
last = now();
|
|
101
|
+
fn(...args);
|
|
102
|
+
}).catch(() => {
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function applyThrottle(base, opts) {
|
|
108
|
+
const real = base._base?.runAction;
|
|
109
|
+
if (typeof real !== "function") return base;
|
|
110
|
+
const wrap = createMinIntervalThrottle(opts);
|
|
111
|
+
const throttled = wrap(real.bind(base._base));
|
|
112
|
+
base._base.runAction = throttled;
|
|
113
|
+
base.runAction = throttled;
|
|
114
|
+
return base;
|
|
115
|
+
}
|
|
116
|
+
var init_throttle = __esm({
|
|
117
|
+
"src/reports/airtable/throttle.ts"() {
|
|
118
|
+
"use strict";
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
90
122
|
// src/reports/airtable/client.ts
|
|
91
123
|
var client_exports = {};
|
|
92
124
|
__export(client_exports, {
|
|
@@ -110,12 +142,20 @@ function readAirtableConfig() {
|
|
|
110
142
|
return { apiKey, baseId };
|
|
111
143
|
}
|
|
112
144
|
function openBase(cfg) {
|
|
113
|
-
|
|
145
|
+
const base = new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);
|
|
146
|
+
return applyThrottle(base, {
|
|
147
|
+
minIntervalMs: MIN_REQUEST_INTERVAL_MS,
|
|
148
|
+
now: () => Date.now(),
|
|
149
|
+
delay: (ms) => new Promise((resolve12) => setTimeout(resolve12, ms))
|
|
150
|
+
});
|
|
114
151
|
}
|
|
152
|
+
var MIN_REQUEST_INTERVAL_MS;
|
|
115
153
|
var init_client = __esm({
|
|
116
154
|
"src/reports/airtable/client.ts"() {
|
|
117
155
|
"use strict";
|
|
118
156
|
init_credentials();
|
|
157
|
+
init_throttle();
|
|
158
|
+
MIN_REQUEST_INTERVAL_MS = 220;
|
|
119
159
|
}
|
|
120
160
|
});
|
|
121
161
|
|
|
@@ -123,12 +163,15 @@ var init_client = __esm({
|
|
|
123
163
|
var websites_exports = {};
|
|
124
164
|
__export(websites_exports, {
|
|
125
165
|
ACTIVE_STATUSES: () => ACTIVE_STATUSES,
|
|
166
|
+
SEVERITY_RANK: () => SEVERITY_RANK,
|
|
126
167
|
WEBSITES_TABLE: () => WEBSITES_TABLE,
|
|
127
168
|
getWebsiteBySlug: () => getWebsiteBySlug,
|
|
128
169
|
isDashboardVisible: () => isDashboardVisible,
|
|
129
170
|
listWebsites: () => listWebsites,
|
|
130
171
|
mapRow: () => mapRow,
|
|
172
|
+
normalizeSecurityAdvisory: () => normalizeSecurityAdvisory,
|
|
131
173
|
parseNotifyRouting: () => parseNotifyRouting,
|
|
174
|
+
parseSecurityAdvisories: () => parseSecurityAdvisories,
|
|
132
175
|
siteSlug: () => siteSlug,
|
|
133
176
|
updateA11yCounts: () => updateA11yCounts,
|
|
134
177
|
updateAuditFields: () => updateAuditFields,
|
|
@@ -209,6 +252,7 @@ function mapRow(rec) {
|
|
|
209
252
|
securityVulnsModerate: f["Security Vulns Moderate"] ?? null,
|
|
210
253
|
securityVulnsLow: f["Security Vulns Low"] ?? null,
|
|
211
254
|
lastSecurityAuditAt: f["Last security audit at"] ?? null,
|
|
255
|
+
securityAdvisories: parseSecurityAdvisories(f["Security advisories"]),
|
|
212
256
|
certDaysRemaining: f["Cert days remaining"] ?? null,
|
|
213
257
|
domainCheckedAt: f["Domain checked at"] ?? null,
|
|
214
258
|
crossbrowserOk: typeof f["Crossbrowser OK"] === "boolean" ? f["Crossbrowser OK"] : null,
|
|
@@ -272,6 +316,38 @@ function depsFields(counts) {
|
|
|
272
316
|
}
|
|
273
317
|
return fields;
|
|
274
318
|
}
|
|
319
|
+
function normalizeSecurityAdvisory(raw) {
|
|
320
|
+
if (!raw || typeof raw !== "object") return null;
|
|
321
|
+
const e = raw;
|
|
322
|
+
const module = typeof e["module"] === "string" ? e["module"] : null;
|
|
323
|
+
const severity = e["severity"];
|
|
324
|
+
if (module === null) return null;
|
|
325
|
+
if (severity !== "low" && severity !== "moderate" && severity !== "high" && severity !== "critical")
|
|
326
|
+
return null;
|
|
327
|
+
const cves = Array.isArray(e["cves"]) ? e["cves"].filter((c) => typeof c === "string") : [];
|
|
328
|
+
return {
|
|
329
|
+
module,
|
|
330
|
+
severity,
|
|
331
|
+
title: typeof e["title"] === "string" ? e["title"] : "",
|
|
332
|
+
cves,
|
|
333
|
+
url: typeof e["url"] === "string" ? e["url"] : null
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function parseSecurityAdvisories(raw) {
|
|
337
|
+
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
338
|
+
let parsed;
|
|
339
|
+
try {
|
|
340
|
+
parsed = JSON.parse(raw);
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
if (!Array.isArray(parsed)) return null;
|
|
345
|
+
return parsed.map(normalizeSecurityAdvisory).filter((a) => a !== null);
|
|
346
|
+
}
|
|
347
|
+
function securityAdvisoryFields(advisories) {
|
|
348
|
+
const capped = [...advisories].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]).slice(0, MAX_PERSISTED_ADVISORIES);
|
|
349
|
+
return { "Security advisories": JSON.stringify(capped) };
|
|
350
|
+
}
|
|
275
351
|
function securityFields(counts) {
|
|
276
352
|
return {
|
|
277
353
|
"Security Vulns Critical": counts.critical,
|
|
@@ -315,6 +391,8 @@ async function updateAuditFields(base, recordId, audits) {
|
|
|
315
391
|
if (audits.a11y) Object.assign(fields, a11yFields(audits.a11y));
|
|
316
392
|
if (audits.deps) Object.assign(fields, depsFields(audits.deps));
|
|
317
393
|
if (audits.security) Object.assign(fields, securityFields(audits.security));
|
|
394
|
+
if (audits.securityAdvisories)
|
|
395
|
+
Object.assign(fields, securityAdvisoryFields(audits.securityAdvisories));
|
|
318
396
|
if (audits.domain) Object.assign(fields, domainFields(audits.domain));
|
|
319
397
|
if (audits.browser) Object.assign(fields, browserFields(audits.browser));
|
|
320
398
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
@@ -335,7 +413,7 @@ async function updateLaunched(base, recordId, at) {
|
|
|
335
413
|
const fields = { Status: "maintenance", "Launched at": at };
|
|
336
414
|
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
337
415
|
}
|
|
338
|
-
var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES;
|
|
416
|
+
var WEBSITES_TABLE, ACTIVE_STATUSES, FREQUENCIES, SEVERITY_RANK, MAX_PERSISTED_ADVISORIES;
|
|
339
417
|
var init_websites = __esm({
|
|
340
418
|
"src/reports/airtable/websites.ts"() {
|
|
341
419
|
"use strict";
|
|
@@ -345,6 +423,13 @@ var init_websites = __esm({
|
|
|
345
423
|
"launch period"
|
|
346
424
|
]);
|
|
347
425
|
FREQUENCIES = ["None", "Monthly", "Quarterly", "Yearly"];
|
|
426
|
+
SEVERITY_RANK = {
|
|
427
|
+
critical: 0,
|
|
428
|
+
high: 1,
|
|
429
|
+
moderate: 2,
|
|
430
|
+
low: 3
|
|
431
|
+
};
|
|
432
|
+
MAX_PERSISTED_ADVISORIES = 25;
|
|
348
433
|
}
|
|
349
434
|
});
|
|
350
435
|
|
|
@@ -505,9 +590,19 @@ function securityCountsFromResult(result) {
|
|
|
505
590
|
const c = details?.counts ?? { low: 0, moderate: 0, high: 0, critical: 0 };
|
|
506
591
|
return { critical: c.critical, high: c.high, moderate: c.moderate, low: c.low };
|
|
507
592
|
}
|
|
593
|
+
function advisoriesFromResult(result) {
|
|
594
|
+
if (result.audit !== "security") {
|
|
595
|
+
throw new Error(`Expected a 'security' AuditResult, got '${result.audit}'`);
|
|
596
|
+
}
|
|
597
|
+
const details = result.details;
|
|
598
|
+
const raw = details?.advisories;
|
|
599
|
+
if (!Array.isArray(raw)) return [];
|
|
600
|
+
return raw.map(normalizeSecurityAdvisory).filter((a) => a !== null);
|
|
601
|
+
}
|
|
508
602
|
var init_security_airtable = __esm({
|
|
509
603
|
"src/audits/security-airtable.ts"() {
|
|
510
604
|
"use strict";
|
|
605
|
+
init_websites();
|
|
511
606
|
}
|
|
512
607
|
});
|
|
513
608
|
|
|
@@ -596,6 +691,7 @@ async function writeAuditsToAirtable(args) {
|
|
|
596
691
|
if (sec && hasSecurityCounts(sec)) {
|
|
597
692
|
const counts = securityCountsFromResult(sec);
|
|
598
693
|
audits.security = counts;
|
|
694
|
+
audits.securityAdvisories = advisoriesFromResult(sec);
|
|
599
695
|
writes.push({ audit: "security", counts });
|
|
600
696
|
}
|
|
601
697
|
const dom = results.find((r) => r.audit === "domain");
|