Merge pull request #157 from prgrms-web-devcourse-final-project/155-f… #316
This file contains 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: | |
push: | |
branches: [main, develop] | |
pull_request: | |
branches: [main, develop] | |
permissions: | |
contents: read | |
pull-requests: write | |
jobs: | |
lighthouse: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: '20.x' | |
- name: Configure npm registry | |
run: | | |
echo "Configuring npm to use public registry" | |
npm config set registry https://registry.npmjs.org/ | |
- name: Clear npm cache | |
run: npm cache clean --force | |
- name: Remove package-lock.json | |
run: rm -f package-lock.json | |
- name: Install dependencies | |
run: npm install --registry=https://registry.npmjs.org/ | |
env: | |
NODE_AUTH_TOKEN: '' | |
- name: Build project | |
run: npm run lint | |
- name: Build project | |
run: npm run build | |
- name: Run Lighthouse CI | |
env: | |
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} | |
NODE_TLS_REJECT_UNAUTHORIZED: '0' # SSL 검증 비활성화 | |
run: | | |
npm install -g @lhci/cli@0.12.x | |
lhci autorun || echo "Lighthouse CI failed, but continuing" | |
- name: Upload Lighthouse results | |
uses: actions/upload-artifact@v4 | |
with: | |
name: lighthouse-results | |
path: .lighthouseci | |
include-hidden-files: true | |
- name: Format lighthouse score | |
id: format_lighthouse_score | |
uses: actions/github-script@v3 | |
with: | |
github-token: ${{secrets.GITHUB_TOKEN}} | |
script: | | |
const fs = require('fs') | |
const REPORT_PATH = './lhci_reports/manifest.json' | |
const SCORE_EMOJIS = { | |
HIGH: '🟢', | |
MEDIUM: '🟠', | |
LOW: '🔴', | |
} | |
const getScoreEmoji = score => { | |
if (score >= 90) return SCORE_EMOJIS.HIGH | |
if (score >= 50) return SCORE_EMOJIS.MEDIUM | |
return SCORE_EMOJIS.LOW | |
} | |
const formatScore = score => Math.round(score * 100) | |
const generateReportForUrl = report => { | |
const { | |
url, | |
details: { audits }, | |
summary, | |
} = report | |
const sections = [ | |
`## ${url}`, | |
generateMetricsTable(summary), | |
generateDetailedMetricsTable(audits), | |
generateSecurityTable(audits), | |
] | |
return sections.join('\n\n') | |
} | |
const generateMetricsTable = summary => { | |
return [ | |
'### Performance Metrics', | |
'| Category | Score |', | |
'| --- | --- |', | |
`| Performance | ${getScoreEmoji(summary.performance)} ${summary.performance} |`, | |
`| Accessibility | ${getScoreEmoji(summary.accessibility)} ${summary.accessibility} |`, | |
`| Best Practices | ${getScoreEmoji(summary['best-practices'])} ${summary['best-practices']} |`, | |
`| SEO | ${getScoreEmoji(summary.seo)} ${summary.seo} |`, | |
].join('\n') | |
} | |
const generateDetailedMetricsTable = audits => { | |
return [ | |
'### Detailed Metrics', | |
'| Metric | Value |', | |
'| --- | --- |', | |
`| First Contentful Paint | ${getScoreEmoji(audits['first-contentful-paint'].score * 100)} ${audits['first-contentful-paint'].displayValue} |`, | |
`| Largest Contentful Paint | ${getScoreEmoji(audits['largest-contentful-paint'].score * 100)} ${audits['largest-contentful-paint'].displayValue} |`, | |
`| Total Blocking Time | ${getScoreEmoji(audits['total-blocking-time'].score * 100)} ${audits['total-blocking-time'].displayValue} |`, | |
`| Cumulative Layout Shift | ${getScoreEmoji(audits['cumulative-layout-shift'].score * 100)} ${audits['cumulative-layout-shift'].displayValue} |`, | |
`| Speed Index | ${getScoreEmoji(audits['speed-index'].score * 100)} ${audits['speed-index'].displayValue} |`, | |
].join('\n') | |
} | |
const generateSecurityTable = audits => { | |
return [ | |
'### Security Checks', | |
'| Check | Status | Details |', | |
'| --- | --- | --- |', | |
`| CSP-XSS | ${audits['csp-xss'] ? (audits['csp-xss'].score === 1 ? '✅' : '⚠️') : '❓'} | ${audits['csp-xss']?.title || 'Not available'} |`, | |
].join('\n') | |
} | |
const generateLighthouseReports = () => { | |
if (!fs.existsSync(REPORT_PATH)) { | |
console.error('No Lighthouse report found') | |
return null | |
} | |
try { | |
const results = JSON.parse(fs.readFileSync(REPORT_PATH)) | |
const medianResults = results.filter(entry => entry.isRepresentativeRun) | |
const reports = medianResults.map(result => ({ | |
url: result.url, | |
details: JSON.parse(fs.readFileSync(result.jsonPath)), | |
summary: Object.fromEntries(Object.entries(result.summary).map(([key, value]) => [key, formatScore(value)])), | |
})) | |
return ['# ⚡ Lighthouse Reports', ...reports.map(report => generateReportForUrl(report))].join('\n\n') | |
} catch (error) { | |
console.error('Error generating Lighthouse reports:', error) | |
return null | |
} | |
} | |
const comments = generateLighthouseReports() | |
if (comments) { | |
core.setOutput('comments', comments) | |
} | |
- name: Comment PR | |
if: github.event_name == 'pull_request' | |
uses: unsplash/comment-on-pr@v1.3.0 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
with: | |
msg: ${{ steps.format_lighthouse_score.outputs.comments}} |