Skip to content

Move PR write permissions #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 18, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 38 additions & 39 deletions .github/workflows/create-preview-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,49 @@ run-name: Preview by @${{ github.actor }}
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch: # Keep manual trigger as fallback
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: write # Required to checkout, create branches, and push
actions: read # Required to run the workflow
pull-requests: write # Required to comment on PRs
contents: read
actions: read

jobs:
create-preview:
# This job needs to push a branch, so contents: write here only.
permissions:
contents: write
runs-on: ubuntu-latest

# 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] Current branch name contains invalid characters. Only alphanumeric, slash, underscore, and hyphen are allowed."
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] Current branch name is too long (max 100 characters)"
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
Expand Down Expand Up @@ -72,19 +77,11 @@ jobs:
id: branch-name
run: |
set -euo pipefail

echo "[INFO] Generating collision-resistant preview branch name"

# Function to generate preview branch name
generate_preview_branch() {
local safe_prefix timestamp short_sha
safe_prefix=$(echo "$SOURCE_BRANCH" | tr -cd '[:alnum:]' | cut -c1-6)
timestamp=$(date +%s)
short_sha="${GITHUB_SHA:0:7}"
echo "preview-${safe_prefix}-${timestamp}-${short_sha}"
}

PREVIEW_BRANCH=$(generate_preview_branch)
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"

Expand All @@ -96,7 +93,6 @@ jobs:
- 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"

Expand All @@ -106,25 +102,25 @@ jobs:
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"
Expand All @@ -135,24 +131,20 @@ jobs:
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
- name: Save preview branch info (log)
run: |
set -euo pipefail

PREVIEW_BRANCH="${{ steps.branch-name.outputs.branch_name }}"
SAFE_BRANCH_LOG="${{ steps.validate.outputs.safe_branch_log }}"

echo "[SUCCESS] Preview branch created successfully!"
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"
Expand All @@ -162,16 +154,23 @@ jobs:
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
if: github.event_name == 'pull_request' # Only run on PR events
uses: actions/github-script@v7
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: `Preview ID generated: ${process.env.PREVIEW_BRANCH}`
body: `Preview ID generated: ${preview}`
});
env:
PREVIEW_BRANCH: ${{ steps.branch-name.outputs.branch_name }}