Skip to content

Commit 83e7787

Browse files
lennessyyclaude
andauthored
Fix preview links for long branch names (#4469)
* Fix preview links for long branch names When a branch name produces a subdomain exceeding 63 characters (the DNS label limit), Vercel truncates it with a hash suffix. Our workflow constructed the URL independently and got it wrong in those cases. Now the workflow checks the subdomain length first. Short branch names use the constructed URL directly with no delay. Long branch names poll for the Vercel bot comment and extract the actual preview URL from its base64 payload, falling back to the constructed URL after 3 minutes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Extract preview URL logic into bin/preview-url-from-vercel.js Moves the inline script from the workflow into a standalone file for easier maintenance. The workflow now requires it and calls resolvePreviewUrl with the actions/github-script context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 728b76b commit 83e7787

2 files changed

Lines changed: 82 additions & 18 deletions

File tree

.github/workflows/docs-preview-links.yml

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,14 @@ jobs:
2828

2929
- name: Compute preview base URL
3030
id: preview-url
31-
env:
32-
REPO_PREVIEW_BASE_URL: ${{ vars.DOCS_PREVIEW_BASE_URL }}
33-
REPO_PREVIEW_TEMPLATE: ${{ vars.DOCS_PREVIEW_BASE_URL_TEMPLATE }}
34-
run: |
35-
BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}"
36-
BRANCH_SLUG=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | tr -d '_' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
37-
38-
if [ -n "$REPO_PREVIEW_BASE_URL" ]; then
39-
BASE_URL="$REPO_PREVIEW_BASE_URL"
40-
elif [ -n "$REPO_PREVIEW_TEMPLATE" ]; then
41-
BASE_URL="${REPO_PREVIEW_TEMPLATE//\{branch\}/$BRANCH_SLUG}"
42-
else
43-
BASE_URL="https://temporal-documentation-git-${BRANCH_SLUG}.preview.thundergun.io"
44-
fi
45-
46-
echo "DOCS_PREVIEW_BASE_URL=$BASE_URL" >> "$GITHUB_ENV"
47-
echo "BRANCH_SLUG=$BRANCH_SLUG" >> "$GITHUB_ENV"
48-
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
31+
uses: actions/github-script@v7
32+
with:
33+
github-token: ${{ secrets.GITHUB_TOKEN }}
34+
script: |
35+
const { resolvePreviewUrl } = require('./bin/preview-url-from-vercel.js');
36+
const baseUrl = await resolvePreviewUrl({ github, context, core });
37+
core.exportVariable('DOCS_PREVIEW_BASE_URL', baseUrl);
38+
core.setOutput('base_url', baseUrl);
4939
5040
- name: Generate docs preview list
5141
env:

bin/preview-url-from-vercel.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env node
2+
3+
const MAX_SUBDOMAIN_LENGTH = 63;
4+
const MAX_ATTEMPTS = 18;
5+
const INTERVAL_MS = 10_000;
6+
const PREVIEW_DOMAIN = 'preview.thundergun.io';
7+
8+
function branchToSlug(branchName) {
9+
return branchName
10+
.toLowerCase()
11+
.replace(/_/g, '')
12+
.replace(/[^a-z0-9]/g, '-')
13+
.replace(/-+/g, '-')
14+
.replace(/^-/, '')
15+
.replace(/-$/, '');
16+
}
17+
18+
function buildSubdomain(branchSlug) {
19+
return `temporal-documentation-git-${branchSlug}`;
20+
}
21+
22+
function extractPreviewUrlFromComment(body) {
23+
const match = body.match(/\[vc\]:\s*#[^:]*:(eyJ[A-Za-z0-9+/=]+)/);
24+
if (!match) return null;
25+
26+
const payload = JSON.parse(Buffer.from(match[1], 'base64').toString('utf8'));
27+
return payload.projects?.[0]?.previewUrl || null;
28+
}
29+
30+
async function resolvePreviewUrl({ github, context, core }) {
31+
const branchName = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
32+
const branchSlug = branchToSlug(branchName);
33+
const subdomain = buildSubdomain(branchSlug);
34+
35+
if (subdomain.length <= MAX_SUBDOMAIN_LENGTH) {
36+
const baseUrl = `https://${subdomain}.${PREVIEW_DOMAIN}`;
37+
core.info(`Branch name is short enough (${subdomain.length} chars), using constructed URL: ${baseUrl}`);
38+
return baseUrl;
39+
}
40+
41+
core.info(`Subdomain would be ${subdomain.length} chars (exceeds ${MAX_SUBDOMAIN_LENGTH}), polling for Vercel comment...`);
42+
43+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
44+
core.info(`Polling for Vercel comment (attempt ${attempt}/${MAX_ATTEMPTS})...`);
45+
const { data: comments } = await github.rest.issues.listComments({
46+
owner: context.repo.owner,
47+
repo: context.repo.repo,
48+
issue_number: context.issue.number,
49+
per_page: 100,
50+
});
51+
52+
const vercelComment = comments.find(
53+
(c) => c.user?.login === 'vercel[bot]' && c.body?.includes('[vc]:'),
54+
);
55+
56+
if (vercelComment) {
57+
const previewUrl = extractPreviewUrlFromComment(vercelComment.body);
58+
if (previewUrl) {
59+
const baseUrl = `https://${previewUrl}`;
60+
core.info(`Found Vercel preview URL: ${baseUrl}`);
61+
return baseUrl;
62+
}
63+
}
64+
65+
if (attempt < MAX_ATTEMPTS) {
66+
await new Promise((resolve) => setTimeout(resolve, INTERVAL_MS));
67+
}
68+
}
69+
70+
core.warning('Vercel comment not found after polling, using constructed URL as fallback (links may be broken for this long branch name)');
71+
return `https://${subdomain}.${PREVIEW_DOMAIN}`;
72+
}
73+
74+
module.exports = { resolvePreviewUrl, branchToSlug, buildSubdomain, extractPreviewUrlFromComment };

0 commit comments

Comments
 (0)