@@ -168,3 +168,121 @@ jobs:
168168 body,
169169 });
170170 }
171+
172+ lighthouse :
173+ name : Lighthouse CI
174+ runs-on : ubuntu-latest
175+ needs : preview
176+ if : github.event.action != 'closed'
177+ continue-on-error : true
178+
179+ steps :
180+ - name : Checkout
181+ uses : actions/checkout@v4
182+
183+ - name : Wait for preview to be ready
184+ run : |
185+ echo "Waiting for preview deployment to be ready..."
186+ for i in {1..30}; do
187+ if curl -s -o /dev/null -w "%{http_code}" "${{ needs.preview.outputs.url }}" | grep -q "200"; then
188+ echo "Preview is ready!"
189+ exit 0
190+ fi
191+ echo "Attempt $i: Preview not ready yet, waiting 10s..."
192+ sleep 10
193+ done
194+ echo "Preview did not become ready in time"
195+ exit 1
196+
197+ - name : Get changed pages
198+ id : changed-pages
199+ uses : actions/github-script@v7
200+ with :
201+ script : |
202+ const { data: files } = await github.rest.pulls.listFiles({
203+ owner: context.repo.owner,
204+ repo: context.repo.repo,
205+ pull_number: context.issue.number,
206+ });
207+
208+ const baseUrl = '${{ needs.preview.outputs.url }}';
209+ const urls = new Set([baseUrl]); // Always test homepage
210+
211+ for (const file of files) {
212+ // Match .mdx files in src/content/docs/
213+ const match = file.filename.match(/^src\/content\/docs\/(.+)\.mdx$/);
214+ if (match && file.status !== 'removed') {
215+ let path = match[1];
216+ // index.mdx maps to root, others map to their path
217+ if (path === 'index') {
218+ urls.add(baseUrl);
219+ } else {
220+ urls.add(`${baseUrl}/${path}/`);
221+ }
222+ }
223+ }
224+
225+ const urlList = Array.from(urls).join('\n');
226+ console.log('URLs to test:\n' + urlList);
227+ core.setOutput('urls', urlList);
228+
229+ - name : Run Lighthouse CI
230+ id : lighthouse
231+ uses : treosh/lighthouse-ci-action@v12
232+ with :
233+ urls : ${{ steps.changed-pages.outputs.urls }}
234+ configPath : ./lighthouserc.json
235+ budgetPath : ./budget.json
236+ uploadArtifacts : true
237+ temporaryPublicStorage : true
238+
239+ - name : Comment Lighthouse results on PR
240+ if : always()
241+ uses : actions/github-script@v7
242+ with :
243+ script : |
244+ const manifest = ${{ steps.lighthouse.outputs.manifest }};
245+ const links = ${{ steps.lighthouse.outputs.links }};
246+
247+ let body = '### Lighthouse Results\n\n';
248+ body += '| URL | Performance | Accessibility | Best Practices | SEO |\n';
249+ body += '|-----|-------------|---------------|----------------|-----|\n';
250+
251+ for (const result of manifest) {
252+ const url = new URL(result.url).pathname || '/';
253+ const perf = Math.round(result.summary.performance * 100);
254+ const a11y = Math.round(result.summary.accessibility * 100);
255+ const bp = Math.round(result.summary['best-practices'] * 100);
256+ const seo = Math.round(result.summary.seo * 100);
257+
258+ const perfEmoji = perf >= 90 ? '🟢' : perf >= 50 ? '🟠' : '🔴';
259+ const a11yEmoji = a11y >= 90 ? '🟢' : a11y >= 50 ? '🟠' : '🔴';
260+ const bpEmoji = bp >= 90 ? '🟢' : bp >= 50 ? '🟠' : '🔴';
261+ const seoEmoji = seo >= 90 ? '🟢' : seo >= 50 ? '🟠' : '🔴';
262+
263+ const reportLink = links[result.url] ? `[${url}](${links[result.url]})` : url;
264+ body += `| ${reportLink} | ${perfEmoji} ${perf} | ${a11yEmoji} ${a11y} | ${bpEmoji} ${bp} | ${seoEmoji} ${seo} |\n`;
265+ }
266+
267+ const { data: comments } = await github.rest.issues.listComments({
268+ owner: context.repo.owner,
269+ repo: context.repo.repo,
270+ issue_number: context.issue.number,
271+ });
272+ const existing = comments.find(c => c.body.includes('### Lighthouse Results'));
273+
274+ if (existing) {
275+ await github.rest.issues.updateComment({
276+ owner: context.repo.owner,
277+ repo: context.repo.repo,
278+ comment_id: existing.id,
279+ body,
280+ });
281+ } else {
282+ await github.rest.issues.createComment({
283+ owner: context.repo.owner,
284+ repo: context.repo.repo,
285+ issue_number: context.issue.number,
286+ body,
287+ });
288+ }
0 commit comments