Skip to content

Commit a97c6ac

Browse files
authored
Merge pull request #25 from ci-sourcerer/feat/versioned-zensical-docs
feat/versioned zensical docs
2 parents b1ccc4b + 7adfe48 commit a97c6ac

5 files changed

Lines changed: 345 additions & 8 deletions

File tree

‎.github/workflows/docs-deploy.yml‎

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
push:
55
branches:
66
- main
7+
tags:
8+
- 'v*'
79
paths:
810
- '.github/workflows/docs.yml'
911
- '.github/workflows/docs-deploy.yml'
@@ -12,23 +14,116 @@ on:
1214
- 'pyproject.toml'
1315
- 'src/common_python_tasks/tasks.py'
1416
workflow_dispatch:
17+
inputs:
18+
source_ref:
19+
description: Optional tag or commit to build for a manual version deployment
20+
required: false
21+
default: ''
22+
type: string
23+
docs_version:
24+
description: Optional version identifier, such as 0.12
25+
required: false
26+
default: ''
27+
type: string
28+
docs_version_title:
29+
description: Optional version title, such as 0.12.1
30+
required: false
31+
default: ''
32+
type: string
33+
update_latest:
34+
description: Move the latest alias to this version
35+
required: false
36+
default: false
37+
type: boolean
38+
default_version:
39+
description: Optional version or alias used as the site root
40+
required: false
41+
default: ''
42+
type: string
1543

1644
concurrency:
1745
group: github-pages
1846
cancel-in-progress: false
1947

2048
jobs:
49+
metadata:
50+
name: Resolve documentation version
51+
runs-on: ubuntu-latest
52+
outputs:
53+
aliases: ${{ steps.metadata.outputs.aliases }}
54+
checkout_ref: ${{ steps.metadata.outputs.checkout_ref }}
55+
default_version: ${{ steps.metadata.outputs.default_version }}
56+
version: ${{ steps.metadata.outputs.version }}
57+
version_title: ${{ steps.metadata.outputs.version_title }}
58+
steps:
59+
- name: Resolve version metadata
60+
id: metadata
61+
env:
62+
DEFAULT_VERSION: ${{ inputs.default_version }}
63+
DOCS_VERSION: ${{ inputs.docs_version }}
64+
DOCS_VERSION_TITLE: ${{ inputs.docs_version_title }}
65+
REF_NAME: ${{ github.ref_name }}
66+
REF_TYPE: ${{ github.ref_type }}
67+
SOURCE_REF: ${{ inputs.source_ref }}
68+
UPDATE_LATEST: ${{ inputs.update_latest }}
69+
run: |
70+
if [[ -n "$DOCS_VERSION" ]]; then
71+
manual_inputs="$DOCS_VERSION$DOCS_VERSION_TITLE$SOURCE_REF$DEFAULT_VERSION"
72+
if [[ "$manual_inputs" == *$'\n'* || "$manual_inputs" == *$'\r'* ]]; then
73+
echo 'Manual documentation inputs cannot contain newlines.' >&2
74+
exit 2
75+
fi
76+
if [[ "$UPDATE_LATEST" == "true" ]]; then
77+
aliases='["latest"]'
78+
else
79+
aliases='[]'
80+
fi
81+
{
82+
echo "aliases=$aliases"
83+
echo "checkout_ref=$SOURCE_REF"
84+
echo "default_version=$DEFAULT_VERSION"
85+
echo "version=$DOCS_VERSION"
86+
echo "version_title=$DOCS_VERSION_TITLE"
87+
} >> "$GITHUB_OUTPUT"
88+
elif [[ "$REF_TYPE" == "tag" ]]; then
89+
if [[ ! "$REF_NAME" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
90+
echo "Stable documentation tags must use vMAJOR.MINOR.PATCH: $REF_NAME" >&2
91+
exit 2
92+
fi
93+
{
94+
echo 'aliases=["latest"]'
95+
echo 'checkout_ref='
96+
echo 'default_version=latest'
97+
echo "version=${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
98+
echo "version_title=${REF_NAME#v}"
99+
} >> "$GITHUB_OUTPUT"
100+
else
101+
{
102+
echo 'aliases=[]'
103+
echo 'checkout_ref='
104+
echo 'default_version='
105+
echo 'version=dev'
106+
echo 'version_title=Development'
107+
} >> "$GITHUB_OUTPUT"
108+
fi
109+
21110
docs:
111+
needs: metadata
22112
uses: ./.github/workflows/docs.yml
23113
with:
24114
python_version: '3.14'
25115
dependency_group: ''
26116
locked: false
27117
artifact_name: common-python-tasks-docs
118+
checkout_ref: ${{ needs.metadata.outputs.checkout_ref }}
28119
generated_docs_check_task: check-docs-references
29120
deploy_github_pages: true
121+
docs_version: ${{ needs.metadata.outputs.version }}
122+
docs_version_title: ${{ needs.metadata.outputs.version_title }}
123+
docs_aliases: ${{ needs.metadata.outputs.aliases }}
124+
docs_default_version: ${{ needs.metadata.outputs.default_version }}
30125
permissions:
31-
contents: read
126+
contents: write
32127
deployments: write
33128
pages: write
34129
id-token: write

‎.github/workflows/docs.yml‎

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,41 @@ on:
3232
required: false
3333
default: docs-site
3434
type: string
35+
checkout_ref:
36+
description: Optional Git ref to check out when building documentation
37+
required: false
38+
default: ''
39+
type: string
3540
deploy_github_pages:
3641
description: Deploy the rendered site to GitHub Pages
3742
required: false
3843
default: false
3944
type: boolean
45+
docs_version:
46+
description: Version identifier used for a GitHub Pages deployment
47+
required: false
48+
default: dev
49+
type: string
50+
docs_version_title:
51+
description: Optional display title for the documentation version
52+
required: false
53+
default: ''
54+
type: string
55+
docs_aliases:
56+
description: JSON array of aliases assigned to the documentation version
57+
required: false
58+
default: '[]'
59+
type: string
60+
docs_default_version:
61+
description: Optional version or alias used as the documentation site root
62+
required: false
63+
default: ''
64+
type: string
65+
docs_versions_branch:
66+
description: Git branch that stores assembled versioned documentation
67+
required: false
68+
default: gh-pages
69+
type: string
4070
publish_cloudflare:
4171
description: Publish an internal pull request to Cloudflare Pages
4272
required: false
@@ -74,19 +104,29 @@ on:
74104
description: Stable Cloudflare Pages branch alias when a preview was published
75105
value: ${{ jobs.build.outputs.preview_url }}
76106

107+
concurrency:
108+
group: >-
109+
${{ inputs.deploy_github_pages &&
110+
format('versioned-docs-{0}-{1}', github.repository, inputs.docs_versions_branch) ||
111+
format('docs-build-{0}', github.run_id) }}
112+
cancel-in-progress: false
113+
77114
jobs:
78115
build:
79116
name: Build documentation
80117
runs-on: ubuntu-latest
81118
permissions:
82-
contents: read
119+
contents: write
83120
deployments: write
84121
pages: write
85122
outputs:
86123
preview_url: ${{ steps.preview_url.outputs.url }}
87124
steps:
88125
- name: Checkout repository
89126
uses: actions/checkout@v7
127+
with:
128+
fetch-depth: 0
129+
ref: ${{ inputs.checkout_ref }}
90130

91131
- name: Set up Python ${{ inputs.python_version }}
92132
uses: actions/setup-python@v7
@@ -131,6 +171,95 @@ jobs:
131171
path: ${{ inputs.site_path }}/
132172
if-no-files-found: error
133173

174+
- name: Validate versioned documentation configuration
175+
if: inputs.deploy_github_pages
176+
env:
177+
DOCS_ALIASES: ${{ inputs.docs_aliases }}
178+
DOCS_DEFAULT_VERSION: ${{ inputs.docs_default_version }}
179+
DOCS_VERSION: ${{ inputs.docs_version }}
180+
DOCS_VERSION_TITLE: ${{ inputs.docs_version_title }}
181+
DOCS_VERSIONS_BRANCH: ${{ inputs.docs_versions_branch }}
182+
run: |
183+
identifier_pattern='^[A-Za-z0-9][A-Za-z0-9._-]*$'
184+
if [[ ! "$DOCS_VERSION" =~ $identifier_pattern ]]; then
185+
echo "Invalid documentation version: $DOCS_VERSION" >&2
186+
exit 2
187+
fi
188+
if [[ "$DOCS_VERSION_TITLE" == *$'\n'* || "$DOCS_VERSION_TITLE" == *$'\r'* ]]; then
189+
echo 'Documentation version titles cannot contain newlines.' >&2
190+
exit 2
191+
fi
192+
if [[ -n "$DOCS_DEFAULT_VERSION" && ! "$DOCS_DEFAULT_VERSION" =~ $identifier_pattern ]]; then
193+
echo "Invalid default documentation version: $DOCS_DEFAULT_VERSION" >&2
194+
exit 2
195+
fi
196+
if ! git check-ref-format "refs/heads/$DOCS_VERSIONS_BRANCH"; then
197+
echo "Invalid documentation versions branch: $DOCS_VERSIONS_BRANCH" >&2
198+
exit 2
199+
fi
200+
if ! jq --exit-status \
201+
--arg pattern "$identifier_pattern" \
202+
'type == "array" and all(.[]; type == "string" and test($pattern))' \
203+
<<< "$DOCS_ALIASES" > /dev/null; then
204+
echo 'Documentation aliases must be a JSON array of valid identifiers.' >&2
205+
exit 2
206+
fi
207+
208+
- name: Install versioned documentation support
209+
if: inputs.deploy_github_pages
210+
env:
211+
MIKE_PACKAGE: mike @ git+https://github.com/squidfunk/mike.git@2d4ad799442f4592db8ad53b179bfb33db8c69ac
212+
run: uv pip install "$MIKE_PACKAGE"
213+
214+
- name: Assemble versioned documentation
215+
if: inputs.deploy_github_pages
216+
env:
217+
DOCS_ALIASES: ${{ inputs.docs_aliases }}
218+
DOCS_DEFAULT_VERSION: ${{ inputs.docs_default_version }}
219+
DOCS_VERSION: ${{ inputs.docs_version }}
220+
DOCS_VERSION_TITLE: ${{ inputs.docs_version_title }}
221+
DOCS_VERSIONS_BRANCH: ${{ inputs.docs_versions_branch }}
222+
SITE_PATH: ${{ inputs.site_path }}
223+
run: |
224+
git config user.name 'github-actions[bot]'
225+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
226+
227+
args=(
228+
deploy
229+
--branch "$DOCS_VERSIONS_BRANCH"
230+
--alias-type redirect
231+
--update-aliases
232+
)
233+
if [[ -n "$DOCS_VERSION_TITLE" ]]; then
234+
args+=(--title "$DOCS_VERSION_TITLE")
235+
fi
236+
args+=("$DOCS_VERSION")
237+
mapfile -t aliases < <(jq --raw-output '.[]' <<< "$DOCS_ALIASES")
238+
args+=("${aliases[@]}")
239+
uv run --no-sync mike "${args[@]}"
240+
241+
if ! grep --recursive --include='*.html' --extended-regexp --quiet \
242+
'"provider"[[:space:]]*:[[:space:]]*"mike"' \
243+
"$SITE_PATH"; then
244+
echo 'The versioned build does not enable the mike version selector.' >&2
245+
exit 2
246+
fi
247+
248+
if [[ -n "$DOCS_DEFAULT_VERSION" ]]; then
249+
uv run --no-sync mike set-default \
250+
--branch "$DOCS_VERSIONS_BRANCH" \
251+
"$DOCS_DEFAULT_VERSION"
252+
elif ! git cat-file --exit-code \
253+
"$DOCS_VERSIONS_BRANCH:index.html" > /dev/null 2>&1; then
254+
uv run --no-sync mike set-default \
255+
--branch "$DOCS_VERSIONS_BRANCH" \
256+
"$DOCS_VERSION"
257+
fi
258+
git push -- origin "$DOCS_VERSIONS_BRANCH"
259+
260+
mkdir versioned-site
261+
git archive "$DOCS_VERSIONS_BRANCH" | tar --extract --directory versioned-site
262+
134263
- name: Validate Cloudflare preview configuration
135264
if: inputs.publish_cloudflare
136265
env:
@@ -343,11 +472,11 @@ jobs:
343472
if: inputs.deploy_github_pages
344473
uses: actions/configure-pages@v6
345474

346-
- name: Upload GitHub Pages artifact
475+
- name: Upload versioned GitHub Pages artifact
347476
if: inputs.deploy_github_pages
348477
uses: actions/upload-pages-artifact@v5
349478
with:
350-
path: ${{ inputs.site_path }}/
479+
path: versioned-site/
351480

352481
deploy:
353482
name: Deploy documentation

0 commit comments

Comments
 (0)