Skip to content
name: Vercel Preview URL Lighthouse Audit
on:
push:
branches:
- main
jobs:
generate_lighthouse_audit:
timeout-minutes: 5
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
actions: read
steps:
- name: Retrieve Vercel Project and Team Info
id: fetch_vercel_info
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
run: |
# Extract the repository name
project_name="${GITHUB_REPOSITORY##*/}"
echo "Using project name: $project_name"
# Fetch team info first
teams_response=$(curl -s \
-H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v2/teams")
# Get the team ID for darkroom-engineering
team_id=$(echo "$teams_response" | jq -r '.teams[] | select(.slug=="darkroom-engineering") | .id')
echo "Retrieved Team ID: $team_id"
# Fetch projects using team ID
response=$(curl -s \
-H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"https://api.vercel.com/v9/projects?teamId=$team_id")
# Get project ID for matching project name
project_id=$(echo "$response" | jq -r ".projects[] | select(.name==\"$project_name\") | .id")
echo "Retrieved Project ID: $project_id"
# Set environment variables
echo "VERCEL_TEAM_ID=$team_id" >> $GITHUB_ENV
echo "VERCEL_PROJECT_ID=$project_id" >> $GITHUB_ENV
- name: Capture Vercel preview URL
id: vercel_preview_url
uses: zentered/[email protected]
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
with:
vercel_team_id: ${{ env.VERCEL_TEAM_ID }}
vercel_project_id: ${{ env.VERCEL_PROJECT_ID }}
- name: Wait for Vercel Deployment
uses: UnlyEd/[email protected]
id: await-vercel
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
with:
deployment-url: "${{ steps.vercel_preview_url.outputs.preview_url }}"
timeout: 300 # 5 minutes timeout
poll-interval: 10
continue-on-error: true # Allow the workflow to continue even if deployment fails
- name: Check deployment status
id: deployment_check
run: |
if [ "${{ steps.await-vercel.outcome }}" == "failure" ]; then
echo "deployment_failed=true" >> $GITHUB_OUTPUT
echo "error_message=⚠️ Vercel deployment failed or timed out. Lighthouse audit could not be performed." >> $GITHUB_OUTPUT
else
echo "deployment_failed=false" >> $GITHUB_OUTPUT
fi
- uses: actions/checkout@v4
if: steps.deployment_check.outputs.deployment_failed != 'true'
- name: Audit preview URL with Lighthouse
id: lighthouse_audit
if: steps.deployment_check.outputs.deployment_failed != 'true'
uses: treosh/lighthouse-ci-action@v12
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
urls: |
"https://${{ steps.vercel_preview_url.outputs.preview_url }}"
uploadArtifacts: true
temporaryPublicStorage: true
- name: Format lighthouse score
id: format_lighthouse_score
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
try {
// Check if deployment failed
const deploymentFailed = '${{ steps.deployment_check.outputs.deployment_failed }}' === 'true';
if (deploymentFailed) {
core.setOutput("comment", '${{ steps.deployment_check.outputs.error_message }}');
return;
}
// Add debug logging to see the raw output
console.log('Raw lighthouse audit output:', JSON.stringify(${{ steps.lighthouse_audit.outputs.manifest }}, null, 2));
console.log('Raw links output:', JSON.stringify(${{ steps.lighthouse_audit.outputs.links }}, null, 2));
// Parse manifest with error checking
let manifest;
try {
manifest = JSON.parse(JSON.stringify(${{ steps.lighthouse_audit.outputs.manifest }}));
} catch (e) {
throw new Error(`Failed to parse manifest: ${e.message}`);
}
// Validate manifest structure
if (!manifest || !Array.isArray(manifest) || manifest.length === 0) {
throw new Error('Lighthouse manifest is invalid or empty');
}
// Get the first (and presumably only) result
const result = manifest[0].summary;
if (!result) {
console.log('Full manifest structure:', JSON.stringify(manifest, null, 2));
throw new Error('Lighthouse summary is undefined in manifest');
}
// Parse and validate links
let links;
try {
links = JSON.parse(JSON.stringify(${{ steps.lighthouse_audit.outputs.links }}));
} catch (e) {
throw new Error(`Failed to parse links: ${e.message}`);
}
if (!links) {
throw new Error('Lighthouse links are undefined');
}
if (Object.keys(links).length === 0) {
throw new Error('Lighthouse links object is empty');
}
// Helper functions
const formatResult = (res) => {
if (typeof res !== 'number') {
console.log(`Warning: Invalid result value: ${res}`);
return 0;
}
return Math.round(res * 100);
};
const score = (res) => {
const numericRes = typeof res === 'number' ? res : 0;
return numericRes >= 0.9 ? '🟢' : numericRes >= 0.5 ? '🟠' : '🔴';
};
// Generate report
const reportUrl = manifest[0].url || Object.values(links)[0];
const formattedScores = [
`⚡️ [Lighthouse report](${Object.values(links)[0]}) for the changes in this commit:`,
'',
`${score(result.performance)} Performance: ${formatResult(result.performance)}`,
`${score(result.accessibility)} Accessibility: ${formatResult(result.accessibility)}`,
`${score(result['best-practices'])} Best practices: ${formatResult(result['best-practices'])}`,
`${score(result.seo)} SEO: ${formatResult(result.seo)}`,
'',
`*Lighthouse ran on [${reportUrl}](${reportUrl})*`
].join('\n');
// Set output
core.setOutput("comment", formattedScores);
} catch (error) {
// Enhanced error logging
console.error('Full error:', error);
console.error('Error stack:', error.stack);
// Set a user-friendly error message as the comment
const errorMessage = '⚠️ Failed to generate Lighthouse report. Please check the workflow logs for more details.';
core.setOutput("comment", errorMessage);
core.setFailed(`Formatting failed: ${error.message}`);
}
- name: Create commit comment
uses: peter-evans/commit-comment@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: ${{ steps.format_lighthouse_score.outputs.comment }}