diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..6d8d1d98 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,132 @@ +name: Checks + +on: + workflow_call: + inputs: + 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: string + lychee-run: + description: "Run the lychee link-check job" + default: true + required: false + type: boolean + pre-commit-run: + description: "Run the pre-commit 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 + 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" + default: true + required: false + type: boolean + +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.pre-commit-run + 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: 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: uvx pre-commit run --show-diff-on-failure --color=always --all-files + env: + SKIP: no-commit-to-branch + + zizmor: + name: Zizmor + if: inputs.zizmor-run + 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 + with: + advanced-security: ${{ inputs.zizmor-advanced-security }} + + lychee: + name: Lychee + if: inputs.lychee-run + 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 }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1a4c11d..199f2bcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,38 +25,31 @@ 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 + # 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 @@ -180,18 +173,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: diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 00000000..c314706e --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,15 @@ +# 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 + +# 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/ +https://github.com/rstudio/rstudio-pro/ diff --git a/justfile b/justfile index d94423cb..ad4563c8 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 --exclude-path presentations '**/*.*md'