Skip to content

Commit 1d88d2e

Browse files
committed
feat: add multi-version documentation support
- Add version switcher UI component - Add cleanup workflow for old previews - Add helper script for version management - Document multi-version setup
1 parent 02d564a commit 1d88d2e

File tree

5 files changed

+587
-6
lines changed

5 files changed

+587
-6
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
name: Cleanup old preview deployments
2+
3+
on:
4+
schedule:
5+
# Run weekly on Sunday at 00:00 UTC
6+
- cron: '0 0 * * 0'
7+
workflow_dispatch: # Allow manual trigger
8+
inputs:
9+
keep_days:
10+
description: 'Keep previews newer than this many days'
11+
required: false
12+
default: '30'
13+
keep_count:
14+
description: 'Keep at least this many newest previews'
15+
required: false
16+
default: '10'
17+
18+
jobs:
19+
cleanup:
20+
runs-on: ubuntu-latest
21+
if: github.ref == 'refs/heads/main' # Only run from main branch
22+
23+
permissions:
24+
contents: write
25+
pages: write
26+
27+
steps:
28+
- name: Checkout gh-pages branch
29+
uses: actions/checkout@v3
30+
with:
31+
ref: gh-pages
32+
33+
- name: Cleanup old preview deployments
34+
run: |
35+
set -e
36+
37+
# Get parameters
38+
KEEP_DAYS="${{ github.event.inputs.keep_days || '30' }}"
39+
KEEP_COUNT="${{ github.event.inputs.keep_count || '10' }}"
40+
41+
echo "Cleaning up preview deployments older than ${KEEP_DAYS} days"
42+
echo "Keeping at least ${KEEP_COUNT} newest previews"
43+
44+
# Check if previews directory exists
45+
if [ ! -d "previews" ]; then
46+
echo "No previews directory found, nothing to clean"
47+
exit 0
48+
fi
49+
50+
cd previews
51+
52+
# Get list of all preview directories with their modification times
53+
# Create a list of directories with their timestamps
54+
for dir in */; do
55+
if [ -d "$dir" ]; then
56+
# Get the last modified timestamp of the directory
57+
timestamp=$(stat -c %Y "$dir" 2>/dev/null || stat -f %m "$dir" 2>/dev/null || echo 0)
58+
echo "$timestamp $dir"
59+
fi
60+
done | sort -rn > /tmp/preview_list.txt
61+
62+
# Count total previews
63+
TOTAL_COUNT=$(wc -l < /tmp/preview_list.txt)
64+
echo "Found ${TOTAL_COUNT} preview deployments"
65+
66+
if [ "$TOTAL_COUNT" -le "$KEEP_COUNT" ]; then
67+
echo "Total count ($TOTAL_COUNT) is less than or equal to keep count ($KEEP_COUNT)"
68+
echo "No cleanup needed"
69+
exit 0
70+
fi
71+
72+
# Calculate cutoff timestamp (days ago)
73+
CUTOFF_TIMESTAMP=$(date -d "${KEEP_DAYS} days ago" +%s 2>/dev/null || \
74+
date -v-${KEEP_DAYS}d +%s 2>/dev/null || \
75+
echo 0)
76+
77+
# Track what will be removed
78+
REMOVED_COUNT=0
79+
REMOVED_LIST=""
80+
81+
# Process each preview
82+
LINE_NUM=0
83+
while IFS=' ' read -r timestamp dir; do
84+
LINE_NUM=$((LINE_NUM + 1))
85+
86+
# Skip if we need to keep minimum count
87+
if [ "$LINE_NUM" -le "$KEEP_COUNT" ]; then
88+
echo "Keeping $dir (within top $KEEP_COUNT)"
89+
continue
90+
fi
91+
92+
# Skip if newer than cutoff date
93+
if [ "$timestamp" -gt "$CUTOFF_TIMESTAMP" ]; then
94+
echo "Keeping $dir (newer than $KEEP_DAYS days)"
95+
continue
96+
fi
97+
98+
# Remove this preview
99+
echo "Removing $dir (older than $KEEP_DAYS days)"
100+
rm -rf "$dir"
101+
REMOVED_COUNT=$((REMOVED_COUNT + 1))
102+
REMOVED_LIST="${REMOVED_LIST}\n - $dir"
103+
104+
done < /tmp/preview_list.txt
105+
106+
cd ..
107+
108+
# Update version switcher to remove deleted previews
109+
if [ "$REMOVED_COUNT" -gt 0 ] && [ -f "_static/switcher.json" ]; then
110+
echo "Updating version switcher..."
111+
# Create a backup
112+
cp _static/switcher.json _static/switcher.json.bak
113+
114+
# Use Python to safely update the JSON
115+
python3 -c "
116+
import json
117+
import os
118+
import sys
119+
120+
with open('_static/switcher.json', 'r') as f:
121+
versions = json.load(f)
122+
123+
# Get list of removed directories from environment
124+
removed_dirs = '''$REMOVED_LIST'''.strip().split('\\n')
125+
removed_dirs = [d.strip().replace('- ', '').replace('/', '') for d in removed_dirs if d.strip()]
126+
127+
# Filter out removed versions
128+
original_count = len(versions)
129+
versions = [v for v in versions if not any(
130+
d in v.get('url', '') for d in removed_dirs if d
131+
)]
132+
removed_from_json = original_count - len(versions)
133+
134+
with open('_static/switcher.json', 'w') as f:
135+
json.dump(versions, f, indent=2)
136+
137+
print(f'Removed {removed_from_json} entries from version switcher')
138+
"
139+
fi
140+
141+
# Commit changes if any removals were made
142+
if [ "$REMOVED_COUNT" -gt 0 ]; then
143+
git config user.name 'github-actions[bot]'
144+
git config user.email 'github-actions[bot]@users.noreply.github.com'
145+
git add -A
146+
git commit -m "cleanup: Remove $REMOVED_COUNT old preview deployment(s)
147+
148+
Removed the following preview deployments:${REMOVED_LIST}
149+
150+
Cleanup policy:
151+
- Keep previews newer than ${KEEP_DAYS} days
152+
- Keep at least ${KEEP_COUNT} newest previews
153+
- Total previews before cleanup: ${TOTAL_COUNT}"
154+
155+
git push
156+
157+
echo "✅ Cleanup complete: Removed $REMOVED_COUNT old preview deployment(s)"
158+
else
159+
echo "✅ No preview deployments needed cleanup"
160+
fi
161+
162+
- name: Report cleanup summary
163+
if: always()
164+
run: |
165+
echo "### Cleanup Summary" >> $GITHUB_STEP_SUMMARY
166+
echo "" >> $GITHUB_STEP_SUMMARY
167+
if [ -f "/tmp/preview_list.txt" ]; then
168+
echo "- Total previews found: $(wc -l < /tmp/preview_list.txt)" >> $GITHUB_STEP_SUMMARY
169+
fi
170+
echo "- Keep policy: ${KEEP_DAYS:-30} days, minimum ${KEEP_COUNT:-10} previews" >> $GITHUB_STEP_SUMMARY
171+
echo "- Cleanup completed successfully" >> $GITHUB_STEP_SUMMARY

MULTI_VERSION_DOCS.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Multi-Version Documentation System
2+
3+
This repository implements a multi-version documentation system that publishes different versions of the documentation to GitHub Pages.
4+
5+
## Overview
6+
7+
The system supports three types of documentation deployments:
8+
9+
1. **Stable Documentation** (`main` branch) → Published at https://docs.openspp.org/
10+
2. **Preview Documentation** (feature branches) → Published at https://docs.openspp.org/previews/{branch-name}/
11+
3. **Version Releases** (future) → Published at https://docs.openspp.org/{version}/
12+
13+
## How It Works
14+
15+
### Environment Variables
16+
17+
The build system uses environment variables to configure each build:
18+
19+
- `DOCS_VERSION`: Version identifier (e.g., "stable", "refactor-structure")
20+
- `DOCS_BASEURL`: Base URL for the documentation (e.g., "https://docs.openspp.org/")
21+
- `IS_PREVIEW`: Set to "1" for preview builds, "0" for stable/release builds
22+
23+
### SEO Protection
24+
25+
Preview builds are protected from search engine indexing:
26+
27+
1. **Meta Tags**: Preview builds include `<meta name="robots" content="noindex,nofollow,noarchive">`
28+
2. **robots.txt**: Only deployed with stable builds, blocks `/previews/` directory
29+
3. **Visual Indicator**: Preview builds show a warning banner to users
30+
31+
### GitHub Actions Workflow
32+
33+
The `.github/workflows/build_deploy.yml` workflow handles automatic deployment:
34+
35+
- Triggers on push to any branch
36+
- Main branch deploys to root as stable documentation
37+
- Other branches deploy to `/previews/{sanitized-branch-name}/`
38+
- Branch names are sanitized (special characters replaced with hyphens, limited to 50 chars)
39+
40+
### Version Switcher
41+
42+
The documentation includes a version switcher dropdown configured via `docs/_static/switcher.json`.
43+
44+
**Important Note:** sphinx-book-theme version 0.3.3 doesn't have built-in version switcher support. The current implementation uses a custom JavaScript solution (`_static/version_switcher.js`). For full native support, consider upgrading to sphinx-book-theme >= 1.0.0.
45+
46+
#### Managing Versions
47+
48+
Use the helper script to manage versions:
49+
50+
```bash
51+
# List current versions
52+
python Scripts/update_version_switcher.py list
53+
54+
# Add a new version
55+
python Scripts/update_version_switcher.py add \
56+
--version "1.2" \
57+
--name "Version 1.2" \
58+
--url "https://docs.openspp.org/1.2/"
59+
60+
# Remove a version
61+
python Scripts/update_version_switcher.py remove --version "old-preview"
62+
```
63+
64+
### Cleanup of Old Previews
65+
66+
The `.github/workflows/cleanup_previews.yml` workflow automatically removes old preview deployments:
67+
68+
- Runs weekly (Sunday at 00:00 UTC)
69+
- Can be manually triggered
70+
- Keeps previews newer than 30 days (configurable)
71+
- Always keeps at least 10 newest previews (configurable)
72+
- Updates version switcher when removing previews
73+
74+
To manually trigger cleanup:
75+
1. Go to Actions → Cleanup old preview deployments
76+
2. Click "Run workflow"
77+
3. Optionally adjust keep_days and keep_count parameters
78+
79+
## Local Testing
80+
81+
To test multi-version builds locally:
82+
83+
```bash
84+
# Test stable build
85+
export DOCS_VERSION=stable
86+
export DOCS_BASEURL=https://docs.openspp.org/
87+
export IS_PREVIEW=0
88+
make html
89+
90+
# Test preview build
91+
export DOCS_VERSION=my-feature
92+
export DOCS_BASEURL=https://docs.openspp.org/previews/my-feature/
93+
export IS_PREVIEW=1
94+
make html
95+
```
96+
97+
## Security Features
98+
99+
The implementation includes several security measures:
100+
101+
1. **Branch Name Sanitization**: Only alphanumeric, dots, underscores, and hyphens allowed
102+
2. **Path Traversal Protection**: Version switcher script validates file paths
103+
3. **Input Validation**: URLs and version names are validated to prevent injection
104+
4. **Atomic File Operations**: Prevents corruption during concurrent updates
105+
5. **Error Handling**: Build failures are properly caught and reported
106+
107+
## Maintenance
108+
109+
### Adding a New Release Version
110+
111+
When creating a new release (e.g., v1.2):
112+
113+
1. Tag the release in git
114+
2. Update the version switcher:
115+
```bash
116+
python Scripts/update_version_switcher.py add \
117+
--version "1.2" \
118+
--name "v1.2 (latest)" \
119+
--url "https://docs.openspp.org/1.2/"
120+
```
121+
3. The GitHub Actions workflow will build and deploy to `/1.2/`
122+
123+
### Monitoring
124+
125+
- Check GitHub Actions for build status
126+
- Review cleanup workflow runs for removed previews
127+
- Monitor disk usage on GitHub Pages
128+
129+
## Troubleshooting
130+
131+
### Preview Not Appearing
132+
133+
1. Check GitHub Actions for build failures
134+
2. Verify branch name doesn't contain invalid characters
135+
3. Check that `keep_files: true` is set in the workflow
136+
137+
### Version Switcher Not Updated
138+
139+
1. Ensure switcher.json is valid JSON
140+
2. Check file permissions on gh-pages branch
141+
3. Verify the update script ran successfully
142+
143+
### SEO Issues
144+
145+
1. Verify preview builds have noindex meta tag
146+
2. Check robots.txt is only present in stable build
147+
3. Test with Google Search Console or similar tools
148+
149+
## Future Enhancements
150+
151+
- Automatic version detection from git tags
152+
- PR comment with preview URL
153+
- Build caching for faster deployments
154+
- Version-specific search functionality

0 commit comments

Comments
 (0)