-
Notifications
You must be signed in to change notification settings - Fork 1
228 lines (205 loc) · 9.43 KB
/
Copy pathquality.yml
File metadata and controls
228 lines (205 loc) · 9.43 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
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
name: Code quality
on:
push:
branches: [main, development]
pull_request:
branches: [main, development]
# No token needed; jobs that do ask for one explicitly.
permissions: {}
# Pushing to the same PR three times in a row started three full runs, and the
# first two are already stale by then. On main do not cancel: there the run is
# the record that the commit passed the checks.
concurrency:
group: quality-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
# Everything that needs the built site, in one job.
#
# Splitting the build, the translation check and the link check into separate
# jobs costs a runner and a Hugo setup each, and GitHub rounds every job up to
# a whole minute. Together they finish inside one.
site:
name: Build and check the site
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out source code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# fetch-depth: 0 for Hugo's .GitInfo and .Lastmod (enableGitInfo).
fetch-depth: 0
persist-credentials: false
# Hextra is a Hugo Module (src/go.mod), so Hugo needs Go on PATH.
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: src/go.mod
- name: Set up Hugo
uses: ./.github/actions/setup-hugo
# Every step runs on !cancelled(), so a red markdown check does not hide
# the Hugo build. You want every failure in one run, not the second only
# after fixing the first. The job still fails as soon as anything does.
# A build that fails here is a broken deploy caught in time. --panicOnWarning
# is deliberately stricter than the deploy build: a new Hugo deprecation
# should block a merge, not a release that is already on its way.
- name: Build site
if: ${{ !cancelled() }}
working-directory: src
env:
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
HUGO_ENVIRONMENT: production
TZ: Europe/Amsterdam
run: hugo --gc --minify --printPathWarnings --panicOnWarning --baseURL "http://localhost/"
# Every English document must have a Dutch counterpart.
- name: Check every .md has a matching .nl.md
if: ${{ !cancelled() }}
run: |
missing=""
for en in src/content/docs/*.md; do
base="${en%.md}"
nl="${base}.nl.md"
# Skip files that are already .nl.md
[[ "$en" == *.nl.md ]] && continue
if [ ! -f "$nl" ]; then
missing="$missing\n $en → $nl missing"
fi
done
if [ -n "$missing" ]; then
echo -e "::error::Missing Dutch translation(s):$missing"
exit 1
fi
echo "All docs have EN + NL versions."
# Images must be AVIF. The friendly "here is how to convert" comment lives
# in pr-checks.yml, which needs pull-requests: write; this is the hard
# gate, and it runs on push too.
- name: Check images are AVIF
if: ${{ !cancelled() }}
run: |
mapfile -t offenders < <(find src/static/images -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort)
if [ "${#offenders[@]}" -gt 0 ]; then
for f in "${offenders[@]}"; do
echo "::error file=$f::Convert to AVIF before merging (see README → Image assets)"
done
exit 1
fi
echo "All images are AVIF."
# Installed by hand rather than through lycheeverse/lychee-action, which
# fetches its binary with a bare `curl -sfLO`: no retry, and no check on
# what comes back. Pinned version, verified checksum, retry.
- name: Install lychee
if: ${{ !cancelled() }}
env:
# extractVersion: lychee tags its releases as "lychee-v0.24.2", not
# "v0.24.2", so the default pattern does not read the version out.
# renovate: datasource=github-releases depName=lycheeverse/lychee extractVersion=^lychee-v(?<version>.+)$
LYCHEE_VERSION: "0.24.2"
# From the release's own lychee-x86_64-unknown-linux-gnu.tar.gz.sha256
LYCHEE_SHA256: "1f4e0ef7f6554a6ed33dd7ac144fb2e1bbed98598e7af973042fc5cd43951c9a"
run: |
curl -sSL --fail-with-body -o lychee.tar.gz \
--retry 5 --retry-delay 3 --retry-all-errors \
"https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz"
echo "${LYCHEE_SHA256} lychee.tar.gz" | sha256sum -c -
tar -xzf lychee.tar.gz lychee-x86_64-unknown-linux-gnu/lychee
sudo install -m 0755 lychee-x86_64-unknown-linux-gnu/lychee /usr/local/bin/lychee
lychee --version
# Reads src/public directly. --index-files: Hugo serves every page as
# <page>/index.html, and without this lychee stops at the directory, so a
# link to /docs/applications/ can never be checked for its #fragment. The
# glob is quoted on purpose: unquoted, bash expands it first and ** without
# globstar collapses to a single directory level.
- name: Check internal links
if: ${{ !cancelled() }}
run: |
lychee --offline --include-fragments --index-files index.html \
--root-dir "${GITHUB_WORKSPACE}/src/public" "src/public/**/*.html"
# Everything that has nothing to do with the built site: the Markdown, the
# workflow files, and the Python. Hugo is not needed for any of it.
#
# Every step runs on !cancelled(), so one red linter does not hide the others.
# The job still fails as soon as anything does.
repo:
name: Check the repository
runs-on: ubuntu-latest
permissions:
contents: read
# For zizmor's SARIF upload below.
security-events: write
steps:
- name: Check out source code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# markdownlint: keeps content/ and the repo's own Markdown consistent.
- name: Run markdownlint
if: ${{ !cancelled() }}
uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
with:
globs: |
src/content/**/*.md
*.md
# Pinned release plus checksum, rather than piping a script off a branch
# straight through bash.
- name: Install actionlint
if: ${{ !cancelled() }}
env:
# renovate: datasource=github-releases depName=rhysd/actionlint
ACTIONLINT_VERSION: "1.7.12"
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
run: |
curl -sSL --fail-with-body -o actionlint.tar.gz \
--retry 5 --retry-delay 3 --retry-all-errors \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c -
tar -xzf actionlint.tar.gz actionlint
sudo install -m 0755 actionlint /usr/local/bin/actionlint
# actionlint: syntax errors and wrong expressions in the workflows.
- name: Run actionlint
if: ${{ !cancelled() }}
run: actionlint -color
# zizmor: a linter on the same files as actionlint, only for security
# rather than syntax. pipx and not pip: the runner's system Python is
# externally managed (PEP 668), so a plain pip install aborts.
- name: Install zizmor
if: ${{ !cancelled() }}
env:
# renovate: datasource=pypi depName=zizmor
ZIZMOR_VERSION: "1.30.0"
run: pipx install "zizmor==${ZIZMOR_VERSION}"
- name: Run zizmor
if: ${{ !cancelled() }}
run: zizmor --format sarif . > zizmor.sarif || true
# The results show up on the repository's Security tab. Advanced Security
# is on organisation-wide, so this works on a private repository too.
- name: Upload zizmor results to GitHub Security
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
if: ${{ !cancelled() }}
continue-on-error: true
with:
sarif_file: zizmor.sarif
category: zizmor
# ── Python: style and security ─────────────────────────────────────────
#
# src/static/scripts/ ships one script, saxion-eduroam.py, that visitors
# download and run. flake8 and bandit also get a weekly re-run in
# security.yml, where new rules can flag something in code that has not
# changed.
- name: Set up Python
if: ${{ !cancelled() }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.14'
- name: Install flake8 and bandit
if: ${{ !cancelled() }}
run: |
python -m pip install --upgrade pip
pip install flake8 bandit
- name: Lint with flake8
if: ${{ !cancelled() }}
run: flake8 src/static/scripts/ --max-line-length=120
- name: Security scan with bandit
if: ${{ !cancelled() }}
run: bandit -r src/static/scripts/ -ll