Skip to content

docs: redirect unversioned deep links to the default version #345

docs: redirect unversioned deep links to the default version

docs: redirect unversioned deep links to the default version #345

Workflow file for this run

name: Docs
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
env:
DOCS_REPO: light-curve/light-curve.snad.space
DOCS_BRANCH: main
jobs:
docs-deploy:
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
# Queue deploys; never cancel — concurrent pushes would corrupt the docs branch
concurrency:
group: docs-deploy
cancel-in-progress: false
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: true
persist-credentials: false
- uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: "light-curve"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgsl-dev
- name: Configure git and docs remote
env:
DOCS_DEPLOY_TOKEN: ${{ secrets.DOCS_DEPLOY_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote add docs-site \
"https://x-access-token:${DOCS_DEPLOY_TOKEN}@github.com/${DOCS_REPO}.git"
- name: Install from source with docs dependencies
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin
cd light-curve && maturin develop --extras=full --group=docs -q
- name: Deploy dev (main branch)
if: github.ref_type == 'branch'
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
source .venv/bin/activate
# Detach HEAD and drop the local branch so mike fetches docs-site/${DOCS_BRANCH}
# fresh instead of reusing the source-code branch of the same name.
git fetch docs-site "${DOCS_BRANCH}"
git checkout --detach HEAD
git branch -D "${DOCS_BRANCH}"
mike deploy --push --remote docs-site --branch "${DOCS_BRANCH}" \
--update-aliases dev
mike list --remote docs-site --branch "${DOCS_BRANCH}" | grep -q "latest" \
|| mike set-default --push --remote docs-site --branch "${DOCS_BRANCH}" dev
- name: Deploy release (tag)
if: github.ref_type == 'tag'
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
source .venv/bin/activate
git fetch docs-site "${DOCS_BRANCH}"
git checkout --detach HEAD
git branch -D "${DOCS_BRANCH}" 2>/dev/null || true
mike deploy --push --remote docs-site --branch "${DOCS_BRANCH}" \
--update-aliases latest
mike set-default --push --remote docs-site --branch "${DOCS_BRANCH}" latest
- name: Deploy root 404 redirect to default version
# mike only redirects the bare root `/` to the default version; this root
# 404.html redirects unversioned deep links (/<path> -> /<default>/<path>).
# It lives at the docs-site root (outside any version dir) and survives
# mike deploys, so we just keep it in sync after each deploy.
env:
DOCS_DEPLOY_TOKEN: ${{ secrets.DOCS_DEPLOY_TOKEN }}
run: |
git clone --depth=1 --branch="${DOCS_BRANCH}" \
"https://x-access-token:${DOCS_DEPLOY_TOKEN}@github.com/${DOCS_REPO}.git" \
/tmp/docs-404
cd /tmp/docs-404
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
cp "${GITHUB_WORKSPACE}/.github/docs-site/404.html" 404.html
git add 404.html
if git diff --cached --quiet; then
echo "Root 404.html already up to date."
else
git commit -m "docs: update root 404 redirect to default version"
for i in 1 2 3; do
git push && break
git pull --rebase origin "${DOCS_BRANCH}"
done
fi
- name: Remove stale PR previews
# pull_request.closed doesn't fire for bot-merged PRs (copilot[bot],
# pre-commit-ci[bot]). Run this after every deploy to catch leftovers.
env:
GH_TOKEN: ${{ github.token }}
DOCS_DEPLOY_TOKEN: ${{ secrets.DOCS_DEPLOY_TOKEN }}
run: |
git clone --depth=1 --branch="${DOCS_BRANCH}" \
"https://x-access-token:${DOCS_DEPLOY_TOKEN}@github.com/${DOCS_REPO}.git" \
/tmp/docs-stale
cd /tmp/docs-stale
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
gh pr list --repo "${{ github.repository }}" --state open \
--json number --limit 200 > /tmp/open_prs.json
python3 - <<'PYEOF'
import json, os, re, subprocess, sys
with open('/tmp/open_prs.json') as f:
open_set = {f"pr{p['number']}" for p in json.load(f)}
with open('versions.json') as f:
versions = json.load(f)
stale = [v['version'] for v in versions
if re.fullmatch(r'pr\d+', v['version']) and v['version'] not in open_set]
if not stale:
print("No stale PR previews.")
sys.exit(0)
print("Removing stale PR previews:", stale)
for ver in stale:
if os.path.isdir(ver):
subprocess.run(['git', 'rm', '-rf', ver], check=True)
cleaned = [v for v in versions if v['version'] not in stale]
with open('versions.json', 'w') as f:
json.dump(cleaned, f, indent=2)
f.write('\n')
subprocess.run(['git', 'add', 'versions.json'], check=True)
PYEOF
if git diff --cached --quiet; then
echo "Nothing to commit."
else
git commit -m "docs: remove stale PR previews"
for i in 1 2 3; do
git push && break
git pull --rebase origin "${DOCS_BRANCH}"
done
fi
docs-preview:
# pull_request_target gives write access even for fork PRs.
# We explicitly check out the PR head so the docs reflect the PR content.
if: github.event_name == 'pull_request_target'
# Cancel older builds for the same PR; only the latest commit's preview matters
concurrency:
group: docs-preview-pr${{ github.event.pull_request.number }}
cancel-in-progress: true
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
submodules: true
persist-credentials: false
- uses: actions/checkout@v6
with:
repository: ${{ env.DOCS_REPO }}
token: ${{ secrets.DOCS_DEPLOY_TOKEN }}
ref: ${{ env.DOCS_BRANCH }}
path: docs-deploy
fetch-depth: 1
- uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: "light-curve"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgsl-dev
- 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: Install from source with docs dependencies
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin
cd light-curve && maturin develop --extras=full --group=docs -q
- name: Build docs
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
source .venv/bin/activate
mkdocs build --site-dir /tmp/pr-site
- name: Push preview to docs repo
env:
PR: ${{ github.event.pull_request.number }}
run: |
rm -rf docs-deploy/pr${PR}
cp -r /tmp/pr-site docs-deploy/pr${PR}
rm -f docs-deploy/pr${PR}/CNAME
python3 -c "
import json, os, re
pr = int(os.environ['PR'])
entry = {'version': f'pr{pr}', 'title': f'pr{pr}', 'aliases': []}
with open('docs-deploy/versions.json') as f:
versions = json.load(f)
versions = [v for v in versions if v['version'] != entry['version']]
non_pr = [v for v in versions if not re.fullmatch(r'pr\d+', v['version'])]
prs = [v for v in versions if re.fullmatch(r'pr\d+', v['version'])]
prs.append(entry)
prs.sort(key=lambda v: int(v['version'][2:]))
with open('docs-deploy/versions.json', 'w') as f:
json.dump(non_pr + prs, f, indent=2)
f.write('\n')
"
cd docs-deploy
git add pr${PR} versions.json
git diff --cached --quiet && echo "No changes" && exit 0
git commit -m "docs: update PR #${PR} preview"
# Retry push with rebase in case of concurrent preview builds
for i in 1 2 3; do
git push && break
git pull --rebase origin "${DOCS_BRANCH}"
done
- name: Comment preview URL on PR
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- docs-preview -->';
const url = `https://light-curve.snad.space/pr${{ github.event.pull_request.number }}/`;
const body = `${marker}\n📖 **Docs preview:** ${url}`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}