@reddoorla/maintenance 0.7.0 → 0.9.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 +8 -2
- package/dist/cli/bin.js +388 -117
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +6 -0
- package/dist/cli/commands/audit.js +284 -26
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.d.ts +35 -9
- package/dist/index.js +259 -142
- package/dist/index.js.map +1 -1
- package/dist/reports/maintenance-email/assets/blurredTests.jpg +0 -0
- package/dist/reports/maintenance-email/assets/check.png +0 -0
- package/package.json +1 -1
|
@@ -4,6 +4,12 @@ type AuditCommandOptions = {
|
|
|
4
4
|
fleet?: string;
|
|
5
5
|
workdir?: string;
|
|
6
6
|
cwd?: string;
|
|
7
|
+
/**
|
|
8
|
+
* After running, push the lighthouse scores to the matching Websites row
|
|
9
|
+
* in Airtable. `true` (no value) = derive slug from cwd/package.json#name;
|
|
10
|
+
* string = explicit slug (e.g. "med-solutions-of-texas").
|
|
11
|
+
*/
|
|
12
|
+
writeAirtable?: string | boolean;
|
|
7
13
|
};
|
|
8
14
|
declare function runAuditCommand(site: string | undefined, opts: AuditCommandOptions): Promise<{
|
|
9
15
|
output: string;
|
|
@@ -1,3 +1,179 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/reports/airtable/client.ts
|
|
12
|
+
var client_exports = {};
|
|
13
|
+
__export(client_exports, {
|
|
14
|
+
openBase: () => openBase,
|
|
15
|
+
readAirtableConfig: () => readAirtableConfig
|
|
16
|
+
});
|
|
17
|
+
import Airtable from "airtable";
|
|
18
|
+
function readAirtableConfig() {
|
|
19
|
+
const apiKey = process.env.AIRTABLE_PAT;
|
|
20
|
+
const baseId = process.env.AIRTABLE_BASE_ID;
|
|
21
|
+
if (!apiKey) throw Object.assign(new Error("AIRTABLE_PAT not set"), { exitCode: 2 });
|
|
22
|
+
if (!baseId) throw Object.assign(new Error("AIRTABLE_BASE_ID not set"), { exitCode: 2 });
|
|
23
|
+
return { apiKey, baseId };
|
|
24
|
+
}
|
|
25
|
+
function openBase(cfg) {
|
|
26
|
+
return new Airtable({ apiKey: cfg.apiKey }).base(cfg.baseId);
|
|
27
|
+
}
|
|
28
|
+
var init_client = __esm({
|
|
29
|
+
"src/reports/airtable/client.ts"() {
|
|
30
|
+
"use strict";
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// src/reports/airtable/websites.ts
|
|
35
|
+
var websites_exports = {};
|
|
36
|
+
__export(websites_exports, {
|
|
37
|
+
WEBSITES_TABLE: () => WEBSITES_TABLE,
|
|
38
|
+
getWebsiteBySlug: () => getWebsiteBySlug,
|
|
39
|
+
listWebsites: () => listWebsites,
|
|
40
|
+
siteSlug: () => siteSlug,
|
|
41
|
+
updateScores: () => updateScores
|
|
42
|
+
});
|
|
43
|
+
function siteSlug(name) {
|
|
44
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
45
|
+
}
|
|
46
|
+
function mapRow(rec) {
|
|
47
|
+
const f = rec.fields;
|
|
48
|
+
const attachments = f["Header image"] ?? [];
|
|
49
|
+
const header = attachments[0] ?? null;
|
|
50
|
+
return {
|
|
51
|
+
id: rec.id,
|
|
52
|
+
name: String(f["Name"] ?? ""),
|
|
53
|
+
url: String(f["url"] ?? ""),
|
|
54
|
+
status: f["Status"] ?? null,
|
|
55
|
+
pointOfContact: f["point of contact"] ?? null,
|
|
56
|
+
maintenanceFreq: f["maintenence freq"] ?? "None",
|
|
57
|
+
testingFreq: f["testing freq"] ?? "None",
|
|
58
|
+
maintenanceDay: f["maintenance day"] ?? null,
|
|
59
|
+
testingDay: f["testing day"] ?? null,
|
|
60
|
+
ga4PropertyId: f["GA4 property ID"] ?? null,
|
|
61
|
+
reportRecipientsTo: f["Report recipients (To)"] ?? null,
|
|
62
|
+
reportRecipientsCc: f["Report recipients (CC)"] ?? null,
|
|
63
|
+
headerImage: header,
|
|
64
|
+
pScore: f["pScore"] ?? null,
|
|
65
|
+
rScore: f["rScore"] ?? null,
|
|
66
|
+
bpScore: f["bpScore"] ?? null,
|
|
67
|
+
seoScore: f["seoScore"] ?? null,
|
|
68
|
+
lastLighthouseAuditAt: f["Last lighthouse audit at"] ?? null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function listWebsites(base) {
|
|
72
|
+
const out = [];
|
|
73
|
+
await base(WEBSITES_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
74
|
+
for (const rec of records) out.push(mapRow({ id: rec.id, fields: rec.fields }));
|
|
75
|
+
fetchNextPage();
|
|
76
|
+
});
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
async function getWebsiteBySlug(base, slug) {
|
|
80
|
+
const all = await listWebsites(base);
|
|
81
|
+
return all.find((w) => siteSlug(w.name) === slug) ?? null;
|
|
82
|
+
}
|
|
83
|
+
async function updateScores(base, recordId, scores) {
|
|
84
|
+
const fields = {
|
|
85
|
+
pScore: scores.performance,
|
|
86
|
+
rScore: scores.accessibility,
|
|
87
|
+
bpScore: scores.bestPractices,
|
|
88
|
+
seoScore: scores.seo,
|
|
89
|
+
"Last lighthouse audit at": (/* @__PURE__ */ new Date()).toISOString()
|
|
90
|
+
};
|
|
91
|
+
await base(WEBSITES_TABLE).update([{ id: recordId, fields }]);
|
|
92
|
+
}
|
|
93
|
+
var WEBSITES_TABLE;
|
|
94
|
+
var init_websites = __esm({
|
|
95
|
+
"src/reports/airtable/websites.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
WEBSITES_TABLE = "Websites";
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// src/inventory/airtable.ts
|
|
102
|
+
var airtable_exports = {};
|
|
103
|
+
__export(airtable_exports, {
|
|
104
|
+
fromAirtableBase: () => fromAirtableBase
|
|
105
|
+
});
|
|
106
|
+
function fromAirtableBase(base, opts = {}) {
|
|
107
|
+
return async () => {
|
|
108
|
+
const workdir = opts.workdir ?? process.env.REDDOOR_FLEET_WORKDIR;
|
|
109
|
+
if (!workdir) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"fromAirtableBase requires `workdir` option or REDDOOR_FLEET_WORKDIR env (sites need a local path)"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const websites = await listWebsites(base);
|
|
115
|
+
return websites.filter((w) => w.maintenanceFreq !== "None" || w.testingFreq !== "None").map((w) => {
|
|
116
|
+
const slug = siteSlug(w.name);
|
|
117
|
+
const site = {
|
|
118
|
+
path: `${workdir}/${slug}`,
|
|
119
|
+
name: slug,
|
|
120
|
+
meta: { airtableRowId: w.id, displayName: w.name }
|
|
121
|
+
};
|
|
122
|
+
if (w.url) site.repoUrl = w.url;
|
|
123
|
+
return site;
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var init_airtable = __esm({
|
|
128
|
+
"src/inventory/airtable.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
init_websites();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// src/audits/lighthouse-airtable.ts
|
|
135
|
+
var lighthouse_airtable_exports = {};
|
|
136
|
+
__export(lighthouse_airtable_exports, {
|
|
137
|
+
lighthouseScoresFromResult: () => lighthouseScoresFromResult,
|
|
138
|
+
resolveSlugFromCwd: () => resolveSlugFromCwd
|
|
139
|
+
});
|
|
140
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
141
|
+
import { join as join7 } from "path";
|
|
142
|
+
function lighthouseScoresFromResult(result) {
|
|
143
|
+
if (result.audit !== "lighthouse") {
|
|
144
|
+
throw new Error(`Expected a 'lighthouse' AuditResult, got '${result.audit}'`);
|
|
145
|
+
}
|
|
146
|
+
const details = result.details ?? {};
|
|
147
|
+
const summary = details.summary ?? {};
|
|
148
|
+
const toPct = (n) => typeof n === "number" && !Number.isNaN(n) ? Math.round(n * 100) : 0;
|
|
149
|
+
return {
|
|
150
|
+
performance: toPct(summary["performance"]),
|
|
151
|
+
accessibility: toPct(summary["accessibility"]),
|
|
152
|
+
bestPractices: toPct(summary["best-practices"]),
|
|
153
|
+
seo: toPct(summary["seo"])
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function resolveSlugFromCwd(cwd) {
|
|
157
|
+
try {
|
|
158
|
+
const pkgPath = join7(cwd, "package.json");
|
|
159
|
+
const raw = await readFile7(pkgPath, "utf-8");
|
|
160
|
+
const pkg = JSON.parse(raw);
|
|
161
|
+
if (!pkg.name) throw new Error("package.json has no 'name' field");
|
|
162
|
+
return siteSlug(pkg.name);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Could not derive site slug from ${cwd}/package.json: ${e.message}. Pass --write-airtable=<slug> explicitly.`,
|
|
166
|
+
{ cause: e }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var init_lighthouse_airtable = __esm({
|
|
171
|
+
"src/audits/lighthouse-airtable.ts"() {
|
|
172
|
+
"use strict";
|
|
173
|
+
init_websites();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
1
177
|
// src/cli/commands/audit.ts
|
|
2
178
|
import { resolve as resolve2 } from "path";
|
|
3
179
|
|
|
@@ -338,9 +514,9 @@ async function securityAudit(ctx) {
|
|
|
338
514
|
}
|
|
339
515
|
|
|
340
516
|
// src/audits/lighthouse.ts
|
|
341
|
-
import { readFile as
|
|
517
|
+
import { readFile as readFile4, writeFile, mkdtemp, rm } from "fs/promises";
|
|
342
518
|
import { tmpdir } from "os";
|
|
343
|
-
import { join as
|
|
519
|
+
import { join as join4 } from "path";
|
|
344
520
|
|
|
345
521
|
// src/configs/lighthouse.ts
|
|
346
522
|
var lighthouseConfig = {
|
|
@@ -373,10 +549,37 @@ var lighthouseConfig = {
|
|
|
373
549
|
}
|
|
374
550
|
};
|
|
375
551
|
|
|
552
|
+
// src/audits/util/site-config.ts
|
|
553
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
554
|
+
import { join as join3 } from "path";
|
|
555
|
+
async function readSiteConfig(sitePath) {
|
|
556
|
+
let raw;
|
|
557
|
+
try {
|
|
558
|
+
raw = await readFile3(join3(sitePath, "package.json"), "utf-8");
|
|
559
|
+
} catch {
|
|
560
|
+
return {};
|
|
561
|
+
}
|
|
562
|
+
let pkg;
|
|
563
|
+
try {
|
|
564
|
+
pkg = JSON.parse(raw);
|
|
565
|
+
} catch {
|
|
566
|
+
return {};
|
|
567
|
+
}
|
|
568
|
+
if (!pkg || typeof pkg !== "object") return {};
|
|
569
|
+
const cfg = pkg.reddoor;
|
|
570
|
+
if (!cfg || typeof cfg !== "object") return {};
|
|
571
|
+
const out = {};
|
|
572
|
+
const url = cfg.lighthouseUrl;
|
|
573
|
+
if (typeof url === "string" && url.length > 0) {
|
|
574
|
+
out.lighthouseUrl = url;
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
|
|
376
579
|
// src/audits/lighthouse.ts
|
|
377
580
|
async function readJsonMaybe(path) {
|
|
378
581
|
try {
|
|
379
|
-
const raw = await
|
|
582
|
+
const raw = await readFile4(path, "utf-8");
|
|
380
583
|
return JSON.parse(raw);
|
|
381
584
|
} catch {
|
|
382
585
|
return null;
|
|
@@ -412,15 +615,28 @@ async function lighthouseAudit(ctx) {
|
|
|
412
615
|
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
413
616
|
const site = ctx.site;
|
|
414
617
|
const label = siteLabel(site);
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
618
|
+
const siteCfg = await readSiteConfig(site.path);
|
|
619
|
+
const resolvedConfig = siteCfg.lighthouseUrl ? {
|
|
620
|
+
...lighthouseConfig,
|
|
621
|
+
ci: {
|
|
622
|
+
...lighthouseConfig.ci,
|
|
623
|
+
collect: { ...lighthouseConfig.ci.collect, url: [siteCfg.lighthouseUrl] }
|
|
624
|
+
}
|
|
625
|
+
} : lighthouseConfig;
|
|
626
|
+
const configDir = await mkdtemp(join4(tmpdir(), "reddoor-lhci-"));
|
|
627
|
+
const configPath = join4(configDir, "lighthouserc.json");
|
|
628
|
+
await writeFile(configPath, JSON.stringify(resolvedConfig), "utf-8");
|
|
629
|
+
const resultsDir = join4(site.path, ".lighthouseci");
|
|
419
630
|
await rm(resultsDir, { recursive: true, force: true });
|
|
420
631
|
let raw;
|
|
421
632
|
try {
|
|
422
633
|
raw = await spawn2("npx", ["--yes", "@lhci/cli", "autorun", `--config=${configPath}`], {
|
|
423
|
-
cwd: site.path
|
|
634
|
+
cwd: site.path,
|
|
635
|
+
// lhci autorun boots the site's dev server, downloads Chrome on first
|
|
636
|
+
// use, and runs the audit — easily 2–3 min on a cold tree. The shared
|
|
637
|
+
// 30 s default in runAudits is fine for deps/lint/security but starves
|
|
638
|
+
// lhci.
|
|
639
|
+
timeoutMs: 5 * 6e4
|
|
424
640
|
});
|
|
425
641
|
} catch (err) {
|
|
426
642
|
await rm(configDir, { recursive: true, force: true });
|
|
@@ -436,7 +652,7 @@ async function lighthouseAudit(ctx) {
|
|
|
436
652
|
throw err;
|
|
437
653
|
}
|
|
438
654
|
await rm(configDir, { recursive: true, force: true });
|
|
439
|
-
const manifest = await readJsonMaybe(
|
|
655
|
+
const manifest = await readJsonMaybe(join4(resultsDir, "manifest.json"));
|
|
440
656
|
if (!manifest || manifest.length === 0) {
|
|
441
657
|
return {
|
|
442
658
|
audit: "lighthouse",
|
|
@@ -445,7 +661,7 @@ async function lighthouseAudit(ctx) {
|
|
|
445
661
|
summary: `lighthouse: no manifest written (exit ${raw.code})${raw.stderr ? ` \u2014 ${raw.stderr.slice(0, 200)}` : ""}`
|
|
446
662
|
};
|
|
447
663
|
}
|
|
448
|
-
const assertionResults = await readJsonMaybe(
|
|
664
|
+
const assertionResults = await readJsonMaybe(join4(resultsDir, "assertion-results.json")) ?? [];
|
|
449
665
|
const failed = assertionResults.filter((a) => !a.passed);
|
|
450
666
|
const assertions = failed.map((a) => ({
|
|
451
667
|
category: categoryFromAssertion(a),
|
|
@@ -471,9 +687,9 @@ async function lighthouseAudit(ctx) {
|
|
|
471
687
|
}
|
|
472
688
|
|
|
473
689
|
// src/audits/a11y.ts
|
|
474
|
-
import { readFile as
|
|
690
|
+
import { readFile as readFile5, writeFile as writeFile2, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
|
|
475
691
|
import { tmpdir as tmpdir2 } from "os";
|
|
476
|
-
import { join as
|
|
692
|
+
import { join as join5 } from "path";
|
|
477
693
|
|
|
478
694
|
// src/configs/playwright-a11y.ts
|
|
479
695
|
import { defineConfig, devices } from "@playwright/test";
|
|
@@ -511,7 +727,7 @@ var playwrightA11yConfig = defineConfig({
|
|
|
511
727
|
var RESULTS_REL = ".reddoor-a11y/results.json";
|
|
512
728
|
async function readJsonMaybe2(path) {
|
|
513
729
|
try {
|
|
514
|
-
const raw = await
|
|
730
|
+
const raw = await readFile5(path, "utf-8");
|
|
515
731
|
return JSON.parse(raw);
|
|
516
732
|
} catch {
|
|
517
733
|
return null;
|
|
@@ -567,11 +783,11 @@ async function a11yAudit(ctx) {
|
|
|
567
783
|
const spawn2 = ctx.spawn ?? defaultSpawn;
|
|
568
784
|
const site = ctx.site;
|
|
569
785
|
const label = siteLabel(site);
|
|
570
|
-
const specDir = await mkdtemp2(
|
|
571
|
-
const specPath =
|
|
786
|
+
const specDir = await mkdtemp2(join5(tmpdir2(), "reddoor-a11y-spec-"));
|
|
787
|
+
const specPath = join5(specDir, "a11y.spec.ts");
|
|
572
788
|
await writeFile2(specPath, buildSpec(), "utf-8");
|
|
573
|
-
const resultsPath =
|
|
574
|
-
await rm2(
|
|
789
|
+
const resultsPath = join5(site.path, RESULTS_REL);
|
|
790
|
+
await rm2(join5(site.path, ".reddoor-a11y"), { recursive: true, force: true });
|
|
575
791
|
let raw;
|
|
576
792
|
try {
|
|
577
793
|
raw = await spawn2("npx", ["--yes", "playwright", "test", "--reporter=line", specPath], {
|
|
@@ -647,6 +863,10 @@ async function runAudits(site, which) {
|
|
|
647
863
|
)
|
|
648
864
|
);
|
|
649
865
|
}
|
|
866
|
+
async function runAuditsAcross(sites, which) {
|
|
867
|
+
const all = await Promise.all(sites.map((s) => runAudits(s, which)));
|
|
868
|
+
return all.flat();
|
|
869
|
+
}
|
|
650
870
|
|
|
651
871
|
// src/cli/fleet/resolve-sites.ts
|
|
652
872
|
import { pathToFileURL } from "url";
|
|
@@ -660,7 +880,7 @@ function localPath(path, opts = {}) {
|
|
|
660
880
|
}
|
|
661
881
|
|
|
662
882
|
// src/inventory/json.ts
|
|
663
|
-
import { readFile as
|
|
883
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
664
884
|
import { isAbsolute } from "path";
|
|
665
885
|
function validate(raw) {
|
|
666
886
|
if (!Array.isArray(raw)) {
|
|
@@ -690,7 +910,7 @@ function validate(raw) {
|
|
|
690
910
|
}
|
|
691
911
|
function fromJsonFile(path) {
|
|
692
912
|
return async () => {
|
|
693
|
-
const raw = JSON.parse(await
|
|
913
|
+
const raw = JSON.parse(await readFile6(path, "utf-8"));
|
|
694
914
|
return validate(raw);
|
|
695
915
|
};
|
|
696
916
|
}
|
|
@@ -702,6 +922,13 @@ async function resolveSites(input) {
|
|
|
702
922
|
exitCode: 2
|
|
703
923
|
});
|
|
704
924
|
}
|
|
925
|
+
if (input.fleet === "airtable") {
|
|
926
|
+
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
927
|
+
const { fromAirtableBase: fromAirtableBase2 } = await Promise.resolve().then(() => (init_airtable(), airtable_exports));
|
|
928
|
+
const base = openBase2(readAirtableConfig2());
|
|
929
|
+
const provider = fromAirtableBase2(base, input.workdir ? { workdir: input.workdir } : {});
|
|
930
|
+
return provider();
|
|
931
|
+
}
|
|
705
932
|
if (input.fleet) {
|
|
706
933
|
const fleetPath = resolve(input.cwd, input.fleet);
|
|
707
934
|
const ext = extname(fleetPath).toLowerCase();
|
|
@@ -729,7 +956,7 @@ async function resolveSites(input) {
|
|
|
729
956
|
|
|
730
957
|
// src/cli/fleet/clone-if-needed.ts
|
|
731
958
|
import { stat, readdir, mkdir } from "fs/promises";
|
|
732
|
-
import { isAbsolute as isAbsolute2, join as
|
|
959
|
+
import { isAbsolute as isAbsolute2, join as join6 } from "path";
|
|
733
960
|
function deriveNameFromRepoUrl(repoUrl) {
|
|
734
961
|
const slash = repoUrl.split("/").pop() ?? repoUrl;
|
|
735
962
|
return slash.replace(/\.git$/, "");
|
|
@@ -770,7 +997,7 @@ async function cloneIfNeeded(site, opts) {
|
|
|
770
997
|
const name = site.name ?? deriveNameFromRepoUrl(site.repoUrl);
|
|
771
998
|
assertSafeName(name);
|
|
772
999
|
assertSafeRepoUrl(site.repoUrl);
|
|
773
|
-
const target =
|
|
1000
|
+
const target = join6(opts.workdir, name);
|
|
774
1001
|
await mkdir(opts.workdir, { recursive: true });
|
|
775
1002
|
if (await isNonEmptyDir(target)) {
|
|
776
1003
|
return { ...site, name, path: target };
|
|
@@ -810,18 +1037,49 @@ async function runAuditCommand(site, opts) {
|
|
|
810
1037
|
let sites = await resolveSites({
|
|
811
1038
|
...site !== void 0 ? { site } : {},
|
|
812
1039
|
...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
|
|
1040
|
+
...opts.workdir !== void 0 ? { workdir: opts.workdir } : {},
|
|
813
1041
|
cwd
|
|
814
1042
|
});
|
|
815
1043
|
if (opts.fleet) {
|
|
816
1044
|
const workdir = opts.workdir ?? `${process.env.HOME ?? ""}/.reddoor-maint/sites`;
|
|
817
1045
|
sites = await Promise.all(sites.map((s) => cloneIfNeeded(s, { workdir })));
|
|
818
1046
|
}
|
|
819
|
-
const results =
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1047
|
+
const results = await runAuditsAcross(sites, which);
|
|
1048
|
+
let output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);
|
|
1049
|
+
if (opts.writeAirtable !== void 0) {
|
|
1050
|
+
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
1051
|
+
const { listWebsites: listWebsites2, updateScores: updateScores2, siteSlug: siteSlug2 } = await Promise.resolve().then(() => (init_websites(), websites_exports));
|
|
1052
|
+
const { lighthouseScoresFromResult: lighthouseScoresFromResult2, resolveSlugFromCwd: resolveSlugFromCwd2 } = await Promise.resolve().then(() => (init_lighthouse_airtable(), lighthouse_airtable_exports));
|
|
1053
|
+
const slug = typeof opts.writeAirtable === "string" && opts.writeAirtable.length > 0 ? opts.writeAirtable : await resolveSlugFromCwd2(cwd);
|
|
1054
|
+
const lhResult = results.find((r) => r.audit === "lighthouse");
|
|
1055
|
+
if (!lhResult) {
|
|
1056
|
+
throw Object.assign(
|
|
1057
|
+
new Error(
|
|
1058
|
+
"--write-airtable requires a lighthouse result; did you pass --only without lighthouse?"
|
|
1059
|
+
),
|
|
1060
|
+
{ exitCode: 2 }
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
if (lhResult.status === "fail") {
|
|
1064
|
+
throw Object.assign(
|
|
1065
|
+
new Error(
|
|
1066
|
+
`Lighthouse audit failed; refusing to write scores to Airtable. Summary: ${lhResult.summary}`
|
|
1067
|
+
),
|
|
1068
|
+
{ exitCode: 1 }
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
const base = openBase2(readAirtableConfig2());
|
|
1072
|
+
const websites = await listWebsites2(base);
|
|
1073
|
+
const target = websites.find((w) => siteSlug2(w.name) === slug);
|
|
1074
|
+
if (!target) {
|
|
1075
|
+
throw Object.assign(new Error(`No Websites row matched slug "${slug}"`), { exitCode: 2 });
|
|
1076
|
+
}
|
|
1077
|
+
const scores = lighthouseScoresFromResult2(lhResult);
|
|
1078
|
+
await updateScores2(base, target.id, scores);
|
|
1079
|
+
output += `
|
|
1080
|
+
|
|
1081
|
+
\u2192 wrote scores to Websites[${target.name}]: P=${scores.performance} A=${scores.accessibility} BP=${scores.bestPractices} SEO=${scores.seo}`;
|
|
823
1082
|
}
|
|
824
|
-
const output = opts.json ? JSON.stringify(results, null, 2) : formatTable(results);
|
|
825
1083
|
return { output, code: exitCode(results) };
|
|
826
1084
|
}
|
|
827
1085
|
export {
|