feat: add Lighthouse CI for quality scoring #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lighthouse CI | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| lighthouse: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22.12.0" | |
| cache: npm | |
| cache-dependency-path: astro-site/package-lock.json | |
| - name: Install dependencies | |
| working-directory: astro-site | |
| run: npm ci | |
| - name: Create assets symlink | |
| run: mkdir -p astro-site/src/content && ln -s ../../../assets astro-site/src/content/assets | |
| - name: Build site | |
| working-directory: astro-site | |
| run: npm run build | |
| - name: Lighthouse CI | |
| id: lhci | |
| uses: treosh/lighthouse-ci-action@v12 | |
| with: | |
| configPath: ./lighthouserc.json | |
| uploadArtifacts: true | |
| - name: Post Lighthouse scores to PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const dir = '.lighthouseci'; | |
| const files = fs.readdirSync(dir).filter(f => f.endsWith('.json') && !f.startsWith('manifest')); | |
| const rows = []; | |
| for (const f of files) { | |
| const raw = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); | |
| const r = raw.lhr || raw; | |
| if (!r.categories) continue; | |
| const rawUrl = r.finalDisplayedUrl || r.finalUrl || r.requestedUrl || ''; | |
| const pagePath = rawUrl.startsWith('http') ? new URL(rawUrl).pathname : rawUrl || f; | |
| const scores = r.categories; | |
| const fmt = (cat) => { | |
| if (!scores[cat]) return 'N/A'; | |
| const s = Math.round(scores[cat].score * 100); | |
| const icon = s >= 90 ? '🟢' : s >= 50 ? '🟠' : '🔴'; | |
| return `${icon} ${s}`; | |
| }; | |
| rows.push(`| ${pagePath} | ${fmt('performance')} | ${fmt('accessibility')} | ${fmt('best-practices')} | ${fmt('seo')} |`); | |
| } | |
| const body = [ | |
| '## Lighthouse Results', | |
| '', | |
| '| Page | Performance | Accessibility | Best Practices | SEO |', | |
| '|------|------------|---------------|----------------|-----|', | |
| ...rows, | |
| '', | |
| '_Scores: 🟢 90+ · 🟠 50-89 · 🔴 0-49_' | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => c.body.includes('## Lighthouse Results')); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } |