-
-
Notifications
You must be signed in to change notification settings - Fork 72
feat: add Lighthouse CI workflow and configuration for automated perf… #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d0000fa
243d12c
f6c1a0d
fbd9b75
82014e7
92c25c2
535de6d
80dbf1c
57b308f
539b40a
156849f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const https = require('https'); | ||
|
|
||
| // Path to LHCI manifest and output directory | ||
| const manifestPath = path.join(__dirname, '../../.lighthouseci/manifest.json'); | ||
| const outputDir = path.join(__dirname, '../../badges'); | ||
|
|
||
| // Ensure output directory exists | ||
| if (!fs.existsSync(outputDir)) { | ||
| fs.mkdirSync(outputDir, { recursive: true }); | ||
| } | ||
|
|
||
| /** | ||
| * Downloads a badge from Shields.io | ||
| * @param {string} label | ||
| * @param {string} value | ||
| * @param {string} color | ||
| * @returns {Promise<void>} | ||
| */ | ||
| function downloadBadge(label, value, color) { | ||
| const url = `https://img.shields.io/badge/${encodeURIComponent(label)}-${encodeURIComponent(value)}-${color}`; | ||
| const filename = `${label.toLowerCase().replace(/ /g, '-')}.svg`; | ||
| const filePath = path.join(outputDir, filename); | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| const req = https.get(url, { headers: { 'User-Agent': 'NodeJS/LighthouseBadgeGenerator' }, timeout: 10000 }, (res) => { | ||
| if (res.statusCode !== 200) { | ||
| res.resume(); | ||
| reject(new Error(`Failed to download ${label} badge: Status code ${res.statusCode}`)); | ||
| return; | ||
| } | ||
| const fileStream = fs.createWriteStream(filePath); | ||
| res.on('error', reject); | ||
| fileStream.on('error', reject); | ||
| res.pipe(fileStream); | ||
| fileStream.on('finish', () => { | ||
| fileStream.close(); | ||
| console.log(`Successfully downloaded: ${filename} (${value})`); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| req.on('error', reject); | ||
| req.on('timeout', () => req.destroy(new Error(`Timed out downloading ${label} badge`))); | ||
| }); | ||
| } | ||
|
|
||
| async function main() { | ||
| try { | ||
| if (!fs.existsSync(manifestPath)) { | ||
| console.error(`Error: Manifest file not found at ${manifestPath}`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); | ||
| if (!Array.isArray(manifest) || manifest.length === 0) { | ||
| console.error('Error: Manifest is empty or invalid.'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Find the representative run (median run) or fallback to first run | ||
| const run = manifest.find(r => r.isRepresentativeRun) || manifest[0]; | ||
| const summary = run.summary; | ||
|
|
||
| if (!summary) { | ||
| console.error('Error: No summary found in the representative run.'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Categories to generate badges for | ||
| const categories = { | ||
| 'Performance': summary.performance, | ||
| 'Accessibility': summary.accessibility, | ||
| 'Best Practices': summary['best-practices'], | ||
| 'SEO': summary.seo, | ||
| 'PWA': summary.pwa | ||
| }; | ||
|
kpj2006 marked this conversation as resolved.
|
||
|
|
||
| let totalScore = 0; | ||
| let count = 0; | ||
|
|
||
| for (const [name, score] of Object.entries(categories)) { | ||
| if (score === undefined || score === null) { | ||
| console.log(`Skipping category ${name} (no score available)`); | ||
| continue; | ||
| } | ||
|
|
||
| const percentage = Math.round(score * 100); | ||
| totalScore += percentage; | ||
| count++; | ||
|
|
||
| // Determine color according to Lighthouse standards | ||
| let color = 'ff3333'; // Red (0-49) | ||
| if (percentage >= 90) { | ||
| color = '00cc66'; // Green (90-100) | ||
| } else if (percentage >= 50) { | ||
| color = 'ffaa33'; // Orange (50-89) | ||
| } | ||
|
|
||
| await downloadBadge(`Lighthouse ${name}`, `${percentage}%`, color); | ||
| } | ||
|
|
||
| // Generate single aggregate score badge | ||
| if (count > 0) { | ||
| const average = Math.round(totalScore / count); | ||
| let color = 'ff3333'; | ||
| if (average >= 90) { | ||
| color = '00cc66'; | ||
| } else if (average >= 50) { | ||
| color = 'ffaa33'; | ||
| } | ||
| await downloadBadge('Lighthouse', `${average}%`, color); | ||
| } | ||
|
|
||
| console.log('Lighthouse badges generated successfully!'); | ||
| } catch (error) { | ||
| console.error('Error generating badges:', error); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| main(); | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||
| name: Lighthouse CI Audit | ||||||
|
|
||||||
| on: | ||||||
| push: | ||||||
| branches: | ||||||
| - main | ||||||
| pull_request: | ||||||
| branches: | ||||||
| - main | ||||||
| workflow_dispatch: | ||||||
|
|
||||||
| concurrency: | ||||||
| group: lighthouse-${{ github.ref }} | ||||||
| cancel-in-progress: true | ||||||
|
|
||||||
| jobs: | ||||||
| lighthouse: | ||||||
| runs-on: ubuntu-latest | ||||||
| permissions: | ||||||
| contents: write | ||||||
|
|
||||||
| steps: | ||||||
| - name: Checkout repository | ||||||
| uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 | ||||||
|
|
||||||
| - name: Setup Node.js | ||||||
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | ||||||
| with: | ||||||
| node-version: 22 | ||||||
| cache: npm | ||||||
|
|
||||||
| - name: Install dependencies | ||||||
| run: npm ci | ||||||
|
|
||||||
| - name: Build application | ||||||
| run: npm run build | ||||||
|
|
||||||
| - name: Run Lighthouse CI | ||||||
| uses: treosh/lighthouse-ci-action@3e7e23fb74242897f95c0ba9cabad3d0227b9b18 | ||||||
| with: | ||||||
| configPath: './lighthouserc.json' | ||||||
| temporaryPublicStorage: true | ||||||
|
|
||||||
| - name: Generate Lighthouse Badges | ||||||
| run: node .github/scripts/generate-lighthouse-badges.cjs | ||||||
|
|
||||||
| - name: Commit and Push Badges | ||||||
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | ||||||
| env: | ||||||
| REF_NAME: ${{ github.ref_name }} | ||||||
| run: | | ||||||
| git config --local user.email "github-actions[bot]`@users.noreply.github.com`" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
workflow=".github/workflows/lighthouse.yml"
if rg -nF 'github-actions[bot]`@users.noreply.github.com`' "$workflow"; then
echo "Invalid command substitution remains"
exit 1
fiRepository: AOSSIE-Org/OrgExplorer Length of output: 289 Use a literal bot email. The backticks enable shell command substitution, so Bash tries to run Proposed fix- git config --local user.email "github-actions[bot]`@users.noreply.github.com`"
+ git config --local user.email "github-actions[bot]`@users.noreply.github.com`"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what exactly, i need to change ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
git config --local user.email "github-actions[bot]`@users.noreply.github.com`"to this: git config --local user.email "github-actions[bot]`@users.noreply.github.com`"Remove the two backtick characters. The email must be one literal string. You are interacting with an AI system. |
||||||
| git config --local user.name "github-actions[bot]" | ||||||
| git add badges/ | ||||||
| if git diff --cached --quiet; then | ||||||
| echo "No changes to commit" | ||||||
| else | ||||||
| git commit -m "chore: update lighthouse badges [skip ci]" | ||||||
| fi | ||||||
| git push origin HEAD:"$REF_NAME" | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "ci": { | ||
| "collect": { | ||
| "staticDistDir": "./dist", | ||
| "numberOfRuns": 3 | ||
| }, | ||
| "upload": { | ||
| "target": "temporary-public-storage" | ||
| }, | ||
| "assert": { | ||
| "assertions": { | ||
| "categories:performance": ["warn", { "minScore": 0.75 }], | ||
| "categories:accessibility": ["error", { "minScore": 0.8 }], | ||
| "categories:best-practices": ["error", { "minScore": 0.85 }], | ||
| "categories:seo": ["error", { "minScore": 0.85 }] | ||
| } | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.