-
-
Notifications
You must be signed in to change notification settings - Fork 8
228 lines (221 loc) 路 10.6 KB
/
Copy pathdocs.yaml
File metadata and controls
228 lines (221 loc) 路 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
---
name: 馃摎 Docs
"on":
workflow_call:
workflow_dispatch:
push:
branches:
- main
# Only rebuild docs when something the docs actually depend on changes:
# Sphinx sources, Python (autodoc), pyproject (project metadata + docs
# group deps), `uv.lock` (resolved env), the docs workflow itself, and
# the top-level Markdown / data files that the docs include or link to.
# `readme.*.md` catches the translated readmes kept beside the English
# one. `check-broken-links` crawls every file `metadata` reports under
# `doc_files`, translations included, so listing `readme.md` alone left a
# translation-only push with no link check at all: on an awesome list,
# where the translation carries the same few hundred links, that is most
# of what the crawl exists to cover.
paths:
- .github/workflows/docs.yaml
- changelog.md
- citation.cff
- docs/**
- pyproject.toml
- readme.*.md
- readme.md
- uv.lock
- "**/*.py"
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ !startsWith(github.event.head_commit.message, '[changelog] Release') }}
# Supply-chain cooldown: no package published within the window can be resolved by
# any command in this workflow. Set here, not per command, so it also covers the
# `metadata` bootstrap and any step added later; a workflow-level `env:` cannot
# reference `needs`, so the window is a literal kept equal to `[tool.repomatic]
# minimum-release-age`. repomatic's own test suite enforces that upstream; a
# synced copy is kept in step by hand. Deliberate bypasses are per-package CLI
# flags (`--exclude-newer-package`, `--min-release-age-exclude`).
# See claude.md for the rationale.
env:
NPM_CONFIG_MIN_RELEASE_AGE: 7
UV_EXCLUDE_NEWER: "1 week"
jobs:
metadata:
name: 馃К Project metadata
runs-on: ubuntu-26.04
timeout-minutes: 15
outputs:
metadata: ${{ steps.metadata.outputs.metadata }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: Run repomatic metadata
id: metadata
run: >
uv --no-progress run --frozen -- repomatic metadata
--format github-json --output "$GITHUB_OUTPUT"
is_python_project is_sphinx doc_files sphinx_builder
deploy-docs:
name: 馃摉 Deploy Sphinx doc
needs:
- metadata
if: >-
fromJSON(needs.metadata.outputs.metadata).is_python_project
&& fromJSON(needs.metadata.outputs.metadata).is_sphinx
runs-on: ubuntu-26.04
# Wider than the default: this job provisions a toolchain, so its runtime tracks how much it has to install.
timeout-minutes: 30
permissions:
id-token: write
pages: write
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: Install Graphviz and mandoc
# Graphviz: backs the sphinx.ext.graphviz directive.
# See: https://www.sphinx-doc.org/en/master/usage/extensions/graphviz.html
# mandoc: renders the roff `.1` files written by the
# click_extra.sphinx.manpages hook into the browser-viewable
# `.html` siblings the docs site links to. Skipped silently when
# the hook is not opted in.
# fonts-liberation is graphviz's only Recommends (via the transitional
# fonts-liberation2), and an image that ships no fonts of its own would
# otherwise lose it: naming it here keeps diagram labels rendering
# whatever the base image carries, while --no-install-recommends stops
# apt pulling anything else unasked.
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
fonts-liberation graphviz mandoc
- name: Build documentation
# Install --all-extras so documentation can cover all features of the project, including the optional ones.
# The builder is `[tool.repomatic] sphinx.builder`, defaulting to `html`:
# a project serving extension-less URLs sets it to `dirhtml`.
# It travels through the environment rather than being interpolated
# into the command, like DOC_FILES below: the value is whatever the
# repository's own `pyproject.toml` holds, so expanding it inline would
# let a `pull_request`-triggered caller run a fork's string as shell
# code.
env:
SPHINX_BUILDER: ${{ fromJSON(needs.metadata.outputs.metadata).sphinx_builder }}
run: >
uv --no-progress run --frozen --all-extras --group docs --
sphinx-build -b "$SPHINX_BUILDER"
./docs ./docs/_build
- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: ./docs/_build
- name: Deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
check-broken-links:
name: 馃挃 Check broken links
needs:
- metadata
permissions:
issues: write
# Skip all PRs as we won't have permissions to create issues.
# Skip the prepare-release branch as it contains URLs pointing to tags that don't exist yet.
# Skip any push containing a post-release bump commit as a precautionary measure.
if: >
github.event_name != 'pull_request'
&& github.ref != 'refs/heads/prepare-release'
&& (! contains(toJSON(github.event.commits.*.message), '[changelog] Post-release bump'))
&& (fromJSON(needs.metadata.outputs.metadata).doc_files
|| (fromJSON(needs.metadata.outputs.metadata).is_python_project
&& fromJSON(needs.metadata.outputs.metadata).is_sphinx))
# ARM rather than the x86 axis: this crawl's runtime is dominated by remote
# servers' throttling and grows with every release (each adds a dozen URLs
# to the binaries page), so it wants the fastest Linux image available. It
# once ran on a lean image with a 15-minute job cap, which killed the whole
# crawl outright rather than failing a step.
runs-on: ubuntu-26.04-arm
# Widest cap in the repository, for the reason just above: this crawl is the
# one job whose runtime is set by other people's servers. Triple the 15
# minutes that already killed it once, against a measured 10.4-minute worst
# case, so the growth has somewhere to go.
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/repomatic/bin
key: >-
repomatic-${{ runner.os }}-${{ runner.arch }}-lychee-${{
github.job_workflow_sha || hashFiles('repomatic/tool_registry.py') }}
restore-keys: |
repomatic-${{ runner.os }}-${{ runner.arch }}-lychee-
- name: Install Graphviz
if: >
fromJSON(needs.metadata.outputs.metadata).is_python_project
&& fromJSON(needs.metadata.outputs.metadata).is_sphinx
# So we can use the sphinx.ext.graphviz plugin.
# See: https://www.sphinx-doc.org/en/master/usage/extensions/graphviz.html
# fonts-liberation named explicitly: see the deploy-docs job above.
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends fonts-liberation graphviz
- name: Run Sphinx linkcheck
if: >
fromJSON(needs.metadata.outputs.metadata).is_python_project
&& fromJSON(needs.metadata.outputs.metadata).is_sphinx
# Do not use -W: we parse output.json directly instead of relying on exit codes.
# GitHub throttles anonymous crawls to ~1 request/minute, enough to blow a
# capped runner's job budget on link-heavy repos (the runner kills the whole
# job, bypassing continue-on-error). Exposing the token lets a repo's conf.py
# authenticate its github.com checks via linkcheck_request_headers.
env:
GITHUB_TOKEN: ${{ github.token }}
run: >
uv --no-progress run --frozen --all-extras --group docs --
sphinx-build -b linkcheck ./docs ./docs/_linkcheck
continue-on-error: true
- name: Run lychee
if: fromJSON(needs.metadata.outputs.metadata).doc_files
id: lychee_run
env:
GITHUB_TOKEN: ${{ github.token }}
DOC_FILES: ${{ fromJSON(needs.metadata.outputs.metadata).doc_files }}
# xargs splits the list and does nothing else. It must not be what launches
# lychee: xargs answers any child status in 1..125 with 123 of its own, so
# lychee's 2 ("broken links found") reaches `repomatic broken-links` looking
# exactly like a crash, and a real report gets filed as a tool failure and
# dropped. Batching is the second casualty, since each batch writes the same
# --output path and only the last one survives. Handing the split to `printf
# '%s\0'` keeps xargs on an operation that cannot fail, and the single
# invocation below then carries back both the status and the whole report.
#
# The split cannot be `eval "set -- ${DOC_FILES}"`: metadata wraps each path
# in bare double quotes with no escaping, so a filename containing $(...)
# would execute. xargs expands neither that nor backticks.
run: |
exit_code=0
files=()
while IFS= read -r -d '' f; do files+=("$f"); done \
< <(echo "${DOC_FILES}" | xargs printf '%s\0')
uv --no-progress run --frozen -- repomatic run lychee -- \
--format markdown --output ./lychee/out.md \
--hidden --suggest --no-progress --include-fragments --exclude-all-private \
"${files[@]}" \
|| exit_code=$?
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
- name: Manage broken links issue
env:
GH_TOKEN: ${{ github.token }}
LYCHEE_EXIT_CODE: ${{ steps.lychee_run.outputs.exit_code }}
run: |
uv --no-progress run --frozen -- repomatic broken-links \
${LYCHEE_EXIT_CODE:+--lychee-exit-code "${LYCHEE_EXIT_CODE}"}