@reddoorla/maintenance 0.10.4 → 0.10.6
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 +34 -16
- package/dist/cli/bin.js +103 -18
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +103 -18
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/index.js +101 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -335,7 +335,7 @@ async function securityAudit(ctx) {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
// src/audits/lighthouse.ts
|
|
338
|
-
import { readFile as readFile4, writeFile, mkdtemp, rm } from "fs/promises";
|
|
338
|
+
import { readFile as readFile4, writeFile, mkdtemp, rm, readdir } from "fs/promises";
|
|
339
339
|
import { tmpdir } from "os";
|
|
340
340
|
import { join as join4 } from "path";
|
|
341
341
|
|
|
@@ -397,6 +397,32 @@ async function readSiteConfig(sitePath) {
|
|
|
397
397
|
return out;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
// src/util/free-port.ts
|
|
401
|
+
import { createServer } from "net";
|
|
402
|
+
async function findFreePort() {
|
|
403
|
+
return new Promise((resolve, reject) => {
|
|
404
|
+
const server = createServer();
|
|
405
|
+
server.unref();
|
|
406
|
+
server.on("error", reject);
|
|
407
|
+
server.listen(0, "127.0.0.1", () => {
|
|
408
|
+
const addr = server.address();
|
|
409
|
+
if (typeof addr === "object" && addr) {
|
|
410
|
+
const port = addr.port;
|
|
411
|
+
server.close(() => resolve(port));
|
|
412
|
+
} else {
|
|
413
|
+
server.close();
|
|
414
|
+
reject(new Error("findFreePort: could not determine assigned port from socket"));
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function withFreePort(url, port) {
|
|
420
|
+
const u = new URL(url);
|
|
421
|
+
u.hostname = "localhost";
|
|
422
|
+
u.port = String(port);
|
|
423
|
+
return u.toString();
|
|
424
|
+
}
|
|
425
|
+
|
|
400
426
|
// src/audits/lighthouse.ts
|
|
401
427
|
async function readJsonMaybe(path) {
|
|
402
428
|
try {
|
|
@@ -406,6 +432,21 @@ async function readJsonMaybe(path) {
|
|
|
406
432
|
return null;
|
|
407
433
|
}
|
|
408
434
|
}
|
|
435
|
+
async function readLhrEntries(resultsDir) {
|
|
436
|
+
const files = await readdir(resultsDir).catch(() => []);
|
|
437
|
+
const entries = [];
|
|
438
|
+
for (const f of files) {
|
|
439
|
+
if (!f.startsWith("lhr-") || !f.endsWith(".json")) continue;
|
|
440
|
+
const lhr = await readJsonMaybe(join4(resultsDir, f));
|
|
441
|
+
if (!lhr || !lhr.categories) continue;
|
|
442
|
+
const summary = {};
|
|
443
|
+
for (const [k, v] of Object.entries(lhr.categories)) {
|
|
444
|
+
if (typeof v?.score === "number") summary[k] = v.score;
|
|
445
|
+
}
|
|
446
|
+
entries.push({ url: lhr.requestedUrl, summary });
|
|
447
|
+
}
|
|
448
|
+
return entries;
|
|
449
|
+
}
|
|
409
450
|
function averageSummaries(entries) {
|
|
410
451
|
if (entries.length === 0) return {};
|
|
411
452
|
const sums = {};
|
|
@@ -437,13 +478,19 @@ async function lighthouseAudit(ctx) {
|
|
|
437
478
|
const site = ctx.site;
|
|
438
479
|
const label = siteLabel(site);
|
|
439
480
|
const siteCfg = await readSiteConfig(site.path);
|
|
440
|
-
const
|
|
481
|
+
const port = await findFreePort();
|
|
482
|
+
const baseUrl = siteCfg.lighthouseUrl ?? lighthouseConfig.ci.collect.url[0];
|
|
483
|
+
const resolvedConfig = {
|
|
441
484
|
...lighthouseConfig,
|
|
442
485
|
ci: {
|
|
443
486
|
...lighthouseConfig.ci,
|
|
444
|
-
collect: {
|
|
487
|
+
collect: {
|
|
488
|
+
...lighthouseConfig.ci.collect,
|
|
489
|
+
url: [withFreePort(baseUrl, port)],
|
|
490
|
+
startServerCommand: `npm run vite:dev -- --port ${port} --strictPort`
|
|
491
|
+
}
|
|
445
492
|
}
|
|
446
|
-
}
|
|
493
|
+
};
|
|
447
494
|
const configDir = await mkdtemp(join4(tmpdir(), "reddoor-lhci-"));
|
|
448
495
|
const configPath = join4(configDir, "lighthouserc.json");
|
|
449
496
|
await writeFile(configPath, JSON.stringify(resolvedConfig), "utf-8");
|
|
@@ -473,13 +520,13 @@ async function lighthouseAudit(ctx) {
|
|
|
473
520
|
throw err;
|
|
474
521
|
}
|
|
475
522
|
await rm(configDir, { recursive: true, force: true });
|
|
476
|
-
const manifest = await
|
|
477
|
-
if (
|
|
523
|
+
const manifest = await readLhrEntries(resultsDir);
|
|
524
|
+
if (manifest.length === 0) {
|
|
478
525
|
return {
|
|
479
526
|
audit: "lighthouse",
|
|
480
527
|
site: label,
|
|
481
528
|
status: "fail",
|
|
482
|
-
summary: `lighthouse: no
|
|
529
|
+
summary: `lighthouse: no lhr-*.json written (exit ${raw.code})${raw.stderr ? ` \u2014 ${raw.stderr.slice(0, 200)}` : ""}`
|
|
483
530
|
};
|
|
484
531
|
}
|
|
485
532
|
const assertionResults = await readJsonMaybe(join4(resultsDir, "assertion-results.json")) ?? [];
|
|
@@ -554,6 +601,37 @@ async function readJsonMaybe2(path) {
|
|
|
554
601
|
return null;
|
|
555
602
|
}
|
|
556
603
|
}
|
|
604
|
+
function buildPlaywrightConfig(port, sitePath) {
|
|
605
|
+
return `import { defineConfig } from "@playwright/test";
|
|
606
|
+
|
|
607
|
+
export default defineConfig({
|
|
608
|
+
testDir: ".",
|
|
609
|
+
testMatch: /.*\\.spec\\.ts$/,
|
|
610
|
+
fullyParallel: true,
|
|
611
|
+
forbidOnly: !!process.env.CI,
|
|
612
|
+
retries: process.env.CI ? 2 : 0,
|
|
613
|
+
reporter: process.env.CI ? "github" : "list",
|
|
614
|
+
use: {
|
|
615
|
+
baseURL: "http://localhost:${port}",
|
|
616
|
+
trace: "on-first-retry",
|
|
617
|
+
},
|
|
618
|
+
webServer: {
|
|
619
|
+
// --strictPort: refuse to bump to a different port if ours is taken,
|
|
620
|
+
// so the audit fails loudly instead of probing a zombie.
|
|
621
|
+
// reuseExistingServer:false: never reuse \u2014 we control the lifecycle.
|
|
622
|
+
// cwd: playwright's default webServer.cwd is the config file's
|
|
623
|
+
// directory. Our config lives in /tmp so without this override,
|
|
624
|
+
// "npm run vite:dev" tries to read /tmp/.../package.json and
|
|
625
|
+
// ENOENTs before vite ever starts. Caltex 2026-05-28 (0.10.5).
|
|
626
|
+
command: "npm run vite:dev -- --port ${port} --strictPort",
|
|
627
|
+
url: "http://localhost:${port}/dev/a11y-fixtures",
|
|
628
|
+
cwd: ${JSON.stringify(sitePath)},
|
|
629
|
+
reuseExistingServer: false,
|
|
630
|
+
timeout: 120_000,
|
|
631
|
+
},
|
|
632
|
+
});
|
|
633
|
+
`;
|
|
634
|
+
}
|
|
557
635
|
function buildSpec() {
|
|
558
636
|
return `import { test, expect } from "@playwright/test";
|
|
559
637
|
import AxeBuilder from "@axe-core/playwright";
|
|
@@ -607,19 +685,26 @@ async function a11yAudit(ctx) {
|
|
|
607
685
|
const specDir = await mkdtemp2(join5(tmpdir2(), "reddoor-a11y-spec-"));
|
|
608
686
|
const specPath = join5(specDir, "a11y.spec.ts");
|
|
609
687
|
await writeFile2(specPath, buildSpec(), "utf-8");
|
|
688
|
+
const port = await findFreePort();
|
|
689
|
+
const configPath = join5(specDir, "playwright.config.ts");
|
|
690
|
+
await writeFile2(configPath, buildPlaywrightConfig(port, site.path), "utf-8");
|
|
610
691
|
const resultsPath = join5(site.path, RESULTS_REL);
|
|
611
692
|
await rm2(join5(site.path, ".reddoor-a11y"), { recursive: true, force: true });
|
|
612
693
|
let raw;
|
|
613
694
|
try {
|
|
614
|
-
raw = await spawn2(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
695
|
+
raw = await spawn2(
|
|
696
|
+
"npx",
|
|
697
|
+
["--yes", "playwright", "test", `--config=${configPath}`, "--reporter=line", specPath],
|
|
698
|
+
{
|
|
699
|
+
cwd: site.path,
|
|
700
|
+
env: { ...process.env, REDDOOR_A11Y_OUTPUT: resultsPath },
|
|
701
|
+
// playwright on a cold tree downloads Chrome, boots the site's dev
|
|
702
|
+
// server, and runs axe over every configured route. The shared 30 s
|
|
703
|
+
// default in runAudits is fine for deps/lint/security but starves
|
|
704
|
+
// playwright (mirrors the lighthouse fix shipped earlier).
|
|
705
|
+
timeoutMs: 5 * 6e4
|
|
706
|
+
}
|
|
707
|
+
);
|
|
623
708
|
} catch (err) {
|
|
624
709
|
await rm2(specDir, { recursive: true, force: true });
|
|
625
710
|
const e = err;
|