Skip to content

vercel.deployment.success #41

vercel.deployment.success

vercel.deployment.success #41

Workflow file for this run

name: Preview links for changed pages
# Vercel sends this event when a deployment finishes. We use it to comment on the
# PR with direct links to the docs pages that PR changed, since the Vercel bot's
# own comment only links to the root of the preview deployment.
#
# GitHub only delivers repository_dispatch to the workflow file on the default
# branch, so workflow_dispatch takes the same two payload fields as inputs for
# testing before merge and for re-running on a PR by hand:
# gh workflow run preview-links.yml --ref <branch> -f url=<preview url> -f sha=<pr head sha>
on:
repository_dispatch:
types: [vercel.deployment.success]
workflow_dispatch:
inputs:
url:
description: Preview deployment URL (client_payload.url)
required: true
sha:
description: Full commit SHA of the PR head (client_payload.git.sha)
required: true
permissions:
contents: read
pull-requests: write
jobs:
comment:
if: github.event.client_payload.environment != 'production'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
env:
PREVIEW_URL: ${{ github.event.client_payload.url || inputs.url }}
COMMIT_SHA: ${{ github.event.client_payload.git.sha || inputs.sha }}
with:
script: |
const {PREVIEW_URL, COMMIT_SHA} = process.env;
if (!PREVIEW_URL || !/^[0-9a-f]{40}$/i.test(COMMIT_SHA ?? '')) {
core.setFailed(`Need a preview URL and a full commit SHA; got url=${JSON.stringify(PREVIEW_URL)} sha=${JSON.stringify(COMMIT_SHA)}. `
+ `Vercel sends these as client_payload.url and client_payload.git.sha; workflow_dispatch takes them as the url and sha inputs.`);
return;
}
const previewUrl = PREVIEW_URL.replace(/\/$/, '');
const sha = COMMIT_SHA;
const {owner, repo} = context.repo;
// The dispatch payload has no PR number; look it up from the commit.
const {data: prs} = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: sha
});
const pr = prs.find(p => p.state === 'open' && p.head.sha === sha);
if (!pr) {
core.info(`No open PR with head ${sha}; nothing to do.`);
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: pr.number, per_page: 100
});
// Mirror contentlayer's flattenedPath: docs/<path>.mdx -> /<path>,
// with a trailing /index dropped. Preview deployments have no basePath.
const pages = files
.filter(f => f.filename.startsWith('docs/') && f.filename.endsWith('.mdx') && f.status !== 'removed')
.map(f => f.filename.slice('docs/'.length, -'.mdx'.length).replace(/^index$/, '').replace(/\/index$/, ''))
.map(path => ({path, url: `${previewUrl}/${path}`}));
if (pages.length === 0) {
core.info(`PR #${pr.number} changes no docs pages; not commenting.`);
return;
}
const marker = '<!-- preview-links-for-changed-pages -->';
const max = 40;
const lines = pages.slice(0, max).map(p => `- [/${p.path}](${p.url})`);
if (pages.length > max) lines.push(`- …and ${pages.length - max} more`);
const body = [
marker,
`Direct preview links to pages changed in this PR:`,
...lines
].join('\n');
// Update our previous comment instead of adding a new one per push.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100
});
const existing = comments.find(c => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body});
} else {
await github.rest.issues.createComment({owner, repo, issue_number: pr.number, body});
}