From 55a8e1acee8109169d96426c5c4bab49c8dd0b5a Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 1 Jun 2026 15:28:28 -0500 Subject: [PATCH 01/10] Add reusable checks.yml (pre-commit, zizmor, lychee) --- .github/workflows/checks.yml | 115 +++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/checks.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..b53a5722 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,115 @@ +name: Checks + +on: + workflow_call: + inputs: + run-pre-commit: + description: "Run the pre-commit job" + default: true + required: false + type: boolean + run-zizmor: + description: "Run the zizmor job" + default: true + required: false + type: boolean + run-lychee: + description: "Run the lychee link-check job" + default: true + required: false + type: boolean + python-version: + description: "Python version for pre-commit (used when python-version-file is empty)" + default: "3.x" + required: false + type: string + python-version-file: + description: "Path to a file specifying the Python version (takes precedence over python-version)" + default: "" + required: false + type: string + lychee-args: + description: "Arguments passed to lychee" + default: "--cache --no-progress --max-cache-age 1d **/*.md" + required: false + type: string + +defaults: + run: + shell: bash + +# Security policy: No ${{ }} expressions in `run:` blocks. All expression values +# are assigned to `env:` and referenced as shell variables. This keeps the rule +# enforceable by zizmor without per-expression exceptions. + +jobs: + pre-commit: + name: Pre-commit + if: inputs.run-pre-commit + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + # Two conditional setup-python steps avoid the python-version vs + # python-version-file precedence ambiguity: pass exactly one input. + - name: Set up Python (version) + if: inputs.python-version-file == '' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ inputs.python-version }} + + - name: Set up Python (version file) + if: inputs.python-version-file != '' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version-file: ${{ inputs.python-version-file }} + + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + env: + SKIP: no-commit-to-branch + + zizmor: + name: Zizmor + if: inputs.run-zizmor + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + + lychee: + name: Lychee + if: inputs.run-lychee + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Restore lychee cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .lycheecache + key: cache-lychee-${{ github.sha }} + restore-keys: cache-lychee- + + - name: Check links + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 + with: + args: ${{ inputs.lychee-args }} + fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8c15b14cde7907fe7017e013c5bcc47278722583 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 1 Jun 2026 15:31:37 -0500 Subject: [PATCH 02/10] Consolidate lint and zizmor into checks.yml caller --- .github/workflows/ci.yml | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1a4c11d..3e8f01be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,38 +25,29 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 needs: - - lint + - checks - test - bakery - bakery-native - bakery-pr - release - pypi-publish - - zizmor steps: - uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: - allowed-skips: bakery-pr, lint, pypi-publish + allowed-skips: bakery-pr, checks, pypi-publish jobs: ${{ toJSON(needs) }} - lint: - name: Lint + checks: + name: Checks if: github.event_name == 'pull_request' || github.event_name == 'merge_group' - runs-on: ubuntu-latest - timeout-minutes: 10 permissions: contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version-file: posit-bakery/pyproject.toml - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 - env: - SKIP: no-commit-to-branch + security-events: write + uses: ./.github/workflows/checks.yml + with: + python-version-file: posit-bakery/pyproject.toml test: name: Test @@ -180,18 +171,6 @@ jobs: context: "./posit-bakery/test/resources/multiplatform/" dev-versions: include - zizmor: - name: Zizmor - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 - with-macros-clean-caches: name: Clean Caches (with-macros suite) permissions: From 5238014cdf8b3660e6e323edabfe8a5aaf6fb65e Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 1 Jun 2026 15:33:42 -0500 Subject: [PATCH 03/10] Add .lycheeignore for lychee link checking --- .lycheeignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .lycheeignore diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 00000000..1d1220ce --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,12 @@ +# Hosts that rate-limit, require auth, or otherwise false-positive. +# One regex per line (matched against each URL). Extend as real failures surface. +https://example.com +https://support.posit.co +https://packagemanager.posit.co +https://p3m.dev +https://twitter.com + +# Private Posit/RStudio source repos (return 404 to unauthenticated link checks) +https://github.com/posit-dev/connect/ +https://github.com/rstudio/package-manager/ +https://github.com/rstudio/rstudio-pro/ From aa0d452e6bde40ccfd4c8ab0fa90fe98696df653 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Mon, 1 Jun 2026 15:33:43 -0500 Subject: [PATCH 04/10] Add check-links justfile recipe --- justfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/justfile b/justfile index d94423cb..18da0071 100755 --- a/justfile +++ b/justfile @@ -26,3 +26,11 @@ _python-executable executable: _setup-pre-commit: pre-commit --version || just _python-executable pre-commit pre-commit install --install-hooks + +################ +# Linting + +# Check links in markdown with lychee +check-links: + docker run --rm -w /input -v "{{ CWD }}":/input:ro \ + lycheeverse/lychee --no-progress '**/*.md' From 1c4723486cb2e15a043f7104a05394ac734e03ed Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Wed, 3 Jun 2026 10:03:31 -0500 Subject: [PATCH 05/10] Check Quarto docs, exclude presentations The shared lychee default now globs **/*.*md, which matches both Markdown and Quarto (.qmd) files, so any repo's docs are covered. images-shared is currently the only repo with .qmd files. Its CI caller excludes the presentations/ directory, which holds dated slide decks rather than maintained docs. The dl.posit.co bare directory path, which 404s with no served index, is added to .lycheeignore. --- .github/workflows/checks.yml | 4 ++-- .github/workflows/ci.yml | 2 ++ .lycheeignore | 3 +++ justfile | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index b53a5722..a053cea2 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -29,8 +29,8 @@ on: required: false type: string lychee-args: - description: "Arguments passed to lychee" - default: "--cache --no-progress --max-cache-age 1d **/*.md" + description: "Arguments passed to lychee. The default **/*.*md glob matches both Markdown (.md) and Quarto (.qmd) files." + default: "--cache --no-progress --max-cache-age 1d **/*.*md" required: false type: string diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e8f01be..199f2bcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,8 @@ jobs: uses: ./.github/workflows/checks.yml with: python-version-file: posit-bakery/pyproject.toml + # Check Markdown and Quarto docs; presentations are dated decks, not maintained docs. + lychee-args: "--cache --no-progress --max-cache-age 1d --exclude-path presentations **/*.*md" test: name: Test diff --git a/.lycheeignore b/.lycheeignore index 1d1220ce..c314706e 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -6,6 +6,9 @@ https://packagemanager.posit.co https://p3m.dev https://twitter.com +# Bare directory paths that 404 (no index served) +https://dl.posit.co/public/open/ + # Private Posit/RStudio source repos (return 404 to unauthenticated link checks) https://github.com/posit-dev/connect/ https://github.com/rstudio/package-manager/ diff --git a/justfile b/justfile index 18da0071..ad4563c8 100755 --- a/justfile +++ b/justfile @@ -33,4 +33,4 @@ _setup-pre-commit: # Check links in markdown with lychee check-links: docker run --rm -w /input -v "{{ CWD }}":/input:ro \ - lycheeverse/lychee --no-progress '**/*.md' + lycheeverse/lychee --no-progress --exclude-path presentations '**/*.*md' From 47f9416f125bca72497ea5f2160c01a7ec9d0205 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Wed, 17 Jun 2026 10:06:25 -0500 Subject: [PATCH 06/10] Expose advanced-security input on zizmor job Allows callers without GitHub Advanced Security to set zizmor-advanced-security: false and skip the SARIF upload step, removing the security-events: write requirement for those repos. --- .github/workflows/checks.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a053cea2..17deaa84 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -13,6 +13,11 @@ on: default: true required: false type: boolean + zizmor-advanced-security: + description: "Upload zizmor results to GitHub Advanced Security. Requires security-events: write on the caller. Set to false for repos without GHAS." + default: true + required: false + type: boolean run-lychee: description: "Run the lychee link-check job" default: true @@ -86,6 +91,8 @@ jobs: with: persist-credentials: false - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + advanced-security: ${{ inputs.zizmor-advanced-security }} lychee: name: Lychee From f7073443c1a711bb04be2e326d7fc214ce348429 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Wed, 17 Jun 2026 10:07:46 -0500 Subject: [PATCH 07/10] Rename run-* inputs to {tool}-run Aligns with the lychee-args / zizmor-advanced-security convention so all inputs follow the {tool}-{option} pattern and group together alphabetically. --- .github/workflows/checks.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 17deaa84..48837cb4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -3,13 +3,13 @@ name: Checks on: workflow_call: inputs: - run-pre-commit: + pre-commit-run: description: "Run the pre-commit job" default: true required: false type: boolean - run-zizmor: - description: "Run the zizmor job" + lychee-run: + description: "Run the lychee link-check job" default: true required: false type: boolean @@ -18,8 +18,8 @@ on: default: true required: false type: boolean - run-lychee: - description: "Run the lychee link-check job" + zizmor-run: + description: "Run the zizmor job" default: true required: false type: boolean @@ -50,7 +50,7 @@ defaults: jobs: pre-commit: name: Pre-commit - if: inputs.run-pre-commit + if: inputs.pre-commit-run runs-on: ubuntu-latest timeout-minutes: 10 permissions: @@ -80,7 +80,7 @@ jobs: zizmor: name: Zizmor - if: inputs.run-zizmor + if: inputs.zizmor-run runs-on: ubuntu-latest timeout-minutes: 10 permissions: @@ -96,7 +96,7 @@ jobs: lychee: name: Lychee - if: inputs.run-lychee + if: inputs.lychee-run runs-on: ubuntu-latest timeout-minutes: 10 permissions: From 7205306c3150f92f8dd11a5b4f7c866b4968581c Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Wed, 17 Jun 2026 10:08:07 -0500 Subject: [PATCH 08/10] Group checks.yml inputs by tool alphabetically lychee-*, pre-commit-* (including python-*), zizmor-* --- .github/workflows/checks.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 48837cb4..c72acc83 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -3,23 +3,18 @@ name: Checks on: workflow_call: inputs: - pre-commit-run: - description: "Run the pre-commit job" - default: true + lychee-args: + description: "Arguments passed to lychee. The default **/*.*md glob matches both Markdown (.md) and Quarto (.qmd) files." + default: "--cache --no-progress --max-cache-age 1d **/*.*md" required: false - type: boolean + type: string lychee-run: description: "Run the lychee link-check job" default: true required: false type: boolean - zizmor-advanced-security: - description: "Upload zizmor results to GitHub Advanced Security. Requires security-events: write on the caller. Set to false for repos without GHAS." - default: true - required: false - type: boolean - zizmor-run: - description: "Run the zizmor job" + pre-commit-run: + description: "Run the pre-commit job" default: true required: false type: boolean @@ -33,11 +28,16 @@ on: default: "" required: false type: string - lychee-args: - description: "Arguments passed to lychee. The default **/*.*md glob matches both Markdown (.md) and Quarto (.qmd) files." - default: "--cache --no-progress --max-cache-age 1d **/*.*md" + zizmor-advanced-security: + description: "Upload zizmor results to GitHub Advanced Security. Requires security-events: write on the caller. Set to false for repos without GHAS." + default: true required: false - type: string + type: boolean + zizmor-run: + description: "Run the zizmor job" + default: true + required: false + type: boolean defaults: run: From b6c799945dcbf5e2e0c3aed2c2036eff45e91b23 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 18 Jun 2026 12:12:24 -0500 Subject: [PATCH 09/10] Replace pre-commit/action with direct invocation pre-commit/action is a composite action that internally uses actions/cache@v4 (a floating tag), which fails the org's transitive SHA-pin policy even though the action itself is pinned. Replace it with a self-controlled cache step (SHA-pinned v5.0.5, matching the lychee cache step) and a direct pip install + run. --- .github/workflows/checks.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c72acc83..da5a4632 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -74,7 +74,15 @@ jobs: with: python-version-file: ${{ inputs.python-version-file }} - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Run pre-commit + run: | + pip install pre-commit + pre-commit run --show-diff-on-failure --color=always --all-files env: SKIP: no-commit-to-branch From 19ca4f440814ad29328b83ba10aa0a3941c54594 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Thu, 18 Jun 2026 12:14:24 -0500 Subject: [PATCH 10/10] Use uvx to run pre-commit instead of pip install Drops the explicit pip install in favour of uvx, which downloads and runs the tool ephemerally. Consistent with how the rest of the project uses uv. --- .github/workflows/checks.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index da5a4632..6d8d1d98 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -74,15 +74,17 @@ jobs: with: python-version-file: ${{ inputs.python-version-file }} + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + enable-cache: false + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.cache/pre-commit key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - name: Run pre-commit - run: | - pip install pre-commit - pre-commit run --show-diff-on-failure --color=always --all-files + run: uvx pre-commit run --show-diff-on-failure --color=always --all-files env: SKIP: no-commit-to-branch