Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ on:
push:
branches: [main, develop]
pull_request:
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
branches: [main, develop]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -18,6 +23,96 @@ jobs:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm format:check
- run: pnpm test:lighthouse
- run: pnpm build
- uses: browser-actions/setup-chrome@v1
id: chrome
- name: Start preview server
run: |
mkdir -p lighthouse-reports
pnpm preview --host 127.0.0.1 > /tmp/wraith-preview.log 2>&1 &
for attempt in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:4173/ > /dev/null; then
exit 0
fi
sleep 1
done
cat /tmp/wraith-preview.log
exit 1
- name: Lighthouse mobile
id: lighthouse_mobile
continue-on-error: true
run: >-
pnpm exec lighthouse http://127.0.0.1:4173/
--only-categories=performance,accessibility,best-practices,seo
--output=json --output-path=lighthouse-reports/mobile.json
--chrome-path="${{ steps.chrome.outputs.chrome-path }}"
--chrome-flags="--headless --no-sandbox"
- name: Lighthouse desktop
id: lighthouse_desktop
continue-on-error: true
run: >-
pnpm exec lighthouse http://127.0.0.1:4173/
--preset=desktop
--only-categories=performance,accessibility,best-practices,seo
--output=json --output-path=lighthouse-reports/desktop.json
--chrome-path="${{ steps.chrome.outputs.chrome-path }}"
--chrome-flags="--headless --no-sandbox"
- name: Evaluate Lighthouse budgets
if: always()
env:
MOBILE_OUTCOME: ${{ steps.lighthouse_mobile.outcome }}
DESKTOP_OUTCOME: ${{ steps.lighthouse_desktop.outcome }}
run: node scripts/lighthouse-budget.mjs summarize
- name: Comment on Lighthouse results
id: lighthouse_comment
if: ${{ always() && github.event_name == 'pull_request' }}
uses: actions/github-script@v7
with:
script: |
const fs = require('node:fs');
const results = JSON.parse(fs.readFileSync('lighthouse-budget-results.json', 'utf8'));
const marker = '<!-- wraith-lighthouse-budget -->';
const labels = context.payload.pull_request.labels.map((label) => label.name);
const reasonMatch = context.payload.pull_request.body?.match(/^Lighthouse budget override:\s*(.+)$/im);
const reason = reasonMatch?.[1]?.trim() ?? '';
const overrideAuthorized = labels.includes('lighthouse-budget-override') && reason.length > 0;
core.setOutput('override-authorized', String(overrideAuthorized));

const lines = [marker, '## Lighthouse budget'];
if (results.budgetFailures.length === 0 && results.auditFailures.length === 0) {
lines.push('All required categories met the 95-point budget in mobile and desktop audits.');
} else {
for (const failure of results.budgetFailures) {
const score = failure.score === null ? 'missing' : `${Math.round(failure.score * 100)}/95`;
lines.push(`- \`${failure.route}\` (${failure.profile}): ${failure.category} ${score}`);
}
for (const failure of results.auditFailures) {
lines.push(`- Lighthouse ${failure.profile} audit error: ${failure.message}`);
}
}
if (overrideAuthorized && results.budgetFailures.length > 0) {
lines.push('', `Override applied: ${reason}`);
} else if (results.budgetFailures.length > 0 && labels.includes('lighthouse-budget-override')) {
lines.push('', 'The override label is present, but a non-empty `Lighthouse budget override: <reason>` line is also required in the PR description.');
}
const body = lines.join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const previous = comments.find((comment) => comment.user?.type === 'Bot' && comment.body?.includes(marker));
if (previous) {
await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: previous.id, body });
} else {
await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body });
}
- name: Enforce Lighthouse budgets
if: always()
env:
LIGHTHOUSE_OVERRIDE_AUTHORIZED: ${{ steps.lighthouse_comment.outputs.override-authorized }}
run: node scripts/lighthouse-budget.mjs enforce
- run: pnpm lint:i18n
- run: pnpm test:a11y
- run: pnpm build
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ The suite runs on every pull request in CI.
pnpm format # write
pnpm format:check # check only
```

## Lighthouse CI

Pull requests are audited in mobile and desktop modes. Performance, accessibility, best practices, and SEO must each score at least 95. Budget failures are reported in a PR comment and fail CI.

For an intentional budget exception, add the `lighthouse-budget-override` label and include a non-empty line in the PR description in this format:

```text
Lighthouse budget override: <reason>
```

The override applies only to category scores below budget; Lighthouse execution errors still fail CI.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"test:a11y:playwright": "playwright test tests/a11y",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test:lighthouse": "node --test scripts/lighthouse-budget.test.mjs",
"lint:i18n": "node scripts/lint-i18n.mjs",
"generate:og": "node scripts/generate-og.mjs",
"validate:jsonld": "tsx scripts/validate-jsonld.mjs",
Expand All @@ -36,6 +37,7 @@
"@axe-core/playwright": "^4.12.1",
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"lighthouse": "^12.8.2",
"@fontsource/space-grotesk": "^5.2.10",
"@mdx-js/rollup": "^3.1.1",
"@playwright/test": "^1.62.0",
Expand Down
Loading
Loading