From 95668a12f8dadceff4b58c9055b52555a9602b61 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:23:19 +0000 Subject: [PATCH 1/8] Level CI pins and Renovate config with the other repositories An audit across the organisation found the same tools pinned differently in each repository, with nothing to make them converge again. codeql-action/upload-sarif sat on three different commits org-wide, all labelled only `# v4`, so the difference was invisible at the pin. All of them now point at v4.37.8 with the exact version in the comment. The Semgrep container was pinned to two different digests, meaning two groups of repositories scanned with a different rule set. Both now use the current `latest` manifest digest. HTMLHint installs with `npm ci` against a checked-in lockfile instead of `npm install -g htmlhint@version`, which resolved through the registry at run time with nothing to verify the result against. Scorecard's Pinned-Dependencies check and zizmor's adhoc-packages audit both flag the old form. Renovate gets the npm manager enabled so the lockfile stays current. The SARIF uploads no longer skip themselves on a private repository. That guard was correct when it was written, but Advanced Security is now enabled across the whole organisation, so the uploads it was suppressing would succeed today. The comments explaining the guard went with it rather than being left to describe behaviour that no longer exists. .gitattributes was four slightly different files across seven repositories. It is now one, covering the union of what the variants covered. Renovate also gains dependencyDashboard, osvVulnerabilityAlerts and configMigration. The last one is the point: it opens a pull request when an option goes stale, which is what would have surfaced this drift on its own. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .gitattributes | 24 ++++++++++++++++++++++++ .github/htmlhint/package-lock.json | 4 ++-- .github/htmlhint/package.json | 2 +- .github/workflows/quality.yml | 14 ++++---------- .github/workflows/security.yml | 7 ++++--- renovate.json | 5 ++++- 6 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..45ff4d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# .gitattributes zorgt voor consistente regeleindes tussen Windows, Mac en Linux. +# Zonder dit kunnen regeleindes per ontwikkelaar of OS verschillen, wat leidt +# tot onnodige git-diffs en merge-conflicten. +# +# Deze versie is organisatiebreed gelijk. Wijk hier niet per repository van af: +# vier licht verschillende varianten leverden alleen ruis op, geen voordeel. + +# Standaard: forceer LF voor alle tekstbestanden +* text=auto eol=lf + +# Binaire bestanden: geen regeleindeconversie +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.avif binary +*.pdf binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary +*.zip binary diff --git a/.github/htmlhint/package-lock.json b/.github/htmlhint/package-lock.json index 1fded9f..6de4af9 100644 --- a/.github/htmlhint/package-lock.json +++ b/.github/htmlhint/package-lock.json @@ -1,11 +1,11 @@ { - "name": "hugo-template-htmlhint", + "name": "thectic-htmlhint", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hugo-template-htmlhint", + "name": "thectic-htmlhint", "version": "1.0.0", "license": "ISC", "dependencies": { diff --git a/.github/htmlhint/package.json b/.github/htmlhint/package.json index c5162c3..1569334 100644 --- a/.github/htmlhint/package.json +++ b/.github/htmlhint/package.json @@ -1,5 +1,5 @@ { - "name": "hugo-template-htmlhint", + "name": "thectic-htmlhint", "private": true, "description": "Pinned HTMLHint install for .github/workflows/quality.yml. Not part of the site.", "dependencies": { diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 290a8f3..c12d755 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -188,17 +188,11 @@ jobs: if: ${{ !cancelled() }} run: zizmor --format sarif . > zizmor.sarif || true - # Results show up on the repository's Security tab. - # - # Skipped while the repository is private: a code scanning upload needs - # GitHub Advanced Security there. continue-on-error kept the job green but - # not the step quiet: it logged "Resource not accessible by integration" - # as ##[error] on every run -- an error inside a green job, which is worse - # than either on its own. The condition drops the step, and gives it back - # by itself the day this repository goes public or gets Advanced Security. + # Results show up on the repository's Security tab. Advanced Security is + # enabled organisation-wide, so this works on a private repository too. - name: Upload zizmor results to GitHub Security - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 - if: ${{ !cancelled() && !github.event.repository.private }} + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + if: ${{ !cancelled() }} continue-on-error: true with: sarif_file: zizmor.sarif diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index b16d989..ef71b3d 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -32,7 +32,7 @@ jobs: permissions: contents: read container: - image: semgrep/semgrep@sha256:bdf7013b2c3634a487671158da77c554f531742326b543a9464d2adf6c433ac8 + image: semgrep/semgrep@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a # Skip Renovate PRs: no token available in that context if: github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' steps: @@ -77,9 +77,10 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} publish_results: false - # continue-on-error: upload fails for private repos without GitHub Advanced Security + # Results show up on the repository's Security tab. Advanced Security is + # enabled organisation-wide, so this works on a private repository too. - name: Upload results to GitHub Security - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 continue-on-error: true with: sarif_file: scorecard.sarif diff --git a/renovate.json b/renovate.json index c44a301..b2f768c 100644 --- a/renovate.json +++ b/renovate.json @@ -66,5 +66,8 @@ ], "automerge": false } - ] + ], + "dependencyDashboard": true, + "osvVulnerabilityAlerts": true, + "configMigration": true } From 1f150dfe9247b23964f1ea9814617b4c2a48726f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:35:55 +0000 Subject: [PATCH 2/8] Trek .editorconfig en Renovate-eigenaarschap gelijk .editorconfig bestond in vijf varianten over zeven repositories. De regels waren overal al hetzelfde; alleen de comments verschilden, deels Engels en deels Nederlands. Dit is de gedeelde versie, in het Nederlands. Renovate wees per repository iemand anders aan: vijf hadden Stensel8, een had Stensel8 en AdiH1310, en vier hadden helemaal niemand staan, waardoor dependency-PR's daar bij niemand terechtkwamen. Nu overal allebei. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .editorconfig | 27 +++++++++++++++++++++++++-- renovate.json | 6 ++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 561cd5e..7c42757 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,35 @@ +# EditorConfig houdt de opmaak gelijk tussen editors en IDE's. +# VS Code, JetBrains en anderen lezen dit bestand vanzelf. +# Meer info: https://editorconfig.org +# +# Deze versie is organisatiebreed gelijk. Wijk hier niet per repository van af: +# vijf licht verschillende varianten leverden alleen ruis op, geen voordeel. +# +# De configbestanden die zelf geen comments kunnen dragen omdat het JSON is, +# staan hier genoemd zodat er ergens een aanwijzing is wat ze doen: +# +# .htmlhintrc - HTML-linting (HTMLHint). Structuur- en toegankelijkheidsregels. +# renovate.json - Renovate-bot. Automatische dependency-updates via pull requests. + root = true [*] charset = utf-8 end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true indent_style = space indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true [*.md] +# Trailing whitespace betekent iets in Markdown (regeleinde) trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[*.py] +indent_size = 4 + +[*.sh] +indent_size = 4 diff --git a/renovate.json b/renovate.json index b2f768c..a1d191c 100644 --- a/renovate.json +++ b/renovate.json @@ -7,10 +7,12 @@ "forkProcessing": "enabled", "pinDigests": true, "assignees": [ - "Stensel8" + "Stensel8", + "AdiH1310" ], "reviewers": [ - "Stensel8" + "Stensel8", + "AdiH1310" ], "semanticCommits": "enabled", "gitIgnoredAuthors": [ From 37ac3964b2e35832ee2be0836844375944f92690 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:41:02 +0000 Subject: [PATCH 3/8] Zet het commentaar in de workflows om naar het Nederlands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De comments waren een mengeling: hugo-template en StenTijhuis stonden volledig in het Engels, LohuisHoveniers en Hagmolenpups door elkaar heen binnen hetzelfde bestand. Dat is nu overal Nederlands, gelijk aan de rest van de organisatie. Alleen commentaar. De workflowlogica is regel voor regel gecontroleerd en ongewijzigd: een diff met alle commentregels weggefilterd is leeg voor elk gewijzigd bestand, en elk bestand is opnieuw als YAML geparsed. De gedeelde bestanden zijn per stuk één keer vertaald en daarna gekopieerd, zodat config-validation.yml en setup-hugo/action.yml byte-identiek blijven over de repositories die ze delen. Twee comments klopten niet meer en zijn meegenomen: bij LohuisHoveniers stond een dubbele regel over de SARIF-upload, en op drie plekken beschreef het commentaar nog de private-guard die er inmiddels uit is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .github/actions/setup-hugo/action.yml | 10 +- .github/workflows/config-validation.yml | 40 ++++---- .github/workflows/deploy-bunny.yml | 2 +- .github/workflows/quality.yml | 129 ++++++++++++------------ .github/workflows/security.yml | 35 +++---- .github/workflows/update-checksums.yml | 43 ++++---- 6 files changed, 131 insertions(+), 128 deletions(-) diff --git a/.github/actions/setup-hugo/action.yml b/.github/actions/setup-hugo/action.yml index f52d0bf..55b8a9a 100644 --- a/.github/actions/setup-hugo/action.yml +++ b/.github/actions/setup-hugo/action.yml @@ -39,12 +39,12 @@ inputs: runs: using: composite steps: - # The release comes off the network, so nothing is executed before the - # checksum says it is the archive we pinned. + # De release komt van het netwerk, dus er wordt niets uitgevoerd voordat de + # checksum zegt dat het het archief is dat we hebben vastgezet. # - # --retry: the release CDN hands out an occasional 503, and without this a - # single one fails the whole build. curl retries 5xx and timeouts on its - # own; --retry-all-errors extends that to connection failures. + # --retry: de release-CDN geeft af en toe een 503, en zonder dit laat één + # zo'n antwoord de hele build falen. curl herhaalt 5xx en timeouts uit + # zichzelf; --retry-all-errors trekt dat door naar verbindingsfouten. - name: Download and verify Hugo shell: bash env: diff --git a/.github/workflows/config-validation.yml b/.github/workflows/config-validation.yml index 9615c5f..cab605e 100644 --- a/.github/workflows/config-validation.yml +++ b/.github/workflows/config-validation.yml @@ -2,9 +2,9 @@ # SPDX-License-Identifier: MIT name: Config validation -# The bot configs are the one part of CI that nothing else exercises: a broken -# renovate.json or dependabot.yml does not fail a build, it just quietly stops -# doing its job. This workflow is the thing that notices. +# De bot-configs zijn het enige deel van CI dat verder nergens door wordt +# geraakt: een kapotte renovate.json of dependabot.yml laat geen build falen, +# die houdt gewoon stilletjes op met zijn werk. Deze workflow merkt dat op. on: push: @@ -46,33 +46,33 @@ jobs: with: node-version: 'lts/*' - # Renovate's own validator. --strict also fails on warnings, such as an - # option that is valid but deprecated. Given no arguments it finds the - # config files itself and validates them as repository config; passing a - # path instead makes it validate them as global config, which is a - # different and weaker set of rules. + # Renovates eigen validator. --strict laat hem ook falen op warnings, + # bijvoorbeeld een optie die geldig maar verouderd is. Zonder argumenten + # zoekt hij de configbestanden zelf op en valideert hij ze als + # repository-config; geef je een pad mee, dan valideert hij ze als + # global config, en dat is een andere en zwakkere set regels. # - # Deliberately unpinned. This is a linter for our config, not something we - # ship, and the newest release is the one that knows about the newest - # deprecations. Its own version is not worth a pull request. + # Bewust niet vastgezet. Dit is een linter op onze eigen config en geen + # onderdeel van wat we uitleveren, en juist de nieuwste release kent de + # nieuwste deprecations. Zijn eigen versie is geen pull request waard. # - # NPM_CONFIG_LOGLEVEL: npm prints "npm warn deprecated ..." for packages - # deep inside Renovate's own dependency tree. Those say nothing about the - # config being validated, and reading them as if they did is the obvious - # mistake to make when they appear directly above the validator's output. + # NPM_CONFIG_LOGLEVEL: npm print "npm warn deprecated ..." voor packages + # diep in Renovates eigen dependency-boom. Die zeggen niets over de config + # die gevalideerd wordt, en ze lezen alsof dat wel zo is, is de fout die + # je vanzelf maakt als ze vlak boven de output van de validator staan. - name: Validate Renovate config env: NPM_CONFIG_LOGLEVEL: error run: npx --yes --package renovate -- renovate-config-validator --strict - # The validator above accepts a well-formed pattern that matches nothing, - # so this covers the gap it leaves. + # De validator hierboven accepteert een correct gevormd patroon dat + # nergens op matcht; dit dekt het gat dat hij daarmee laat. - name: Check Renovate file patterns run: python3 .github/scripts/check-renovate-patterns.py renovate.json .github/renovate.json - # GitHub validates dependabot.yml only after it is on the default branch, - # and reports the result on a tab nobody opens. This brings that forward - # to the pull request. + # GitHub valideert dependabot.yml pas als die op de default branch staat, + # en meldt het resultaat op een tabblad dat niemand opent. Dit haalt dat + # naar voren, naar de pull request. - name: Validate Dependabot config env: # renovate: datasource=pypi depName=check-jsonschema diff --git a/.github/workflows/deploy-bunny.yml b/.github/workflows/deploy-bunny.yml index ddd4f1b..2820f07 100644 --- a/.github/workflows/deploy-bunny.yml +++ b/.github/workflows/deploy-bunny.yml @@ -32,7 +32,7 @@ concurrency: group: deploy cancel-in-progress: true -# No token needed; jobs that do opt in explicitly. +# Geen token nodig; jobs die dat wel zijn, vragen er expliciet om. permissions: {} jobs: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c12d755..0c86b77 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -8,28 +8,28 @@ on: pull_request: branches: [main] -# No token needed; jobs that do opt in explicitly. +# Geen token nodig; jobs die dat wel zijn, vragen er expliciet om. permissions: {} -# Pushing three times in a row to the same PR started three full runs, and the -# first two are already stale by then. Not on main: there the run is the record -# that this commit passed the checks. +# Drie keer achter elkaar naar dezelfde PR pushen startte drie volledige runs, +# en de eerste twee zijn op dat moment al achterhaald. Op main niet annuleren: +# daar is de run de vastlegging dat die commit door de controles kwam. concurrency: group: quality-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - # Everything that needs the built site, in one job. + # Alles wat de gebouwde site nodig heeft, in één job. # - # This used to be three separate jobs: a build, HTMLHint on a second build, - # and lychee on an artifact of the first. That cost three runners and three - # Hugo setups, and GitHub rounds every job up to a whole minute -- three - # minutes billed for work that finishes in one. + # Dit waren drie losse jobs: een build, HTMLHint op een tweede build, en + # lychee op een artifact van de eerste. Dat kostte drie runners en drie keer + # Hugo opzetten, en GitHub rondt elke job naar boven af op een hele minuut -- + # drie minuten in rekening voor werk dat in één minuut klaar is. # - # In one job it saves more than those two minutes: the second build does not - # have to process the images again, because resources/_gen is already warm - # from the first. The artifact between build and link-check disappears with - # it, and with that the upload, the download and the storage. + # In één job levert het meer op dan die twee minuten: de tweede build hoeft + # de afbeeldingen niet opnieuw te verwerken, omdat resources/_gen er al staat + # van de eerste. Het artifact tussen build en linkcheck verdwijnt ermee, en + # daarmee ook de upload, de download en de opslag. site: name: Build and check the site runs-on: ubuntu-latest @@ -44,18 +44,19 @@ jobs: - name: Set up Hugo uses: ./.github/actions/setup-hugo - # A build that fails here is a broken deploy caught early. --panicOnWarning - # is stricter than the deploy build on purpose: a fresh Hugo deprecation - # should block a merge, not a release that is already on its way out. + # Een build die hier faalt is een kapotte deploy die op tijd gepakt is. + # --panicOnWarning is bewust strenger dan de deploy-build: een nieuwe + # Hugo-deprecation hoort een merge te blokkeren, niet een release die al + # onderweg is. - name: Build site working-directory: src env: TZ: Europe/Amsterdam run: hugo --minify --gc --printPathWarnings --panicOnWarning - # No --minify here: minifying drops the quotes around attribute values - # wherever it can, which is exactly what attr-value-double-quotes flags. - # It goes to its own directory so lychee below keeps the real output. + # Hier geen --minify: minifyen haalt de quotes rond attribuutwaarden weg + # waar dat kan, en dat is precies waar attr-value-double-quotes op slaat. + # Gaat naar een eigen map, zodat lychee hieronder de echte output houdt. - name: Build site unminified for HTMLHint working-directory: src run: hugo --gc --destination public-lint @@ -65,37 +66,38 @@ jobs: with: node-version: 'lts/*' - # `npm ci` against a checked-in package-lock.json (.github/htmlhint/), - # rather than `npm install -g htmlhint@version`: the latter resolves - # through the registry at run time with nothing to verify what comes - # back against, which is what Scorecard's Pinned-Dependencies check and - # zizmor's adhoc-packages audit both flag it for. `npm ci` installs - # exactly what the lockfile's integrity hashes say, and bumping the - # version is a normal Renovate PR against that lockfile. + # `npm ci` tegen een ingecheckte package-lock.json (.github/htmlhint/) in + # plaats van `npm install -g htmlhint@versie`: die tweede haalt op runtime + # iets uit de registry zonder dat er iets is om het resultaat tegen te + # verifiëren, en daar slaan zowel Scorecards Pinned-Dependencies als + # zizmors adhoc-packages op aan. `npm ci` installeert exact wat de + # integrity-hashes in de lockfile zeggen, en een versiebump is gewoon een + # Renovate-PR op die lockfile. - name: Install HTMLHint run: npm ci --prefix .github/htmlhint - # HTMLHint: structural errors, missing attributes, accessibility. Runs on - # the built output, not on the templates: a Hugo layout is not a - # standalone HTML document, so linting one only produces noise. + # HTMLHint: structuurfouten, ontbrekende attributen, toegankelijkheid. + # Draait op de gebouwde output en niet op de templates: een Hugo-layout is + # geen zelfstandig HTML-document, dus daarop linten geeft alleen ruis. - name: Check HTML run: .github/htmlhint/node_modules/.bin/htmlhint "src/public-lint/**/*.html" - # 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. + # Met de hand geïnstalleerd in plaats van via lycheeverse/lychee-action, + # dat zijn binary met een kale `curl -sfLO` ophaalt: geen retry, en geen + # controle op wat er terugkomt. # - # !cancelled() rather than success(): a broken HTMLHint rule should not - # hide the link check. You want both failures in one run, not the second - # only after you have fixed the first. + # !cancelled() en niet success(): een kapotte HTMLHint-regel hoort de + # linkcheck niet te verbergen. Je wilt beide fouten in één run zien, niet + # de tweede pas nadat je de eerste hebt opgelost. - name: Install lychee if: ${{ !cancelled() }} env: - # extractVersion: lychee tags its releases "lychee-v0.24.2", not - # "v0.24.2", so the default pattern cannot read the version out. + # extractVersion: lychee tagt zijn releases als "lychee-v0.24.2" en + # niet als "v0.24.2", dus het standaardpatroon leest de versie er niet + # uit. # renovate: datasource=github-releases depName=lycheeverse/lychee extractVersion=^lychee-v(?.+)$ LYCHEE_VERSION: "0.24.2" - # From the release's own lychee-x86_64-unknown-linux-gnu.tar.gz.sha256 + # Uit de lychee-x86_64-unknown-linux-gnu.tar.gz.sha256 van de release zelf LYCHEE_SHA256: "1f4e0ef7f6554a6ed33dd7ac144fb2e1bbed98598e7af973042fc5cd43951c9a" run: | curl -sSL --fail-with-body -o lychee.tar.gz \ @@ -105,35 +107,33 @@ jobs: 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 in offline mode: every internal href and src must resolve to a - # file the build actually produced. + # lychee in offline modus: elke interne href en src moet uitkomen op een + # bestand dat de build daadwerkelijk heeft opgeleverd. # - # --index-files: Hugo serves every page as /index.html, and without - # this lychee stops at the directory. That makes a link to /about/ pass as - # long as the directory exists, even if it is empty, and makes #fragments - # into another page impossible to resolve. + # --index-files: Hugo serveert elke pagina als /index.html, en + # zonder dit stopt lychee bij de map. Daardoor slaagt een link naar + # /about/ zolang die map bestaat, ook als hij leeg is, en zijn #fragments + # naar een andere pagina niet te controleren. # - # The glob is quoted deliberately: unquoted, bash expands it first, and - # without globstar ** collapses to a single directory level. + # De glob staat bewust tussen quotes: zonder quotes vult bash hem eerst + # in, en zonder globstar valt ** terug op één mapniveau. - 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 and the - # workflow files. None of it needs Hugo. + # Alles wat niets met de gebouwde site te maken heeft: de Markdown en de + # workflowbestanden. Daar is Hugo nergens voor nodig. # - # Every step runs on !cancelled(), so one red linter does not hide the others. - # The job still fails as soon as anything is wrong. + # Elke stap draait op !cancelled(), zodat één rode linter de andere niet + # verbergt. De job faalt alsnog zodra er iets fout is. repo: name: Check the repository runs-on: ubuntu-latest permissions: contents: read - # For zizmor's SARIF upload below. That step skips itself while the - # repository is private; the permission is here so it starts working by - # itself the day that changes. + # Voor de SARIF-upload van zizmor hieronder. security-events: write steps: - name: Check out source code @@ -141,7 +141,7 @@ jobs: with: persist-credentials: false - # markdownlint: keeps content/ and the repo's own Markdown consistent. + # markdownlint: houdt content/ en de Markdown van de repo zelf consistent. - name: Run markdownlint if: ${{ !cancelled() }} uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0 @@ -150,8 +150,8 @@ jobs: src/content/**/*.md *.md - # Pinned release plus checksum, rather than piping a script from a branch - # straight into bash. + # Vastgezette release plus checksum, in plaats van een script vanaf een + # branch rechtstreeks door bash halen. - name: Install actionlint if: ${{ !cancelled() }} env: @@ -166,17 +166,17 @@ jobs: tar -xzf actionlint.tar.gz actionlint sudo install -m 0755 actionlint /usr/local/bin/actionlint - # actionlint: syntax errors and incorrect expressions in the workflows. + # actionlint: syntaxfouten en onjuiste expressies in de workflows. - name: Run actionlint if: ${{ !cancelled() }} run: actionlint -color - # zizmor used to be its own job in security.yml. It is a linter on the - # same files as actionlint, only on security rather than syntax, so it - # belongs next to it instead of on a runner of its own. + # zizmor stond als eigen job in security.yml. Het is een linter op + # dezelfde bestanden als actionlint, alleen op beveiliging in plaats van + # syntax, dus hij hoort hiernaast en niet op een eigen runner. # - # pipx and not pip: the runner's system Python is externally managed - # (PEP 668), so a plain pip install aborts. + # pipx en niet pip: de systeem-Python van de runner is externally managed + # (PEP 668), dus een gewone pip install breekt af. - name: Install zizmor if: ${{ !cancelled() }} env: @@ -188,8 +188,9 @@ jobs: if: ${{ !cancelled() }} run: zizmor --format sarif . > zizmor.sarif || true - # Results show up on the repository's Security tab. Advanced Security is - # enabled organisation-wide, so this works on a private repository too. + # De resultaten verschijnen op het Security-tabblad van de repository. + # Advanced Security staat organisatiebreed aan, dus dit werkt ook op een + # privérepository. - name: Upload zizmor results to GitHub Security uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 if: ${{ !cancelled() }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ef71b3d..6c2b852 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -7,25 +7,25 @@ on: branches: [main] pull_request: branches: [main] - # Weekly scan to catch new vulnerabilities in existing code + # Wekelijkse scan, om nieuwe kwetsbaarheden in bestaande code te vinden schedule: - cron: '0 5 * * 1' - # So Scorecard can be run on demand; it no longer runs on pull requests + # Zodat Scorecard op verzoek te draaien is; hij loopt niet meer op PR's workflow_dispatch: permissions: {} -# Pushing three times in a row to the same PR started three full scans, and the -# first two are already stale by then. Not on main: there the run is the record -# that this commit was scanned. +# Drie keer achter elkaar naar dezelfde PR pushen startte drie volledige scans, +# en de eerste twee zijn op dat moment al achterhaald. Op main niet annuleren: +# daar is de run de vastlegging dat die commit gescand is. concurrency: group: security-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - # Semgrep: SAST scanner for XSS and other vulnerabilities in the templates - # and the little JavaScript the site ships + # Semgrep: SAST-scanner voor XSS en andere kwetsbaarheden in de templates en + # het beetje JavaScript dat de site meelevert semgrep: name: Semgrep SAST scan runs-on: ubuntu-latest @@ -33,7 +33,7 @@ jobs: contents: read container: image: semgrep/semgrep@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a - # Skip Renovate PRs: no token available in that context + # Renovate-PR's overslaan: daar is geen token beschikbaar if: github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' steps: - name: Check out source code @@ -41,19 +41,19 @@ jobs: with: persist-credentials: false - # Exclusions live in .semgrepignore + # Uitzonderingen staan in .semgrepignore - name: Run Semgrep scan run: semgrep scan --config auto --error src/layouts/ src/assets/ - # OpenSSF Scorecard: evaluates the security hygiene of the repository - # Checks: branch protection, pinned dependencies, code review requirements + # OpenSSF Scorecard: beoordeelt de beveiligingshygiëne van de repository. + # Kijkt naar branch protection, vastgezette dependencies en code review. scorecard: name: OpenSSF Scorecard runs-on: ubuntu-latest - # Weekly and on main, not on every pull request. Scorecard rates the - # repository's hygiene -- branch protection, pinned dependencies, code - # review -- and none of that changes per commit. Running it on every PR - # cost a runner for an outcome that was the same anyway. + # Wekelijks en op main, niet op elke pull request. Scorecard beoordeelt de + # hygiëne van de repository -- branch protection, vastgezette dependencies, + # code review -- en dat verandert niet per commit. Op elke PR draaien kostte + # een runner voor een uitkomst die toch hetzelfde was. if: >- github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || @@ -77,8 +77,9 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} publish_results: false - # Results show up on the repository's Security tab. Advanced Security is - # enabled organisation-wide, so this works on a private repository too. + # De resultaten verschijnen op het Security-tabblad van de repository. + # Advanced Security staat organisatiebreed aan, dus dit werkt ook op een + # privérepository. - name: Upload results to GitHub Security uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 continue-on-error: true diff --git a/.github/workflows/update-checksums.yml b/.github/workflows/update-checksums.yml index 4a5feb4..7a2250c 100644 --- a/.github/workflows/update-checksums.yml +++ b/.github/workflows/update-checksums.yml @@ -2,14 +2,14 @@ # SPDX-License-Identifier: MIT name: Update tool SHA256 checksums -# Renovate bumps the pinned tool versions but cannot compute a checksum, so on -# its own every bump lands with the previous release's hash still in place and -# the build stops at "computed checksum did NOT match". This recalculates the -# hashes on Renovate's pull requests and commits them back onto the branch. +# Renovate bumpt de vastgezette toolversies maar kan geen checksum berekenen, +# dus op eigen kracht landt elke bump met de hash van de vorige release er nog +# in, en stopt de build op "computed checksum did NOT match". Dit herberekent +# de hashes op Renovates pull requests en commit ze terug op de branch. # -# Renovate must be told to ignore those commits, or it treats the branch as -# modified by someone else and stops maintaining the pull request. That is the -# gitIgnoredAuthors entry in renovate.json. +# Renovate moet die commits leren negeren, anders ziet hij de branch als door +# iemand anders gewijzigd en onderhoudt hij de pull request niet meer. Dat is +# de gitIgnoredAuthors-regel in renovate.json. on: pull_request: @@ -26,33 +26,34 @@ jobs: update-checksums: name: Recalculate SHA256 checksums runs-on: ubuntu-latest - # Only Renovate's own branches. Running this on a human's pull request - # would mean pushing commits to a branch someone is actively working on. + # Alleen Renovates eigen branches. Dit op een pull request van een mens + # draaien betekent commits pushen naar een branch waar iemand op dat moment + # aan werkt. # - # The author of the pull request, not github.actor. actor is whoever - # triggered the most recent event, which on a synchronize is whoever pushed - # last; comparing that to a bot name is a check zizmor rightly calls - # spoofable. The author is fixed when the pull request is opened and cannot - # be set to another account. + # De auteur van de pull request, niet github.actor. actor is degene die het + # meest recente event veroorzaakte, en dat is bij een synchronize degene die + # als laatste pushte; dat vergelijken met een botnaam is een controle die + # zizmor terecht spoofbaar noemt. De auteur ligt vast zodra de pull request + # geopend wordt en is niet naar een ander account te zetten. if: >- startsWith(github.head_ref, 'renovate/') && github.event.pull_request.user.login == 'renovate[bot]' permissions: contents: write steps: - # persist-credentials: false, even though this job pushes. Otherwise the - # token sits in .git/config for the whole job, including while the script - # below downloads release tarballs off the internet. The push step gets - # the token explicitly instead, for exactly one command. + # persist-credentials: false, ook al pusht deze job. Anders staat het + # token de hele job in .git/config, ook terwijl het script hieronder + # release-tarballs van internet haalt. De push-stap krijgt het token in + # plaats daarvan expliciet mee, voor precies één commando. - name: Check out the pull request branch uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.head_ref }} persist-credentials: false - # The script verifies each download against the checksum the project - # publishes next to the release before writing anything, so a hash only - # lands here if upstream vouches for it too. + # Het script controleert elke download tegen de checksum die het project + # naast de release publiceert voordat er iets wordt weggeschreven, dus een + # hash landt hier alleen als upstream er ook voor instaat. - name: Recalculate and apply checksums run: .github/scripts/update-tool-checksums.sh --apply From a64e96ffaaf44aabb7466609e8923c6f34065d67 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:49:56 +0000 Subject: [PATCH 4/8] chore: dwing Conventional Commits af op elke pull request Alleen Zephyrus-Linux controleerde of de titel van een pull request het Conventional Commits-formaat volgt. Dat is nu overal een aparte workflow, byte-identiek en met dezelfde jobnaam, zodat de check in elke repository "Conventional commit title" heet. De titel en niet de losse commits: de titel is wat er op main terechtkomt zodra je squasht. Renovate levert zijn eigen titels al in dit formaat aan via semanticCommits, wat in alle negen repositories al aanstond; deze controle dekt de rest. De check draait ook op `edited`. Zonder dat blijft hij rood staan nadat iemand de titel heeft verbeterd, want een titelwijziging is geen nieuwe push -- een gat dat in de opzet van Zephyrus-Linux zat. In Zephyrus-Linux verhuist de bestaande job uit pr-checks.yml naar deze workflow. De jobnaam blijft gelijk, dus een branch protection-regel die op die check wacht blijft werken. De job daar meeverhuizen was nodig omdat `types` voor een hele workflow geldt: hem in pr-checks.yml zetten had de Hugo-build en de bandit-scan laten draaien zodra iemand een PR-omschrijving aanpast. CONTRIBUTING.md in hugo-template en LohuisHoveniers.nl documenteerde zes types terwijl de check er acht accepteert. `content` en `revert` stonden er niet in, dus die leken ongeldig terwijl ze dat niet zijn. Nu gelijk aan Zephyrus-Linux, dat ze wel allemaal noemde. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .github/workflows/pr-title.yml | 39 ++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 ++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/pr-title.yml diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..38d4989 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,39 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: PR title + +# De titel van de pull request is wat er op main terechtkomt zodra je squasht, +# dus dat is de plek waar Conventional Commits gecontroleerd moet worden en niet +# op de losse commits in de branch. +# +# Renovate levert zijn eigen titels al in dit formaat aan; dat is de +# semanticCommits-instelling in renovate.json. Deze controle dekt de rest. + +on: + pull_request: + # edited hoort erbij: zonder dat blijft de check rood staan nadat iemand de + # titel heeft verbeterd, want een titelwijziging is geen nieuwe push. + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + pr-title: + name: Conventional commit title + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + content + docs + chore + refactor + style + revert diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9987e4c..68b3e85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,8 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/). | `chore` | Maintenance — dependencies, CI/CD, config | | `style` | Formatting, whitespace, typo fixes | | `refactor` | Restructure without changing behavior (e.g. rename files, move sections) | +| `content` | Update or improve existing page content | +| `revert` | Reverting a previous commit | **Examples:** From b5bcf18f6da31c21c8dbe7cd054e80a628957857 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 12:08:49 +0000 Subject: [PATCH 5/8] docs: geef elke repository dezelfde bijdrage-ervaring Wie een pull request opende kreeg per repository iets anders te zien. Vier van de negen hadden een PR-template en die drie verschilden onderling in opbouw en taal; vier hadden een CODEOWNERS, drie een CONTRIBUTING en vijf een SECURITY.md. Nu heeft elke repository alle vier, met hetzelfde skelet: Samenvatting, Type wijziging, Checklist. De eerste 24 regels van de template zijn byte-identiek in alle acht Nederlandstalige repositories; Zephyrus-Linux houdt zijn Engelse versie omdat dat project tweetalig is en een EN/NL- pariteitscontrole heeft. Wat per repository verschilt, is de checklist eronder, en alleen omdat de stack verschilt: Hugo-sites vragen om `hugo server`, Tijhuis-Tuinen om PHP en welke van de twee sites je raakt, Web-Dev om `docker compose up`, en Blocklists om het lijstformaat en een bron. Een Hugo-vraag stellen in een PHP-repository helpt niemand. De "Type wijziging"-lijst komt overeen met de acht types die de controle "Conventional commit title" accepteert en die CONTRIBUTING.md beschrijft. Zephyrus-Linux miste `revert` in zijn template terwijl de check die wel toestond; dat is rechtgezet. Een nieuwe workflow pr-checklist.yml vinkt de twee punten af die uit de pull request zelf af te leiden zijn en ruimt de niet-gekozen types op. Anders dan de variant in Zephyrus-Linux gebeurt dat opruimen alleen als er al een type is aangevinkt: zonder die voorwaarde stript de eerste run alle acht regels weg voordat de auteur er een kon kiezen. Zephyrus-Linux houdt zijn eigen, uitgebreidere job die daarnaast EN/NL-pariteit, AVIF en de linkcheck afvinkt. CODEOWNERS is overal `* @Stensel8`, de vorm die de vier bestaande al gebruikten. Bewust niet AdiH1310 erbij: waar "Require review from Code Owners" aanstaat zou dat een merge blokkeren op iemand die niet in elke repository actief is. Voor Renovate staan ze wel allebei als reviewer. Gecontroleerd met actionlint 1.7.12 (alle negen schoon) en met markdownlint tegen de eigen configuratie van de drie repositories die root-Markdown linten (nul issues). De herschrijflogica van de checklist is apart getest op vijf gevallen, waaronder een lege omschrijving en of een tweede run niets meer verandert. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .github/CODEOWNERS | 2 + .github/pull_request_template.md | 28 ++++++++++++ .github/workflows/pr-checklist.yml | 73 ++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/pr-checklist.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..aaeb137 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owner +* @Stensel8 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..52adcfc --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ +## Samenvatting + + + +## Type wijziging + + + +- [ ] `feat` — nieuwe pagina, sectie of functionaliteit +- [ ] `fix` — bugfix: kapotte link, verkeerde configuratie, renderfout +- [ ] `content` — bestaande inhoud bijwerken of verbeteren +- [ ] `docs` — README, CONTRIBUTING of andere metabestanden +- [ ] `chore` — onderhoud: dependencies, CI/CD, configuratie +- [ ] `refactor` — herstructurering zonder gedragsverandering +- [ ] `style` — opmaak, witruimte, typefouten +- [ ] `revert` — een eerdere commit terugdraaien + +## Checklist + +- [ ] PR-titel volgt de commit-conventie (`type: korte omschrijving`) +- [ ] Geen secrets, tokens of persoonsgegevens in de diff +- [ ] `hugo server` lokaal gedraaid vanuit `src/` en de pagina bekeken +- [ ] Licht én donker thema gecontroleerd, en op een smal scherm +- [ ] Geen gebroken links of verwijzingen naar bestanden die niet bestaan +- [ ] Versiebump van Hugo, actionlint of lychee? De bijbehorende SHA-256 ook bijgewerkt diff --git a/.github/workflows/pr-checklist.yml b/.github/workflows/pr-checklist.yml new file mode 100644 index 0000000..a98a385 --- /dev/null +++ b/.github/workflows/pr-checklist.yml @@ -0,0 +1,73 @@ +# Copyright (C) 2026 Sten Tijhuis +# SPDX-License-Identifier: MIT +name: PR checklist + +# Vinkt de twee punten af die uit de pull request zelf af te leiden zijn, en +# ruimt de niet-aangevinkte regels onder "Type wijziging" op. De rest van de +# checklist blijft handwerk: of iemand de pagina echt in de browser heeft +# bekeken, valt hier niet te controleren. +# +# Zephyrus-Linux heeft een eigen, uitgebreidere versie van deze job in +# pr-checks.yml die daarnaast nog EN/NL-pariteit, AVIF en de linkcheck afvinkt. +# Die blijft daar staan; deze workflow draait daar niet. + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: {} + +jobs: + update-checklist: + name: Update PR checklist + runs-on: ubuntu-latest + # pull-requests: write om de omschrijving te herschrijven. + permissions: + pull-requests: write + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + let body = pr.body || ''; + if (!body.trim()) return; + + const setCheck = (keyword, passed) => { + body = body.replace( + new RegExp(`- \\[[ xX]\\] (.*${keyword}.*)`, 'i'), + `- [${passed ? 'x' : ' '}] $1` + ); + }; + + // Dezelfde typelijst als pr-title.yml en CONTRIBUTING.md. Scope en + // een `!` voor een breaking change zijn toegestaan: feat(nav)!: ... + const TITLE_RE = + /^(feat|fix|content|docs|chore|refactor|style|revert)(\([^)]+\))?!?: .+/; + setCheck('PR-titel volgt', TITLE_RE.test(pr.title)); + + // De niet-gekozen types weghalen, maar alleen als er al een gekozen + // is. Zonder die voorwaarde stript de eerste run alle acht regels + // weg voordat de auteur er een heeft aangevinkt, en is de sectie + // weg voordat hij gebruikt kon worden. + const TYPE_LINE = /^- \[([ xX])\] `\w+` —[^\n]*\n?/gm; + const ticked = [...body.matchAll(TYPE_LINE)] + .some(m => m[1].toLowerCase() === 'x'); + if (ticked) { + body = body.replace(/^- \[ \] `\w+` —[^\n]*\n?/gm, ''); + } + + body = body.replace(/\n{3,}/g, '\n\n'); + + if (body !== pr.body) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + body, + }); + } From 15d0193d04018f698a3b018f300f1c7b5cde5127 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 12:23:15 +0000 Subject: [PATCH 6/8] =?UTF-8?q?chore:=20laat=20=C3=A9=C3=A9n=20van=20beide?= =?UTF-8?q?=20eigenaren=20een=20merge=20kunnen=20vrijgeven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CODEOWNERS stond op alleen @Stensel8, met als redenering dat een tweede naam een merge zou kunnen ophouden. Dat was onjuist: staan er meerdere eigenaren op hetzelfde pad, dan vraagt GitHub ze allebei om review maar is de goedkeuring van één van hen genoeg om aan "Require review from Code Owners" te voldoen. Beide namen erop betekent dus niet meer wachten maar minder: het werk ligt niet stil als een van de twee er even niet is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .github/CODEOWNERS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aaeb137..867b553 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,4 @@ -# Default owner -* @Stensel8 +# Beide eigenaren worden om review gevraagd, maar de goedkeuring van één van +# hen is genoeg om de merge vrij te geven. Zo ligt het werk niet stil als de +# ander er even niet is. +* @Stensel8 @AdiH1310 From c94af1e3e4ac4b19573f1c84a5f0560ed21ffedf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 17:45:31 +0000 Subject: [PATCH 7/8] =?UTF-8?q?ci:=20voeg=20de=20twee=20PR-controles=20sam?= =?UTF-8?q?en=20tot=20=C3=A9=C3=A9n=20gefactureerde=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De vorige commits zetten de titelcontrole en het bijwerken van de checklist in twee losse workflows. Dat was precies de fout die commit 530ba29 in THectic.nl al had rechtgezet: GitHub rekent per job en rondt elke job naar boven af op een hele minuut. Beide stappen zijn in een paar seconden klaar en hebben geen checkout nodig, dus als losse jobs kostten ze twee volle minuten voor tien seconden werk. In één job is dat één minuut. Dat scheelt een gefactureerde minuut per pull request-event, in acht repositories. Zephyrus-Linux hield zijn eigen, uitgebreidere checklist-job in pr-checks.yml en verandert hier alleen door de concurrency-groep. Die concurrency-groep is de tweede besparing: snel achter elkaar de omschrijving aanpassen startte evenveel runs, terwijl alleen de laatste nog iets zegt. cancel-in-progress ruimt de rest op. De job draagt nu `pull-requests: write` terwijl de titelcontrole aan lezen genoeg heeft. Dat is de prijs van het samenvoegen; beide actions staan op een vastgezette SHA, dus dat is hier een acceptabele ruil. Gecontroleerd met actionlint 1.7.12 (alle negen schoon) en de herschrijflogica opnieuw getest op zes gevallen, waaronder een lege omschrijving en of een tweede run niets meer verandert. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XRouUjCEayFPybaPnv6GGu --- .github/workflows/pr-checklist.yml | 73 -------------------------- .github/workflows/pr-title.yml | 82 +++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 79 deletions(-) delete mode 100644 .github/workflows/pr-checklist.yml diff --git a/.github/workflows/pr-checklist.yml b/.github/workflows/pr-checklist.yml deleted file mode 100644 index a98a385..0000000 --- a/.github/workflows/pr-checklist.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2026 Sten Tijhuis -# SPDX-License-Identifier: MIT -name: PR checklist - -# Vinkt de twee punten af die uit de pull request zelf af te leiden zijn, en -# ruimt de niet-aangevinkte regels onder "Type wijziging" op. De rest van de -# checklist blijft handwerk: of iemand de pagina echt in de browser heeft -# bekeken, valt hier niet te controleren. -# -# Zephyrus-Linux heeft een eigen, uitgebreidere versie van deze job in -# pr-checks.yml die daarnaast nog EN/NL-pariteit, AVIF en de linkcheck afvinkt. -# Die blijft daar staan; deze workflow draait daar niet. - -on: - pull_request: - types: [opened, edited, synchronize, reopened] - -permissions: {} - -jobs: - update-checklist: - name: Update PR checklist - runs-on: ubuntu-latest - # pull-requests: write om de omschrijving te herschrijven. - permissions: - pull-requests: write - steps: - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - }); - - let body = pr.body || ''; - if (!body.trim()) return; - - const setCheck = (keyword, passed) => { - body = body.replace( - new RegExp(`- \\[[ xX]\\] (.*${keyword}.*)`, 'i'), - `- [${passed ? 'x' : ' '}] $1` - ); - }; - - // Dezelfde typelijst als pr-title.yml en CONTRIBUTING.md. Scope en - // een `!` voor een breaking change zijn toegestaan: feat(nav)!: ... - const TITLE_RE = - /^(feat|fix|content|docs|chore|refactor|style|revert)(\([^)]+\))?!?: .+/; - setCheck('PR-titel volgt', TITLE_RE.test(pr.title)); - - // De niet-gekozen types weghalen, maar alleen als er al een gekozen - // is. Zonder die voorwaarde stript de eerste run alle acht regels - // weg voordat de auteur er een heeft aangevinkt, en is de sectie - // weg voordat hij gebruikt kon worden. - const TYPE_LINE = /^- \[([ xX])\] `\w+` —[^\n]*\n?/gm; - const ticked = [...body.matchAll(TYPE_LINE)] - .some(m => m[1].toLowerCase() === 'x'); - if (ticked) { - body = body.replace(/^- \[ \] `\w+` —[^\n]*\n?/gm, ''); - } - - body = body.replace(/\n{3,}/g, '\n\n'); - - if (body !== pr.body) { - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.pull_request.number, - body, - }); - } diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 38d4989..95ffd5f 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -2,12 +2,20 @@ # SPDX-License-Identifier: MIT name: PR title -# De titel van de pull request is wat er op main terechtkomt zodra je squasht, -# dus dat is de plek waar Conventional Commits gecontroleerd moet worden en niet -# op de losse commits in de branch. +# Twee controles op de pull request zelf, bewust in één job. # -# Renovate levert zijn eigen titels al in dit formaat aan; dat is de -# semanticCommits-instelling in renovate.json. Deze controle dekt de rest. +# GitHub rekent per job en rondt elke job naar boven af op een hele minuut. +# Beide stappen hieronder zijn in een paar seconden klaar en hebben geen +# checkout nodig, dus als losse jobs kosten ze twee volle minuten voor werk +# van tien seconden. In één job is dat één minuut. +# +# Dat de job `pull-requests: write` heeft terwijl de titelcontrole aan lezen +# genoeg heeft, is de prijs van die samenvoeging. Beide actions staan op een +# vastgezette SHA, dus dat is hier een acceptabele ruil. +# +# De titel en niet de losse commits: de titel is wat er op main terechtkomt +# zodra je squasht. Renovate levert zijn eigen titels al in dit formaat aan; +# dat is de semanticCommits-instelling in renovate.json. on: pull_request: @@ -15,6 +23,12 @@ on: # titel heeft verbeterd, want een titelwijziging is geen nieuwe push. types: [opened, edited, synchronize, reopened] +# Snel achter elkaar de omschrijving aanpassen startte evenveel runs. Alleen de +# laatste zegt nog iets, dus de rest mag weg. +concurrency: + group: pr-title-${{ github.event.pull_request.number }} + cancel-in-progress: true + permissions: {} jobs: @@ -22,7 +36,7 @@ jobs: name: Conventional commit title runs-on: ubuntu-latest permissions: - pull-requests: read + pull-requests: write steps: - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: @@ -37,3 +51,59 @@ jobs: refactor style revert + + # Vinkt de twee punten af die uit de pull request zelf af te leiden zijn, + # en ruimt de niet-gekozen regels onder "Type wijziging" op. De rest van + # de checklist blijft handwerk: of iemand de pagina echt in de browser + # heeft bekeken, valt hier niet te controleren. + # + # Draait op !cancelled(), zodat een afgekeurde titel het opruimen niet + # tegenhoudt -- juist dan wil je de checklist bijgewerkt zien. + - name: Update PR checklist + if: ${{ !cancelled() }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + let body = pr.body || ''; + if (!body.trim()) return; + + const setCheck = (keyword, passed) => { + body = body.replace( + new RegExp(`- \\[[ xX]\\] (.*${keyword}.*)`, 'i'), + `- [${passed ? 'x' : ' '}] $1` + ); + }; + + // Dezelfde typelijst als hierboven en als CONTRIBUTING.md. Scope en + // een `!` voor een breaking change zijn toegestaan: feat(nav)!: ... + const TITLE_RE = + /^(feat|fix|content|docs|chore|refactor|style|revert)(\([^)]+\))?!?: .+/; + setCheck('PR-titel volgt', TITLE_RE.test(pr.title)); + + // De niet-gekozen types weghalen, maar alleen als er al een gekozen + // is. Zonder die voorwaarde stript de eerste run alle acht regels + // weg voordat de auteur er een heeft aangevinkt, en is de sectie + // weg voordat hij gebruikt kon worden. + const TYPE_LINE = /^- \[([ xX])\] `\w+` —[^\n]*\n?/gm; + const ticked = [...body.matchAll(TYPE_LINE)] + .some(m => m[1].toLowerCase() === 'x'); + if (ticked) { + body = body.replace(/^- \[ \] `\w+` —[^\n]*\n?/gm, ''); + } + + body = body.replace(/\n{3,}/g, '\n\n'); + + if (body !== pr.body) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + body, + }); + } From 70605ab0e4d151393afe502157d722e10fd74583 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 18:11:33 +0000 Subject: [PATCH 8/8] =?UTF-8?q?chore:=20=C3=A9=C3=A9n=20security.yml=20en?= =?UTF-8?q?=20=C3=A9=C3=A9n=20markdownlint-config=20voor=20alle=20Hugo-rep?= =?UTF-8?q?ositories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit security.yml is nu in alle vijf de Hugo-repositories hetzelfde bestand, op de regel met het semgrep-scanpad na. Dat pad verschilt terecht: niet elke repository heeft dezelfde mappenstructuur. Wat hier verschilde was alleen de plaatsing van permissies en de formulering van het commentaar. Dat leest als betekenisverschil terwijl het er geen is, en juist bij een securityworkflow wil je in één oogopslag kunnen zien dat vijf repositories hetzelfde doen. .markdownlint.yml gaat om dezelfde reden mee: het commentaar naar het Nederlands, en MD033 erbij zodat het bestand ook bruikbaar is in de repositories die
in hun README of
in hun content gebruiken. Hier stond alleen MD060 al uit, daar week deze van de rest af. --- .github/workflows/security.yml | 15 ++++++++++----- .markdownlint.yml | 22 +++++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 6c2b852..429a127 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -13,6 +13,7 @@ on: # Zodat Scorecard op verzoek te draaien is; hij loopt niet meer op PR's workflow_dispatch: +# Geen token nodig; jobs die dat wel zijn, vragen er expliciet om. permissions: {} # Drie keer achter elkaar naar dezelfde PR pushen startte drie volledige scans, @@ -25,7 +26,13 @@ concurrency: jobs: # Semgrep: SAST-scanner voor XSS en andere kwetsbaarheden in de templates en - # het beetje JavaScript dat de site meelevert + # het beetje JavaScript dat de site meelevert. + # + # De enige job hier die op een pull request draait. Scorecard hieronder niet: + # die beoordeelt de repository en niet de commit, dus per PR draaien leverde + # een runner op voor een uitkomst die toch hetzelfde was. zizmor is een stap + # in de repo-job van quality.yml geworden, om diezelfde reden: die job stond + # er al, met dezelfde checkout, dus daar kost hij geen minuut extra. semgrep: name: Semgrep SAST scan runs-on: ubuntu-latest @@ -50,10 +57,8 @@ jobs: scorecard: name: OpenSSF Scorecard runs-on: ubuntu-latest - # Wekelijks en op main, niet op elke pull request. Scorecard beoordeelt de - # hygiëne van de repository -- branch protection, vastgezette dependencies, - # code review -- en dat verandert niet per commit. Op elke PR draaien kostte - # een runner voor een uitkomst die toch hetzelfde was. + # Wekelijks en op main, niet op elke pull request. Zie de toelichting bij + # semgrep hierboven. if: >- github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || diff --git a/.markdownlint.yml b/.markdownlint.yml index 81b2b3d..bed9743 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,19 +1,27 @@ default: true -# ── Disabled: not applicable to this repo ────────────────────────────────── +# ── Uitgezet: niet van toepassing op deze repositories ────────────────────── -# Line length — content is prose-wrapped by hand, not to a column limit +# Regellengte -- de tekst wordt met de hand afgebroken op de zin, niet op een +# kolombreedte. MD013: false -# First line must be a top-level heading — content files start with front matter +# Inline HTML -- bewust gebruikt en niet te vervangen door Markdown:
+# en voor de inklapbare secties in de README, en
in de +# contactgegevens omdat dat het juiste semantische element is. +MD033: false + +# Eerste regel moet een kop op het hoogste niveau zijn -- contentbestanden +# beginnen met front matter. MD041: false -# Bare URLs — used in code blocks and command examples in the README +# Kale URL's -- gebruikt in codeblokken en commandovoorbeelden in de README. MD034: false -# Code block language — the README's blocks are all shell, tagging is optional +# Taal bij een codeblok -- de blokken in de README zijn allemaal shell, dat +# labelen voegt niets toe. MD040: false -# Table column style (pipe spacing/alignment) — overly pedantic, tables render -# correctly regardless of exact pipe spacing +# Kolomstijl in tabellen (spaties rond de pipes) -- overdreven streng, tabellen +# renderen hoe dan ook goed. MD060: false