vital-core / src /issue-comment.js
vital-core sync
Deploy snapshot for Hugging Face Spaces
50841a0
Raw
History Blame Contribute Delete
3.84 kB
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { loadConfig, DIRS } from './lib/config.js';
import { compareWeeks } from './lib/week.js';
/**
* Emits the weekly Markdown summary posted as a GitHub issue comment,
* matching the open-scans pattern: one comment per week, table per
* domain, deltas against the previous week, links to the Pages report.
*
* Reads the same data/ summaries the HTML reports use. One truth.
*
* Usage: node src/issue-comment.js [--pages-base https://USER.github.io/REPO]
* Output: docs/comment.md (also printed to stdout)
*/
const args = Object.fromEntries(
process.argv.slice(2).join(' ').split('--').filter(Boolean).map((s) => {
const [k, ...v] = s.trim().split(' ');
return [k, v.join(' ') || true];
})
);
const pagesBase = (args['pages-base'] ?? process.env.PAGES_BASE_URL ?? '').replace(/\/$/, '');
const config = loadConfig();
let out = `## Weekly scan summary\n\n`;
let latestWeek = null;
for (const target of config.targets) {
const domainDir = path.join(DIRS.data, target.key);
if (!fs.existsSync(domainDir)) continue;
const weeks = fs
.readdirSync(domainDir)
.filter((w) => /^\d{4}-W\d{2}$/.test(w) && fs.existsSync(path.join(domainDir, w, 'summary.json')))
.sort(compareWeeks);
if (weeks.length === 0) continue;
const week = weeks[weeks.length - 1];
latestWeek = latestWeek && compareWeeks(latestWeek, week) > 0 ? latestWeek : week;
const curr = JSON.parse(fs.readFileSync(path.join(domainDir, week, 'summary.json'), 'utf8'));
const prev =
weeks.length > 1
? JSON.parse(fs.readFileSync(path.join(domainDir, weeks[weeks.length - 2], 'summary.json'), 'utf8'))
: null;
const d = (a, b) => (prev ? ` (${a - b >= 0 ? '+' : ''}${a - b})` : '');
const reportLink = pagesBase ? `${pagesBase}/reports/${target.key}/${week}/` : `docs/reports/${target.key}/${week}/`;
out += `### ${target.domain}: ${week}\n\n`;
out += `| Metric | This week | Notes |\n|---|---:|---|\n`;
out += `| Pages audited (unique) | ${curr.pagesAudited ?? curr.pagesScanned}${prev ? d(curr.pagesAudited ?? curr.pagesScanned, prev.pagesAudited ?? prev.pagesScanned) : ''} | scanned by axe and/or Alfa |\n`;
out += `| Median axe violations / page | ${curr.axe.medianViolations ?? 'n/a'}${prev && curr.axe.medianViolations != null && prev.axe.medianViolations != null ? d(curr.axe.medianViolations, prev.axe.medianViolations) : ''} | ${curr.axe.pagesWithViolations} pages affected |\n`;
out += `| Median Alfa failures / page | ${curr.alfa.medianFailures ?? 'n/a'}${prev && curr.alfa.medianFailures != null && prev.alfa.medianFailures != null ? d(curr.alfa.medianFailures, prev.alfa.medianFailures) : ''} | ${curr.alfa.pagesWithFailures} pages affected |\n`;
if (curr.sustainability) {
out += `| Median page weight | ${Math.round(curr.sustainability.medianBytes / 1024)} KB${prev?.sustainability ? d(Math.round(curr.sustainability.medianBytes / 1024), Math.round(prev.sustainability.medianBytes / 1024)) : ''} | est. ${curr.sustainability.meanCo2g} g CO₂/page (SWD v4) |\n`;
}
const topAxe = Object.entries(curr.axe.rules)
.sort(([, a], [, b]) => b.pages - a.pages)
.slice(0, 5);
if (topAxe.length) {
out += `\nTop axe rules by pages affected: ${topAxe.map(([id, r]) => `\`${id}\` (${r.pages})`).join(', ')}.\n`;
}
out += `\n[Full report](${reportLink})\n\n`;
}
if (!latestWeek) out += `No scan data found yet.\n`;
out += `\n---\n*Generated by [vital-scans](https://github.com/${process.env.GITHUB_REPOSITORY ?? 'mgifford/vital-core'}). Automated checks cover roughly a third of accessibility barriers; manual testing with assistive technology remains essential.*\n`;
fs.mkdirSync(DIRS.docs, { recursive: true });
fs.writeFileSync(path.join(DIRS.docs, 'comment.md'), out);
console.log(out);