-
Notifications
You must be signed in to change notification settings - Fork 2.2k
237 lines (197 loc) Β· 8.98 KB
/
create-preview-branch.yml
File metadata and controls
237 lines (197 loc) Β· 8.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
---
name: Create Preview Branch
run-name: Preview by @${{ github.actor }}
on:
pull_request:
types: [opened, synchronize, reopened, closed]
workflow_dispatch:
concurrency:
group: create-preview-${{ github.ref_name }}
cancel-in-progress: true
# Least-privileged defaults. We'll elevate at the job level only where needed.
permissions:
contents: read
actions: read
jobs:
cleanup-preview-branches:
# Run only when PR is closed or merged
if: github.event.action == 'closed'
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Delete preview branches for this PR
run: |
set -euo pipefail
PR_STATE="${{ github.event.pull_request.merged && 'merged' || 'closed' }}"
echo "[INFO] Cleaning up preview branches for $PR_STATE PR #${{ github.event.pull_request.number }}"
# Get the source branch name
SOURCE_BRANCH="${{ github.event.pull_request.head.ref }}"
echo "[INFO] Source branch: $SOURCE_BRANCH"
# Generate the safe prefix that would have been used for this branch
# This transforms the branch name to match the preview branch naming convention:
# 1. Remove all non-alphanumeric characters (tr -cd '[:alnum:]')
# 2. Take only the first 6 characters (cut -c1-6)
# Example: "feature/my-branch-123" -> "featur"
safe_prefix=$(echo "$SOURCE_BRANCH" | tr -cd '[:alnum:]' | cut -c1-6)
echo "[INFO] Looking for preview branches matching pattern: preview-${safe_prefix}-*"
# Find and delete all preview branches with this prefix
git fetch origin
BRANCHES=$(git branch -r | grep "origin/preview-${safe_prefix}-" | sed 's|origin/||' || true)
if [ -z "$BRANCHES" ]; then
echo "[INFO] No preview branches found for this PR"
else
echo "[INFO] Found preview branches to delete:"
echo "$BRANCHES"
for branch in $BRANCHES; do
# Safety check: ensure branch starts with 'preview-'
if [[ ! "$branch" =~ ^preview- ]]; then
echo "[ERROR] Branch '$branch' does not start with 'preview-'"
echo "[ERROR] Refusing to delete non-preview branch for safety"
exit 1
fi
echo "[INFO] Deleting branch: $branch"
git push origin --delete "$branch" || echo "[WARN] Failed to delete $branch (may already be deleted)"
done
echo "[SUCCESS] Cleanup complete"
fi
create-preview:
# This job needs to push a branch, so contents: write here only.
permissions:
contents: write
runs-on: ubuntu-latest
# Skip for PRs from forks - they don't have write access
# Skip when PR is closed
if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch') && github.event.action != 'closed'
# Expose the generated preview branch name for the next job
outputs:
preview_branch: ${{ steps.branch-name.outputs.branch_name }}
env:
SOURCE_BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
GITHUB_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
steps:
- name: Validate and sanitize branch context
id: validate
run: |
set -euo pipefail
echo "[INFO] Validating current branch context"
echo "[INFO] Current ref: ${{ github.ref }}"
echo "[INFO] Branch name: $SOURCE_BRANCH"
# Validate source branch contains only safe characters and isn't too long
if [[ ! "$SOURCE_BRANCH" =~ ^[\.a-zA-Z0-9/_-]+$ ]]; then
echo "[ERROR] Branch name contains invalid characters. Only alphanumeric, slash, underscore, and hyphen are allowed."
exit 1
fi
if [[ ${#SOURCE_BRANCH} -gt 100 ]]; then
echo "[ERROR] Branch name too long (max 100)."
exit 1
fi
# Sanitize for logging (first 20 chars max)
SAFE_LOG_BRANCH=$(echo "$SOURCE_BRANCH" | cut -c1-20)
echo "safe_branch_log=$SAFE_LOG_BRANCH" >> $GITHUB_OUTPUT
echo "[SUCCESS] Branch validation passed for: $SAFE_LOG_BRANCH"
- name: Checkout current branch
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install dependencies
run: make install
- name: Build documentation
run: make build
- name: Generate secure preview branch name
id: branch-name
run: |
set -euo pipefail
echo "[INFO] Generating collision-resistant preview branch name"
safe_prefix=$(echo "$SOURCE_BRANCH" | tr -cd '[:alnum:]' | cut -c1-6)
timestamp=$(date +%s)
short_sha="${GITHUB_SHA:0:7}"
PREVIEW_BRANCH="preview-${safe_prefix}-${timestamp}-${short_sha}"
echo "branch_name=$PREVIEW_BRANCH" >> $GITHUB_OUTPUT
echo "[INFO] Preview branch will be: $PREVIEW_BRANCH"
- name: Configure Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Check if preview branch already exists
run: |
set -euo pipefail
PREVIEW_BRANCH="${{ steps.branch-name.outputs.branch_name }}"
echo "[INFO] Checking if preview branch already exists: $PREVIEW_BRANCH"
# Check if the branch exists remotely
if git ls-remote --exit-code --heads origin "$PREVIEW_BRANCH" >/dev/null 2>&1; then
echo "[ERROR] Preview branch $PREVIEW_BRANCH already exists. This should be extremely rare."
echo "[INFO] Please retry the workflow to generate a new branch name."
exit 1
fi
echo "[SUCCESS] Preview branch name is unique"
- name: Create and push preview branch
run: |
set -euo pipefail
PREVIEW_BRANCH="${{ steps.branch-name.outputs.branch_name }}"
SAFE_BRANCH_LOG="${{ steps.validate.outputs.safe_branch_log }}"
echo "[INFO] Creating preview branch: $PREVIEW_BRANCH"
# Create new branch from current state
git checkout -b "$PREVIEW_BRANCH"
# Add build artifacts (force add since build/ is likely in .gitignore)
echo "[INFO] Adding build artifacts to preview branch (forced)"
git add -f build/
# Check if there are changes to commit
if git diff --cached --quiet; then
echo "[WARN] No build artifacts to commit"
else
echo "[INFO] Committing build artifacts"
git commit -m "Add build artifacts for preview deployment
Source branch: $SAFE_BRANCH_LOG
Generated from commit: $GITHUB_SHA
Timestamp: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
This branch contains the built documentation for preview deployment.
Do not merge this branch to main."
fi
# Push the preview branch
echo "[INFO] Pushing preview branch to origin"
git push origin "$PREVIEW_BRANCH"
echo "[SUCCESS] Successfully pushed preview branch"
- name: Save preview branch info (log)
run: |
PREVIEW_BRANCH="${{ steps.branch-name.outputs.branch_name }}"
SAFE_BRANCH_LOG="${{ steps.validate.outputs.safe_branch_log }}"
echo "[SUCCESS] Preview branch created: $PREVIEW_BRANCH"
echo "[INFO] Branch name: $PREVIEW_BRANCH"
echo "[INFO] Source branch: $SAFE_BRANCH_LOG"
echo "[INFO] Source commit: $GITHUB_SHA"
echo "[INFO] Created at: $(date -u +"%Y-%m-%d %H:%M:%S UTC")"
echo ""
echo "[INFO] The preview branch is now ready for Mintlify deployment."
echo ""
echo "[INFO] π Branch URL: https://github.com/${{ github.repository }}/tree/$PREVIEW_BRANCH"
comment-on-pr:
# Only this job gets permission to write PR comments.
permissions:
pull-requests: write
runs-on: ubuntu-latest
needs: create-preview
if: github.event_name == 'pull_request' && needs.create-preview.result == 'success'
steps:
- name: Comment on PR
uses: actions/github-script@v8
with:
script: |
const preview = `${{ needs.create-preview.outputs.preview_branch }}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Mintlify preview ID generated: ${preview}`
});