diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2a2c651..267c9ca 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -54,7 +54,7 @@ jobs: release: name: Create GitHub release - needs: publish + needs: build runs-on: ubuntu-latest permissions: contents: write diff --git a/template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/{% if enable_pypi %}release.yaml{% endif %}.jinja b/template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/release.yaml.jinja similarity index 97% rename from template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/{% if enable_pypi %}release.yaml{% endif %}.jinja rename to template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/release.yaml.jinja index db2e864..d2df6c9 100644 --- a/template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/{% if enable_pypi %}release.yaml{% endif %}.jinja +++ b/template/{% if project_visibility == 'public' %}.github{% endif %}/workflows/release.yaml.jinja @@ -31,31 +31,9 @@ jobs: name: dist path: dist/ - publish: - name: Publish to PyPI - needs: build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/project/{{ project_name }}/ - permissions: - id-token: write - attestations: write - steps: - - name: 🔽 Download artifact - uses: actions/download-artifact@v8 - with: - name: dist - path: dist/ - - - name: ☢️ Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - attestations: true - release: name: Create GitHub release - needs: publish + needs: build runs-on: ubuntu-latest permissions: contents: write @@ -75,4 +53,28 @@ jobs: run: gh release create {% raw %}${{ github.ref_name }}{% endraw %} dist/* --generate-notes env: GH_TOKEN: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %} +{%- if enable_pypi %} + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/{{ project_name }}/ + permissions: + id-token: write + attestations: write + steps: + - name: 🔽 Download artifact + uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + + - name: ☢️ Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true +{%- endif %} {#- vim: set ft=yaml: #} diff --git a/tests/__snapshots__/test_workflows.ambr b/tests/__snapshots__/test_workflows.ambr new file mode 100644 index 0000000..7142d69 --- /dev/null +++ b/tests/__snapshots__/test_workflows.ambr @@ -0,0 +1,481 @@ +# serializer version: 1 +# name: test_workflows_ci[docs-coverage] + ''' + name: CI + + on: + push: + branches: + - main + pull_request: + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + jobs: + ci: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + + - name: 🪪 Configure git identity for tests + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: ${{ matrix.python-version }} + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📐 Lint + run: uv run poe lint + + - name: 🔥 Test + run: uv run poe test + + - name: 🔍 Collect coverage results + run: uv run coverage xml + if: ${{ matrix.python-version == '3.14' }} + + - name: 📊 Upload coverage to Codecov + uses: codecov/codecov-action@v6 + if: ${{ matrix.python-version == '3.14' }} + with: + fail_ci_if_error: true + + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: '3.14' + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📖 Build documentation + run: uv run poe docs-build + + gitleaks: + name: Secret scan + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔍 Check for secrets + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ''' +# --- +# name: test_workflows_ci[docs-no_coverage] + ''' + name: CI + + on: + push: + branches: + - main + pull_request: + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + jobs: + ci: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + + - name: 🪪 Configure git identity for tests + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: ${{ matrix.python-version }} + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📐 Lint + run: uv run poe lint + + - name: 🔥 Test + run: uv run poe test + + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: '3.14' + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📖 Build documentation + run: uv run poe docs-build + + gitleaks: + name: Secret scan + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔍 Check for secrets + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ''' +# --- +# name: test_workflows_ci[no_docs-coverage] + ''' + name: CI + + on: + push: + branches: + - main + pull_request: + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + jobs: + ci: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + + - name: 🪪 Configure git identity for tests + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: ${{ matrix.python-version }} + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📐 Lint + run: uv run poe lint + + - name: 🔥 Test + run: uv run poe test + + - name: 🔍 Collect coverage results + run: uv run coverage xml + if: ${{ matrix.python-version == '3.14' }} + + - name: 📊 Upload coverage to Codecov + uses: codecov/codecov-action@v6 + if: ${{ matrix.python-version == '3.14' }} + with: + fail_ci_if_error: true + + + gitleaks: + name: Secret scan + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔍 Check for secrets + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ''' +# --- +# name: test_workflows_ci[no_docs-no_coverage] + ''' + name: CI + + on: + push: + branches: + - main + pull_request: + + concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + + jobs: + ci: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - '3.14' + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + + - name: 🪪 Configure git identity for tests + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: ${{ matrix.python-version }} + + - name: 🐍 Install dependencies + run: uv sync + + - name: 📐 Lint + run: uv run poe lint + + - name: 🔥 Test + run: uv run poe test + + + gitleaks: + name: Secret scan + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔍 Check for secrets + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ''' +# --- +# name: test_workflows_release[no_pypi] + ''' + name: Release + + on: + push: + tags: + - v[0-9]*.[0-9]* + + jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: '3.14' + + - name: 📦 Build package + run: uv build + + - name: 🔼 Upload artifact + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + release: + name: Create GitHub release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔽 Download artifact + uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + + - name: 🏷️ Create GitHub release + run: gh release create ${{ github.ref_name }} dist/* --generate-notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ''' +# --- +# name: test_workflows_release[pypi] + ''' + name: Release + + on: + push: + tags: + - v[0-9]*.[0-9]* + + jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🦀 Set up Python project with uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + python-version: '3.14' + + - name: 📦 Build package + run: uv build + + - name: 🔼 Upload artifact + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + release: + name: Create GitHub release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: 💾 Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 🔽 Download artifact + uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + + - name: 🏷️ Create GitHub release + run: gh release create ${{ github.ref_name }} dist/* --generate-notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/PKFire/ + permissions: + id-token: write + attestations: write + steps: + - name: 🔽 Download artifact + uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + + - name: ☢️ Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true + + ''' +# --- diff --git a/tests/test_structure.py b/tests/test_structure.py index cac4397..26af306 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -26,12 +26,12 @@ ".github/workflows/ci.yaml", ".github/workflows/docs.yaml", ".github/workflows/audit.yaml", + ".github/workflows/release.yaml", ], [ "Dockerfile", ".dockerignore", "compose.yaml", - ".github/workflows/release.yaml", ".github/workflows/container.yaml", ".github/workflows/ghcr.yaml", ], diff --git a/tests/test_workflows.py b/tests/test_workflows.py new file mode 100644 index 0000000..3bf7cc7 --- /dev/null +++ b/tests/test_workflows.py @@ -0,0 +1,53 @@ +"""Snapshot tests for GitHub workflow templates.""" + +from collections.abc import Callable +from pathlib import Path + +import pytest +from syrupy.assertion import SnapshotAssertion + + +@pytest.mark.parametrize( + "enable_coverage", + [pytest.param(True, id="coverage"), pytest.param(False, id="no_coverage")], +) +@pytest.mark.parametrize( + "enable_docs", + [pytest.param(True, id="docs"), pytest.param(False, id="no_docs")], +) +def test_workflows_ci( + render_template: Callable[..., Path], + snapshot: SnapshotAssertion, + *, + enable_coverage: bool, + enable_docs: bool, +) -> None: + rendered = render_template( + project_type="application", + project_visibility="public", + enable_coverage=enable_coverage, + enable_docs=enable_docs, + ) + assert ( + rendered / ".github" / "workflows" / "ci.yaml" + ).read_text() == snapshot + + +@pytest.mark.parametrize( + "enable_pypi", + [pytest.param(True, id="pypi"), pytest.param(False, id="no_pypi")], +) +def test_workflows_release( + render_template: Callable[..., Path], + snapshot: SnapshotAssertion, + *, + enable_pypi: bool, +) -> None: + rendered = render_template( + project_type="application", + project_visibility="public", + enable_pypi=enable_pypi, + ) + assert ( + rendered / ".github" / "workflows" / "release.yaml" + ).read_text() == snapshot