Preview by @christian-bromann #3154
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| 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@v5 | |
| 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@v5 | |
| 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@v6 | |
| - 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}` | |
| }); |