Skip to content
Merged
Show file tree
Hide file tree
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
130 changes: 130 additions & 0 deletions .github/actions/cleanup-site/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Cleanup site state: remove pr-preview directories for closed PRs.
# Keeps only directories for currently open PRs so saved state matches reality.
# This action keeps the existing pages only for the actually opened PRs to avoid restoring
# the pages of closed PRs.

name: 'Cleanup site state'
description: 'Remove PR preview directories for closed PRs; keep only open PRs in site state'

inputs:
site_dir:
description: 'Path to the site directory (e.g. _site) containing pr-preview subdirs'
required: true

runs:
using: 'composite'
steps:
- name: Collect list of open PRs
id: open-prs
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -e

# Build JSON array of open PR numbers for use in next step.
if ! OPEN_PRS_JSON=$(gh pr list --state open --json number -q '.[].number' --limit 100 | jq -R -s -c '[split("\n")[] | select(length > 0) | tonumber]'); then
echo "Error: Failed to list open PRs" >&2
exit 1
fi

# Validate the output is valid JSON array
if ! echo "$OPEN_PRS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
echo "Error: Invalid JSON output from PR list" >&2
exit 1
fi

echo "open_prs_json=$OPEN_PRS_JSON" >> "$GITHUB_OUTPUT"
echo "Found $(echo "$OPEN_PRS_JSON" | jq 'length') open PR(s)"

- name: Remove closed PR preview directories
shell: bash
env:
SITE_DIR: ${{ inputs.site_dir }}
OPEN_PRS_JSON: ${{ steps.open-prs.outputs.open_prs_json }}
run: |
set -e

# Validate site_dir input
if [ -z "$SITE_DIR" ]; then
echo "Error: site_dir is empty" >&2
exit 1
fi

# Reject paths with path traversal, absolute paths, or control characters
if [[ "$SITE_DIR" == *".."* ]] || \
[[ "$SITE_DIR" == /* ]] || \
[[ "$SITE_DIR" == *$'\n'* ]] || \
[[ "$SITE_DIR" == *$'\r'* ]] || \
[[ "$SITE_DIR" == *$'\t'* ]]; then
echo "Error: Invalid site_dir (path traversal or absolute path)" >&2
exit 1
fi

# Get absolute path of workspace root for boundary checking
WORKSPACE_ROOT=$(cd "$GITHUB_WORKSPACE" && pwd)

# Resolve and validate site_dir path
if [ ! -d "$SITE_DIR" ]; then
echo "Info: site_dir does not exist: $SITE_DIR (first run or no previous state)" >&2
exit 0
fi

RESOLVED_SITE_DIR=$(cd "$SITE_DIR" && pwd)

# Ensure resolved path is within workspace
if [[ "$RESOLVED_SITE_DIR" != "$WORKSPACE_ROOT"* ]]; then
echo "Error: site_dir resolves outside workspace" >&2
exit 1
fi

PR_PREVIEW="${RESOLVED_SITE_DIR}/pr-preview"

# Validate PR_PREVIEW path is still within workspace
if [[ "$PR_PREVIEW" != "$WORKSPACE_ROOT"* ]]; then
echo "Error: PR_PREVIEW path escapes workspace" >&2
exit 1
fi

# If there is no previews, just exit
if [ ! -d "$PR_PREVIEW" ]; then
exit 0
fi

# Process each pr-* directory
for dir in "$PR_PREVIEW"/pr-*; do
RESOLVED_DIR=$(cd "$dir" && pwd)

# Skip if glob didn't match anything, files, symlinks, etc.
if [ ! -e "$dir" ] || [ ! -d "$dir" ] || [[ "$RESOLVED_DIR" != "$WORKSPACE_ROOT"* ]] || [[ "$RESOLVED_DIR" != "$PR_PREVIEW"* ]]; then
continue
fi
name=$(basename "$dir")

# Validate directory name matches expected pattern (pr- followed by digits only)
if [[ "$name" =~ ^pr-([0-9]+)$ ]]; then
pr_num="${BASH_REMATCH[1]}"

# Validate pr_num is numeric (extra safety)
if ! [[ "$pr_num" =~ ^[0-9]+$ ]]; then
echo "Warning: Invalid PR number extracted: $pr_num" >&2
continue
fi

# Validate OPEN_PRS_JSON is valid before using it
if ! echo "$OPEN_PRS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
echo "Error: Invalid OPEN_PRS_JSON format" >&2
exit 1
fi

# Check if PR is still open
if ! echo "$OPEN_PRS_JSON" | jq -e --argjson n "$pr_num" 'index($n) != null' >/dev/null 2>&1; then
rm -rf "$dir"
fi
fi
done

# Clean up empty pr-preview directory
if [ -d "$PR_PREVIEW" ] && [ -z "$(ls -A "$PR_PREVIEW" 2>/dev/null)" ]; then
rm -rf "$PR_PREVIEW"
fi
77 changes: 38 additions & 39 deletions .github/actions/generate-docs-comment/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,58 +40,57 @@ runs:
build_timestamp="$BUILD_TIMESTAMP"
commit_sha="$COMMIT_SHA"

cat << 'EOF' > comment.md
## Documentation Preview

The documentation has been built successfully and is available for preview.

| Build Info | Value |
|------------|-------|
| **Generated at** | `$TIMESTAMP_PLACEHOLDER` |
| **Commit** | `$COMMIT_PLACEHOLDER` |

### Preview Links

| Page | Preview Link |
|------|--------------|
| **Index (Home)** | [View Preview]($preview_url_placeholder/index.html) |
EOF

# Replace placeholders with actual values
sed -i "s|\$preview_url_placeholder|$preview_url|g" comment.md
sed -i "s|\$TIMESTAMP_PLACEHOLDER|$build_timestamp|g" comment.md
sed -i "s|\$COMMIT_PLACEHOLDER|$commit_sha|g" comment.md
# Initialize comment.md file
echo "## Documentation Preview" > comment.md
echo "" >> comment.md
echo "The documentation has been built successfully. You can view the preview here: [preview]($preview_url/index.html)" >> comment.md
echo "" >> comment.md
echo "**Generated at**: \`$build_timestamp\` with commit \`$commit_sha\`." >> comment.md

# Add links to changed files
if [ "$changed_docs" != "[]" ] && [ -n "$changed_docs" ]; then
# Parse JSON and get count of items
doc_count=$(echo "$changed_docs" | jq -r 'if type == "array" then length else 0 end' 2>/dev/null || echo "0")

# Always show details section to ensure GitHub renders it
# Check if we have any files to show
has_files=false
if [ "$doc_count" != "0" ] && [ "$doc_count" != "null" ] && [ -n "$doc_count" ]; then
# Verify it's actually a number and > 0
if [ "$doc_count" -gt 0 ] 2>/dev/null; then
has_files=true
fi
fi

echo "" >> comment.md
echo "<details>" >> comment.md
echo "<summary>Expand to view changed pages</summary>" >> comment.md
echo "" >> comment.md

if [ "$has_files" = "true" ]; then
echo "" >> comment.md
echo "### Changed Pages" >> comment.md
echo "" >> comment.md
echo "| Source File | Preview Link |" >> comment.md
echo "|-------------|--------------|" >> comment.md

echo "$changed_docs" | jq -r '.[]' | while read -r file; do
if [ -n "$file" ]; then
# Convert .rst/.md path to .html path
# Remove 'docs/' prefix and change extension
echo "| File | Preview |" >> comment.md
echo "| ---- | ------- |" >> comment.md
while IFS= read -r file; do
if [ -n "$file" ] && [ "$file" != "null" ]; then
html_path=$(echo "$file" | sed 's|^docs/||' | sed 's|\.rst$|.html|' | sed 's|\.md$|.html|')
# Handle index files in subdirectories
if [[ "$html_path" == */index.html ]]; then
html_path="${html_path}"
fi
filename=$(basename "$file")
echo "| \`$file\` | [View]($preview_url/$html_path) |" >> comment.md
fi
done
done < <(echo "$changed_docs" | jq -r '.[]' 2>/dev/null)
else
echo "No documentation files were changed in this PR." >> comment.md
fi

cat << 'EOF' >> comment.md

---
echo "" >> comment.md
echo "</details>" >> comment.md

> **Note:** The preview will be available after GitHub Pages deployment completes (usually 1-2 minutes).
> If you see stale content, hard-refresh your browser (Ctrl+Shift+R) or wait a moment for GitHub Pages to update.
EOF
echo "" >> comment.md
echo "---" >> comment.md
echo "" >> comment.md
echo "> **Note:** The preview will be available after GitHub Pages deployment completes (usually 1-2 minutes). Ensure that **generated at** timestamp is up to date. If the Pull Request is stale for more than 30 days, the preview may be deleted. In this case, please update the Pull Request, or re-run the Build Documentation workflow to generate a new preview." >> comment.md

echo "Comment content:"
cat comment.md
2 changes: 1 addition & 1 deletion .github/actions/get-changed-docs/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ runs:
HEAD_SHA: ${{ inputs.head_sha }}
run: |
# Get list of changed .rst and .md files in docs/
changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'docs/*.rst' 'docs/*.md' 'docs/**/*.rst' 'docs/**/*.md' | head -20)
changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'docs/*.rst' 'docs/*.md' 'docs/**/*.rst' 'docs/**/*.md' 'samples/**/*.rst' | head -20)
echo "Changed documentation files:"
echo "$changed_files"

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/docbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ on:
- main
paths:
- 'docs/**'
- '.github/workflows/docbuild.yml'
- '.github/workflows/doc*'
- 'samples/**/*.rst'

permissions:
contents: read
Expand Down
45 changes: 28 additions & 17 deletions .github/workflows/docpreview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,51 @@ jobs:
- name: Checkout repository
uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4

- name: Download site state from cleanup
id: download-cleanup
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: docremove.yml
name: site-state
path: _existing_pages
workflow_conclusion: success
search_artifacts: true
if_no_artifact_found: warn
continue-on-error: true

- name: Download site state from deploy
id: download-deploy
if: steps.download-cleanup.outcome == 'failure'
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: docpreview.yml
name: site-state
path: _existing_pages
path: ${{ runner.temp }}/existing_pages
workflow_conclusion: success
search_artifacts: true
if_no_artifact_found: warn
continue-on-error: true
continue-on-error: false

- name: Remove symlinks from downloaded artifacts
run: |
find _existing_pages -type l -delete 2>/dev/null || true
if [ -d "${{ runner.temp }}/existing_pages" ]; then
find "${{ runner.temp }}/existing_pages" -type l -delete 2>/dev/null || true
rm -rf _existing_pages
mkdir -p _existing_pages
cp -a "${{ runner.temp }}/existing_pages/." _existing_pages/ || true
fi

- name: Download build artifacts
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
with:
workflow: docbuild.yml
run_id: ${{ github.event.workflow_run.id }}
path: ${{ runner.temp }}

- name: Move doc-preview from temp
run: |
if [ -d "doc-preview" ]; then
rm -rf doc-preview
fi
if [ -d "${{ runner.temp }}/doc-preview" ]; then
mv "${{ runner.temp }}/doc-preview" ./doc-preview
else
echo "Expected doc-preview directory not found in runner.temp" >&2
exit 1
fi

- name: Cleanup site state
Comment thread
edmont marked this conversation as resolved.
id: download-site-state
uses: ./.github/actions/cleanup-site
with:
site_dir: _existing_pages

- name: Extract version and unzip docs
working-directory: doc-preview
Expand Down Expand Up @@ -123,7 +134,7 @@ jobs:
with:
name: site-state
path: _site
retention-days: 14 # 2 weeks
retention-days: 30 # 1 month, recall Build Documentation workflow to re-generate preview
overwrite: true

- name: Find existing comment
Expand Down
Loading